In 3bb16a8bf2 (tag, branch, for-each-ref: add --ignore-case for sorting and filtering, 2016-12-04), support was added for filtering and sorting refs in a case insensitive way. This is a behavior that seems appropriate to enable with shell completion. Many shells provide case insensitive completion as an option, even on filesystems that remain case sensitive. This patch adds a new variable that, when set, will allow Bash completion to use the --ignore-case option to match refs. Additionally, some basic support is implemented to match pseudorefs like HEAD. Changes since v1: * Improved comments and commit messages to clarify behavior on case sensitive filesystems * Replaced some lengthy if blocks with inline substitution * As a result of the above change, GIT_COMPLETION_IGNORE_CASE no longer needs to be set to "1" and now just needs to be present in the environment to work * Removed unnecessary exports in tests Alison Winters (2): completion: add optional ignore-case when matching refs completion: add case-insensitive match of pseudorefs contrib/completion/git-completion.bash | 26 +++++++++++++++++++--- t/t9902-completion.sh | 30 ++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) base-commit: a0789512c5a4ae7da935cd2e419f253cb3cb4ce7 Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1374%2Falisonatwork%2Fbash-insensitive-v2 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1374/alisonatwork/bash-insensitive-v2 Pull-Request: https://github.com/git/git/pull/1374 Range-diff vs v1: 1: cef9a12b575 ! 1: a261a94877a completion: add optional ignore-case when matching refs @@ Metadata ## Commit message ## completion: add optional ignore-case when matching refs - If GIT_COMPLETION_IGNORE_CASE=1 is set, --ignore-case will be added to - git for-each-ref calls so that branches and tags can be matched case - insensitively. + If GIT_COMPLETION_IGNORE_CASE is set, --ignore-case will be added to + git for-each-ref calls so that refs can be matched case insensitively, + even when running on case sensitive filesystems. Signed-off-by: Alison Winters <alisonatwork@xxxxxxxxxxx> @@ contrib/completion/git-completion.bash +# +# GIT_COMPLETION_IGNORE_CASE +# -+# When set to "1", suggest refs that match case insensitively (e.g., -+# completing "FOO" on "git checkout f<TAB>"). ++# When set, uses for-each-ref '--ignore-case' to find refs that match ++# case insensitively, even on systems with case sensitive file systems ++# (e.g., completing tag name "FOO" on "git checkout f<TAB>"). case "$COMP_WORDBREAKS" in *:*) : great ;; -@@ contrib/completion/git-completion.bash: __git_complete_index_file () - __git_heads () - { +@@ contrib/completion/git-completion.bash: __git_heads () local pfx="${1-}" cur_="${2-}" sfx="${3-}" -+ local ignore_case="" -+ -+ if test "${GIT_COMPLETION_IGNORE_CASE-}" = "1" -+ then -+ ignore_case="--ignore-case" -+ fi __git for-each-ref --format="${pfx//\%/%%}%(refname:strip=2)$sfx" \ -+ $ignore_case \ ++ ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \ "refs/heads/$cur_*" "refs/heads/$cur_*/**" } -@@ contrib/completion/git-completion.bash: __git_heads () - __git_remote_heads () - { +@@ contrib/completion/git-completion.bash: __git_remote_heads () local pfx="${1-}" cur_="${2-}" sfx="${3-}" -+ local ignore_case="" -+ -+ if test "${GIT_COMPLETION_IGNORE_CASE-}" = "1" -+ then -+ ignore_case="--ignore-case" -+ fi __git for-each-ref --format="${pfx//\%/%%}%(refname:strip=2)$sfx" \ -+ $ignore_case \ ++ ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \ "refs/remotes/$cur_*" "refs/remotes/$cur_*/**" } -@@ contrib/completion/git-completion.bash: __git_remote_heads () - __git_tags () - { +@@ contrib/completion/git-completion.bash: __git_tags () local pfx="${1-}" cur_="${2-}" sfx="${3-}" -+ local ignore_case="" -+ -+ if test "${GIT_COMPLETION_IGNORE_CASE-}" = "1" -+ then -+ ignore_case="--ignore-case" -+ fi __git for-each-ref --format="${pfx//\%/%%}%(refname:strip=2)$sfx" \ -+ $ignore_case \ ++ ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \ "refs/tags/$cur_*" "refs/tags/$cur_*/**" } @@ contrib/completion/git-completion.bash: __git_dwim_remote_heads () - { - local pfx="${1-}" cur_="${2-}" sfx="${3-}" - local fer_pfx="${pfx//\%/%%}" # "escape" for-each-ref format specifiers -+ local ignore_case="" -+ -+ if test "${GIT_COMPLETION_IGNORE_CASE-}" = "1" -+ then -+ ignore_case="--ignore-case" -+ fi - - # 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" \ -+ $ignore_case \ ++ ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \ "refs/remotes/*/$cur_*" "refs/remotes/*/$cur_*/**" | \ uniq -u } -@@ contrib/completion/git-completion.bash: __git_refs () - local pfx="${3-}" cur_="${4-$cur}" sfx="${5-}" - local match="${4-}" - local fer_pfx="${pfx//\%/%%}" # "escape" for-each-ref format specifiers -+ local ignore_case="" - - __git_find_repo_path - dir="$__git_repo_path" -@@ contrib/completion/git-completion.bash: __git_refs () - fi - fi - -+ if test "${GIT_COMPLETION_IGNORE_CASE-}" = "1" -+ then -+ ignore_case="--ignore-case" -+ fi -+ - if [ "$list_refs_from" = path ]; then - if [[ "$cur_" == ^* ]]; then - pfx="$pfx^" @@ contrib/completion/git-completion.bash: __git_refs () ;; esac __git_dir="$dir" __git for-each-ref --format="$fer_pfx%($format)$sfx" \ -+ $ignore_case \ ++ ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \ "${refs[@]}" if [ -n "$track" ]; then __git_dwim_remote_heads "$pfx" "$match" "$sfx" @@ contrib/completion/git-completion.bash: __git_refs () $match*) echo "${pfx}HEAD$sfx" ;; esac __git for-each-ref --format="$fer_pfx%(refname:strip=3)$sfx" \ -+ $ignore_case \ ++ ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \ "refs/remotes/$remote/$match*" \ "refs/remotes/$remote/$match*/**" else @@ t/t9902-completion.sh: test_expect_success 'checkout completes ref names' ' + +test_expect_success 'checkout matches case insensitively with GIT_COMPLETION_IGNORE_CASE' ' + ( -+ . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && -+ GIT_COMPLETION_IGNORE_CASE=1 && export GIT_COMPLETION_IGNORE_CASE && ++ GIT_COMPLETION_IGNORE_CASE=1 && + test_completion "git checkout M" <<-\EOF + main Z + mybranch Z 2: c455e855395 ! 2: 480f6554c93 completion: add case-insensitive match of pseudorefs @@ Metadata ## Commit message ## completion: add case-insensitive match of pseudorefs - When GIT_COMPLETION_IGNORE_CASE=1, also allow lowercase completion text - like "head" to match HEAD and other pseudorefs. + When GIT_COMPLETION_IGNORE_CASE is set, also allow lowercase completion + text like "head" to match uppercase HEAD and other pseudorefs. Signed-off-by: Alison Winters <alisonatwork@xxxxxxxxxxx> @@ contrib/completion/git-completion.bash: __git_refs () local match="${4-}" + local umatch="${4-}" local fer_pfx="${pfx//\%/%%}" # "escape" for-each-ref format specifiers - local ignore_case="" + __git_find_repo_path @@ contrib/completion/git-completion.bash: __git_refs () - if test "${GIT_COMPLETION_IGNORE_CASE-}" = "1" - then - ignore_case="--ignore-case" -+ # use tr instead of ${match,^^} to preserve bash 3.2 compatibility -+ umatch=$(echo "$match" | tr a-z A-Z 2> /dev/null || echo "$match") + fi fi ++ if test "${GIT_COMPLETION_IGNORE_CASE:+1}" = "1" ++ then ++ # uppercase with tr instead of ${match,^^} for bash 3.2 compatibility ++ umatch=$(echo "$match" | tr a-z A-Z 2>/dev/null || echo "$match") ++ fi ++ if [ "$list_refs_from" = path ]; then -@@ contrib/completion/git-completion.bash: __git_refs () + if [[ "$cur_" == ^* ]]; then + pfx="$pfx^" fer_pfx="$fer_pfx^" cur_=${cur_#^} match=${match#^} @@ contrib/completion/git-completion.bash: __git_refs () + $match*|$umatch*) echo "${pfx}HEAD$sfx" ;; esac __git for-each-ref --format="$fer_pfx%(refname:strip=3)$sfx" \ - $ignore_case \ + ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \ @@ contrib/completion/git-completion.bash: __git_refs () else local query_symref @@ t/t9902-completion.sh: test_expect_success 'checkout matches case insensitively + +test_expect_success 'checkout completes pseudo refs case insensitively with GIT_COMPLETION_IGNORE_CASE' ' + ( -+ . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && -+ GIT_COMPLETION_IGNORE_CASE=1 && export GIT_COMPLETION_IGNORE_CASE && ++ GIT_COMPLETION_IGNORE_CASE=1 && + test_completion "git checkout h" <<-\EOF + HEAD Z + EOF -- gitgitgadget