diff options
Diffstat (limited to 'contrib/completion/git-completion.bash')
-rw-r--r-- | contrib/completion/git-completion.bash | 870 |
1 files changed, 580 insertions, 290 deletions
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 00fbe6c03d..8108eda1e8 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -29,6 +29,15 @@ # tell the completion to use commit completion. This also works with aliases # of form "!sh -c '...'". For example, "!sh -c ': git commit ; ... '". # +# If you have a command that is not part of git, but you would still +# like completion, you can use __git_complete: +# +# __git_complete gl git_log +# +# Or if it's a main command (i.e. git or gitk): +# +# __git_complete gk gitk +# # Compatible with bash 3.2.57. # # You can set the following environment variables to influence the behavior of @@ -39,6 +48,11 @@ # When set to "1", do not include "DWIM" suggestions in git-checkout # and git-switch completion (e.g., completing "foo" when "origin/foo" # exists). +# +# GIT_COMPLETION_SHOW_ALL +# +# When set to "1" suggest all options, including options which are +# typically hidden (e.g. '--allow-empty' for 'git commit'). case "$COMP_WORDBREAKS" in *:*) : great ;; @@ -50,7 +64,7 @@ esac # variable. __git_find_repo_path () { - if [ -n "$__git_repo_path" ]; then + if [ -n "${__git_repo_path-}" ]; then # we already know where it is return fi @@ -63,7 +77,7 @@ __git_find_repo_path () test -d "$__git_dir" && __git_repo_path="$__git_dir" elif [ -n "${GIT_DIR-}" ]; then - test -d "${GIT_DIR-}" && + test -d "$GIT_DIR" && __git_repo_path="$GIT_DIR" elif [ -d .git ]; then __git_repo_path=.git @@ -301,6 +315,19 @@ __gitcomp_direct () COMPREPLY=($1) } +# Similar to __gitcomp_direct, but appends to COMPREPLY instead. +# Callers must take care of providing only words that match the current word +# to be completed and adding any prefix and/or suffix (trailing space!), if +# necessary. +# 1: List of newline-separated matching completion words, complete with +# prefix and suffix. +__gitcomp_direct_append () +{ + local IFS=$'\n' + + COMPREPLY+=($1) +} + __gitcompappend () { local x i=${#COMPREPLY[@]} @@ -329,7 +356,7 @@ __gitcomp () local cur_="${3-$cur}" case "$cur_" in - --*=) + *=) ;; --no-*) local c i=0 IFS=$' \t\n' @@ -373,7 +400,7 @@ __gitcomp () # Clear the variables caching builtins' options when (re-)sourcing # the completion script. if [[ -n ${ZSH_VERSION-} ]]; then - unset $(set |sed -ne 's/^\(__gitcomp_builtin_[a-zA-Z0-9_][a-zA-Z0-9_]*\)=.*/\1/p') 2>/dev/null + unset ${(M)${(k)parameters[@]}:#__gitcomp_builtin_*} 2>/dev/null else unset $(compgen -v __gitcomp_builtin_) fi @@ -391,17 +418,23 @@ __gitcomp_builtin () # spaces must be replaced with underscore for multi-word # commands, e.g. "git remote add" becomes remote_add. local cmd="$1" - local incl="$2" - local excl="$3" + local incl="${2-}" + local excl="${3-}" - local var=__gitcomp_builtin_"${cmd/-/_}" + local var=__gitcomp_builtin_"${cmd//-/_}" local options - eval "options=\$$var" + eval "options=\${$var-}" if [ -z "$options" ]; then + local completion_helper + if [ "${GIT_COMPLETION_SHOW_ALL-}" = "1" ]; then + completion_helper="--git-completion-helper-all" + else + completion_helper="--git-completion-helper" + fi # leading and trailing spaces are significant to make # option removal work correctly. - options=" $incl $(__git ${cmd/_/ } --git-completion-helper) " || return + options=" $incl $(__git ${cmd/_/ } $completion_helper) " || return for i in $excl; do options="${options/ $i / }" @@ -504,7 +537,7 @@ __git_index_files () { local root="$2" match="$3" - __git_ls_files_helper "$root" "$1" "$match" | + __git_ls_files_helper "$root" "$1" "${match:-?}" | awk -F / -v pfx="${2//\\/\\\\}" '{ paths[$1] = 1 } @@ -550,7 +583,7 @@ __git_index_files () esc_idx, 1) } else if (esc == "n") { # Uh-oh, a newline character. - # We cant reliably put a pathname + # We cannot reliably put a pathname # containing a newline into COMPREPLY, # and the newline would create a mess. # Skip this path. @@ -565,7 +598,7 @@ __git_index_files () } } # Drop closing double quote, if there is one. - # (There isnt any if this is a directory, as it was + # (There is not any if this is a directory, as it was # already stripped with the trailing path components.) if (substr(p, length(p), 1) == "\"") out = out substr(p, 1, length(p) - 1) @@ -611,6 +644,19 @@ __git_heads () "refs/heads/$cur_*" "refs/heads/$cur_*/**" } +# Lists branches from remote repositories. +# 1: A prefix to be added to each listed branch (optional). +# 2: List only branches matching this word (optional; list all branches if +# unset or empty). +# 3: A suffix to be appended to each listed branch (optional). +__git_remote_heads () +{ + local pfx="${1-}" cur_="${2-}" sfx="${3-}" + + __git for-each-ref --format="${pfx//\%/%%}%(refname:strip=2)$sfx" \ + "refs/remotes/$cur_*" "refs/remotes/$cur_*/**" +} + # Lists tags from the local repository. # Accepts the same positional parameters as __git_heads() above. __git_tags () @@ -621,6 +667,26 @@ __git_tags () "refs/tags/$cur_*" "refs/tags/$cur_*/**" } +# List unique branches from refs/remotes used for 'git checkout' and 'git +# switch' tracking DWIMery. +# 1: A prefix to be added to each listed branch (optional) +# 2: List only branches matching this word (optional; list all branches if +# unset or empty). +# 3: A suffix to be appended to each listed branch (optional). +__git_dwim_remote_heads () +{ + local pfx="${1-}" cur_="${2-}" sfx="${3-}" + local fer_pfx="${pfx//\%/%%}" # "escape" for-each-ref format specifiers + + # employ the heuristic used by git checkout and git switch + # Try to find a remote branch that cur_es the completion word + # but only output if the branch name is unique + __git for-each-ref --format="$fer_pfx%(refname:strip=3)$sfx" \ + --sort="refname:strip=3" \ + "refs/remotes/*/$cur_*" "refs/remotes/*/$cur_*/**" | \ + uniq -u +} + # Lists refs from the local (by default) or from a remote repository. # It accepts 0, 1 or 2 arguments: # 1: The remote to list refs from (optional; ignored, if set but empty). @@ -678,7 +744,7 @@ __git_refs () track="" ;; *) - for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD REBASE_HEAD; do + for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD REBASE_HEAD CHERRY_PICK_HEAD; do case "$i" in $match*) if [ -e "$dir/$i" ]; then @@ -696,13 +762,7 @@ __git_refs () __git_dir="$dir" __git for-each-ref --format="$fer_pfx%($format)$sfx" \ "${refs[@]}" if [ -n "$track" ]; then - # employ the heuristic used by git checkout - # Try to find a remote branch that matches the completion word - # but only output if the branch name is unique - __git for-each-ref --format="$fer_pfx%(refname:strip=3)$sfx" \ - --sort="refname:strip=3" \ - "refs/remotes/*/$match*" "refs/remotes/*/$match*/**" | \ - uniq -u + __git_dwim_remote_heads "$pfx" "$match" "$sfx" fi return fi @@ -749,29 +809,51 @@ __git_refs () # Usage: __git_complete_refs [<option>]... # --remote=<remote>: The remote to list refs from, can be the name of a # configured remote, a path, or a URL. -# --track: List unique remote branches for 'git checkout's tracking DWIMery. +# --dwim: List unique remote branches for 'git switch's tracking DWIMery. # --pfx=<prefix>: A prefix to be added to each ref. # --cur=<word>: The current ref to be completed. Defaults to the current # word to be completed. # --sfx=<suffix>: A suffix to be appended to each ref instead of the default # space. +# --mode=<mode>: What set of refs to complete, one of 'refs' (the default) to +# complete all refs, 'heads' to complete only branches, or +# 'remote-heads' to complete only remote branches. Note that +# --remote is only compatible with --mode=refs. __git_complete_refs () { - local remote track pfx cur_="$cur" sfx=" " + local remote= dwim= pfx= cur_="$cur" sfx=" " mode="refs" while test $# != 0; do case "$1" in --remote=*) remote="${1##--remote=}" ;; - --track) track="yes" ;; + --dwim) dwim="yes" ;; + # --track is an old spelling of --dwim + --track) dwim="yes" ;; --pfx=*) pfx="${1##--pfx=}" ;; --cur=*) cur_="${1##--cur=}" ;; --sfx=*) sfx="${1##--sfx=}" ;; + --mode=*) mode="${1##--mode=}" ;; *) return 1 ;; esac shift done - __gitcomp_direct "$(__git_refs "$remote" "$track" "$pfx" "$cur_" "$sfx")" + # complete references based on the specified mode + case "$mode" in + refs) + __gitcomp_direct "$(__git_refs "$remote" "" "$pfx" "$cur_" "$sfx")" ;; + heads) + __gitcomp_direct "$(__git_heads "$pfx" "$cur_" "$sfx")" ;; + remote-heads) + __gitcomp_direct "$(__git_remote_heads "$pfx" "$cur_" "$sfx")" ;; + *) + return 1 ;; + esac + + # Append DWIM remote branch names if requested + if [ "$dwim" = "yes" ]; then + __gitcomp_direct_append "$(__git_dwim_remote_heads "$pfx" "$cur_" "$sfx")" + fi } # __git_refs2 requires 1 argument (to pass to __git_refs) @@ -924,8 +1006,8 @@ __git_complete_revlist () __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" cmd="${words[__git_cmd_idx]}" + local i c=$((__git_cmd_idx+1)) remote="" pfx="" lhs=1 no_complete_refspec=0 if [ "$cmd" = "remote" ]; then ((c++)) fi @@ -1047,37 +1129,72 @@ __git_pretty_aliases () # __git_aliased_command requires 1 argument __git_aliased_command () { - local word cmdline=$(__git config --get "alias.$1") - for word in $cmdline; do - case "$word" in - \!gitk|gitk) - echo "gitk" - return - ;; - \!*) : shell command alias ;; - -*) : option ;; - *=*) : setting env ;; - git) : git itself ;; - \(\)) : skip parens of shell function definition ;; - {) : skip start of shell helper function ;; - :) : skip null command ;; - \'*) : skip opening quote after sh -c ;; - *) - echo "$word" + local cur=$1 last list= word cmdline + + while [[ -n "$cur" ]]; do + if [[ "$list" == *" $cur "* ]]; then + # loop detected return - esac + fi + + cmdline=$(__git config --get "alias.$cur") + list=" $cur $list" + last=$cur + cur= + + for word in $cmdline; do + case "$word" in + \!gitk|gitk) + cur="gitk" + break + ;; + \!*) : shell command alias ;; + -*) : option ;; + *=*) : setting env ;; + git) : git itself ;; + \(\)) : skip parens of shell function definition ;; + {) : skip start of shell helper function ;; + :) : skip null command ;; + \'*) : skip opening quote after sh -c ;; + *) + cur="$word" + break + esac + done done + + cur=$last + if [[ "$cur" != "$1" ]]; then + echo "$cur" + fi } -# __git_find_on_cmdline requires 1 argument +# Check whether one of the given words is present on the command line, +# and print the first word found. +# +# Usage: __git_find_on_cmdline [<option>]... "<wordlist>" +# --show-idx: Optionally show the index of the found word in the $words array. __git_find_on_cmdline () { - local word subcommand c=1 + local word c="$__git_cmd_idx" show_idx + + while test $# -gt 1; do + case "$1" in + --show-idx) show_idx=y ;; + *) return 1 ;; + esac + shift + done + local wordlist="$1" + while [ $c -lt $cword ]; do - word="${words[c]}" - for subcommand in $1; do - if [ "$subcommand" = "$word" ]; then - echo "$subcommand" + for word in $wordlist; do + if [ "$word" = "${words[c]}" ]; then + if [ -n "${show_idx-}" ]; then + echo "$c $word" + else + echo "$word" + fi return fi done @@ -1085,6 +1202,40 @@ __git_find_on_cmdline () done } +# Similar to __git_find_on_cmdline, except that it loops backwards and thus +# prints the *last* word found. Useful for finding which of two options that +# supersede each other came last, such as "--guess" and "--no-guess". +# +# Usage: __git_find_last_on_cmdline [<option>]... "<wordlist>" +# --show-idx: Optionally show the index of the found word in the $words array. +__git_find_last_on_cmdline () +{ + local word c=$cword show_idx + + while test $# -gt 1; do + case "$1" in + --show-idx) show_idx=y ;; + *) return 1 ;; + esac + shift + done + local wordlist="$1" + + while [ $c -gt "$__git_cmd_idx" ]; do + ((c--)) + for word in $wordlist; do + if [ "$word" = "${words[c]}" ]; then + if [ -n "$show_idx" ]; then + echo "$c $word" + else + echo "$word" + fi + return + fi + done + done +} + # Echo the value of an option set on the command line or config # # $1: short option name @@ -1155,7 +1306,7 @@ __git_count_arguments () local word i c=0 # Skip "git" (first argument) - for ((i=1; i < ${#words[@]}; i++)); do + for ((i=$__git_cmd_idx; i < ${#words[@]}; i++)); do word="${words[i]}" case "$word" in @@ -1180,7 +1331,9 @@ __git_count_arguments () __git_whitespacelist="nowarn warn error error-all fix" __git_patchformat="mbox stgit stgit-series hg mboxrd" +__git_showcurrentpatch="diff raw" __git_am_inprogress_options="--skip --continue --resolved --abort --quit --show-current-patch" +__git_quoted_cr="nowarn warn strip" _git_am () { @@ -1198,6 +1351,14 @@ _git_am () __gitcomp "$__git_patchformat" "" "${cur##--patch-format=}" return ;; + --show-current-patch=*) + __gitcomp "$__git_showcurrentpatch" "" "${cur##--show-current-patch=}" + return + ;; + --quoted-cr=*) + __gitcomp "$__git_quoted_cr" "" "${cur##--quoted-cr=}" + return + ;; --*) __gitcomp_builtin am "" \ "$__git_am_inprogress_options" @@ -1286,13 +1447,15 @@ __git_ref_fieldlist="refname objecttype objectsize objectname upstream push HEAD _git_branch () { - local i c=1 only_local_ref="n" has_r="n" + local i c="$__git_cmd_idx" only_local_ref="n" has_r="n" while [ $c -lt $cword ]; do i="${words[c]}" case "$i" in - -d|--delete|-m|--move) only_local_ref="y" ;; - -r|--remotes) has_r="y" ;; + -d|-D|--delete|-m|-M|--move|-c|-C|--copy) + only_local_ref="y" ;; + -r|--remotes) + has_r="y" ;; esac ((c++)) done @@ -1316,12 +1479,12 @@ _git_branch () _git_bundle () { - local cmd="${words[2]}" + local cmd="${words[__git_cmd_idx+1]}" case "$cword" in - 2) + $((__git_cmd_idx+1))) __gitcomp "create list-heads verify unbundle" ;; - 3) + $((__git_cmd_idx+2))) # looking for a file ;; *) @@ -1334,10 +1497,73 @@ _git_bundle () esac } +# Helper function to decide whether or not we should enable DWIM logic for +# git-switch and git-checkout. +# +# To decide between the following rules in decreasing priority order: +# - the last provided of "--guess" or "--no-guess" explicitly enable or +# disable completion of DWIM logic respectively. +# - If checkout.guess is false, disable completion of DWIM logic. +# - If the --no-track option is provided, take this as a hint to disable the +# DWIM completion logic +# - If GIT_COMPLETION_CHECKOUT_NO_GUESS is set, disable the DWIM completion +# logic, as requested by the user. +# - Enable DWIM logic otherwise. +# +__git_checkout_default_dwim_mode () +{ + local last_option dwim_opt="--dwim" + + if [ "${GIT_COMPLETION_CHECKOUT_NO_GUESS-}" = "1" ]; then + dwim_opt="" + fi + + # --no-track disables DWIM, but with lower priority than + # --guess/--no-guess/checkout.guess + if [ -n "$(__git_find_on_cmdline "--no-track")" ]; then + dwim_opt="" + fi + + # checkout.guess = false disables DWIM, but with lower priority than + # --guess/--no-guess + if [ "$(__git config --type=bool checkout.guess)" = "false" ]; then + dwim_opt="" + fi + + # Find the last provided --guess or --no-guess + last_option="$(__git_find_last_on_cmdline "--guess --no-guess")" + case "$last_option" in + --guess) + dwim_opt="--dwim" + ;; + --no-guess) + dwim_opt="" + ;; + esac + + echo "$dwim_opt" +} + _git_checkout () { __git_has_doubledash && return + local dwim_opt="$(__git_checkout_default_dwim_mode)" + + case "$prev" in + -b|-B|--orphan) + # Complete local branches (and DWIM branch + # remote branch names) for an option argument + # specifying a new branch name. This is for + # convenience, assuming new branches are + # possibly based on pre-existing branch names. + __git_complete_refs $dwim_opt --mode="heads" + return + ;; + *) + ;; + esac + case "$cur" in --conflict=*) __gitcomp "diff3 merge" "" "${cur##--conflict=}" @@ -1346,14 +1572,21 @@ _git_checkout () __gitcomp_builtin checkout ;; *) - # check if --track, --no-track, or --no-guess was specified - # if so, disable DWIM mode - local flags="--track --no-track --no-guess" track_opt="--track" - if [ "$GIT_COMPLETION_CHECKOUT_NO_GUESS" = "1" ] || - [ -n "$(__git_find_on_cmdline "$flags")" ]; then - track_opt='' + # At this point, we've already handled special completion for + # the arguments to -b/-B, and --orphan. There are 3 main + # things left we can possibly complete: + # 1) a start-point for -b/-B, -d/--detach, or --orphan + # 2) a remote head, for --track + # 3) an arbitrary reference, possibly including DWIM names + # + + if [ -n "$(__git_find_on_cmdline "-b -B -d --detach --orphan")" ]; then + __git_complete_refs --mode="refs" + elif [ -n "$(__git_find_on_cmdline "--track")" ]; then + __git_complete_refs --mode="remote-heads" + else + __git_complete_refs $dwim_opt --mode="refs" fi - __git_complete_refs $track_opt ;; esac } @@ -1470,9 +1703,16 @@ __git_diff_algorithms="myers minimal patience histogram" __git_diff_submodule_formats="diff log short" +__git_color_moved_opts="no default plain blocks zebra dimmed-zebra" + +__git_color_moved_ws_opts="no ignore-space-at-eol ignore-space-change + ignore-all-space allow-indentation-change" + __git_diff_common_options="--stat --numstat --shortstat --summary --patch-with-stat --name-only --name-status --color --no-color --color-words --no-renames --check + --color-moved --color-moved= --no-color-moved + --color-moved-ws= --no-color-moved-ws --full-index --binary --abbrev --diff-filter= --find-copies-harder --ignore-cr-at-eol --text --ignore-space-at-eol --ignore-space-change @@ -1488,8 +1728,14 @@ __git_diff_common_options="--stat --numstat --shortstat --summary --submodule --submodule= --ignore-submodules --indent-heuristic --no-indent-heuristic --textconv --no-textconv + --patch --no-patch + --anchored= " +__git_diff_difftool_options="--cached --staged --pickaxe-all --pickaxe-regex + --base --ours --theirs --no-index --relative --merge-base + $__git_diff_common_options" + _git_diff () { __git_has_doubledash && return @@ -1503,11 +1749,16 @@ _git_diff () __gitcomp "$__git_diff_submodule_formats" "" "${cur##--submodule=}" return ;; + --color-moved=*) + __gitcomp "$__git_color_moved_opts" "" "${cur##--color-moved=}" + return + ;; + --color-moved-ws=*) + __gitcomp "$__git_color_moved_ws_opts" "" "${cur##--color-moved-ws=}" + return + ;; --*) - __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex - --base --ours --theirs --no-index - $__git_diff_common_options - " + __gitcomp "$__git_diff_difftool_options" return ;; esac @@ -1515,8 +1766,8 @@ _git_diff () } __git_mergetools_common="diffuse diffmerge ecmerge emerge kdiff3 meld opendiff - tkdiff vimdiff gvimdiff xxdiff araxis p4merge bc - codecompare smerge + tkdiff vimdiff nvimdiff gvimdiff xxdiff araxis p4merge + bc codecompare smerge " _git_difftool () @@ -1529,11 +1780,7 @@ _git_difftool () return ;; --*) - __gitcomp_builtin difftool "$__git_diff_common_options - --base --cached --ours --theirs - --pickaxe-all --pickaxe-regex - --relative --staged - " + __gitcomp_builtin difftool "$__git_diff_difftool_options" return ;; esac @@ -1575,6 +1822,10 @@ _git_format_patch () " "" "${cur##--thread=}" return ;; + --base=*|--interdiff=*|--range-diff=*) + __git_complete_refs --cur="${cur#--*=}" + return + ;; --*) __gitcomp_builtin format-patch "$__git_format_patch_extra_options" return @@ -1595,7 +1846,7 @@ _git_fsck () _git_gitk () { - _gitk + __gitk_main } # Lists matching symbol names from a tag (as in ctags) file. @@ -1649,7 +1900,7 @@ _git_grep () esac case "$cword,$prev" in - 2,*|*,-*) + $((__git_cmd_idx+1)),*|*,-*) __git_complete_symbol && return ;; esac @@ -1665,7 +1916,7 @@ _git_help () return ;; esac - if test -n "$GIT_TESTING_ALL_COMMAND_LIST" + if test -n "${GIT_TESTING_ALL_COMMAND_LIST-}" then __gitcomp "$GIT_TESTING_ALL_COMMAND_LIST $(__git --list-cmds=alias,list-guide) gitk" else @@ -1749,7 +2000,7 @@ __git_log_shortlog_options=" --all-match --invert-grep " -__git_log_pretty_formats="oneline short medium full fuller email raw format: mboxrd" +__git_log_pretty_formats="oneline short medium full fuller reference email raw format: tformat: mboxrd" __git_log_date_formats="relative iso8601 iso8601-strict rfc2822 short local default raw unix format:" _git_log () @@ -1819,7 +2070,6 @@ _git_log () --no-walk --no-walk= --do-walk --parents --children --expand-tabs --expand-tabs= --no-expand-tabs - --patch $merge $__git_diff_common_options --pickaxe-all --pickaxe-regex @@ -2043,6 +2293,10 @@ _git_rebase () __gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}" return ;; + --onto=*) + __git_complete_refs --cur="${cur##--onto=}" + return + ;; --*) __gitcomp_builtin rebase "" \ "$__git_rebase_interactive_inprogress_options" @@ -2174,6 +2428,22 @@ _git_status () _git_switch () { + local dwim_opt="$(__git_checkout_default_dwim_mode)" + + case "$prev" in + -c|-C|--orphan) + # Complete local branches (and DWIM branch + # remote branch names) for an option argument + # specifying a new branch name. This is for + # convenience, assuming new branches are + # possibly based on pre-existing branch names. + __git_complete_refs $dwim_opt --mode="heads" + return + ;; + *) + ;; + esac + case "$cur" in --conflict=*) __gitcomp "diff3 merge" "" "${cur##--conflict=}" @@ -2182,29 +2452,26 @@ _git_switch () __gitcomp_builtin switch ;; *) - # check if --track, --no-track, or --no-guess was specified - # if so, disable DWIM mode - local track_opt="--track" only_local_ref=n - if [ "$GIT_COMPLETION_CHECKOUT_NO_GUESS" = "1" ] || - [ -n "$(__git_find_on_cmdline "--track --no-track --no-guess")" ]; then - track_opt='' - fi - # explicit --guess enables DWIM mode regardless of - # $GIT_COMPLETION_CHECKOUT_NO_GUESS - if [ -n "$(__git_find_on_cmdline "--guess")" ]; then - track_opt='--track' - fi - if [ -z "$(__git_find_on_cmdline "-d --detach")" ]; then - only_local_ref=y - else - # --guess --detach is invalid combination, no - # dwim will be done when --detach is specified - track_opt= + # Unlike in git checkout, git switch --orphan does not take + # a start point. Thus we really have nothing to complete after + # the branch name. + if [ -n "$(__git_find_on_cmdline "--orphan")" ]; then + return fi - if [ $only_local_ref = y -a -z "$track_opt" ]; then - __gitcomp_direct "$(__git_heads "" "$cur" " ")" + + # At this point, we've already handled special completion for + # -c/-C, and --orphan. There are 3 main things left to + # complete: + # 1) a start-point for -c/-C or -d/--detach + # 2) a remote head, for --track + # 3) a branch name, possibly including DWIM remote branches + + if [ -n "$(__git_find_on_cmdline "-c -C -d --detach")" ]; then + __git_complete_refs --mode="refs" + elif [ -n "$(__git_find_on_cmdline "--track")" ]; then + __git_complete_refs --mode="remote-heads" else - __git_complete_refs $track_opt + __git_complete_refs $dwim_opt --mode="heads" fi ;; esac @@ -2213,7 +2480,7 @@ _git_switch () __git_config_get_set_variables () { local prevword word config_file= c=$cword - while [ $c -gt 1 ]; do + while [ $c -gt "$__git_cmd_idx" ]; do word="${words[c]}" case "$word" in --system|--global|--local|--file=*) @@ -2383,10 +2650,10 @@ __git_complete_config_variable_name () return ;; branch.*) - local pfx="${cur%.*}." - cur_="${cur#*.}" + local pfx="${cur_%.*}." + cur_="${cur_#*.}" __gitcomp_direct "$(__git_heads "$pfx" "$cur_" ".")" - __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_" "$sfx" + __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_" "${sfx- }" return ;; guitool.*.*) @@ -2420,7 +2687,7 @@ __git_complete_config_variable_name () local pfx="${cur_%.*}." cur_="${cur_#*.}" __git_compute_all_commands - __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_" "$sfx" + __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_" "${sfx- }" return ;; remote.*.*) @@ -2436,7 +2703,7 @@ __git_complete_config_variable_name () local pfx="${cur_%.*}." cur_="${cur_#*.}" __gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "." - __gitcomp_nl_append "pushDefault" "$pfx" "$cur_" "$sfx" + __gitcomp_nl_append "pushDefault" "$pfx" "$cur_" "${sfx- }" return ;; url.*.*) @@ -2611,6 +2878,13 @@ _git_reset () _git_restore () { + case "$prev" in + -s) + __git_complete_refs + return + ;; + esac + case "$cur" in --conflict=*) __gitcomp "diff3 merge" "" "${cur##--conflict=}" @@ -2691,9 +2965,17 @@ _git_show () __gitcomp "$__git_diff_submodule_formats" "" "${cur##--submodule=}" return ;; + --color-moved=*) + __gitcomp "$__git_color_moved_opts" "" "${cur##--color-moved=}" + return + ;; + --color-moved-ws=*) + __gitcomp "$__git_color_moved_ws_opts" "" "${cur##--color-moved-ws=}" + return + ;; --*) __gitcomp "--pretty= --format= --abbrev-commit --no-abbrev-commit - --oneline --show-signature --patch + --oneline --show-signature --expand-tabs --expand-tabs= --no-expand-tabs $__git_diff_common_options " @@ -2714,72 +2996,78 @@ _git_show_branch () __git_complete_revlist } +_git_sparse_checkout () +{ + local subcommands="list init set disable" + local subcommand="$(__git_find_on_cmdline "$subcommands")" + if [ -z "$subcommand" ]; then + __gitcomp "$subcommands" + return + fi + + case "$subcommand,$cur" in + init,--*) + __gitcomp "--cone" + ;; + set,--*) + __gitcomp "--stdin" + ;; + *) + ;; + esac +} + _git_stash () { - local save_opts='--all --keep-index --no-keep-index --quiet --patch --include-untracked' local subcommands='push list show apply clear drop pop create branch' local subcommand="$(__git_find_on_cmdline "$subcommands save")" - if [ -n "$(__git_find_on_cmdline "-p")" ]; then - subcommand="push" - fi + if [ -z "$subcommand" ]; then - case "$cur" in - --*) - __gitcomp "$save_opts" + case "$((cword - __git_cmd_idx)),$cur" in + *,--*) + __gitcomp_builtin stash_push ;; - sa*) - if [ -z "$(__git_find_on_cmdline "$save_opts")" ]; then - __gitcomp "save" - fi + 1,sa*) + __gitcomp "save" ;; - *) - if [ -z "$(__git_find_on_cmdline "$save_opts")" ]; then - __gitcomp "$subcommands" - fi + 1,*) + __gitcomp "$subcommands" ;; esac - else - case "$subcommand,$cur" in - push,--*) - __gitcomp "$save_opts --message" - ;; - save,--*) - __gitcomp "$save_opts" - ;; - apply,--*|pop,--*) - __gitcomp "--index --quiet" - ;; - drop,--*) - __gitcomp "--quiet" - ;; - list,--*) - __gitcomp "--name-status --oneline --patch-with-stat" - ;; - show,--*|branch,--*) - ;; - branch,*) - if [ $cword -eq 3 ]; then - __git_complete_refs - else - __gitcomp_nl "$(__git stash list \ - | sed -n -e 's/:.*//p')" - fi - ;; - show,*|apply,*|drop,*|pop,*) + return + fi + + case "$subcommand,$cur" in + list,--*) + # NEEDSWORK: can we somehow unify this with the options in _git_log() and _git_show() + __gitcomp_builtin stash_list "$__git_log_common_options $__git_diff_common_options" + ;; + show,--*) + __gitcomp_builtin stash_show "$__git_diff_common_options" + ;; + *,--*) + __gitcomp_builtin "stash_$subcommand" + ;; + branch,*) + if [ $cword -eq $((__git_cmd_idx+2)) ]; then + __git_complete_refs + else __gitcomp_nl "$(__git stash list \ | sed -n -e 's/:.*//p')" - ;; - *) - ;; - esac - fi + fi + ;; + show,*|apply,*|drop,*|pop,*) + __gitcomp_nl "$(__git stash list \ + | sed -n -e 's/:.*//p')" + ;; + esac } _git_submodule () { __git_has_doubledash && return - local subcommands="add status init deinit update set-branch summary foreach sync absorbgitdirs" + local subcommands="add status init deinit update set-branch set-url summary foreach sync absorbgitdirs" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then case "$cur" in @@ -2843,6 +3131,7 @@ _git_svn () --log-window-size= --no-checkout --quiet --repack-flags --use-log-author --localtime --add-author-from + --recursive --ignore-paths= --include-paths= $remote_opts " local init_opts=" @@ -2924,7 +3213,7 @@ _git_svn () _git_tag () { - local i c=1 f=0 + local i c="$__git_cmd_idx" f=0 while [ $c -lt $cword ]; do i="${words[c]}" case "$i" in @@ -2964,33 +3253,84 @@ _git_whatchanged () _git_log } +__git_complete_worktree_paths () +{ + local IFS=$'\n' + # Generate completion reply from worktree list skipping the first + # entry: it's the path of the main worktree, which can't be moved, + # removed, locked, etc. + __gitcomp_nl "$(git worktree list --porcelain | + sed -n -e '2,$ s/^worktree //p')" +} + _git_worktree () { local subcommands="add list lock move prune remove unlock" - local subcommand="$(__git_find_on_cmdline "$subcommands")" - if [ -z "$subcommand" ]; then + local subcommand subcommand_idx + + subcommand="$(__git_find_on_cmdline --show-idx "$subcommands")" + subcommand_idx="${subcommand% *}" + subcommand="${subcommand#* }" + + case "$subcommand,$cur" in + ,*) __gitcomp "$subcommands" - else - case "$subcommand,$cur" in - add,--*) - __gitcomp_builtin worktree_add - ;; - list,--*) - __gitcomp_builtin worktree_list - ;; - lock,--*) - __gitcomp_builtin worktree_lock - ;; - prune,--*) - __gitcomp_builtin worktree_prune - ;; - remove,--*) - __gitcomp "--force" + ;; + *,--*) + __gitcomp_builtin worktree_$subcommand + ;; + add,*) # usage: git worktree add [<options>] <path> [<commit-ish>] + # Here we are not completing an --option, it's either the + # path or a ref. + case "$prev" in + -b|-B) # Complete refs for branch to be created/reseted. + __git_complete_refs ;; - *) + -*) # The previous word is an -o|--option without an + # unstuck argument: have to complete the path for + # the new worktree, so don't list anything, but let + # Bash fall back to filename completion. + ;; + *) # The previous word is not an --option, so it must + # be either the 'add' subcommand, the unstuck + # argument of an option (e.g. branch for -b|-B), or + # the path for the new worktree. + if [ $cword -eq $((subcommand_idx+1)) ]; then + # Right after the 'add' subcommand: have to + # complete the path, so fall back to Bash + # filename completion. + : + else + case "${words[cword-2]}" in + -b|-B) # After '-b <branch>': have to + # complete the path, so fall back + # to Bash filename completion. + ;; + *) # After the path: have to complete + # the ref to be checked out. + __git_complete_refs + ;; + esac + fi ;; esac - fi + ;; + lock,*|remove,*|unlock,*) + __git_complete_worktree_paths + ;; + move,*) + if [ $cword -eq $((subcommand_idx+1)) ]; then + # The first parameter must be an existing working + # tree to be moved. + __git_complete_worktree_paths + else + # The second parameter is the destination: it could + # be any path, so don't list anything, but let Bash + # fall back to filename completion. + : + fi + ;; + esac } __git_complete_common () { @@ -3018,15 +3358,19 @@ __git_support_parseopt_helper () { esac } +__git_have_func () { + declare -f -- "$1" >/dev/null 2>&1 +} + __git_complete_command () { local command="$1" local completion_func="_git_${command//-/_}" - if ! declare -f $completion_func >/dev/null 2>/dev/null && - declare -f _completion_loader >/dev/null 2>/dev/null + if ! __git_have_func $completion_func && + __git_have_func _completion_loader then _completion_loader "git-$command" fi - if declare -f $completion_func >/dev/null 2>/dev/null + if __git_have_func $completion_func then $completion_func return 0 @@ -3043,26 +3387,45 @@ __git_main () { local i c=1 command __git_dir __git_repo_path local __git_C_args C_args_count=0 + local __git_cmd_idx while [ $c -lt $cword ]; do i="${words[c]}" case "$i" in - --git-dir=*) __git_dir="${i#--git-dir=}" ;; - --git-dir) ((c++)) ; __git_dir="${words[c]}" ;; - --bare) __git_dir="." ;; - --help) command="help"; break ;; - -c|--work-tree|--namespace) ((c++)) ;; - -C) __git_C_args[C_args_count++]=-C + --git-dir=*) + __git_dir="${i#--git-dir=}" + ;; + --git-dir) + ((c++)) + __git_dir="${words[c]}" + ;; + --bare) + __git_dir="." + ;; + --help) + command="help" + break + ;; + -c|--work-tree|--namespace) + ((c++)) + ;; + -C) + __git_C_args[C_args_count++]=-C ((c++)) __git_C_args[C_args_count++]="${words[c]}" ;; - -*) ;; - *) command="$i"; break ;; + -*) + ;; + *) + command="$i" + __git_cmd_idx="$c" + break + ;; esac ((c++)) done - if [ -z "$command" ]; then + if [ -z "${command-}" ]; then case "$prev" in --git-dir|-C|--work-tree) # these need a path argument, let's fall back to @@ -3079,7 +3442,8 @@ __git_main () ;; esac case "$cur" in - --*) __gitcomp " + --*) + __gitcomp " --paginate --no-pager --git-dir= @@ -3097,7 +3461,7 @@ __git_main () " ;; *) - if test -n "$GIT_TESTING_PORCELAIN_COMMAND_LIST" + if test -n "${GIT_TESTING_PORCELAIN_COMMAND_LIST-}" then __gitcomp "$GIT_TESTING_PORCELAIN_COMMAND_LIST" else @@ -3141,102 +3505,20 @@ __gitk_main () __git_complete_revlist } -if [[ -n ${ZSH_VERSION-} ]] && - # Don't define these functions when sourced from 'git-completion.zsh', - # it has its own implementations. - [[ -z ${GIT_SOURCING_ZSH_COMPLETION-} ]]; then - echo "WARNING: this script is deprecated, please see git-completion.zsh" 1>&2 - - autoload -U +X compinit && compinit - - __gitcomp () - { - emulate -L zsh - - local cur_="${3-$cur}" - - case "$cur_" in - --*=) - ;; - *) - local c IFS=$' \t\n' - local -a array - for c in ${=1}; do - c="$c${4-}" - case $c in - --*=*|*.) ;; - *) c="$c " ;; - esac - array[${#array[@]}+1]="$c" - done - compset -P '*[=:]' - compadd -Q -S '' -p "${2-}" -a -- array && _ret=0 - ;; - esac - } - - __gitcomp_direct () - { - emulate -L zsh - - local IFS=$'\n' - compset -P '*[=:]' - compadd -Q -- ${=1} && _ret=0 - } - - __gitcomp_nl () - { - emulate -L zsh - - local IFS=$'\n' - compset -P '*[=:]' - compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0 - } - - __gitcomp_file_direct () - { - emulate -L zsh - - local IFS=$'\n' - compset -P '*[=:]' - compadd -f -- ${=1} && _ret=0 - } - - __gitcomp_file () - { - emulate -L zsh - - local IFS=$'\n' - compset -P '*[=:]' - compadd -p "${2-}" -f -- ${=1} && _ret=0 - } - - _git () - { - local _ret=1 cur cword prev - cur=${words[CURRENT]} - prev=${words[CURRENT-1]} - let cword=CURRENT-1 - emulate ksh -c __${service}_main - let _ret && _default && _ret=0 - return _ret - } - - compdef _git git gitk +if [[ -n ${ZSH_VERSION-} && -z ${GIT_SOURCING_ZSH_COMPLETION-} ]]; then + echo "ERROR: this script is obsolete, please see git-completion.zsh" 1>&2 return fi __git_func_wrap () { local cur words cword prev + local __git_cmd_idx=0 _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 () +___git_complete () { local wrapper="__git_wrap${2}" eval "$wrapper () { __git_func_wrap $2 ; }" @@ -3244,25 +3526,33 @@ __git_complete () || complete -o default -o nospace -F $wrapper $1 } -# wrapper for backwards compatibility -_git () +# Setup the completion for git commands +# 1: command or alias +# 2: function to call (e.g. `git`, `gitk`, `git_fetch`) +__git_complete () { - __git_wrap__git_main -} + local func -# wrapper for backwards compatibility -_gitk () -{ - __git_wrap__gitk_main + if __git_have_func $2; then + func=$2 + elif __git_have_func __$2_main; then + func=__$2_main + elif __git_have_func _$2; then + func=_$2 + else + echo "ERROR: could not find function '$2'" 1>&2 + return 1 + fi + ___git_complete $1 $func } -__git_complete git __git_main -__git_complete gitk __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 -__git_complete git.exe __git_main +if [ "$OSTYPE" = cygwin ]; then + ___git_complete git.exe __git_main fi |