Skip to content

Commit 3204218

Browse files
committed
Merge branch 'jk/complete-git-switch'
The command line completion (in contrib/) learned to complete options that the "git switch" command takes. * jk/complete-git-switch: completion: improve handling of --orphan option of switch/checkout completion: improve handling of -c/-C and -b/-B in switch/checkout completion: improve handling of --track in switch/checkout completion: improve handling of --detach in checkout completion: improve completion for git switch with no options completion: improve handling of DWIM mode for switch/checkout completion: perform DWIM logic directly in __git_complete_refs completion: extract function __git_dwim_remote_heads completion: replace overloaded track term for __git_complete_refs completion: add tests showing subpar switch/checkout --orphan logic completion: add tests showing subpar -c/C argument completion completion: add tests showing subpar -c/-C startpoint completion completion: add tests showing subpar switch/checkout --track logic completion: add tests showing subar checkout --detach logic completion: add tests showing subpar DWIM logic for switch/checkout completion: add test showing subpar git switch completion
2 parents c9c318d + 9143992 commit 3204218

File tree

2 files changed

+668
-39
lines changed

2 files changed

+668
-39
lines changed

contrib/completion/git-completion.bash

Lines changed: 213 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)