git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v3] completion: repair config completion for Zsh

From
D. Ben Knoble via GitGitGadget <gitgitgadget@gmail.com>
Date
Jan 6, 2025, 21:47 UTC
Message-ID
<pull.1860.v3.git.git.1736200026899.gitgitgadget@gmail.com>
In-Reply-To
<pull.1860.v2.git.git.1736002073641.gitgitgadget@gmail.com>
From: "D. Ben Knoble" <ben.knoble+github@gmail.com>

Commit 1e0ee4087e (completion: add and use __git_compute_first_level_config_vars_for_section, 2024-02-10) uses an indirect variable syntax that is only valid for Bash, but the Zsh completion code relies on the Bash completion code to function. Zsh supports a different indirect variable expansion using ${(P)var}, but in `emulate ksh` mode does not support Bash's ${!var}.

This manifests as completing strange config options like "__git_first_level_config_vars_for_section_remote" as a choice for the command line

    git config set remote.

Using Zsh's C-x ? _complete_debug widget with the cursor at the end of that command line captures a trace, in which we see (some details elided):

    +__git_complete_config_variable_name:7> __git_compute_first_level_config_vars_for_section remote
     +__git_compute_first_level_config_vars_for_section:7> local section=remote
     +__git_compute_first_level_config_vars_for_section:7> __git_compute_config_vars
      +__git_compute_config_vars:7> test -n $'add.ignoreErrors\nadvice.addEmbeddedRepo\nadvice.addEmptyPathspec\nadvice.addIgnoredFile[…]'
     +__git_compute_first_level_config_vars_for_section:7> local this_section=__git_first_level_config_vars_for_section_remote
     +__git_compute_first_level_config_vars_for_section:7> test -n __git_first_level_config_vars_for_section_remote
    +__git_complete_config_variable_name:7> local this_section=__git_first_level_config_vars_for_section_remote
    +__git_complete_config_variable_name:7> __gitcomp_nl_append __git_first_level_config_vars_for_section_remote remote. '' ' '
     +__gitcomp_nl_append:7> __gitcomp_nl __git_first_level_config_vars_for_section_remote remote. '' ' '
      +__gitcomp_nl:7> emulate -L zsh
      +__gitcomp_nl:7> compset -P '*[=:]'
      +__gitcomp_nl:7> compadd -Q -S ' ' -p remote. -- __git_first_level_config_vars_for_section_remote

We perform the test for __git_compute_config_vars correctly, but the ${!this_section} references are not expanded as expected.

Instead, portably expand indirect references through the new __git_indirect. Contrary to some versions you might find online [1], this version avoids echo non-portabilities [2] [3] and correctly quotes the indirect expansion after eval (so that the result is not split or globbed before being handed to printf).

[1]: https://unix.stackexchange.com/a/41409/301073 [2]: https://askubuntu.com/questions/715765/mysterious-behavior-of-echo-command#comment1056038_715769 [3]: https://mywiki.wooledge.org/CatEchoLs

The following demo program demonstrates how this works:
    b=1
    indirect() {
      eval printf '%s' "\"\$$1\""
    }
    f() {
      # Comment this out to see that it works for globals, too. Or, use
      # a value with spaces like '2 3 4' to see how it handles those.
      local b=2
      local a=b
      test -n "$(indirect $a)" && echo nice
    }
    f
When placed in a file "demo", then both
    bash -x demo
and
    zsh -xc 'emulate ksh -c ". ./demo"' |& tail
provide traces showing that "$(indirect $a)" produces 2 (or 1, with the
global, or "2 3 4" as a single string, etc.).
Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>
Acked-by: Philippe Blain <levraiphilippeblain@gmail.com>
---
    completion: repair config completion for Zsh
    
    With apologies to both CC'd if I've misunderstood the flow in
    https://git-scm.com/docs/SubmittingPatches#_choosing_your_reviewers or
    Phillipe's comments on v1.
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1860%2Fbenknoble%2Ffix-zsh-config-completion-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1860/benknoble/fix-zsh-config-completion-v3
Pull-Request: https://github.com/git/git/pull/1860
Range-diff vs v2:
 1:  daee7636106 ! 1:  13a42324658 completion: repair config completion for Zsh
     @@ Commit message
          global, or "2 3 4" as a single string, etc.).
      
          Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>
     +    Acked-by: Philippe Blain <levraiphilippeblain@gmail.com>
      
       ## contrib/completion/git-completion.bash ##
      @@ contrib/completion/git-completion.bash: __git_compute_config_vars_all ()
 contrib/completion/git-completion.bash | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index b3b6aa3bae2..413911be3be 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -2737,12 +2737,17 @@ __git_compute_config_vars_all ()
 	__git_config_vars_all="$(git --no-pager help --config)"
 }
 
+__git_indirect()
+{
+	eval printf '%s' "\"\$$1\""
+}
+
 __git_compute_first_level_config_vars_for_section ()
 {
 	local section="$1"
 	__git_compute_config_vars
 	local this_section="__git_first_level_config_vars_for_section_${section}"
-	test -n "${!this_section}" ||
+	test -n "$(__git_indirect "${this_section}")" ||
 	printf -v "__git_first_level_config_vars_for_section_${section}" %s \
 		"$(echo "$__git_config_vars" | awk -F. "/^${section}\.[a-z]/ { print \$2 }")"
 }
@@ -2752,7 +2757,7 @@ __git_compute_second_level_config_vars_for_section ()
 	local section="$1"
 	__git_compute_config_vars_all
 	local this_section="__git_second_level_config_vars_for_section_${section}"
-	test -n "${!this_section}" ||
+	test -n "$(__git_indirect "${this_section}")" ||
 	printf -v "__git_second_level_config_vars_for_section_${section}" %s \
 		"$(echo "$__git_config_vars_all" | awk -F. "/^${section}\.</ { print \$3 }")"
 }
@@ -2907,7 +2912,7 @@ __git_complete_config_variable_name ()
 		local section="${pfx%.*.}"
 		__git_compute_second_level_config_vars_for_section "${section}"
 		local this_section="__git_second_level_config_vars_for_section_${section}"
-		__gitcomp "${!this_section}" "$pfx" "$cur_" "$sfx"
+		__gitcomp "$(__git_indirect "${this_section}")" "$pfx" "$cur_" "$sfx"
 		return
 		;;
 	branch.*)
@@ -2917,7 +2922,7 @@ __git_complete_config_variable_name ()
 		__gitcomp_direct "$(__git_heads "$pfx" "$cur_" ".")"
 		__git_compute_first_level_config_vars_for_section "${section}"
 		local this_section="__git_first_level_config_vars_for_section_${section}"
-		__gitcomp_nl_append "${!this_section}" "$pfx" "$cur_" "${sfx:- }"
+		__gitcomp_nl_append "$(__git_indirect "${this_section}")" "$pfx" "$cur_" "${sfx:- }"
 		return
 		;;
 	pager.*)
@@ -2934,7 +2939,7 @@ __git_complete_config_variable_name ()
 		__gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "."
 		__git_compute_first_level_config_vars_for_section "${section}"
 		local this_section="__git_first_level_config_vars_for_section_${section}"
-		__gitcomp_nl_append "${!this_section}" "$pfx" "$cur_" "${sfx:- }"
+		__gitcomp_nl_append "$(__git_indirect "${this_section}")" "$pfx" "$cur_" "${sfx:- }"
 		return
 		;;
 	submodule.*)
@@ -2944,7 +2949,7 @@ __git_complete_config_variable_name ()
 		__gitcomp_nl "$(__git config -f "$(__git rev-parse --show-toplevel)/.gitmodules" --get-regexp 'submodule.*.path' | awk -F. '{print $2}')" "$pfx" "$cur_" "."
 		__git_compute_first_level_config_vars_for_section "${section}"
 		local this_section="__git_first_level_config_vars_for_section_${section}"
-		__gitcomp_nl_append "${!this_section}" "$pfx" "$cur_" "${sfx:- }"
+		__gitcomp_nl_append "$(__git_indirect "${this_section}")" "$pfx" "$cur_" "${sfx:- }"
 		return
 		;;
 	*.*)

base-commit: 306ab352f4e98f6809ce52fc4e5d63fb947d0635
-- 
gitgitgadget
Previous: D. Ben Knoble via GitGitGadgetNext: Junio C Hamano
Message 5 of 6 in “completion: repair config completion for Zsh”
  1. completion: repair config completion for ZshD. Ben Knoble via GitGitGadget, Dec 30, 2024
  2. Philippe BlainJan 3, 2025
  3. D. Ben KnobleJan 3, 2025
  4. completion: repair config completion for ZshD. Ben Knoble via GitGitGadget, Jan 4, 2025
  5. completion: repair config completion for ZshD. Ben Knoble via GitGitGadget, Jan 6, 2025
  6. Junio C HamanoJan 7, 2025

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.