Skip to content

Commit

Permalink
completion: repair config completion for Zsh
Browse files Browse the repository at this point in the history
Commit 1e0ee40 (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>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
benknoble authored and gitster committed Jan 6, 2025
1 parent c2b3f2b commit 8776470
Showing 1 changed file with 11 additions and 6 deletions.
17 changes: 11 additions & 6 deletions contrib/completion/git-completion.bash
Original file line number Diff line number Diff line change
Expand Up @@ -2723,12 +2723,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 }")"
}
Expand All @@ -2738,7 +2743,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 }")"
}
Expand Down Expand Up @@ -2893,7 +2898,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.*)
Expand All @@ -2903,7 +2908,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.*)
Expand All @@ -2920,7 +2925,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.*)
Expand All @@ -2930,7 +2935,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
;;
*.*)
Expand Down

0 comments on commit 8776470

Please sign in to comment.