diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index d26312899d..41e6589313 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -213,6 +213,20 @@ _get_comp_words_by_ref () } fi +# Fills the COMPREPLY array with prefiltered words without any additional +# processing. +# 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 () +{ + local IFS=$'\n' + + COMPREPLY=($1) +} + __gitcompappend () { local x i=${#COMPREPLY[@]} @@ -354,18 +368,21 @@ __git_tags () # Can be the name of a configured remote, a path, or a URL. # 2: In addition to local refs, list unique branches from refs/remotes/ for # 'git checkout's tracking DWIMery (optional; ignored, if set but empty). -# 3: Currently ignored. +# 3: A prefix to be added to each listed ref (optional). # 4: List only refs matching this word (optional; list all refs if unset or # empty). +# 5: A suffix to be appended to each listed ref (optional; ignored, if set +# but empty). # # Use __git_complete_refs() instead. __git_refs () { local i hash dir track="${2-}" local list_refs_from=path remote="${1-}" - local format refs pfx - local cur_="${4-$cur}" + local format refs + local pfx="${3-}" cur_="${4-$cur}" sfx="${5-}" local match="${4-}" + local fer_pfx="${pfx//\%/%%}" # "escape" for-each-ref format specifiers __git_find_repo_path dir="$__git_repo_path" @@ -390,7 +407,8 @@ __git_refs () if [ "$list_refs_from" = path ]; then if [[ "$cur_" == ^* ]]; then - pfx="^" + pfx="$pfx^" + fer_pfx="$fer_pfx^" cur_=${cur_#^} match=${match#^} fi @@ -405,7 +423,7 @@ __git_refs () case "$i" in $match*) if [ -e "$dir/$i" ]; then - echo $pfx$i + echo "$pfx$i$sfx" fi ;; esac @@ -416,13 +434,13 @@ __git_refs () "refs/remotes/$match*" "refs/remotes/$match*/**") ;; esac - __git_dir="$dir" __git for-each-ref --format="$pfx%($format)" \ + __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="%(refname:strip=3)" \ + __git for-each-ref --format="$fer_pfx%(refname:strip=3)$sfx" \ --sort="refname:strip=3" \ "refs/remotes/*/$match*" "refs/remotes/*/$match*/**" | \ uniq -u @@ -435,16 +453,16 @@ __git_refs () while read -r hash i; do case "$i" in *^{}) ;; - *) echo "$i" ;; + *) echo "$pfx$i$sfx" ;; esac done ;; *) if [ "$list_refs_from" = remote ]; then case "HEAD" in - $match*) echo "HEAD" ;; + $match*) echo "${pfx}HEAD$sfx" ;; esac - __git for-each-ref --format="%(refname:strip=3)" \ + __git for-each-ref --format="$fer_pfx%(refname:strip=3)$sfx" \ "refs/remotes/$remote/$match*" \ "refs/remotes/$remote/$match*/**" else @@ -458,8 +476,8 @@ __git_refs () while read -r hash i; do case "$i" in *^{}) ;; - refs/*) echo "${i#refs/*/}" ;; - *) echo "$i" ;; # symbolic refs + refs/*) echo "$pfx${i#refs/*/}$sfx" ;; + *) echo "$pfx$i$sfx" ;; # symbolic refs esac done fi @@ -494,8 +512,7 @@ __git_complete_refs () shift done - __gitcomp_nl "$(__git_refs "$remote" "$track" "" "$cur_")" \ - "$pfx" "$cur_" "$sfx" + __gitcomp_direct "$(__git_refs "$remote" "$track" "$pfx" "$cur_" "$sfx")" } # __git_refs2 requires 1 argument (to pass to __git_refs) @@ -2997,6 +3014,15 @@ if [[ -n ${ZSH_VERSION-} ]]; then esac } + __gitcomp_direct () + { + emulate -L zsh + + local IFS=$'\n' + compset -P '*[=:]' + compadd -Q -- ${=1} && _ret=0 + } + __gitcomp_nl () { emulate -L zsh diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh index e25541308a..c3521fbfc4 100644 --- a/contrib/completion/git-completion.zsh +++ b/contrib/completion/git-completion.zsh @@ -67,6 +67,15 @@ __gitcomp () esac } +__gitcomp_direct () +{ + emulate -L zsh + + local IFS=$'\n' + compset -P '*[=:]' + compadd -Q -- ${=1} && _ret=0 +} + __gitcomp_nl () { emulate -L zsh diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index cc9e741f92..5ed28135be 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -400,6 +400,22 @@ test_expect_success '__gitdir - remote as argument' ' test_cmp expected "$actual" ' +test_expect_success '__gitcomp_direct - puts everything into COMPREPLY as-is' ' + sed -e "s/Z$//g" >expected <<-EOF && + with-trailing-space Z + without-trailing-spaceZ + --option Z + --option=Z + $invalid_variable_name Z + EOF + ( + cur=should_be_ignored && + __gitcomp_direct "$(cat expected)" && + print_comp + ) && + test_cmp expected out +' + test_expect_success '__gitcomp - trailing space - options' ' test_gitcomp "--re" "--dry-run --reuse-message= --reedit-message= --reset-author" <<-EOF @@ -961,6 +977,17 @@ test_expect_success 'teardown after filtering matching refs' ' git -C otherrepo branch -D matching/branch-in-other ' +test_expect_success '__git_refs - for-each-ref format specifiers in prefix' ' + cat >expected <<-EOF && + evil-%%-%42-%(refname)..master + EOF + ( + cur="evil-%%-%42-%(refname)..mas" && + __git_refs "" "" "evil-%%-%42-%(refname).." mas >"$actual" + ) && + test_cmp expected "$actual" +' + test_expect_success '__git_complete_refs - simple' ' sed -e "s/Z$//" >expected <<-EOF && HEAD Z