push: require force for annotated tags
[gitweb.git] / contrib / completion / git-completion.bash
old mode 100755 (executable)
new mode 100644 (file)
index bacf403..bc0657a
 #    1) Copy this file to somewhere (e.g. ~/.git-completion.sh).
 #    2) Add the following line to your .bashrc/.zshrc:
 #        source ~/.git-completion.sh
-#
-#    3) Consider changing your PS1 to also show the current branch:
-#         Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
-#         ZSH:  PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
-#
-#       The argument to __git_ps1 will be displayed only if you
-#       are currently in a git repository.  The %s token will be
-#       the name of the current branch.
-#
-#       In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty
-#       value, unstaged (*) and staged (+) changes will be shown next
-#       to the branch name.  You can configure this per-repository
-#       with the bash.showDirtyState variable, which defaults to true
-#       once GIT_PS1_SHOWDIRTYSTATE is enabled.
-#
-#       You can also see if currently something is stashed, by setting
-#       GIT_PS1_SHOWSTASHSTATE to a nonempty value. If something is stashed,
-#       then a '$' will be shown next to the branch name.
-#
-#       If you would like to see if there're untracked files, then you can
-#       set GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're
-#       untracked files, then a '%' will be shown next to the branch name.
-#
-#       If you would like to see the difference between HEAD and its
-#       upstream, set GIT_PS1_SHOWUPSTREAM="auto".  A "<" indicates
-#       you are behind, ">" indicates you are ahead, and "<>"
-#       indicates you have diverged.  You can further control
-#       behaviour by setting GIT_PS1_SHOWUPSTREAM to a space-separated
-#       list of values:
-#           verbose       show number of commits ahead/behind (+/-) upstream
-#           legacy        don't use the '--count' option available in recent
-#                         versions of git-rev-list
-#           git           always compare HEAD to @{upstream}
-#           svn           always compare HEAD to your SVN upstream
-#       By default, __git_ps1 will compare HEAD to your SVN upstream
-#       if it can find one, or @{upstream} otherwise.  Once you have
-#       set GIT_PS1_SHOWUPSTREAM, you can override it on a
-#       per-repository basis by setting the bash.showUpstream config
-#       variable.
-#
-#
-# To submit patches:
-#
-#    *) Read Documentation/SubmittingPatches
-#    *) Send all patches to the current maintainer:
-#
-#       "Shawn O. Pearce" <spearce@spearce.org>
-#
-#    *) Always CC the Git mailing list:
-#
-#       git@vger.kernel.org
-#
+#    3) Consider changing your PS1 to also show the current branch,
+#       see git-prompt.sh for details.
 
 if [[ -n ${ZSH_VERSION-} ]]; then
        autoload -U +X bashcompinit && bashcompinit
@@ -86,9 +36,14 @@ esac
 # returns location of .git repo
 __gitdir ()
 {
+       # Note: this function is duplicated in git-prompt.sh
+       # When updating it, make sure you update the other one to match.
        if [ -z "${1-}" ]; then
                if [ -n "${__git_dir-}" ]; then
                        echo "$__git_dir"
+               elif [ -n "${GIT_DIR-}" ]; then
+                       test -d "${GIT_DIR-}" || return 1
+                       echo "$GIT_DIR"
                elif [ -d .git ]; then
                        echo .git
                else
@@ -101,232 +56,16 @@ __gitdir ()
        fi
 }
 
-# stores the divergence from upstream in $p
-# used by GIT_PS1_SHOWUPSTREAM
-__git_ps1_show_upstream ()
-{
-       local key value
-       local svn_remote=() svn_url_pattern count n
-       local upstream=git legacy="" verbose=""
-
-       # get some config options from git-config
-       local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')"
-       while read -r key value; do
-               case "$key" in
-               bash.showupstream)
-                       GIT_PS1_SHOWUPSTREAM="$value"
-                       if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
-                               p=""
-                               return
-                       fi
-                       ;;
-               svn-remote.*.url)
-                       svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
-                       svn_url_pattern+="\\|$value"
-                       upstream=svn+git # default upstream is SVN if available, else git
-                       ;;
-               esac
-       done <<< "$output"
-
-       # parse configuration values
-       for option in ${GIT_PS1_SHOWUPSTREAM}; do
-               case "$option" in
-               git|svn) upstream="$option" ;;
-               verbose) verbose=1 ;;
-               legacy)  legacy=1  ;;
-               esac
-       done
-
-       # Find our upstream
-       case "$upstream" in
-       git)    upstream="@{upstream}" ;;
-       svn*)
-               # 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>/dev/null))
-               if [[ 0 -ne ${#svn_upstream[@]} ]]; then
-                       svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
-                       svn_upstream=${svn_upstream%@*}
-                       local n_stop="${#svn_remote[@]}"
-                       for ((n=1; n <= n_stop; ++n)); do
-                               svn_upstream=${svn_upstream#${svn_remote[$n]}}
-                       done
-
-                       if [[ -z "$svn_upstream" ]]; then
-                               # default branch name for checkouts with no layout:
-                               upstream=${GIT_SVN_ID:-git-svn}
-                       else
-                               upstream=${svn_upstream#/}
-                       fi
-               elif [[ "svn+git" = "$upstream" ]]; then
-                       upstream="@{upstream}"
-               fi
-               ;;
-       esac
-
-       # Find how many commits we are ahead/behind our upstream
-       if [[ -z "$legacy" ]]; then
-               count="$(git rev-list --count --left-right \
-                               "$upstream"...HEAD 2>/dev/null)"
-       else
-               # produce equivalent output to --count for older versions of git
-               local commits
-               if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)"
-               then
-                       local commit behind=0 ahead=0
-                       for commit in $commits
-                       do
-                               case "$commit" in
-                               "<"*) let ++behind
-                                       ;;
-                               *)    let ++ahead
-                                       ;;
-                               esac
-                       done
-                       count="$behind  $ahead"
-               else
-                       count=""
-               fi
-       fi
-
-       # calculate the result
-       if [[ -z "$verbose" ]]; then
-               case "$count" in
-               "") # no upstream
-                       p="" ;;
-               "0      0") # equal to upstream
-                       p="=" ;;
-               "0      "*) # ahead of upstream
-                       p=">" ;;
-               *"      0") # behind upstream
-                       p="<" ;;
-               *)          # diverged from upstream
-                       p="<>" ;;
-               esac
-       else
-               case "$count" in
-               "") # no upstream
-                       p="" ;;
-               "0      0") # equal to upstream
-                       p=" u=" ;;
-               "0      "*) # ahead of upstream
-                       p=" u+${count#0 }" ;;
-               *"      0") # behind upstream
-                       p=" u-${count%  0}" ;;
-               *)          # diverged from upstream
-                       p=" u+${count#* }-${count%      *}" ;;
-               esac
-       fi
-
-}
-
-
-# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
-# returns text to add to bash PS1 prompt (includes branch name)
-__git_ps1 ()
-{
-       local g="$(__gitdir)"
-       if [ -n "$g" ]; then
-               local r=""
-               local b=""
-               if [ -f "$g/rebase-merge/interactive" ]; then
-                       r="|REBASE-i"
-                       b="$(cat "$g/rebase-merge/head-name")"
-               elif [ -d "$g/rebase-merge" ]; then
-                       r="|REBASE-m"
-                       b="$(cat "$g/rebase-merge/head-name")"
-               else
-                       if [ -d "$g/rebase-apply" ]; then
-                               if [ -f "$g/rebase-apply/rebasing" ]; then
-                                       r="|REBASE"
-                               elif [ -f "$g/rebase-apply/applying" ]; then
-                                       r="|AM"
-                               else
-                                       r="|AM/REBASE"
-                               fi
-                       elif [ -f "$g/MERGE_HEAD" ]; then
-                               r="|MERGING"
-                       elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
-                               r="|CHERRY-PICKING"
-                       elif [ -f "$g/BISECT_LOG" ]; then
-                               r="|BISECTING"
-                       fi
-
-                       b="$(git symbolic-ref HEAD 2>/dev/null)" || {
-
-                               b="$(
-                               case "${GIT_PS1_DESCRIBE_STYLE-}" in
-                               (contains)
-                                       git describe --contains HEAD ;;
-                               (branch)
-                                       git describe --contains --all HEAD ;;
-                               (describe)
-                                       git describe HEAD ;;
-                               (* | default)
-                                       git describe --tags --exact-match HEAD ;;
-                               esac 2>/dev/null)" ||
-
-                               b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." ||
-                               b="unknown"
-                               b="($b)"
-                       }
-               fi
-
-               local w=""
-               local i=""
-               local s=""
-               local u=""
-               local c=""
-               local p=""
-
-               if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
-                       if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then
-                               c="BARE:"
-                       else
-                               b="GIT_DIR!"
-                       fi
-               elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
-                       if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then
-                               if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then
-                                       git diff --no-ext-diff --quiet --exit-code || w="*"
-                                       if git rev-parse --quiet --verify HEAD >/dev/null; then
-                                               git diff-index --cached --quiet HEAD -- || i="+"
-                                       else
-                                               i="#"
-                                       fi
-                               fi
-                       fi
-                       if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
-                               git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
-                       fi
-
-                       if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then
-                          if [ -n "$(git ls-files --others --exclude-standard)" ]; then
-                             u="%"
-                          fi
-                       fi
-
-                       if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
-                               __git_ps1_show_upstream
-                       fi
-               fi
-
-               local f="$w$i$s$u"
-               printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
-       fi
-}
-
-# __gitcomp_1 requires 2 arguments
 __gitcomp_1 ()
 {
-       local c IFS=' '$'\t'$'\n'
+       local c IFS=$' \t\n'
        for c in $1; do
-               case "$c$2" in
-               --*=*) printf %s$'\n' "$c$2" ;;
-               *.)    printf %s$'\n' "$c$2" ;;
-               *)     printf %s$'\n' "$c$2 " ;;
+               c="$c$2"
+               case $c in
+               --*=*|*.) ;;
+               *) c="$c " ;;
                esac
+               printf '%s\n' "$c"
        done
 }
 
@@ -495,11 +234,8 @@ fi
 # 4: A suffix to be appended to each possible completion word (optional).
 __gitcomp ()
 {
-       local cur_="$cur"
+       local cur_="${3-$cur}"
 
-       if [ $# -gt 2 ]; then
-               cur_="$3"
-       fi
        case "$cur_" in
        --*=)
                COMPREPLY=()
@@ -524,18 +260,8 @@ __gitcomp ()
 #    appended.
 __gitcomp_nl ()
 {
-       local s=$'\n' IFS=' '$'\t'$'\n'
-       local cur_="$cur" suffix=" "
-
-       if [ $# -gt 2 ]; then
-               cur_="$3"
-               if [ $# -gt 3 ]; then
-                       suffix="$4"
-               fi
-       fi
-
-       IFS=$s
-       COMPREPLY=($(compgen -P "${2-}" -S "$suffix" -W "$1" -- "$cur_"))
+       local IFS=$'\n'
+       COMPREPLY=($(compgen -P "${2-}" -S "${4- }" -W "$1" -- "${3-$cur}"))
 }
 
 __git_heads ()
@@ -643,13 +369,8 @@ __git_refs_remotes ()
 
 __git_remotes ()
 {
-       local i ngoff IFS=$'\n' d="$(__gitdir)"
-       __git_shopt -q nullglob || ngoff=1
-       __git_shopt -s nullglob
-       for i in "$d/remotes"/*; do
-               echo ${i#$d/remotes/}
-       done
-       [ "$ngoff" ] && __git_shopt -u nullglob
+       local i IFS=$'\n' d="$(__gitdir)"
+       test -d "$d/remotes" && ls -1 "$d/remotes"
        for i in $(git --git-dir="$d" config --get-regexp 'remote\..*\.url' 2>/dev/null); do
                i="${i#remote.}"
                echo "${i/.url*/}"
@@ -676,7 +397,8 @@ __git_merge_strategies=
 # is needed.
 __git_compute_merge_strategies ()
 {
-       : ${__git_merge_strategies:=$(__git_list_merge_strategies)}
+       test -n "$__git_merge_strategies" ||
+       __git_merge_strategies=$(__git_list_merge_strategies)
 }
 
 __git_complete_revlist_file ()
@@ -706,9 +428,7 @@ __git_complete_revlist_file ()
                *)   pfx="$ref:$pfx" ;;
                esac
 
-               local IFS=$'\n'
-               COMPREPLY=($(compgen -P "$pfx" \
-                       -W "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \
+               __gitcomp_nl "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \
                                | sed '/^100... blob /{
                                           s,^.*        ,,
                                           s,$, ,
@@ -722,7 +442,7 @@ __git_complete_revlist_file ()
                                           s,$,/,
                                       }
                                       s/^.*    //')" \
-                       -- "$cur_"))
+                       "$pfx" "$cur_" ""
                ;;
        *...*)
                pfx="${cur_%...*}..."
@@ -755,6 +475,9 @@ __git_complete_remote_or_refspec ()
 {
        local cur_="$cur" cmd="${words[1]}"
        local i c=2 remote="" pfx="" lhs=1 no_complete_refspec=0
+       if [ "$cmd" = "remote" ]; then
+               ((c++))
+       fi
        while [ $c -lt $cword ]; do
                i="${words[c]}"
                case "$i" in
@@ -772,7 +495,7 @@ __git_complete_remote_or_refspec ()
                -*) ;;
                *) remote="$i"; break ;;
                esac
-               c=$((++c))
+               ((c++))
        done
        if [ -z "$remote" ]; then
                __gitcomp_nl "$(__git_remotes)"
@@ -805,7 +528,7 @@ __git_complete_remote_or_refspec ()
                        __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
                fi
                ;;
-       pull)
+       pull|remote)
                if [ $lhs = 1 ]; then
                        __gitcomp_nl "$(__git_refs "$remote")" "$pfx" "$cur_"
                else
@@ -854,7 +577,8 @@ __git_list_all_commands ()
 __git_all_commands=
 __git_compute_all_commands ()
 {
-       : ${__git_all_commands:=$(__git_list_all_commands)}
+       test -n "$__git_all_commands" ||
+       __git_all_commands=$(__git_list_all_commands)
 }
 
 __git_list_porcelain_commands ()
@@ -874,6 +598,8 @@ __git_list_porcelain_commands ()
                checkout-index)   : plumbing;;
                commit-tree)      : plumbing;;
                count-objects)    : infrequent;;
+               credential-cache) : credentials helper;;
+               credential-store) : credentials helper;;
                cvsexportcommit)  : export;;
                cvsimport)        : import;;
                cvsserver)        : daemon;;
@@ -947,7 +673,8 @@ __git_porcelain_commands=
 __git_compute_porcelain_commands ()
 {
        __git_compute_all_commands
-       : ${__git_porcelain_commands:=$(__git_list_porcelain_commands)}
+       test -n "$__git_porcelain_commands" ||
+       __git_porcelain_commands=$(__git_list_porcelain_commands)
 }
 
 __git_pretty_aliases ()
@@ -1010,7 +737,7 @@ __git_find_on_cmdline ()
                                return
                        fi
                done
-               c=$((++c))
+               ((c++))
        done
 }
 
@@ -1021,7 +748,7 @@ __git_has_doubledash ()
                if [ "--" = "${words[c]}" ]; then
                        return 0
                fi
-               c=$((++c))
+               ((c++))
        done
        return 1
 }
@@ -1144,15 +871,19 @@ _git_branch ()
                -d|-m)  only_local_ref="y" ;;
                -r)     has_r="y" ;;
                esac
-               c=$((++c))
+               ((c++))
        done
 
        case "$cur" in
+       --set-upstream-to=*)
+               __gitcomp "$(__git_refs)" "" "${cur##--set-upstream-to=}"
+               ;;
        --*)
                __gitcomp "
                        --color --no-color --verbose --abbrev= --no-abbrev
                        --track --no-track --contains --merged --no-merged
-                       --set-upstream
+                       --set-upstream-to= --edit-description --list
+                       --unset-upstream
                        "
                ;;
        *)
@@ -1287,7 +1018,8 @@ _git_commit ()
        --*)
                __gitcomp "
                        --all --author= --signoff --verify --no-verify
-                       --edit --amend --include --only --interactive
+                       --edit --no-edit
+                       --amend --include --only --interactive
                        --dry-run --reuse-message= --reedit-message=
                        --reset-author --file= --message= --template=
                        --cleanup= --untracked-files --untracked-files=
@@ -1344,7 +1076,7 @@ _git_diff ()
 }
 
 __git_mergetools_common="diffuse ecmerge emerge kdiff3 meld opendiff
-                       tkdiff vimdiff gvimdiff xxdiff araxis p4merge bc3
+                       tkdiff vimdiff gvimdiff xxdiff araxis p4merge bc3 codecompare
 "
 
 _git_difftool ()
@@ -1384,6 +1116,14 @@ _git_fetch ()
        __git_complete_remote_or_refspec
 }
 
+__git_format_patch_options="
+       --stdout --attach --no-attach --thread --thread= --output-directory
+       --numbered --start-number --numbered-files --keep-subject --signoff
+       --signature --no-signature --in-reply-to= --cc= --full-index --binary
+       --not --all --cover-letter --no-prefix --src-prefix= --dst-prefix=
+       --inline --suffix= --ignore-if-in-upstream --subject-prefix=
+"
+
 _git_format_patch ()
 {
        case "$cur" in
@@ -1394,21 +1134,7 @@ _git_format_patch ()
                return
                ;;
        --*)
-               __gitcomp "
-                       --stdout --attach --no-attach --thread --thread=
-                       --output-directory
-                       --numbered --start-number
-                       --numbered-files
-                       --keep-subject
-                       --signoff --signature --no-signature
-                       --in-reply-to= --cc=
-                       --full-index --binary
-                       --not --all
-                       --cover-letter
-                       --no-prefix --src-prefix= --dst-prefix=
-                       --inline --suffix= --ignore-if-in-upstream
-                       --subject-prefix=
-                       "
+               __gitcomp "$__git_format_patch_options"
                return
                ;;
        esac
@@ -1683,7 +1409,7 @@ _git_notes ()
                __gitcomp '--ref'
                ;;
        ,*)
-               case "${words[cword-1]}" in
+               case "$prev" in
                --ref)
                        __gitcomp_nl "$(__git_refs)"
                        ;;
@@ -1709,7 +1435,7 @@ _git_notes ()
        prune,*)
                ;;
        *)
-               case "${words[cword-1]}" in
+               case "$prev" in
                -m|-F)
                        ;;
                *)
@@ -1822,6 +1548,12 @@ _git_send_email ()
                __gitcomp "ssl tls" "" "${cur##--smtp-encryption=}"
                return
                ;;
+       --thread=*)
+               __gitcomp "
+                       deep shallow
+                       " "" "${cur##--thread=}"
+               return
+               ;;
        --*)
                __gitcomp "--annotate --bcc --cc --cc-cmd --chain-reply-to
                        --compose --confirm= --dry-run --envelope-sender
@@ -1831,11 +1563,12 @@ _git_send_email ()
                        --signed-off-by-cc --smtp-pass --smtp-server
                        --smtp-server-port --smtp-encryption= --smtp-user
                        --subject --suppress-cc= --suppress-from --thread --to
-                       --validate --no-validate"
+                       --validate --no-validate
+                       $__git_format_patch_options"
                return
                ;;
        esac
-       COMPREPLY=()
+       __git_complete_revlist
 }
 
 _git_stage ()
@@ -2305,7 +2038,7 @@ _git_config ()
 
 _git_remote ()
 {
-       local subcommands="add rename rm show prune update set-head"
+       local subcommands="add rename remove set-head set-branches set-url show prune update"
        local subcommand="$(__git_find_on_cmdline "$subcommands")"
        if [ -z "$subcommand" ]; then
                __gitcomp "$subcommands"
@@ -2313,9 +2046,12 @@ _git_remote ()
        fi
 
        case "$subcommand" in
-       rename|rm|show|prune)
+       rename|remove|set-url|show|prune)
                __gitcomp_nl "$(__git_remotes)"
                ;;
+       set-head|set-branches)
+               __git_complete_remote_or_refspec
+               ;;
        update)
                local i c='' IFS=$'\n'
                for i in $(git --git-dir="$(__gitdir)" config --get-regexp "remotes\..*" 2>/dev/null); do
@@ -2528,7 +2264,7 @@ _git_svn ()
                        __gitcomp "
                                --merge --strategy= --verbose --dry-run
                                --fetch-all --no-rebase --commit-url
-                               --revision $cmt_opts $fc_opts
+                               --revision --interactive $cmt_opts $fc_opts
                                "
                        ;;
                set-tree,--*)
@@ -2596,7 +2332,7 @@ _git_tag ()
                        f=1
                        ;;
                esac
-               c=$((++c))
+               ((c++))
        done
 
        case "$prev" in
@@ -2621,35 +2357,21 @@ _git_whatchanged ()
        _git_log
 }
 
-_git ()
+__git_main ()
 {
        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 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="." ;;
-               --version|-p|--paginate) ;;
                --help) command="help"; break ;;
+               -c) c=$((++c)) ;;
+               -*) ;;
                *) command="$i"; break ;;
                esac
-               c=$((++c))
+               ((c++))
        done
 
        if [ -z "$command" ]; then
@@ -2661,9 +2383,12 @@ _git ()
                        --bare
                        --version
                        --exec-path
+                       --exec-path=
                        --html-path
+                       --info-path
                        --work-tree=
                        --namespace=
+                       --no-replace-objects
                        --help
                        "
                        ;;
@@ -2683,24 +2408,8 @@ _git ()
        fi
 }
 
-_gitk ()
+__gitk_main ()
 {
-       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)"
@@ -2721,46 +2430,55 @@ _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
+__git_func_wrap ()
+{
+       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
+       $1
+}
+
+# Setup completion for certain functions defined above by setting common
+# variables and workarounds.
+# This is NOT a public function; use at your own risk.
+__git_complete ()
+{
+       local wrapper="__git_wrap${2}"
+       eval "$wrapper () { __git_func_wrap $2 ; }"
+       complete -o bashdefault -o default -o nospace -F $wrapper $1 2>/dev/null \
+               || complete -o default -o nospace -F $wrapper $1
+}
+
+# wrapper for backwards compatibility
+_git ()
+{
+       __git_wrap__git_main
+}
+
+# wrapper for backwards compatibility
+_gitk ()
+{
+       __git_wrap__gitk_main
+}
+
+__git_complete git __git_main
+__git_complete gitk __gitk_main
 
 # 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
-fi
-
-if [[ -n ${ZSH_VERSION-} ]]; then
-       __git_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
-       }
-else
-       __git_shopt () {
-               shopt "$@"
-       }
+__git_complete git.exe __git_main
 fi