@@ -301,6 +301,19 @@ __gitcomp_direct ()
301301 COMPREPLY=($1 )
302302}
303303
304+ # Similar to __gitcomp_direct, but appends to COMPREPLY instead.
305+ # Callers must take care of providing only words that match the current word
306+ # to be completed and adding any prefix and/or suffix (trailing space!), if
307+ # necessary.
308+ # 1: List of newline-separated matching completion words, complete with
309+ # prefix and suffix.
310+ __gitcomp_direct_append ()
311+ {
312+ local IFS=$' \n '
313+
314+ COMPREPLY+=($1 )
315+ }
316+
304317__gitcompappend ()
305318{
306319 local x i=${# COMPREPLY[@]}
@@ -611,6 +624,19 @@ __git_heads ()
611624 " refs/heads/$cur_ *" " refs/heads/$cur_ */**"
612625}
613626
627+ # Lists branches from remote repositories.
628+ # 1: A prefix to be added to each listed branch (optional).
629+ # 2: List only branches matching this word (optional; list all branches if
630+ # unset or empty).
631+ # 3: A suffix to be appended to each listed branch (optional).
632+ __git_remote_heads ()
633+ {
634+ local pfx=" ${1-} " cur_=" ${2-} " sfx=" ${3-} "
635+
636+ __git for-each-ref --format=" ${pfx// \% /%% } %(refname:strip=2)$sfx " \
637+ " refs/remotes/$cur_ *" " refs/remotes/$cur_ */**"
638+ }
639+
614640# Lists tags from the local repository.
615641# Accepts the same positional parameters as __git_heads() above.
616642__git_tags ()
@@ -621,6 +647,26 @@ __git_tags ()
621647 " refs/tags/$cur_ *" " refs/tags/$cur_ */**"
622648}
623649
650+ # List unique branches from refs/remotes used for 'git checkout' and 'git
651+ # switch' tracking DWIMery.
652+ # 1: A prefix to be added to each listed branch (optional)
653+ # 2: List only branches matching this word (optional; list all branches if
654+ # unset or empty).
655+ # 3: A suffix to be appended to each listed branch (optional).
656+ __git_dwim_remote_heads ()
657+ {
658+ local pfx=" ${1-} " cur_=" ${2-} " sfx=" ${3-} "
659+ local fer_pfx=" ${pfx// \% /%% } " # "escape" for-each-ref format specifiers
660+
661+ # employ the heuristic used by git checkout and git switch
662+ # Try to find a remote branch that cur_es the completion word
663+ # but only output if the branch name is unique
664+ __git for-each-ref --format=" $fer_pfx %(refname:strip=3)$sfx " \
665+ --sort=" refname:strip=3" \
666+ " refs/remotes/*/$cur_ *" " refs/remotes/*/$cur_ */**" | \
667+ uniq -u
668+ }
669+
624670# Lists refs from the local (by default) or from a remote repository.
625671# It accepts 0, 1 or 2 arguments:
626672# 1: The remote to list refs from (optional; ignored, if set but empty).
@@ -696,13 +742,7 @@ __git_refs ()
696742 __git_dir=" $dir " __git for-each-ref --format=" $fer_pfx %($format )$sfx " \
697743 " ${refs[@]} "
698744 if [ -n " $track " ]; then
699- # employ the heuristic used by git checkout
700- # Try to find a remote branch that matches the completion word
701- # but only output if the branch name is unique
702- __git for-each-ref --format=" $fer_pfx %(refname:strip=3)$sfx " \
703- --sort=" refname:strip=3" \
704- " refs/remotes/*/$match *" " refs/remotes/*/$match */**" | \
705- uniq -u
745+ __git_dwim_remote_heads " $pfx " " $match " " $sfx "
706746 fi
707747 return
708748 fi
@@ -749,29 +789,51 @@ __git_refs ()
749789# Usage: __git_complete_refs [<option>]...
750790# --remote=<remote>: The remote to list refs from, can be the name of a
751791# configured remote, a path, or a URL.
752- # --track : List unique remote branches for 'git checkout 's tracking DWIMery.
792+ # --dwim : List unique remote branches for 'git switch 's tracking DWIMery.
753793# --pfx=<prefix>: A prefix to be added to each ref.
754794# --cur=<word>: The current ref to be completed. Defaults to the current
755795# word to be completed.
756796# --sfx=<suffix>: A suffix to be appended to each ref instead of the default
757797# space.
798+ # --mode=<mode>: What set of refs to complete, one of 'refs' (the default) to
799+ # complete all refs, 'heads' to complete only branches, or
800+ # 'remote-heads' to complete only remote branches. Note that
801+ # --remote is only compatible with --mode=refs.
758802__git_complete_refs ()
759803{
760- local remote track pfx cur_=" $cur " sfx=" "
804+ local remote dwim pfx cur_=" $cur " sfx=" " mode= " refs "
761805
762806 while test $# ! = 0; do
763807 case " $1 " in
764808 --remote=* ) remote=" ${1## --remote=} " ;;
765- --track) track=" yes" ;;
809+ --dwim) dwim=" yes" ;;
810+ # --track is an old spelling of --dwim
811+ --track) dwim=" yes" ;;
766812 --pfx=* ) pfx=" ${1## --pfx=} " ;;
767813 --cur=* ) cur_=" ${1## --cur=} " ;;
768814 --sfx=* ) sfx=" ${1## --sfx=} " ;;
815+ --mode=* ) mode=" ${1## --mode=} " ;;
769816 * ) return 1 ;;
770817 esac
771818 shift
772819 done
773820
774- __gitcomp_direct " $( __git_refs " $remote " " $track " " $pfx " " $cur_ " " $sfx " ) "
821+ # complete references based on the specified mode
822+ case " $mode " in
823+ refs)
824+ __gitcomp_direct " $( __git_refs " $remote " " " " $pfx " " $cur_ " " $sfx " ) " ;;
825+ heads)
826+ __gitcomp_direct " $( __git_heads " $pfx " " $cur_ " " $sfx " ) " ;;
827+ remote-heads)
828+ __gitcomp_direct " $( __git_remote_heads " $pfx " " $cur_ " " $sfx " ) " ;;
829+ * )
830+ return 1 ;;
831+ esac
832+
833+ # Append DWIM remote branch names if requested
834+ if [ " $dwim " = " yes" ]; then
835+ __gitcomp_direct_append " $( __git_dwim_remote_heads " $pfx " " $cur_ " " $sfx " ) "
836+ fi
775837}
776838
777839# __git_refs2 requires 1 argument (to pass to __git_refs)
@@ -1102,6 +1164,40 @@ __git_find_on_cmdline ()
11021164 done
11031165}
11041166
1167+ # Similar to __git_find_on_cmdline, except that it loops backwards and thus
1168+ # prints the *last* word found. Useful for finding which of two options that
1169+ # supersede each other came last, such as "--guess" and "--no-guess".
1170+ #
1171+ # Usage: __git_find_last_on_cmdline [<option>]... "<wordlist>"
1172+ # --show-idx: Optionally show the index of the found word in the $words array.
1173+ __git_find_last_on_cmdline ()
1174+ {
1175+ local word c=$cword show_idx
1176+
1177+ while test $# -gt 1; do
1178+ case " $1 " in
1179+ --show-idx) show_idx=y ;;
1180+ * ) return 1 ;;
1181+ esac
1182+ shift
1183+ done
1184+ local wordlist=" $1 "
1185+
1186+ while [ $c -gt 1 ]; do
1187+ (( c-- ))
1188+ for word in $wordlist ; do
1189+ if [ " $word " = " ${words[c]} " ]; then
1190+ if [ -n " $show_idx " ]; then
1191+ echo " $c $word "
1192+ else
1193+ echo " $word "
1194+ fi
1195+ return
1196+ fi
1197+ done
1198+ done
1199+ }
1200+
11051201# Echo the value of an option set on the command line or config
11061202#
11071203# $1: short option name
@@ -1356,6 +1452,46 @@ _git_bundle ()
13561452 esac
13571453}
13581454
1455+ # Helper function to decide whether or not we should enable DWIM logic for
1456+ # git-switch and git-checkout.
1457+ #
1458+ # To decide between the following rules in priority order
1459+ # 1) the last provided of "--guess" or "--no-guess" explicitly enable or
1460+ # disable completion of DWIM logic respectively.
1461+ # 2) If the --no-track option is provided, take this as a hint to disable the
1462+ # DWIM completion logic
1463+ # 3) If GIT_COMPLETION_CHECKOUT_NO_GUESS is set, disable the DWIM completion
1464+ # logic, as requested by the user.
1465+ # 4) Enable DWIM logic otherwise.
1466+ #
1467+ __git_checkout_default_dwim_mode ()
1468+ {
1469+ local last_option dwim_opt=" --dwim"
1470+
1471+ if [ " $GIT_COMPLETION_CHECKOUT_NO_GUESS " = " 1" ]; then
1472+ dwim_opt=" "
1473+ fi
1474+
1475+ # --no-track disables DWIM, but with lower priority than
1476+ # --guess/--no-guess
1477+ if [ -n " $( __git_find_on_cmdline " --no-track" ) " ]; then
1478+ dwim_opt=" "
1479+ fi
1480+
1481+ # Find the last provided --guess or --no-guess
1482+ last_option=" $( __git_find_last_on_cmdline " --guess --no-guess" ) "
1483+ case " $last_option " in
1484+ --guess)
1485+ dwim_opt=" --dwim"
1486+ ;;
1487+ --no-guess)
1488+ dwim_opt=" "
1489+ ;;
1490+ esac
1491+
1492+ echo " $dwim_opt "
1493+ }
1494+
13591495_git_checkout ()
13601496{
13611497 __git_has_doubledash && return
@@ -1368,14 +1504,38 @@ _git_checkout ()
13681504 __gitcomp_builtin checkout
13691505 ;;
13701506 * )
1371- # check if --track, --no-track, or --no-guess was specified
1372- # if so, disable DWIM mode
1373- local flags=" --track --no-track --no-guess" track_opt=" --track"
1374- if [ " $GIT_COMPLETION_CHECKOUT_NO_GUESS " = " 1" ] ||
1375- [ -n " $( __git_find_on_cmdline " $flags " ) " ]; then
1376- track_opt=' '
1507+ local dwim_opt=" $( __git_checkout_default_dwim_mode) "
1508+ local prevword prevword=" ${words[cword-1]} "
1509+
1510+ case " $prevword " in
1511+ -b|-B|--orphan)
1512+ # Complete local branches (and DWIM branch
1513+ # remote branch names) for an option argument
1514+ # specifying a new branch name. This is for
1515+ # convenience, assuming new branches are
1516+ # possibly based on pre-existing branch names.
1517+ __git_complete_refs $dwim_opt --mode=" heads"
1518+ return
1519+ ;;
1520+ * )
1521+ ;;
1522+ esac
1523+
1524+ # At this point, we've already handled special completion for
1525+ # the arguments to -b/-B, and --orphan. There are 3 main
1526+ # things left we can possibly complete:
1527+ # 1) a start-point for -b/-B, -d/--detach, or --orphan
1528+ # 2) a remote head, for --track
1529+ # 3) an arbitrary reference, possibly including DWIM names
1530+ #
1531+
1532+ if [ -n " $( __git_find_on_cmdline " -b -B -d --detach --orphan" ) " ]; then
1533+ __git_complete_refs --mode=" refs"
1534+ elif [ -n " $( __git_find_on_cmdline " --track" ) " ]; then
1535+ __git_complete_refs --mode=" remote-heads"
1536+ else
1537+ __git_complete_refs $dwim_opt --mode=" refs"
13771538 fi
1378- __git_complete_refs $track_opt
13791539 ;;
13801540 esac
13811541}
@@ -2224,29 +2384,43 @@ _git_switch ()
22242384 __gitcomp_builtin switch
22252385 ;;
22262386 * )
2227- # check if --track, --no-track, or --no-guess was specified
2228- # if so, disable DWIM mode
2229- local track_opt=" --track" only_local_ref=n
2230- if [ " $GIT_COMPLETION_CHECKOUT_NO_GUESS " = " 1" ] ||
2231- [ -n " $( __git_find_on_cmdline " --track --no-track --no-guess" ) " ]; then
2232- track_opt=' '
2233- fi
2234- # explicit --guess enables DWIM mode regardless of
2235- # $GIT_COMPLETION_CHECKOUT_NO_GUESS
2236- if [ -n " $( __git_find_on_cmdline " --guess" ) " ]; then
2237- track_opt=' --track'
2238- fi
2239- if [ -z " $( __git_find_on_cmdline " -d --detach" ) " ]; then
2240- only_local_ref=y
2241- else
2242- # --guess --detach is invalid combination, no
2243- # dwim will be done when --detach is specified
2244- track_opt=
2387+ local dwim_opt=" $( __git_checkout_default_dwim_mode) "
2388+ local prevword prevword=" ${words[cword-1]} "
2389+
2390+ case " $prevword " in
2391+ -c|-C|--orphan)
2392+ # Complete local branches (and DWIM branch
2393+ # remote branch names) for an option argument
2394+ # specifying a new branch name. This is for
2395+ # convenience, assuming new branches are
2396+ # possibly based on pre-existing branch names.
2397+ __git_complete_refs $dwim_opt --mode=" heads"
2398+ return
2399+ ;;
2400+ * )
2401+ ;;
2402+ esac
2403+
2404+ # Unlike in git checkout, git switch --orphan does not take
2405+ # a start point. Thus we really have nothing to complete after
2406+ # the branch name.
2407+ if [ -n " $( __git_find_on_cmdline " --orphan" ) " ]; then
2408+ return
22452409 fi
2246- if [ $only_local_ref = y -a -z " $track_opt " ]; then
2247- __gitcomp_direct " $( __git_heads " " " $cur " " " ) "
2410+
2411+ # At this point, we've already handled special completion for
2412+ # -c/-C, and --orphan. There are 3 main things left to
2413+ # complete:
2414+ # 1) a start-point for -c/-C or -d/--detach
2415+ # 2) a remote head, for --track
2416+ # 3) a branch name, possibly including DWIM remote branches
2417+
2418+ if [ -n " $( __git_find_on_cmdline " -c -C -d --detach" ) " ]; then
2419+ __git_complete_refs --mode=" refs"
2420+ elif [ -n " $( __git_find_on_cmdline " --track" ) " ]; then
2421+ __git_complete_refs --mode=" remote-heads"
22482422 else
2249- __git_complete_refs $track_opt
2423+ __git_complete_refs $dwim_opt --mode= " heads "
22502424 fi
22512425 ;;
22522426 esac
0 commit comments