Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

fix zsh completion

This `_hub` completion script should correctly wrap modern versions of
the `_git` scripts distributed by both zsh (native) and git
(bash-based).  The bash-based version could use a bit more love to
complete arguments to the actual subcommands.  Right now it can only
complete the names of the subcommands themselves.

The `compdef` that was previously emitted by `hub alias -s` prevents the
new `_git` script from working, so it has been eliminated.

Fixes #231
  • Loading branch information...
commit 563209dbd36af322203c8cfef2bb1f8adddedcf9 1 parent 27bef98
@goodell authored
Showing with 157 additions and 30 deletions.
  1. +156 −21 etc/hub.zsh_completion
  2. +1 −4 features/alias.feature
  3. +0 −5 lib/hub/commands.rb
View
177 etc/hub.zsh_completion
@@ -1,22 +1,157 @@
-# hub tab-completion script for zsh.
-# This script complements the completion script that ships with git.
+#compdef hub
+
+# Zsh will source this file when attempting to autoload the "_hub" function,
+# typically on the first attempt to complete the hub command. We define two new
+# setup helper routines (one for the zsh-distributed version, one for the
+# git-distributed, bash-based version). Then we redefine the "_hub" function to
+# call "_git" after some other interception.
#
-# vim: ft=zsh sw=2 ts=2 et
-
-# Autoload _git completion functions
-if declare -f _git > /dev/null; then
- _git
-fi
-
-if declare -f _git_commands > /dev/null; then
- _hub_commands=(
- 'alias:show shell instructions for wrapping git'
- 'pull-request:open a pull request on GitHub'
- 'fork:fork origin repo on GitHub'
- 'create:create new repo on GitHub for the current project'
- 'browse:browse the project on GitHub'
- 'compare:open GitHub compare view'
- )
- # Extend the '_git_commands' function with hub commands
- eval "$(declare -f _git_commands | sed -e 's/base_commands=(/base_commands=(${_hub_commands} /')"
-fi
+# This is pretty fragile, if you think about it. Any number of implementation
+# changes in the "_git" scripts could cause problems down the road. It would be
+# better if the stock git completions were just a bit more permissive about how
+# it allowed third-party commands to be added.
+
+(( $+functions[__hub_setup_zsh_fns] )) ||
+__hub_setup_zsh_fns () {
+ (( $+functions[_git-alias] )) ||
+ _git-alias () {
+ _arguments \
+ '-s[output shell script suitable for eval]' \
+ '1::shell:(zsh bash csh)'
+ }
+
+ (( $+functions[_git-browse] )) ||
+ _git-browse () {
+ _arguments \
+ '-u[user or user/repository]:user or user/repository:' \
+ '::subpage:(wiki commits issues)'
+ }
+
+ (( $+functions[_git-compare] )) ||
+ _git-compare () {
+ _arguments \
+ '-u[user]:user:' \
+ ':[start...]end range:'
+ }
+
+ (( $+functions[_git-create] )) ||
+ _git-create () {
+ _arguments \
+ '::name (REPOSITORY or ORGANIZATION/REPOSITORY):' \
+ '-p[make repository private]' \
+ '-d[description]:description' \
+ '-h[home page]:repository home page URL:_urls'
+ }
+
+ (( $+functions[_git-fork] )) ||
+ _git-fork () {
+ _arguments \
+ '--no-remote[do not add a remote for the new fork]'
+ }
+
+ (( $+functions[_git-pull-request] )) ||
+ _git-pull-request () {
+ _arguments \
+ '-f[force (skip check for local commits)]' \
+ '-b[base]:base ("branch", "owner\:branch", "owner/repo\:branch"):' \
+ '-h[head]:head ("branch", "owner\:branch", "owner/repo\:branch"):' \
+ - set1 \
+ '::title:' \
+ - set2 \
+ '::issue-url:_urls' \
+ - set3 \
+ '-i[issue]:issue number:'
+ }
+
+ # stash the "real" command for later
+ functions[_hub_orig_git_commands]=$functions[_git_commands]
+
+ # Replace it with our own wrapper.
+ declare -f _git_commands >& /dev/null && unfunction _git_commands
+ _git_commands () {
+ local ret=1
+ # call the original routine
+ _call_function ret _hub_orig_git_commands
+
+ # Effectively "append" our hub commands to the behavior of the original
+ # _git_commands function. Using this wrapper function approach ensures
+ # that we only offer the user the hub subcommands when the user is
+ # actually trying to complete subcommands.
+ hub_commands=(
+ alias:'show shell instructions for wrapping git'
+ pull-request:'open a pull request on GitHub'
+ fork:'fork origin repo on GitHub'
+ create:'create new repo on GitHub for the current project'
+ browse:'browse the project on GitHub'
+ compare:'open GitHub compare view'
+ )
+ _describe -t hub-commands 'hub command' hub_commands && ret=0
+
+ return ret
+ }
+}
+
+(( $+functions[__hub_setup_bash_fns] )) ||
+__hub_setup_bash_fns () {
+ # TODO more bash-style fns needed here to complete subcommand args. They take
+ # the form "_git_CMD" where "CMD" is something like "pull-request".
+
+ # Duplicate and rename the 'list_all_commands' function
+ eval "$(declare -f __git_list_all_commands | \
+ sed 's/__git_list_all_commands/__git_list_all_commands_without_hub/')"
+
+ # Wrap the 'list_all_commands' function with extra hub commands
+ __git_list_all_commands() {
+ cat <<-EOF
+alias
+pull-request
+fork
+create
+browse
+compare
+EOF
+ __git_list_all_commands_without_hub
+ }
+
+ # Ensure cached commands are cleared
+ __git_all_commands=""
+}
+
+# redefine _hub to a much smaller function in the steady state
+_hub () {
+ # only attempt to intercept the normal "_git" helper functions once
+ (( $+__hub_func_replacement_done )) ||
+ () {
+ # At this stage in the shell's execution the "_git" function has not yet
+ # been autoloaded, so the "_git_commands" or "__git_list_all_commands"
+ # functions will not be defined. Call it now (with a bogus no-op service
+ # to prevent premature completion) so that we can wrap them.
+ if declare -f _git >& /dev/null ; then
+ _hub_noop () { __hub_zsh_provided=1 } # zsh-provided will call this one
+ __hub_noop_main () { __hub_git_provided=1 } # git-provided will call this one
+ local service=hub_noop
+ _git
+ unfunction _hub_noop
+ unfunction __hub_noop_main
+ service=git
+ fi
+
+ if (( $__hub_zsh_provided )) ; then
+ __hub_setup_zsh_fns
+ elif (( $__hub_git_provided )) ; then
+ __hub_setup_bash_fns
+ fi
+
+ __hub_func_replacement_done=1
+ }
+
+ # Now perform the actual completion, allowing the "_git" function to call our
+ # replacement "_git_commands" function as needed. Both versions expect
+ # service=git or they will call nonexistent routines or end up in an infinite
+ # loop.
+ service=git
+ declare -f _git >& /dev/null && _git
+}
+
+# make sure we actually attempt to complete on the first "tab" from the user
+_hub
View
5 features/alias.feature
@@ -33,10 +33,7 @@ Feature: hub alias
When I successfully run `hub alias -s`
Then the output should contain exactly:
"""
- alias git=hub
- if type compdef >/dev/null; then
- compdef hub=git
- fi\n
+ alias git=hub\n
"""
Scenario: unsupported shell
View
5 lib/hub/commands.rb
@@ -676,11 +676,6 @@ def alias(args)
if script
puts "alias git=hub"
- if 'zsh' == shell
- puts "if type compdef >/dev/null; then"
- puts " compdef hub=git"
- puts "fi"
- end
else
profile = case shell
when 'bash' then '~/.bash_profile'
Please sign in to comment.
Something went wrong with that request. Please try again.