From: Felipe Contreras Date: Sun, 06 May 2012 20:37:06 GMT Subject: Re: [PATCH v3] completion: add new _GIT_complete helper Message-ID: In-Reply-To: <20120506111425.GJ2164@goldbirke> Hi, On Sun, May 6, 2012 at 1:14 PM, SZEDER Gábor wrote: > On Sat, May 05, 2012 at 05:23:20PM +0200, Felipe Contreras wrote: >> This simplifies the completions, and makes it easier to define aliases: >> >>  _GIT_complete gf git_fetch > > So, 'gf' is an alias for 'git fetch', for which the user would like to > use the completion for 'git fetch', right?  But that completion > function is caled _git_fetch(), so the underscore prefix is missing > here. No, it's not missing: local name="${2-$1}" eval "$(typeset -f __git_func_wrap | sed -e "s/__git_func/_$name/")" complete -o bashdefault -o default -o nospace -F _${name}_wrap $1 2>/dev/null \ || complete -o default -o nospace -F _${name}_wrap $1 See how we add '_' before $name? There's not point in burdening the user with adding a prefix that will _always_ be there anyway. > Besides, this example won't work, because the completion for 'git > fetch' uses __git_complete_remote_or_refspec(), which in turn relies > on finding out the name of the git command from the word on the > command line, and it won't be able to do that from 'gf'. That's irrelevant, it's an example; replace with another command that doesn't use 'words', and it would work. > I remember we discussed this in an earlier round, and you even > suggested a possible fix (passing the command name as argument to > __git_complete_remote_or_refspec()).  I think that's the right thing > to do here. Yeah, but I suggested that in order to avoid the eval and the typeset that I require for future future patches, but it turns out it's still needed anyway, so my suggestion is to have a 'cmd' variable that stores the command; __git_func_wrap would take the responsibility of doing that. >> +     __git_func "$@" > > What is this "$@" for and why?  None of the _git_() functions > take any arguments, nor does _git() and _gitk(), and AFAICT Bash won't > pass any either. bash's complete passes 3 arguments. They might not be used, but it doesn't hurt to pass them either. >> +} >> + >> +_GIT_complete () >> +{ >> +     local name="${2-$1}" >> +     eval "$(typeset -f __git_func_wrap | sed -e "s/__git_func/_$name/")" > > Still don't like the subshell and sed here ... I haven't found any other way. As for the fix to 'git fetch' and others, I've attached a crude diff of all the fixes I have queued together, and there it works perfectly fine, but there's a lot of modifications required to properly traverse the words array. I've meant to create a special function to traverse this array, but I haven't had time time for that; I haven't even landed this patch due to apparently irrelevant discussions. Cheers. -- Felipe Contreras diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 9f56ec7..ec2c3cc 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -721,10 +721,18 @@ __git_complete_revlist () __git_complete_revlist_file } +__git_get_pos () +{ + echo $(( t = cmd_pos - 2 + $1 )) +} + __git_complete_remote_or_refspec () { - local cur_="$cur" cmd="${words[1]}" - local i c=2 remote="" pfx="" lhs=1 no_complete_refspec=0 + local cur_="$cur" + local i c remote="" pfx="" lhs=1 no_complete_refspec=0 + + c=$(__git_get_pos 2) + if [ "$cmd" = "remote" ]; then ((c++)) fi @@ -976,7 +984,8 @@ __git_aliased_command () # __git_find_on_cmdline requires 1 argument __git_find_on_cmdline () { - local word subcommand c=1 + local word subcommand c + c=$(__git_get_pos 1) while [ $c -lt $cword ]; do word="${words[c]}" for subcommand in $1; do @@ -991,7 +1000,8 @@ __git_find_on_cmdline () __git_has_doubledash () { - local c=1 + local c + c=$(__git_get_pos 1) while [ $c -lt $cword ]; do if [ "--" = "${words[c]}" ]; then return 0 @@ -1111,8 +1121,8 @@ _git_bisect () _git_branch () { - local i c=1 only_local_ref="n" has_r="n" - + local i c only_local_ref="n" has_r="n" + c=$(__git_get_pos 1) while [ $c -lt $cword ]; do i="${words[c]}" case "$i" in @@ -1142,16 +1152,19 @@ _git_branch () _git_bundle () { - local cmd="${words[2]}" - case "$cword" in + local subcommands='create list-heads verify unbundle' + local subcommand="$(__git_find_on_cmdline "$subcommands")" + local r + (( r = cword - cmd_pos + 2 )) + case "$r" in 2) - __gitcomp "create list-heads verify unbundle" + __gitcomp "$subcommands" ;; 3) # looking for a file ;; *) - case "$cmd" in + case "$subcommand" in create) __git_complete_revlist ;; @@ -1427,6 +1440,7 @@ __git_match_ctag() { _git_grep () { __git_has_doubledash && return + local p=$(__git_get_pos 2) case "$cur" in --*) @@ -1447,7 +1461,7 @@ _git_grep () esac case "$cword,$prev" in - 2,*|*,-*) + $p,*|*,-*) if test -r tags; then __gitcomp_nl "$(__git_match_ctag "$cur" tags)" return @@ -2562,7 +2576,8 @@ _git_svn () _git_tag () { - local i c=1 f=0 + local i c f=0 + c=$(__git_get_pos 1) while [ $c -lt $cword ]; do i="${words[c]}" case "$i" in @@ -2599,39 +2614,24 @@ _git_whatchanged () _git_log } -_git () +_main_git () { - local i c=1 command __git_dir - - if [[ -n ${ZSH_VERSION-} ]]; then - emulate -L bash - setopt KSH_TYPESET - - # workaround zsh's bug that leaves 'words' as a special - # variable in versions < 4.3.12 - typeset -h words - - # workaround zsh's bug that quotes spaces in the COMPREPLY - # array if IFS doesn't contain spaces. - typeset -h IFS - fi + local i c=1 cmd cmd_pos __git_dir - local cur words cword prev - _get_comp_words_by_ref -n =: cur words cword prev while [ $c -lt $cword ]; do i="${words[c]}" case "$i" in --git-dir=*) __git_dir="${i#--git-dir=}" ;; --bare) __git_dir="." ;; - --help) command="help"; break ;; + --help) cmd="help"; break ;; -c) c=$((++c)) ;; -*) ;; - *) command="$i"; break ;; + *) cmd="$i"; break ;; esac ((c++)) done - if [ -z "$command" ]; then + if [ -z "$cmd" ]; then case "$cur" in --*) __gitcomp " --paginate @@ -2655,34 +2655,20 @@ _git () return fi - local completion_func="_git_${command//-/_}" + (( cmd_pos = c + 1 )) + + local completion_func="_git_${cmd//-/_}" declare -f $completion_func >/dev/null && $completion_func && return - local expansion=$(__git_aliased_command "$command") + local expansion=$(__git_aliased_command "$cmd") if [ -n "$expansion" ]; then completion_func="_git_${expansion//-/_}" declare -f $completion_func >/dev/null && $completion_func fi } -_gitk () +_main_gitk () { - if [[ -n ${ZSH_VERSION-} ]]; then - emulate -L bash - setopt KSH_TYPESET - - # workaround zsh's bug that leaves 'words' as a special - # variable in versions < 4.3.12 - typeset -h words - - # workaround zsh's bug that quotes spaces in the COMPREPLY - # array if IFS doesn't contain spaces. - typeset -h IFS - fi - - local cur words cword prev - _get_comp_words_by_ref -n =: cur words cword prev - __git_has_doubledash && return local g="$(__gitdir)" @@ -2703,16 +2689,42 @@ _gitk () __git_complete_revlist } -complete -o bashdefault -o default -o nospace -F _git git 2>/dev/null \ - || complete -o default -o nospace -F _git git -complete -o bashdefault -o default -o nospace -F _gitk gitk 2>/dev/null \ - || complete -o default -o nospace -F _gitk gitk +foo_wrap () +{ + local cmd=foo_cmd cmd_pos=1 + if [[ -n ${ZSH_VERSION-} ]]; then + emulate -L bash + setopt KSH_TYPESET + + # workaround zsh's bug that leaves 'words' as a special + # variable in versions < 4.3.12 + typeset -h words + + # workaround zsh's bug that quotes spaces in the COMPREPLY + # array if IFS doesn't contain spaces. + typeset -h IFS + fi + local cur words cword prev + _get_comp_words_by_ref -n =: cur words cword prev + foo "$@" +} + +git_complete () +{ + local name="${2-main_$1}" + local cmd="${name#git_}" + eval "$(typeset -f foo_wrap | sed -e "s/foo/_$name/" -e "s/foo_cmd/$cmd/")" + complete -o bashdefault -o default -o nospace -F _${name}_wrap $1 2>/dev/null \ + || complete -o default -o nospace -F _${name}_wrap $1 +} + +git_complete git +git_complete gitk # The following are necessary only for Cygwin, and only are needed # when the user has tab-completed the executable name and consequently # included the '.exe' suffix. # if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then -complete -o bashdefault -o default -o nospace -F _git git.exe 2>/dev/null \ - || complete -o default -o nospace -F _git git.exe +git_complete git.exe main_git fi diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index cc12732..2966a18 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -63,7 +63,7 @@ run_completion () local _cword _words=( $1 ) (( _cword = ${#_words[@]} - 1 )) - _git && print_comp + ${comp_wrap-_main_git_wrap} && print_comp } test_completion () @@ -73,6 +73,22 @@ test_completion () test_cmp expected out } +setup_repository () +{ + mkdir "$1" && ( + cd "$1" && + git init && + test_tick && + git commit --allow-empty -m "Initial" + ) +} + +test_expect_success 'prepare' ' + setup_repository one && + git clone one test && + cd test +' + test_expect_success 'basic' ' run_completion "git \"\"" && # built-in @@ -155,4 +171,71 @@ test_expect_success 'general options plus command' ' test_completion "git --no-replace-objects check" "checkout " ' +test_expect_success 'remote or refspec' ' + test_completion "git fetch o" "origin " && + test_completion "git fetch origin m" "master:master " && + test_completion "git pull o" "origin " && + test_completion "git pull origin m" "master " && + test_completion "git push o" "origin " && + test_completion "git push origin m" "master " +' + +test_expect_success 'subcomands' ' + test_completion "git bisect st" "start " +' + +test_expect_success 'has double dash' ' + test_completion "git add -- foo" "" +' + +test_expect_success 'config' ' + git config --file=foo color.ui auto && + test_completion "git config --file=foo --get c" "color.ui " +' + +test_expect_success 'other' ' + cat >expected <<-\EOF && + origin/HEAD + origin/master + EOF + test_completion "git branch -r o" && + test_completion "git bundle cr" "create " && + + echo foobar > tags && + test_completion "git grep f" "foobar " && + + test_completion "git notes --ref m" "master " && + + git tag v0.1 && + test_completion "git tag -d v" "v0.1 " +' + +test_expect_success 'global options extra checks' ' + test_completion "git --no-pager fetch o" "origin " && + test_completion "git --no-pager fetch origin m" "master:master " && + test_completion "git --no-pager pull o" "origin " && + test_completion "git --no-pager pull origin m" "master " && + test_completion "git --no-pager push o" "origin " && + test_completion "git --no-pager push origin m" "master " && + test_completion "git --no-pager bisect st" "start " && + test_completion "git --no-pager add -- foo" "" && + test_completion "git --no-pager config --file=foo --get c" "color.ui " && + cat >expected <<-\EOF && + origin/HEAD + origin/master + EOF + test_completion "git --no-pager branch -r o" && + test_completion "git --no-pager bundle cr" "create " && + test_completion "git --no-pager grep f" "foobar " && + test_completion "git --no-pager notes --ref m" "master " && + test_completion "git --no-pager tag -d v" "v0.1 " +' + +test_expect_success 'aliases' ' + local comp_wrap=_git_fetch_wrap && + git_complete gf git_fetch && + test_completion "gf o" "origin " && + test_completion "gf origin m" "master:master " +' + test_done