threads / patch / 53571

v3, 16 partscompletion: perform DWIM logic directly in __git_complete_refs

Subject: [PATCH v3 10/16] completion: perform DWIM logic directly in __git_complete_refs

## tl;dr

17 messages between May 28, 2020 and May 28, 2020. Diffs are folded; open one to read it.

replies: 16people: 1as markdown or json

Jacob Keller· May 28, 2020, 18:10 UTC · lore

[PATCH v3 00/16] improve switch/checkout completion

From: Jacob Keller <jacob.keller@gmail.com>

Recently I noticed that the completion support for git switch is subpar, and can lead to frustrating moments. Specifically the difference between completing `git switch` vs `git switch --track`:

  $git switch <TAB>
  Display all 784 possibilities? (y or n)
  <list of all references and DWIM remotes>

Notice that git switch will complete basically all references, including things like HEAD, and remote references like "origin/master". Many of these values are useless and clutter the output. It's not too bad, but could be somewhat improved. At least it still completes useful words.

However, the same cannot be said for --track:
  $git switch --track<TAB>
  jk-refactor-git-switch-completion master

Notice that with --track, we suddenly start completing only local branches. Not only does this severely limit what we complete, it includes *only* useless words. This leads to incredibly frustrating moments, as I often use "git switch --track remote/<TAB>" to create tracking branches. I found myself often having to backspace --track just to complete what I wanted and then re-add it.

The primary original motivation of this series was to improve the handling of track. Several other issues were discovered, and I attempt to improve pretty much every mode of switch.

The first few patches simply add new test cases, most of which fail. This is done in order to give time to fully explain what I believe is deficient about the specific case.

Following these patches are some cleanup improvements to remove some confusing terminology and introduce new functions that will be useful to improve the completion functions.

Finally, several patches which improve completion follow. By the end, completion for both git switch and git checkout should be more useful and more aware of the intention of the current argument being completed.

A patch to initially fix just the --track behavior was posted at https://lore.kernel.org/git/20200422201541.3766173-1-jacob.e.keller@intel.com/

This original patch was reviewed by Jonathan Nieder, and he suggested further improvements, which led to v1 of this series https://lore.kernel.org/git/20200425022045.1089291-1-jacob.e.keller@intel.com/

This was further reviewed, and some issues with the handling of completing the argument to -c/-C were brought up by Junio, which I then improved in v2 https://lore.kernel.org/git/20200527113831.3294409-1-jacob.e.keller@intel.com/

Junio mentioned that the commit messages for this v2 series were difficult to follow. I have split the tests from the implementation patches and re-written the commit messages. Hopefully this now better highlights the places where completion isn't behaving as I would expect.

Once again, given the nature of the rework I did not find a suitable range-diff that expressed the evolution from v2 to v3, so I haven't included it here.

I would really like opinions on whether the suggested -c/-C argument completion makes sense. I opted to limit it to complete local branch names, and optionally the unique remote branch names. I'm open to alternative suggestions if anyone has a better suggestion? I had thought perhaps also some users might wish to see tags, if they have branches named after tags or similar.. I don't think completing all references makes sense: branches with remote prefixes in their name would be confusing.

Jacob Keller (16):
  completion: add test showing subpar git switch completion
  completion: add tests showing subpar DWIM logic for switch/checkout
  completion: add tests showing subar checkout --detach logic
  completion: add tests showing subpar switch/checkout --track logic
  completion: add tests showing subpar -c/-C startpoint completion
  completion: add tests showing subpar -c/C argument completion
  completion: add tests showing subpar switch/checkout --orphan logic
  completion: replace overloaded track term for __git_complete_refs
  completion: extract function __git_dwim_remote_heads
  completion: perform DWIM logic directly in __git_complete_refs
  completion: improve handling of DWIM mode for switch/checkout
  completion: improve completion for git switch with no options
  completion: improve handling of --detach in checkout
  completion: improve handling of --track in switch/checkout
  completion: improve handling of -c/-C and -b/-B in switch/checkout
  completion: improve handling of --orphan option of switch/checkout
 contrib/completion/git-completion.bash | 252 +++++++++++---
 t/t9902-completion.sh                  | 456 +++++++++++++++++++++++++
 2 files changed, 669 insertions(+), 39 deletions(-)
-- 
2.25.2
Jacob Keller· May 28, 2020, 18:10 UTC · re: Jacob Keller · lore
From: Jacob Keller <jacob.keller@gmail.com>

__git_complete_refs is the main function used for completing references. It is primarily used as a wrapper around __git_refs, and is easier to extend since its arguments are option-like.

One major downside of __git_complete_refs and __git_refs currently, is the lack of ability to complete only a subset of refs such as branches (refs/heads) or tags (refs/tags).

Normally, a caller might just decide to use __git_heads() or __git_tags(). However, in the case of git-switch, it is useful to complete both branches *and* DWIM remote branch names.

Due to the complexity and implementation of __git_refs, it is not easy to extend it to support listing only a subset of references.

Instead, we can extend __git_complete_refs to do this. For this to be done, we must first ensure that "--dwim" support is not tied to calling __git_refs.

Instead of passing $dwim into __git_refs, we can implement a __gitcomp_direct_append function which can append to COMPREPLY after a call to __gitcomp_direct.

If --dwim is passed to __git_complete_refs, use __gitcomp_direct_append to add the output of __git_dwim_remote_heads to the completion list.

In this way, --dwim support is now independent of calling __git_refs.

A future change will add an additional option to control what set of references __git_complete_refs will output.

Signed-off-by: Jacob Keller <jacob.keller@gmail.com>
---
 contrib/completion/git-completion.bash | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)
Show changes to contrib/completion/git-completion.bash +18 −1
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index ffe3c499e1a4..5c13d2cd0fde 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -301,6 +301,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[@]}
@@ -787,7 +800,11 @@ __git_complete_refs ()
 		shift
 	done
 
-	__gitcomp_direct "$(__git_refs "$remote" "$dwim" "$pfx" "$cur_" "$sfx")"
+	__gitcomp_direct "$(__git_refs "$remote" "" "$pfx" "$cur_" "$sfx")"
+
+	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)
-- 
2.25.2
Jacob Keller· May 28, 2020, 18:10 UTC · re: Jacob Keller · lore

[PATCH v3 08/16] completion: replace overloaded track term for __git_complete_refs

From: Jacob Keller <jacob.keller@gmail.com>

The __git_complete_refs uses the "--track" option to specify when to enable listing of unique remote branches which are used by the DWIM logic of git checkout and git switch.

Using the term '--track' here is confusing because the git commands themselves have '--track' as an argument. Additionally, the completion logic for _git_switch also checks for --track. Keeping the meaning of track_opt and --track for __git_complete_refs straight from the --track git switch and git checkout option is difficult when reading this code.

Use the option '--dwim' instead, indicating this is about enabling or disabling logic related to DWIM mode. Also rename the local variable track_opt to dwim_opt to further reduce the confusion when reading the completion code for _git_switch.

Because it is plausible for users to have developed their own completions which rely on __git_complete_ref, keep --track as a synonym for --dwim, even though we no longer use it in any of the core git completion logic. Add a comment explaining why it remains as an alternative spelling for --dwim.

Signed-off-by: Jacob Keller <jacob.keller@gmail.com>
---
 contrib/completion/git-completion.bash | 28 ++++++++++++++------------
 1 file changed, 15 insertions(+), 13 deletions(-)
Show changes to contrib/completion/git-completion.bash +15 −13
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 70ad04e1b2a8..2972df4cb4c9 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -749,7 +749,7 @@ __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.
@@ -757,12 +757,14 @@ __git_refs ()
 #                 space.
 __git_complete_refs ()
 {
-	local remote track pfx cur_="$cur" sfx=" "
+	local remote dwim pfx cur_="$cur" sfx=" "
 
 	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=}" ;;
@@ -771,7 +773,7 @@ __git_complete_refs ()
 		shift
 	done
 
-	__gitcomp_direct "$(__git_refs "$remote" "$track" "$pfx" "$cur_" "$sfx")"
+	__gitcomp_direct "$(__git_refs "$remote" "$dwim" "$pfx" "$cur_" "$sfx")"
 }
 
 # __git_refs2 requires 1 argument (to pass to __git_refs)
@@ -1370,12 +1372,12 @@ _git_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"
+		local flags="--track --no-track --no-guess" dwim_opt="--dwim"
 		if [ "$GIT_COMPLETION_CHECKOUT_NO_GUESS" = "1" ] ||
 		   [ -n "$(__git_find_on_cmdline "$flags")" ]; then
-			track_opt=''
+			dwim_opt=''
 		fi
-		__git_complete_refs $track_opt
+		__git_complete_refs $dwim_opt
 		;;
 	esac
 }
@@ -2226,27 +2228,27 @@ _git_switch ()
 	*)
 		# check if --track, --no-track, or --no-guess was specified
 		# if so, disable DWIM mode
-		local track_opt="--track" only_local_ref=n
+		local dwim_opt="--dwim" only_local_ref=n
 		if [ "$GIT_COMPLETION_CHECKOUT_NO_GUESS" = "1" ] ||
 		   [ -n "$(__git_find_on_cmdline "--track --no-track --no-guess")" ]; then
-			track_opt=''
+			dwim_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'
+			dwim_opt='--dwim'
 		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=
+			dwim_opt=
 		fi
-		if [ $only_local_ref = y -a -z "$track_opt" ]; then
+		if [ $only_local_ref = y -a -z "$dwim_opt" ]; then
 			__gitcomp_direct "$(__git_heads "" "$cur" " ")"
 		else
-			__git_complete_refs $track_opt
+			__git_complete_refs $dwim_opt
 		fi
 		;;
 	esac
-- 
2.25.2
Jacob Keller· May 28, 2020, 18:10 UTC · re: Jacob Keller · lore

[PATCH v3 09/16] completion: extract function __git_dwim_remote_heads

From: Jacob Keller <jacob.keller@gmail.com>

__git_refs() has the ability to report unique remote names for supporting completion of remote branch names for the DWIMery of git checkout and git switch.

For git checkout, this is fine, because it always supports completing all local references.

However, git switch by default only supports either switching branches or using this DWIMery to create a local branch tracking the remote branch.

Future work to cleanup and improve completion support for git switch will be aided if the remote branch names can be completed separately from __git_refs.

Extract this logic to a function __git_dwim_remote_heads(), and use it in __git_refs.

Signed-off-by: Jacob Keller <jacob.keller@gmail.com>
---
 contrib/completion/git-completion.bash | 28 +++++++++++++++++++-------
 1 file changed, 21 insertions(+), 7 deletions(-)
Show changes to contrib/completion/git-completion.bash +21 −7
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 2972df4cb4c9..ffe3c499e1a4 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -621,6 +621,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).
@@ -696,13 +716,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
-- 
2.25.2
Jacob Keller· May 28, 2020, 18:10 UTC · re: Jacob Keller · lore

[PATCH v3 14/16] completion: improve handling of --track in switch/checkout

From: Jacob Keller <jacob.keller@gmail.com>

Current completion for the --track option of git switch and git checkout is sub par. In addition to the DWIM logic of a bare branch name, --track has DWIM logic to convert specified remote/branch names into a local branch tracking that remote. For example

  $git switch --track origin/master

This will create a local branch name master, that tracks the master branch of the origin remote.

In fact, git switch --track on its own will not accept other forms of references. These must instead be specified manually via the -c/-C/-b/-B options.

Introduce __git_remote_heads() and the "remote-heads" mode for __git_complete_refs. Use this when the --track option is provided while completing in _git_switch and _git_checkout. Just as in the --detach case, we never enable DWIM mode for --track, because it doesn't make sense.

It should be noted that completion support is still a bit sub par when it comes to handling -c/-C and --orphan. This will be resolved in a future change.

Signed-off-by: Jacob Keller <jacob.keller@gmail.com>
---
 contrib/completion/git-completion.bash | 24 ++++++++++++++++++++++--
 t/t9902-completion.sh                  |  6 ++----
 2 files changed, 24 insertions(+), 6 deletions(-)
Show changes to 2 files +24 −6

contrib/completion/git-completion.bash, t/t9902-completion.sh

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 38b5a5a0d874..4cdf09987725 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -624,6 +624,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 ()
@@ -783,8 +796,9 @@ __git_refs ()
 # --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. Note
-#                that --remote is only compatible with --mode=refs.
+#                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 dwim pfx cur_="$cur" sfx=" " mode="refs"
@@ -810,6 +824,8 @@ __git_complete_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
@@ -1492,6 +1508,8 @@ _git_checkout ()
 
 		if [ -n "$(__git_find_on_cmdline "-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 $dwim_opt --mode="refs"
 		fi
@@ -2347,6 +2365,8 @@ _git_switch ()
 
 		if [ -n "$(__git_find_on_cmdline "-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 $dwim_opt --mode="heads"
 		fi
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 7e56a62a9bb3..f8319868080f 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1405,16 +1405,14 @@ test_expect_success 'git checkout - with -d, complete only references' '
 	EOF
 '
 
-#TODO: --track should only complete fully specified remote branches
-test_expect_failure 'git switch - with --track, complete only remote branches' '
+test_expect_success 'git switch - with --track, complete only remote branches' '
 	test_completion "git switch --track " <<-\EOF
 	other/branch-in-other Z
 	other/master-in-other Z
 	EOF
 '
 
-#TODO: --track should only complete fully specified remote branches
-test_expect_failure 'git checkout - with --track, complete only remote branches' '
+test_expect_success 'git checkout - with --track, complete only remote branches' '
 	test_completion "git checkout --track " <<-\EOF
 	other/branch-in-other Z
 	other/master-in-other Z
-- 
2.25.2
Jacob Keller· May 28, 2020, 18:10 UTC · re: Jacob Keller · lore

[PATCH v3 16/16] completion: improve handling of --orphan option of switch/checkout

From: Jacob Keller <jacob.keller@gmail.com>

The --orphan option is used to create a local branch which is detached from the current history. In git switch, it always resets to the empty tree, and thus the only completion we can provide is a branch name. Follow the same rules for -c/-C (and -b/-B) when completing the argument to --orphan.

In the case of git switch, after we complete the argument, there is nothing more we can complete for git switch, so do not even try. Nothing else would be valid.

In the case of git checkout, --orphan takes a start point which it uses to determine the checked out tree, even though it created orphaned history.

Update the previously added test cases as they are now passing.
Signed-off-by: Jacob Keller <jacob.keller@gmail.com>
---
 contrib/completion/git-completion.bash | 21 ++++++++++++++-------
 t/t9902-completion.sh                  |  9 +++------
 2 files changed, 17 insertions(+), 13 deletions(-)
Show changes to 2 files +17 −13

contrib/completion/git-completion.bash, t/t9902-completion.sh

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 60666429dba4..de85b84f8116 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1508,7 +1508,7 @@ _git_checkout ()
 		local prevword prevword="${words[cword-1]}"
 
 		case "$prevword" in
-			-b|-B)
+			-b|-B|--orphan)
 				# Complete local branches (and DWIM branch
 				# remote branch names) for an option argument
 				# specifying a new branch name. This is for
@@ -1522,14 +1522,14 @@ _git_checkout ()
 		esac
 
 		# At this point, we've already handled special completion for
-		# the arguments to -b/-B. There are 3 main things left we can
-		# possibly complete:
-		# 1) a start-point for -b/-B or -d/--detach
+		# 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")" ]; then
+		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"
@@ -2388,7 +2388,7 @@ _git_switch ()
 		local prevword prevword="${words[cword-1]}"
 
 		case "$prevword" in
-			-c|-C)
+			-c|-C|--orphan)
 				# Complete local branches (and DWIM branch
 				# remote branch names) for an option argument
 				# specifying a new branch name. This is for
@@ -2401,8 +2401,15 @@ _git_switch ()
 				;;
 		esac
 
+		# 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
+
 		# At this point, we've already handled special completion for
-		# -c/-C. There are 3 main things left to
+		# -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
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 810661b10b33..d42b4191b9cb 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1670,15 +1670,13 @@ test_expect_success 'git switch - with --orphan completes local branch names and
 	EOF
 '
 
-#TODO: switch --orphan does not take a start-point and thus has nothing to complete
-test_expect_failure 'git switch - --orphan with branch already provided completes nothing else' '
+test_expect_success 'git switch - --orphan with branch already provided completes nothing else' '
 	test_completion "git switch --orphan master " <<-\EOF
 
 	EOF
 '
 
-#TODO: --orphan argument completion should not include all references
-test_expect_failure 'git checkout - with --orphan completes local branch names and unique remote branch names' '
+test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '
 	test_completion "git checkout --orphan " <<-\EOF
 	branch-in-other Z
 	master Z
@@ -1687,8 +1685,7 @@ test_expect_failure 'git checkout - with --orphan completes local branch names a
 	EOF
 '
 
-#TODO: checkout --orphan start-point completion should not included DWIM remote unique branch names
-test_expect_failure 'git checkout - --orphan with branch already provided completes local refs for a start-point' '
+test_expect_success 'git checkout - --orphan with branch already provided completes local refs for a start-point' '
 	test_completion "git checkout --orphan master " <<-\EOF
 	HEAD Z
 	master Z
-- 
2.25.2
Jacob Keller· May 28, 2020, 18:10 UTC · re: Jacob Keller · lore

[PATCH v3 12/16] completion: improve completion for git switch with no options

From: Jacob Keller <jacob.keller@gmail.com>

Add a new --mode option to __git_complete_refs, which allows changing the behavior to call __git_heads instead of __git_refs.

By passing --mode=heads, __git_complete_refs will only output local branches. This enables using "--mode=heads --dwim" to enable listing local branches and the remote unique branch names for DWIM.

Refactor completion support to use the new mode option, rather than calling __git_heads directly. This has the advantage that we can now correctly allow local branches along with suitable DWIM refs, rather than only allowing DWIM when we complete all references.

Choose what mode it uses when calling __git_complete_refs. If -d or --detach have been provided, then simply complete all refs, but *without* the DWIM option as these DWIM names won't work properly in --detach mode.

Otherwise, call __git_complete_refs with the default dwim_opt value and use the new "heads" mode.

In this way, the basic support for completing just "git switch <TAB>" will result in only local branches and remote unique names for DWIM.

The basic no-options tests for git switch, as well as several of the -c/-C tests now pass, so remove the known breakage tags.

Signed-off-by: Jacob Keller <jacob.keller@gmail.com>
---
 contrib/completion/git-completion.bash | 33 ++++++++++++++++----------
 t/t9902-completion.sh                  | 19 ++++++---------
 2 files changed, 27 insertions(+), 25 deletions(-)
Show changes to 2 files +27 −25

contrib/completion/git-completion.bash, t/t9902-completion.sh

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 54cd676fdc9d..53afd72d0e4e 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -782,9 +782,12 @@ __git_refs ()
 #               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. Note
+#                that --remote is only compatible with --mode=refs.
 __git_complete_refs ()
 {
-	local remote dwim pfx cur_="$cur" sfx=" "
+	local remote dwim pfx cur_="$cur" sfx=" " mode="refs"
 
 	while test $# != 0; do
 		case "$1" in
@@ -795,13 +798,23 @@ __git_complete_refs ()
 		--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" "" "$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")" ;;
+		*)
+			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
@@ -2325,18 +2338,12 @@ _git_switch ()
 		__gitcomp_builtin switch
 		;;
 	*)
-		local dwim_opt="$(__git_checkout_default_dwim_mode)" only_local_ref=n
-		if [ -z "$(__git_find_on_cmdline "-d --detach")" ]; then
-			only_local_ref=y
+		local dwim_opt="$(__git_checkout_default_dwim_mode)"
+
+		if [ -n "$(__git_find_on_cmdline "-d --detach")" ]; then
+			__git_complete_refs --mode="refs"
 		else
-			# --guess --detach is invalid combination, no
-			# dwim will be done when --detach is specified
-			dwim_opt=
-		fi
-		if [ $only_local_ref = y -a -z "$dwim_opt" ]; then
-			__gitcomp_direct "$(__git_heads "" "$cur" " ")"
-		else
-			__git_complete_refs $dwim_opt
+			__git_complete_refs $dwim_opt --mode="heads"
 		fi
 		;;
 	esac
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 13d5a47e2912..f4f3a3ca3d55 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1240,8 +1240,7 @@ test_expect_success '__git_complete_fetch_refspecs - fully qualified & prefix' '
 	test_cmp expected out
 '
 
-#TODO: git switch completion includes unexpected references
-test_expect_failure 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '
+test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '
 	test_completion "git switch " <<-\EOF
 	branch-in-other Z
 	master Z
@@ -1277,8 +1276,8 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl
 	EOF
 '
 
-#TODO: git switch completion includes unexpected references
-test_expect_failure 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '
+
+test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '
 	GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git switch --guess " <<-\EOF
 	branch-in-other Z
 	master Z
@@ -1287,8 +1286,7 @@ test_expect_failure 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G
 	EOF
 '
 
-#TODO: git switch completion includes unexpected references
-test_expect_failure 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '
+test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '
 	test_completion "git switch --no-guess --guess " <<-\EOF
 	branch-in-other Z
 	master Z
@@ -1585,8 +1583,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen
 	EOF
 '
 
-#TODO: -c/-C argument completion should not include all references
-test_expect_failure 'git switch - for -c, complete local branches and unique remote branches' '
+test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '
 	test_completion "git switch -c " <<-\EOF
 	branch-in-other Z
 	master Z
@@ -1595,8 +1592,7 @@ test_expect_failure 'git switch - for -c, complete local branches and unique rem
 	EOF
 '
 
-#TODO: -c/-C argument completion should not include all references
-test_expect_failure 'git switch - for -C, complete local branches and unique remote branches' '
+test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '
 	test_completion "git switch -C " <<-\EOF
 	branch-in-other Z
 	master Z
@@ -1685,8 +1681,7 @@ test_expect_failure 'git checkout - for -B with --no-track, complete local branc
 	EOF
 '
 
-#TODO: --orphan argument completion should not include all references
-test_expect_failure 'git switch - with --orphan completes local branch names and unique remote branch names' '
+test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '
 	test_completion "git switch --orphan " <<-\EOF
 	branch-in-other Z
 	master Z
-- 
2.25.2
Jacob Keller· May 28, 2020, 18:10 UTC · re: Jacob Keller · lore

[PATCH v3 07/16] completion: add tests showing subpar switch/checkout --orphan logic

From: Jacob Keller <jacob.keller@gmail.com>

Similar to -c/-C, --orphan takes an argument which is the branch name to use. We ought to complete this branch name using similar rules as to how we complete new branch names for -c/-C and -b/-B. Namely, limit the total number of options provided by completing to the local branches.

Additionally, git switch --orphan does not take any start point and will always create using the empty-tree. Thus, after the branch name is completed, git switch --orphan should not complete any references.

Add test cases showing the expected behavior of --orphan, for both the argument and starting point.

Signed-off-by: Jacob Keller <jacob.keller@gmail.com>
---
 t/t9902-completion.sh | 39 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)
Show changes to t/t9902-completion.sh +39 −0
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 63daf43b89ec..e4c48b20d39d 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1688,6 +1688,45 @@ test_expect_failure 'git checkout - for -B with --no-track, complete local branc
 	EOF
 '
 
+#TODO: --orphan argument completion should not include all references
+test_expect_failure 'git switch - with --orphan completes local branch names and unique remote branch names' '
+	test_completion "git switch --orphan " <<-\EOF
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	EOF
+'
+
+#TODO: switch --orphan does not take a start-point and thus has nothing to complete
+test_expect_failure 'git switch - --orphan with branch already provided completes nothing else' '
+	test_completion "git switch --orphan master " <<-\EOF
+
+	EOF
+'
+
+#TODO: --orphan argument completion should not include all references
+test_expect_failure 'git checkout - with --orphan completes local branch names and unique remote branch names' '
+	test_completion "git checkout --orphan " <<-\EOF
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	EOF
+'
+
+#TODO: checkout --orphan start-point completion should not included DWIM remote unique branch names
+test_expect_failure 'git checkout - --orphan with branch already provided completes local refs for a start-point' '
+	test_completion "git checkout --orphan master " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
 test_expect_success 'teardown after ref completion' '
 	git branch -d matching-branch &&
 	git tag -d matching-tag &&
-- 
2.25.2
Jacob Keller· May 28, 2020, 18:10 UTC · re: Jacob Keller · lore

[PATCH v3 11/16] completion: improve handling of DWIM mode for switch/checkout

From: Jacob Keller <jacob.keller@gmail.com>

A new helper, __git_find_last_on_cmdline is introduced, similar to the already existing __git_find_on_cmdline, but which operates in reverse, finding the *last* matching word of the provided wordlist.

Use this in a new __git_checkout_default_dwim_mode() function that will determine when to enable listing of DWIM remote branches.

The __git_find_last_on_cmdline() function is used to determine which --guess or --no-guess is in effect. If either one is provided, then we unconditionally enable or disable the DWIM mode based on the last provided option.

If neither --guess nor --no-guess is provided, then we check for --no-track, and finally for GIT_COMPLETION_CHECKOUT_NO_GUESS=1.

This function is then used in _git_switch and _git_checkout to improve the handling for when we enable listing of these DWIM remote branches.

This new logic is more robust, as we will correctly identify superseded options, and ensure that both _git_switch and _git_checkout enable DWIM in similar ways.

We can now update a few tests to indicate they pass. A few of the tests previously added to highlight issues with the old DWIM logic still fail. This is because of a separate issue related to the default completion behavior of git switch, which will be addressed in a future change.

Additionally, due to this change, a few tests for the -b/-B handling of git checkout now fail. This is a minor regression, and will be fixed by a following change that improves the overall handling of -b/-B. Mark these tests as known breakages for now.

Signed-off-by: Jacob Keller <jacob.keller@gmail.com>
---
 contrib/completion/git-completion.bash | 95 ++++++++++++++++++++------
 t/t9902-completion.sh                  | 17 ++---
 2 files changed, 83 insertions(+), 29 deletions(-)
Show changes to 2 files +83 −29

contrib/completion/git-completion.bash, t/t9902-completion.sh

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 5c13d2cd0fde..54cd676fdc9d 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1135,6 +1135,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 1 ]; 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
@@ -1389,6 +1423,46 @@ _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 priority order
+# 1) the last provided of "--guess" or "--no-guess" explicitly enable or
+#    disable completion of DWIM logic respectively.
+# 2) If the --no-track option is provided, take this as a hint to disable the
+#    DWIM completion logic
+# 3) If GIT_COMPLETION_CHECKOUT_NO_GUESS is set, disable the DWIM completion
+#    logic, as requested by the user.
+# 4) 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
+	if [ -n "$(__git_find_on_cmdline "--no-track")" ]; 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
@@ -1401,13 +1475,7 @@ _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" dwim_opt="--dwim"
-		if [ "$GIT_COMPLETION_CHECKOUT_NO_GUESS" = "1" ] ||
-		   [ -n "$(__git_find_on_cmdline "$flags")" ]; then
-			dwim_opt=''
-		fi
+		local dwim_opt="$(__git_checkout_default_dwim_mode)"
 		__git_complete_refs $dwim_opt
 		;;
 	esac
@@ -2257,18 +2325,7 @@ _git_switch ()
 		__gitcomp_builtin switch
 		;;
 	*)
-		# check if --track, --no-track, or --no-guess was specified
-		# if so, disable DWIM mode
-		local dwim_opt="--dwim" only_local_ref=n
-		if [ "$GIT_COMPLETION_CHECKOUT_NO_GUESS" = "1" ] ||
-		   [ -n "$(__git_find_on_cmdline "--track --no-track --no-guess")" ]; then
-			dwim_opt=''
-		fi
-		# explicit --guess enables DWIM mode regardless of
-		# $GIT_COMPLETION_CHECKOUT_NO_GUESS
-		if [ -n "$(__git_find_on_cmdline "--guess")" ]; then
-			dwim_opt='--dwim'
-		fi
+		local dwim_opt="$(__git_checkout_default_dwim_mode)" only_local_ref=n
 		if [ -z "$(__git_find_on_cmdline "-d --detach")" ]; then
 			only_local_ref=y
 		else
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index e4c48b20d39d..13d5a47e2912 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1277,7 +1277,6 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl
 	EOF
 '
 
-#TODO: --guess/--no-guess ordering is not taken into account
 #TODO: git switch completion includes unexpected references
 test_expect_failure 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '
 	GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git switch --guess " <<-\EOF
@@ -1288,7 +1287,6 @@ test_expect_failure 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G
 	EOF
 '
 
-#TODO: --guess/--no-guess ordering is not taken into account
 #TODO: git switch completion includes unexpected references
 test_expect_failure 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '
 	test_completion "git switch --no-guess --guess " <<-\EOF
@@ -1299,8 +1297,7 @@ test_expect_failure 'git switch - a later --guess overrides previous --no-guess,
 	EOF
 '
 
-#TODO: --guess/--no-guess ordering is not taken into account
-test_expect_failure 'git switch - a later --no-guess overrides previous --guess, complete only local branches' '
+test_expect_success 'git switch - a later --no-guess overrides previous --guess, complete only local branches' '
 	test_completion "git switch --guess --no-guess " <<-\EOF
 	master Z
 	matching-branch Z
@@ -1318,8 +1315,7 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete
 	EOF
 '
 
-#TODO: git checkout does not override variable when --guess is provided
-test_expect_failure 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '
+test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '
 	GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git checkout --guess " <<-\EOF
 	HEAD Z
 	branch-in-other Z
@@ -1343,8 +1339,7 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '
 	EOF
 '
 
-#TODO: --guess/--no-guess ordering is not taken into account
-test_expect_failure 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '
+test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '
 	test_completion "git checkout --no-guess --guess " <<-\EOF
 	HEAD Z
 	branch-in-other Z
@@ -1544,7 +1539,8 @@ test_expect_failure 'git checkout - with -B, complete all references' '
 	EOF
 '
 
-test_expect_success 'git checkout - with -b and --track, complete all references' '
+#TODO: completing the start point of -b/-B should not include DWIM references
+test_expect_failure 'git checkout - with -b and --track, complete all references' '
 	test_completion "git checkout -b new-branch --track " <<-EOF
 	HEAD Z
 	master Z
@@ -1555,7 +1551,8 @@ test_expect_success 'git checkout - with -b and --track, complete all references
 	EOF
 '
 
-test_expect_success 'git checkout - with -B and --track, complete all references' '
+#TODO: completing the start point of -b/-B should not include DWIM references
+test_expect_failure 'git checkout - with -B and --track, complete all references' '
 	test_completion "git checkout -B new-branch --track " <<-EOF
 	HEAD Z
 	master Z
-- 
2.25.2
Jacob Keller· May 28, 2020, 18:10 UTC · re: Jacob Keller · lore

[PATCH v3 03/16] completion: add tests showing subar checkout --detach logic

From: Jacob Keller <jacob.keller@gmail.com>

When completing words for git switch, the completion function correctly disables the DWIM remote branch names when in the '--detach' mode. These DWIM remote branch names will not work when the --detach option is specified, so it does not make sense to complete them.

git checkout, however, does not disable the completion of DWIM remote branch names in this case.

Add test cases for both git switch and git checkout showing the expected behavior.

Signed-off-by: Jacob Keller <jacob.keller@gmail.com>
---
 t/t9902-completion.sh | 46 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 46 insertions(+)
Show changes to t/t9902-completion.sh +46 −0
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index ff234dee4da9..01aba598e7fc 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1368,6 +1368,52 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues
 	EOF
 '
 
+test_expect_success 'git switch - with --detach, complete all references' '
+	test_completion "git switch --detach " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+#TODO: checkout --detach incorrectly includes DWIM remote branch names
+test_expect_failure 'git checkout - with --detach, complete only references' '
+	test_completion "git checkout --detach " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git switch - with -d, complete all references' '
+	test_completion "git switch -d " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+#TODO: checkout -d incorrectly includes DWIM remote branch names
+test_expect_failure 'git checkout - with -d, complete only references' '
+	test_completion "git checkout -d " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
 test_expect_success 'teardown after ref completion' '
 	git branch -d matching-branch &&
 	git tag -d matching-tag &&
-- 
2.25.2
Jacob Keller· May 28, 2020, 18:10 UTC · re: Jacob Keller · lore

[PATCH v3 04/16] completion: add tests showing subpar switch/checkout --track logic

From: Jacob Keller <jacob.keller@gmail.com>

When the --track option is provided to git switch or git checkout, and no branch is specified by -c or -b, git will interpret the tracking branch to determine the local branch name to use. This "Do What I Mean" logic is similar but distinct from the default DWIM logic of interpreting a unique remote branch name as a request to create and track that branch.

For example, `git switch --track origin/master` is interpreted as a request to create a local branch named master that is tracking origin/master.

The current completion for git checkout in this regard is only somewhat poor:

 $git checkout --track <TAB>
 HEAD
 master
 matching-branch
 matching-tag
 other/branch-in-other
 other/master-in-other

At least it still includes remote references. The clutter from including all references isn't too bad.

However, git switch completion is terrible:
 $git switch --track <TAB>
 master
 matching-branch

It only shows local branches, not even allowing any form of completion of the remote references!

Add tests which highlight the expected behavior of completing --track on its own.

Note that when -c/-C or -b/-B are provided we do expect completing more references, but this will be discussed in a future change that addresses these options specifically.

Signed-off-by: Jacob Keller <jacob.keller@gmail.com>
---
 t/t9902-completion.sh | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)
Show changes to t/t9902-completion.sh +34 −0
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 01aba598e7fc..8a3995f82f38 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1414,6 +1414,40 @@ test_expect_failure 'git checkout - with -d, complete only references' '
 	EOF
 '
 
+#TODO: --track should only complete fully specified remote branches
+test_expect_failure 'git switch - with --track, complete only remote branches' '
+	test_completion "git switch --track " <<-\EOF
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+#TODO: --track should only complete fully specified remote branches
+test_expect_failure 'git checkout - with --track, complete only remote branches' '
+	test_completion "git checkout --track " <<-\EOF
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git switch - with --no-track, complete only local branch names' '
+	test_completion "git switch --no-track " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git checkout - with --no-track, complete only local references' '
+	test_completion "git checkout --no-track " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
 test_expect_success 'teardown after ref completion' '
 	git branch -d matching-branch &&
 	git tag -d matching-tag &&
-- 
2.25.2
Jacob Keller· May 28, 2020, 18:10 UTC · re: Jacob Keller · lore

[PATCH v3 02/16] completion: add tests showing subpar DWIM logic for switch/checkout

From: Jacob Keller <jacob.keller@gmail.com>

When provided with a single argument that is the name of a remote branch that does not yet exist locally, both git switch and git checkout can interpret this as a request to create a local branch that tracks that remote branch. We call this behavior "Do What I Mean", or DWIM for short.

To aid in using this DWIM, it makes sense for completion to list these unique remote branch names when completing possible arguments for git switch and git checkout. Indeed, both _git_checkout and _git_switch implement support for completing such DWIM branch names.

In other words, in addition to the usual completions provided for git switch, this "DWIM" logic means completion will include the names of branches on remotes that are unique and thus there can be no ambiguity of which remote to track when creating the local branch.

However, the DWIM logic is not always active. Many options, such as --no-guess, --no-track, and --track disable this DWIM logic, as they cause git switch and git checkout to behave in different modes.

Additionally, some completion users do not wish to have tab completion include these remote names by default, and thus introduced GIT_COMPLETION_CHECKOUT_NO_GUESS as an optional way to configure the completion support to disable this feature of completion support.

For this reason, _git_checkout and _git_switch have many rules about when to enable or disable completing of these remote refs. The two commands follow similar but not identical rules.

Set aside the question of command modes that do not accept this DWIM logic (--track, -c, --orphan, --detach) for now. Thinking just about the main mode of git checkout and git switch, the following guidelines will help explain the basic rules we ought to support when deciding whether to list the remote branches for DWIM in completion.

1.  if --guess is enabled, we should list DWIM remote branch names, even
    if something else would disable it
2.  if --no-guess, --no-track or GIT_COMPLETION_CHECKOUT_NO_GUESS=1,
    then we should disable listing DWIM remote branch names.
3.  Since the '--guess' option is a boolean option, a later --guess
    should override --no-guess, and a later --no-guess should override
    --guess.

Putting all of these together, add some tests that highlight the expected behavior of this DWIM logic.

Signed-off-by: Jacob Keller <jacob.keller@gmail.com>
---
 t/t9902-completion.sh | 105 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 105 insertions(+)
Show changes to t/t9902-completion.sh +105 −0
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 8f25fd24b43e..ff234dee4da9 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1263,6 +1263,111 @@ test_expect_success 'git checkout - completes refs and unique remote branches fo
 	EOF
 '
 
+test_expect_success 'git switch - with --no-guess, complete only local branches' '
+	test_completion "git switch --no-guess " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete only local branches' '
+	GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git switch " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+#TODO: --guess/--no-guess ordering is not taken into account
+#TODO: git switch completion includes unexpected references
+test_expect_failure 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '
+	GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git switch --guess " <<-\EOF
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	EOF
+'
+
+#TODO: --guess/--no-guess ordering is not taken into account
+#TODO: git switch completion includes unexpected references
+test_expect_failure 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '
+	test_completion "git switch --no-guess --guess " <<-\EOF
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	EOF
+'
+
+#TODO: --guess/--no-guess ordering is not taken into account
+test_expect_failure 'git switch - a later --no-guess overrides previous --guess, complete only local branches' '
+	test_completion "git switch --guess --no-guess " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only completes refs' '
+	GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git checkout " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+#TODO: git checkout does not override variable when --guess is provided
+test_expect_failure 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '
+	GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git checkout --guess " <<-\EOF
+	HEAD Z
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git checkout - with --no-guess, only completes refs' '
+	test_completion "git checkout --no-guess " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+#TODO: --guess/--no-guess ordering is not taken into account
+test_expect_failure 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '
+	test_completion "git checkout --no-guess --guess " <<-\EOF
+	HEAD Z
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git checkout - a later --no-guess overrides previous --guess, complete only refs' '
+	test_completion "git checkout --guess --no-guess " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
 test_expect_success 'teardown after ref completion' '
 	git branch -d matching-branch &&
 	git tag -d matching-tag &&
-- 
2.25.2
Jacob Keller· May 28, 2020, 18:10 UTC · re: Jacob Keller · lore

[PATCH v3 01/16] completion: add test showing subpar git switch completion

From: Jacob Keller <jacob.keller@gmail.com>

When provided with no options, git switch only allows switching between branches. The one exception to this is the "Do What I Mean" logic that allows a unique remote branch name to be interpreted as a request to create a branch of the same name that is tracking that remote branch.

Unfortunately, the logic for the completion of git switch results in completing not just branch names, but also pseudorefs like HEAD, tags, and fully specified <remote>/<branch> references.

For example, we currently complete the following:
 $git switch <TAB>
 HEAD
 branch-in-other
 master
 master-in-other
 matching-branch
 matching-tag
 other/branch-in-other
 other/master-in-other

Indeed, if one were to attempt to use git switch with some of these provided options, git will reject the request:

 $git switch HEAD
 fatal: a branch is expected, got 'HEAD
 $git switch matching-tag
 fatal: a branch is expected, got tag 'matching-tag'
 $git switch other/branch-in-other
 fatal: a branch is expected, got remote branch 'other/branch-in-other'

Ideally, git switch without options ought to complete only words which will be accepted. Without options, this means to list local branch names and the unique remote branch names without their remote name pre-pended.

 $git switch <TAB>
 branch-in-other
 master
 master-in-other
 matching-branch

Add a test case that highlights this subpar completion. Also add a similar test for git checkout completion that shows that due to the complex nature of git checkout, it must complete all references.

Signed-off-by: Jacob Keller <jacob.keller@gmail.com>
---
 t/t9902-completion.sh | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)
Show changes to t/t9902-completion.sh +23 −0
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 3c44af694015..8f25fd24b43e 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1240,6 +1240,29 @@ test_expect_success '__git_complete_fetch_refspecs - fully qualified & prefix' '
 	test_cmp expected out
 '
 
+#TODO: git switch completion includes unexpected references
+test_expect_failure 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '
+	test_completion "git switch " <<-\EOF
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '
+	test_completion "git checkout " <<-\EOF
+	HEAD Z
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
 test_expect_success 'teardown after ref completion' '
 	git branch -d matching-branch &&
 	git tag -d matching-tag &&
-- 
2.25.2
Jacob Keller· May 28, 2020, 18:10 UTC · re: Jacob Keller · lore

[PATCH v3 13/16] completion: improve handling of --detach in checkout

From: Jacob Keller <jacob.keller@gmail.com>

Just like git switch, we should not complete DWIM remote branch names if --detach has been specified. To avoid this, refactor _git_checkout in a similar way to _git_switch.

Note that we don't simply clear dwim_opt when we find -d or --detach, as we will be adding other modes and checks, making this flow easier to follow.

Update the previously failing tests to show that the breakage has been resolved.

Signed-off-by: Jacob Keller <jacob.keller@gmail.com>
---
 contrib/completion/git-completion.bash | 7 ++++++-
 t/t9902-completion.sh                  | 6 ++----
 2 files changed, 8 insertions(+), 5 deletions(-)
Show changes to 2 files +8 −5

contrib/completion/git-completion.bash, t/t9902-completion.sh

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 53afd72d0e4e..38b5a5a0d874 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1489,7 +1489,12 @@ _git_checkout ()
 		;;
 	*)
 		local dwim_opt="$(__git_checkout_default_dwim_mode)"
-		__git_complete_refs $dwim_opt
+
+		if [ -n "$(__git_find_on_cmdline "-d --detach")" ]; then
+			__git_complete_refs --mode="refs"
+		else
+			__git_complete_refs $dwim_opt --mode="refs"
+		fi
 		;;
 	esac
 }
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index f4f3a3ca3d55..7e56a62a9bb3 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1372,8 +1372,7 @@ test_expect_success 'git switch - with --detach, complete all references' '
 	EOF
 '
 
-#TODO: checkout --detach incorrectly includes DWIM remote branch names
-test_expect_failure 'git checkout - with --detach, complete only references' '
+test_expect_success 'git checkout - with --detach, complete only references' '
 	test_completion "git checkout --detach " <<-\EOF
 	HEAD Z
 	master Z
@@ -1395,8 +1394,7 @@ test_expect_success 'git switch - with -d, complete all references' '
 	EOF
 '
 
-#TODO: checkout -d incorrectly includes DWIM remote branch names
-test_expect_failure 'git checkout - with -d, complete only references' '
+test_expect_success 'git checkout - with -d, complete only references' '
 	test_completion "git checkout -d " <<-\EOF
 	HEAD Z
 	master Z
-- 
2.25.2
Jacob Keller· May 28, 2020, 18:10 UTC · re: Jacob Keller · lore

[PATCH v3 05/16] completion: add tests showing subpar -c/-C startpoint completion

From: Jacob Keller <jacob.keller@gmail.com>

When using the branch creation argument for git switch or git checkout, -c/-C or -b/-B, the commands operate in a different mode: `git switch -c <branch> <some-reference>` means to create a branch named <branch> at the commit referred to by <some-reference>.

When completing the start-point, we ought to always complete all valid references.

Add tests for the completion of the start-point to -c/-C and -b/-B.
Signed-off-by: Jacob Keller <jacob.keller@gmail.com>
---
 t/t9902-completion.sh | 140 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 140 insertions(+)
Show changes to t/t9902-completion.sh +140 −0
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 8a3995f82f38..609981244e9b 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1448,6 +1448,146 @@ test_expect_success 'git checkout - with --no-track, complete only local referen
 	EOF
 '
 
+#TODO: completing the start point of -c/-C should not include DWIM references
+test_expect_failure 'git switch - with -c, complete all references' '
+	test_completion "git switch -c new-branch " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+#TODO: completing the start point of -c/-C should not include DWIM references
+test_expect_failure 'git switch - with -C, complete all references' '
+	test_completion "git switch -C new-branch " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+#TODO: completing the start point of -c/-C should include all references, not just local branches
+test_expect_failure 'git switch - with -c and --track, complete all references' '
+	test_completion "git switch -c new-branch --track " <<-EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+#TODO: completing the start point of -c/-C should include all references, not just local branches
+test_expect_failure 'git switch - with -C and --track, complete all references' '
+	test_completion "git switch -C new-branch --track " <<-EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+#TODO: completing the start point of -c/-C should include all references, not just local branches
+test_expect_failure 'git switch - with -c and --no-track, complete all references' '
+	test_completion "git switch -c new-branch --no-track " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+#TODO: completing the start point of -c/-C should include all references, not just local branches
+test_expect_failure 'git switch - with -C and --no-track, complete all references' '
+	test_completion "git switch -C new-branch --no-track " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+#TODO: completing the start point of -b/-B should not include DWIM references
+test_expect_failure 'git checkout - with -b, complete all references' '
+	test_completion "git checkout -b new-branch " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+#TODO: completing the start point of -b/-B should not include DWIM references
+test_expect_failure 'git checkout - with -B, complete all references' '
+	test_completion "git checkout -B new-branch " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git checkout - with -b and --track, complete all references' '
+	test_completion "git checkout -b new-branch --track " <<-EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git checkout - with -B and --track, complete all references' '
+	test_completion "git checkout -B new-branch --track " <<-EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git checkout - with -b and --no-track, complete all references' '
+	test_completion "git checkout -b new-branch --no-track " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
+test_expect_success 'git checkout - with -B and --no-track, complete all references' '
+	test_completion "git checkout -B new-branch --no-track " <<-\EOF
+	HEAD Z
+	master Z
+	matching-branch Z
+	matching-tag Z
+	other/branch-in-other Z
+	other/master-in-other Z
+	EOF
+'
+
 test_expect_success 'teardown after ref completion' '
 	git branch -d matching-branch &&
 	git tag -d matching-tag &&
-- 
2.25.2
Jacob Keller· May 28, 2020, 18:10 UTC · re: Jacob Keller · lore

[PATCH v3 06/16] completion: add tests showing subpar -c/C argument completion

From: Jacob Keller <jacob.keller@gmail.com>

When using the branch creation argument for git switch or git checkout (-c/-C or -b/-B), the commands switch to a different mode: `git switch -c <branch> <some-referance>` means to create a branch named <branch> at the commit referred to by <some-reference>.

When completing git switch or git checkout, it makes sense to complete the branch name differently from the start point.

When completing a branch, one might consider that we do not have anything worth completing. After all, a new branch must have an entirely new name. Consider, however, that if a user names branches using some similar scheme, they might wish to name a new branch by modifying the name of an existing branch.

To avoid overloading completion for the argument, it seems reasonable to complete only the local branch names and the valid "Do What I Mean" remote branch names.

Add tests for the completion of the argument to -c/-C and -b/-B, highlighting this preferred completion behavior.

Signed-off-by: Jacob Keller <jacob.keller@gmail.com>
---
 t/t9902-completion.sh | 100 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 100 insertions(+)
Show changes to t/t9902-completion.sh +100 −0
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 609981244e9b..63daf43b89ec 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1588,6 +1588,106 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen
 	EOF
 '
 
+#TODO: -c/-C argument completion should not include all references
+test_expect_failure 'git switch - for -c, complete local branches and unique remote branches' '
+	test_completion "git switch -c " <<-\EOF
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	EOF
+'
+
+#TODO: -c/-C argument completion should not include all references
+test_expect_failure 'git switch - for -C, complete local branches and unique remote branches' '
+	test_completion "git switch -C " <<-\EOF
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git switch - for -c with --no-guess, complete local branches only' '
+	test_completion "git switch --no-guess -c " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git switch - for -C with --no-guess, complete local branches only' '
+	test_completion "git switch --no-guess -C " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git switch - for -c with --no-track, complete local branches only' '
+	test_completion "git switch --no-track -c " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+test_expect_success 'git switch - for -C with --no-track, complete local branches only' '
+	test_completion "git switch --no-track -C " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+#TODO: -b/-B argument completion should not include all references
+test_expect_failure 'git checkout - for -b, complete local branches and unique remote branches' '
+	test_completion "git checkout -b " <<-\EOF
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	EOF
+'
+
+#TODO: -b/-B argument completion should not include all references
+test_expect_failure 'git checkout - for -B, complete local branches and unique remote branches' '
+	test_completion "git checkout -B " <<-\EOF
+	branch-in-other Z
+	master Z
+	master-in-other Z
+	matching-branch Z
+	EOF
+'
+
+#TODO: -b/-B argument completion should not include all references
+test_expect_failure 'git checkout - for -b with --no-guess, complete local branches only' '
+	test_completion "git checkout --no-guess -b " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+#TODO: -b/-B argument completion should not include all references
+test_expect_failure 'git checkout - for -B with --no-guess, complete local branches only' '
+	test_completion "git checkout --no-guess -B " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+#TODO: -b/-B argument completion should not include all references
+test_expect_failure 'git checkout - for -b with --no-track, complete local branches only' '
+	test_completion "git checkout --no-track -b " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
+#TODO: -b/-B argument completion should not include all references
+test_expect_failure 'git checkout - for -B with --no-track, complete local branches only' '
+	test_completion "git checkout --no-track -B " <<-\EOF
+	master Z
+	matching-branch Z
+	EOF
+'
+
 test_expect_success 'teardown after ref completion' '
 	git branch -d matching-branch &&
 	git tag -d matching-tag &&
-- 
2.25.2
Jacob Keller· May 28, 2020, 18:10 UTC · re: Jacob Keller · lore

[PATCH v3 15/16] completion: improve handling of -c/-C and -b/-B in switch/checkout

From: Jacob Keller <jacob.keller@gmail.com>

A previous commit added several test cases highlighting the subpar completion logic for -c/-C and -b/-B when completing git switch and git checkout.

In order to distinguish completing the argument vs the start-point for this option, we now use the wordlist to determine the previous full word on the command line.

If it's -c or -C (-b/-B for checkout), then we know that we are completing the argument for the branch name.

Given that a user who already knows the branch name they want to complete will simply not use completion, it makes sense to complete the small subset of local branches when completing the argument for -c/-C.

In all other cases, if -c/-C are on the command line but are not the most recent option, then we must be completing a start-point, and should allow completing against all references.

Update the -c/-C and -b/-B tests to indicate they now pass.
Signed-off-by: Jacob Keller <jacob.keller@gmail.com>
---
 contrib/completion/git-completion.bash | 49 ++++++++++++++++++++++++--
 t/t9902-completion.sh                  | 48 +++++++++----------------
 2 files changed, 63 insertions(+), 34 deletions(-)
Show changes to 2 files +63 −34

contrib/completion/git-completion.bash, t/t9902-completion.sh

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 4cdf09987725..60666429dba4 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1505,8 +1505,31 @@ _git_checkout ()
 		;;
 	*)
 		local dwim_opt="$(__git_checkout_default_dwim_mode)"
+		local prevword prevword="${words[cword-1]}"
 
-		if [ -n "$(__git_find_on_cmdline "-d --detach")" ]; then
+		case "$prevword" in
+			-b|-B)
+				# 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
+
+		# At this point, we've already handled special completion for
+		# the arguments to -b/-B. There are 3 main things left we can
+		# possibly complete:
+		# 1) a start-point for -b/-B or -d/--detach
+		# 2) a remote head, for --track
+		# 3) an arbitrary reference, possibly including DWIM names
+		#
+
+		if [ -n "$(__git_find_on_cmdline "-b -B -d --detach")" ]; then
 			__git_complete_refs --mode="refs"
 		elif [ -n "$(__git_find_on_cmdline "--track")" ]; then
 			__git_complete_refs --mode="remote-heads"
@@ -2362,8 +2385,30 @@ _git_switch ()
 		;;
 	*)
 		local dwim_opt="$(__git_checkout_default_dwim_mode)"
+		local prevword prevword="${words[cword-1]}"
 
-		if [ -n "$(__git_find_on_cmdline "-d --detach")" ]; then
+		case "$prevword" in
+			-c|-C)
+				# 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
+
+		# At this point, we've already handled special completion for
+		# -c/-C. 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"
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index f8319868080f..810661b10b33 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1437,8 +1437,7 @@ test_expect_success 'git checkout - with --no-track, complete only local referen
 	EOF
 '
 
-#TODO: completing the start point of -c/-C should not include DWIM references
-test_expect_failure 'git switch - with -c, complete all references' '
+test_expect_success 'git switch - with -c, complete all references' '
 	test_completion "git switch -c new-branch " <<-\EOF
 	HEAD Z
 	master Z
@@ -1449,8 +1448,7 @@ test_expect_failure 'git switch - with -c, complete all references' '
 	EOF
 '
 
-#TODO: completing the start point of -c/-C should not include DWIM references
-test_expect_failure 'git switch - with -C, complete all references' '
+test_expect_success 'git switch - with -C, complete all references' '
 	test_completion "git switch -C new-branch " <<-\EOF
 	HEAD Z
 	master Z
@@ -1461,8 +1459,7 @@ test_expect_failure 'git switch - with -C, complete all references' '
 	EOF
 '
 
-#TODO: completing the start point of -c/-C should include all references, not just local branches
-test_expect_failure 'git switch - with -c and --track, complete all references' '
+test_expect_success 'git switch - with -c and --track, complete all references' '
 	test_completion "git switch -c new-branch --track " <<-EOF
 	HEAD Z
 	master Z
@@ -1473,8 +1470,7 @@ test_expect_failure 'git switch - with -c and --track, complete all references'
 	EOF
 '
 
-#TODO: completing the start point of -c/-C should include all references, not just local branches
-test_expect_failure 'git switch - with -C and --track, complete all references' '
+test_expect_success 'git switch - with -C and --track, complete all references' '
 	test_completion "git switch -C new-branch --track " <<-EOF
 	HEAD Z
 	master Z
@@ -1485,8 +1481,7 @@ test_expect_failure 'git switch - with -C and --track, complete all references'
 	EOF
 '
 
-#TODO: completing the start point of -c/-C should include all references, not just local branches
-test_expect_failure 'git switch - with -c and --no-track, complete all references' '
+test_expect_success 'git switch - with -c and --no-track, complete all references' '
 	test_completion "git switch -c new-branch --no-track " <<-\EOF
 	HEAD Z
 	master Z
@@ -1497,8 +1492,7 @@ test_expect_failure 'git switch - with -c and --no-track, complete all reference
 	EOF
 '
 
-#TODO: completing the start point of -c/-C should include all references, not just local branches
-test_expect_failure 'git switch - with -C and --no-track, complete all references' '
+test_expect_success 'git switch - with -C and --no-track, complete all references' '
 	test_completion "git switch -C new-branch --no-track " <<-\EOF
 	HEAD Z
 	master Z
@@ -1509,8 +1503,7 @@ test_expect_failure 'git switch - with -C and --no-track, complete all reference
 	EOF
 '
 
-#TODO: completing the start point of -b/-B should not include DWIM references
-test_expect_failure 'git checkout - with -b, complete all references' '
+test_expect_success 'git checkout - with -b, complete all references' '
 	test_completion "git checkout -b new-branch " <<-\EOF
 	HEAD Z
 	master Z
@@ -1521,8 +1514,7 @@ test_expect_failure 'git checkout - with -b, complete all references' '
 	EOF
 '
 
-#TODO: completing the start point of -b/-B should not include DWIM references
-test_expect_failure 'git checkout - with -B, complete all references' '
+test_expect_success 'git checkout - with -B, complete all references' '
 	test_completion "git checkout -B new-branch " <<-\EOF
 	HEAD Z
 	master Z
@@ -1533,8 +1525,7 @@ test_expect_failure 'git checkout - with -B, complete all references' '
 	EOF
 '
 
-#TODO: completing the start point of -b/-B should not include DWIM references
-test_expect_failure 'git checkout - with -b and --track, complete all references' '
+test_expect_success 'git checkout - with -b and --track, complete all references' '
 	test_completion "git checkout -b new-branch --track " <<-EOF
 	HEAD Z
 	master Z
@@ -1545,8 +1536,7 @@ test_expect_failure 'git checkout - with -b and --track, complete all references
 	EOF
 '
 
-#TODO: completing the start point of -b/-B should not include DWIM references
-test_expect_failure 'git checkout - with -B and --track, complete all references' '
+test_expect_success 'git checkout - with -B and --track, complete all references' '
 	test_completion "git checkout -B new-branch --track " <<-EOF
 	HEAD Z
 	master Z
@@ -1625,8 +1615,7 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche
 	EOF
 '
 
-#TODO: -b/-B argument completion should not include all references
-test_expect_failure 'git checkout - for -b, complete local branches and unique remote branches' '
+test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '
 	test_completion "git checkout -b " <<-\EOF
 	branch-in-other Z
 	master Z
@@ -1635,8 +1624,7 @@ test_expect_failure 'git checkout - for -b, complete local branches and unique r
 	EOF
 '
 
-#TODO: -b/-B argument completion should not include all references
-test_expect_failure 'git checkout - for -B, complete local branches and unique remote branches' '
+test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '
 	test_completion "git checkout -B " <<-\EOF
 	branch-in-other Z
 	master Z
@@ -1645,32 +1633,28 @@ test_expect_failure 'git checkout - for -B, complete local branches and unique r
 	EOF
 '
 
-#TODO: -b/-B argument completion should not include all references
-test_expect_failure 'git checkout - for -b with --no-guess, complete local branches only' '
+test_expect_success 'git checkout - for -b with --no-guess, complete local branches only' '
 	test_completion "git checkout --no-guess -b " <<-\EOF
 	master Z
 	matching-branch Z
 	EOF
 '
 
-#TODO: -b/-B argument completion should not include all references
-test_expect_failure 'git checkout - for -B with --no-guess, complete local branches only' '
+test_expect_success 'git checkout - for -B with --no-guess, complete local branches only' '
 	test_completion "git checkout --no-guess -B " <<-\EOF
 	master Z
 	matching-branch Z
 	EOF
 '
 
-#TODO: -b/-B argument completion should not include all references
-test_expect_failure 'git checkout - for -b with --no-track, complete local branches only' '
+test_expect_success 'git checkout - for -b with --no-track, complete local branches only' '
 	test_completion "git checkout --no-track -b " <<-\EOF
 	master Z
 	matching-branch Z
 	EOF
 '
 
-#TODO: -b/-B argument completion should not include all references
-test_expect_failure 'git checkout - for -B with --no-track, complete local branches only' '
+test_expect_success 'git checkout - for -B with --no-track, complete local branches only' '
 	test_completion "git checkout --no-track -B " <<-\EOF
 	master Z
 	matching-branch Z
-- 
2.25.2

← back to recent threads