threads / discuss / 24877

[PATCHv2] completion: make compatible with zsh

Subject: [PATCHv2] completion: make compatible with zsh

## tl;dr

5 messages between Aug 27, 2010 and Aug 31, 2010.

replies: 4people: 4as markdown or json

Mark Lodato· Aug 27, 2010, 02:45 UTC · lore

Modify git-completion.bash so that it also works with zsh when using bashcompinit. In particular:

declare -F
    Zsh doesn't have the same 'declare -F' as bash, but 'declare -f'
    is the same, and it works just as well for our purposes.
${var:2}
    Zsh does not implement ${var:2} to skip the first 2 characters, but
    ${var#??} works in both shells to replace the first 2 characters
    with nothing.  Thanks to Jonathan Nieder for the suggestion.
for (( n=1; "$n" ... ))
    Zsh does not allow "$var" in arithmetic loops.  Instead, pre-compute
    the endpoint and use the variables without $'s or quotes.
shopt
    Zsh uses 'setopt', which has a different syntax than 'shopt'.  Since
    'shopt' is used infrequently in git-completion, we provide
    a bare-bones emulation.
emulate -L bash
KSH_TYPESET
    Zsh offers bash emulation, which turns on a set of features to
    closely resemble bash. In particular, this enables SH_WORDSPLIT,
    which splits scalar variables on word boundaries in 'for' loops.
    We also need to set KSH_TYPESET, to fix "local var=$(echo foo bar)"
    issues.

The last set of options are turned on only in _git and _gitk. Some of the sub-functions may not work correctly if called directly.

Signed-off-by: Mark Lodato <lodatom@gmail.com>
---
Those on the CC list either responded to the original patch or are the authors
of the lines affected by this patch:
    - Andrew wrote __git_ps1_show_upstream().
    - SZEDER wrote the part using 'declare -F'.
 contrib/completion/git-completion.bash |   50 +++++++++++++++++++++++++++++--
 1 files changed, 46 insertions(+), 4 deletions(-)
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 6756990..6a7aae6 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -21,6 +21,11 @@
 #    2) Added the following line to your .bashrc:
 #        source ~/.git-completion.sh
 #
+#       Or, add the following lines to your .zshrc:
+#        autoload bashcompinit
+#        bashcompinit
+#        source ~/.git-completion.sh
+#
 #    3) Consider changing your PS1 to also show the current branch:
 #        PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
 #
@@ -138,11 +143,12 @@ __git_ps1_show_upstream ()
 		# get the upstream from the "git-svn-id: ..." in a commit message
 		# (git-svn uses essentially the same procedure internally)
 		local svn_upstream=($(git log --first-parent -1 \
-					--grep="^git-svn-id: \(${svn_url_pattern:2}\)" 2>/dev/null))
+					--grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null))
 		if [[ 0 -ne ${#svn_upstream[@]} ]]; then
 			svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
 			svn_upstream=${svn_upstream%@*}
-			for ((n=1; "$n" <= "${#svn_remote[@]}"; ++n)); do
+			local n_stop="${#svn_remote[@]}"
+			for ((n=1; n <= n_stop; ++n)); do
 				svn_upstream=${svn_upstream#${svn_remote[$n]}}
 			done
 
@@ -2339,6 +2345,11 @@ _git ()
 {
 	local i c=1 command __git_dir
 
+	if [[ -n $ZSH_VERSION ]]; then
+		emulate -L bash
+		setopt KSH_TYPESET
+	fi
+
 	while [ $c -lt $COMP_CWORD ]; do
 		i="${COMP_WORDS[c]}"
 		case "$i" in
@@ -2372,17 +2383,22 @@ _git ()
 	fi
 
 	local completion_func="_git_${command//-/_}"
-	declare -F $completion_func >/dev/null && $completion_func && return
+	declare -f $completion_func >/dev/null && $completion_func && return
 
 	local expansion=$(__git_aliased_command "$command")
 	if [ -n "$expansion" ]; then
 		completion_func="_git_${expansion//-/_}"
-		declare -F $completion_func >/dev/null && $completion_func
+		declare -f $completion_func >/dev/null && $completion_func
 	fi
 }
 
 _gitk ()
 {
+	if [[ -n $ZSH_VERSION ]]; then
+		emulate -L bash
+		setopt KSH_TYPESET
+	fi
+
 	__git_has_doubledash && return
 
 	local cur="${COMP_WORDS[COMP_CWORD]}"
@@ -2417,3 +2433,29 @@ 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
 fi
+
+if [[ -z $ZSH_VERSION ]]; then
+	shopt () {
+		local option
+		if [ $# -ne 2 ]; then
+			echo "USAGE: $0 (-q|-s|-u) <option>" >&2
+			return 1
+		fi
+		case "$2" in
+		nullglob)
+			option="$2"
+			;;
+		*)
+			echo "$0: invalid option: $2" >&2
+			return 1
+		esac
+		case "$1" in
+		-q)	setopt | grep -q "$option" ;;
+		-u)	unsetopt "$option" ;;
+		-s)	setopt "$option" ;;
+		*)
+			echo "$0: invalid flag: $1" >&2
+			return 1
+		esac
+	}
+fi
-- 
1.7.2.2
Ævar Arnfjörð Bjarmason· Aug 28, 2010, 10:43 UTC · re: Mark Lodato · lore

Re: [PATCHv2] completion: make compatible with zsh

On Fri, Aug 27, 2010 at 02:45, Mark Lodato <lodatom@gmail.com> wrote:
> Modify git-completion.bash so that it also works with zsh when using
> bashcompinit.  In particular:
FWIW:
Reviewed-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
SZEDER Gábor· Aug 30, 2010, 14:19 UTC · re: Mark Lodato · lore

Re: [PATCHv2] completion: make compatible with zsh

Hi,
On Thu, Aug 26, 2010 at 10:45:56PM -0400, Mark Lodato wrote:
Show 6 quoted lines
> Modify git-completion.bash so that it also works with zsh when using
> bashcompinit.  In particular:
> 
> declare -F
>     Zsh doesn't have the same 'declare -F' as bash, but 'declare -f'
>     is the same, and it works just as well for our purposes.
Show 14 quoted lines
> @@ -2372,17 +2383,22 @@ _git ()
>  	fi
>  
>  	local completion_func="_git_${command//-/_}"
> -	declare -F $completion_func >/dev/null && $completion_func && return
> +	declare -f $completion_func >/dev/null && $completion_func && return
>  
>  	local expansion=$(__git_aliased_command "$command")
>  	if [ -n "$expansion" ]; then
>  		completion_func="_git_${expansion//-/_}"
> -		declare -F $completion_func >/dev/null && $completion_func
> +		declare -f $completion_func >/dev/null && $completion_func
>  	fi
>  }
I chose -F back then because it does not print the definition of the
completion function, so there is less stuff to throw away immediately.
However, the run-time difference seems to be barely measureable (i.e.
'time declare -f _git_config >/dev/null' vs. 'declare -F _git_config
 >/dev/null' sometimes shows 0.001s difference), so I'm fine with this
change.
Show 6 quoted lines
> @@ -2417,3 +2433,29 @@ 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
>  fi
> +
> +if [[ -z $ZSH_VERSION ]]; then
-z?  I think you wanted to use -n here, like at the other places.
Nit: why "if [[ ... ]]"?  FWIW "if [ ... ]" would be enough.
Show 27 quoted lines
> +	shopt () {
> +		local option
> +		if [ $# -ne 2 ]; then
> +			echo "USAGE: $0 (-q|-s|-u) <option>" >&2
> +			return 1
> +		fi
> +		case "$2" in
> +		nullglob)
> +			option="$2"
> +			;;
> +		*)
> +			echo "$0: invalid option: $2" >&2
> +			return 1
> +		esac
> +		case "$1" in
> +		-q)	setopt | grep -q "$option" ;;
> +		-u)	unsetopt "$option" ;;
> +		-s)	setopt "$option" ;;
> +		*)
> +			echo "$0: invalid flag: $1" >&2
> +			return 1
> +		esac
> +	}
> +fi
> -- 
> 1.7.2.2
> 
Mark Lodato· Aug 31, 2010, 00:55 UTC · re: SZEDER Gábor · lore

Re: [PATCHv2] completion: make compatible with zsh

2010/8/30 SZEDER Gábor <szeder@ira.uka.de>
Show 9 quoted lines
> On Thu, Aug 26, 2010 at 10:45:56PM -0400, Mark Lodato wrote:
> > @@ -2417,3 +2433,29 @@ 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
> >  fi
> > +
> > +if [[ -z $ZSH_VERSION ]]; then
>
> -z?  I think you wanted to use -n here, like at the other places.
Oh, yes, sorry.  This was a mistake.  Thanks for catching it.
> Nit: why "if [[ ... ]]"?  FWIW "if [ ... ]" would be enough.

Because you don't need to quote variables with [[ ... ]] --- e.g. [ $lines = 0 ] fails, though in this case this feature does not matter --- and because [[ ... ]] is faster.

Bash 4.1.5:
> time (for (( i = 0; i < 200000; i++ )); do [ -z $foo ]; done)

real 0m3.430s user 0m3.240s sys 0m0.180s

> time (for (( i = 0; i < 200000; i++ )); do [[ -z $foo ]]; done)

real 0m2.219s user 0m2.090s sys 0m0.100s

Zsh 4.3.10:
> time (for (( i = 0; i < 2000000; i++ )); do [ -z $foo ]; done)

(; for ((i = 0; i < 2000000; i++ )) do; [ -z $foo ]; done; ) 13.56s user 1.64s system 99% cpu 15.327 total

> time (for (( i = 0; i < 2000000; i++ )); do [[ -z $foo ]]; done)

(; for ((i = 0; i < 2000000; i++ )) do; [[ -z $foo ]]; done; ) 4.62s user 0.01s system 99% cpu 4.644 total

Is there a reason to prefer [ ... ] ?
Peter Kjellerstedt· Aug 31, 2010, 07:52 UTC · re: Mark Lodato · lore

RE: [PATCHv2] completion: make compatible with zsh

Show 48 quoted lines
> -----Original Message-----
> From: git-owner@vger.kernel.org [mailto:git-owner@vger.kernel.org] On
> Behalf Of Mark Lodato
> Sent: den 31 augusti 2010 02:56
> To: SZEDER Gábor
> Cc: Shawn O. Pearce; git@vger.kernel.org; avarab@gmail.com; Jonathan
> Nieder; Andrew Sayers
> Subject: Re: [PATCHv2] completion: make compatible with zsh
> 
> 2010/8/30 SZEDER Gábor <szeder@ira.uka.de>
> > On Thu, Aug 26, 2010 at 10:45:56PM -0400, Mark Lodato wrote:
> > > @@ -2417,3 +2433,29 @@ 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
> > >  fi
> > > +
> > > +if [[ -z $ZSH_VERSION ]]; then
> >
> > -z?  I think you wanted to use -n here, like at the other places.
> 
> Oh, yes, sorry.  This was a mistake.  Thanks for catching it.
> 
> 
> > Nit: why "if [[ ... ]]"?  FWIW "if [ ... ]" would be enough.
> 
> Because you don't need to quote variables with [[ ... ]] --- e.g. [
> $lines = 0 ] fails, though in this case this feature does not matter
> --- and because [[ ... ]] is faster.
> 
> Bash 4.1.5:
> > time (for (( i = 0; i < 200000; i++ )); do [ -z $foo ]; done)
> real    0m3.430s
> user    0m3.240s
> sys     0m0.180s
> > time (for (( i = 0; i < 200000; i++ )); do [[ -z $foo ]]; done)
> real    0m2.219s
> user    0m2.090s
> sys     0m0.100s
> 
> Zsh 4.3.10:
> > time (for (( i = 0; i < 2000000; i++ )); do [ -z $foo ]; done)
> (; for ((i = 0; i < 2000000; i++ )) do; [ -z $foo ]; done; )  13.56s
> user 1.64s system 99% cpu 15.327 total
> > time (for (( i = 0; i < 2000000; i++ )); do [[ -z $foo ]]; done)
> (; for ((i = 0; i < 2000000; i++ )) do; [[ -z $foo ]]; done; )  4.62s
> user 0.01s system 99% cpu 4.644 total
> 
> Is there a reason to prefer [ ... ] ?

The [ command is defined by POSIX, while [[ is an extension to the shell language implemented by bash (and zsh). Thus the [[ construct would fail on a stricter POSIX implementation of the shell language, e.g., as implemented by dash. Thus to be POSIX compliant (which is required for shell scripts installed by a multi system supporting application like git), the [[ construct cannot be used.

Of course, this does not really apply to a script explicitly written for bash, but that is the common reason to prefer [ over [[ in shell scripts.

//Peter

← back to recent threads