diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index e3d88b06721b39..59964a8056e190 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -80,12 +80,37 @@ # 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"). +# +# GIT_COMPLETION_REFS_SORT_BY_FIELDNAME +# +# Fieldname string to use for --sort option of for-each-ref. If empty or +# not defined it defaults to "refname" which is the same default git uses +# when no --sort option is provided. Some example values: +# '-committerdate' to descending sort by committer date +# '-version:refname' to descending sort by refname interpreted as version +# More info and examples: https://git-scm.com/docs/git-for-each-ref#_field_names case "$COMP_WORDBREAKS" in *:*) : great ;; *) COMP_WORDBREAKS="$COMP_WORDBREAKS:" esac +# Reads and validates GIT_COMPLETION_REFS_SORT_BY_FIELDNAME configuration var, +# returning the content of it when it's valid, or if not valid or is empty or +# not defined, then it returns the documented default i.e. 'refname'. +__git_get_sort_by_fieldname () +{ + if [ -n "${GIT_COMPLETION_REFS_SORT_BY_FIELDNAME-}" ]; then + # Validate by using a regex pattern which only allows a set + # of characters that may appear in a --sort expression + if [[ "$GIT_COMPLETION_REFS_SORT_BY_FIELDNAME" =~ ^[a-zA-Z0-9%:=*(),_\ -]+$ ]]; then + echo "$GIT_COMPLETION_REFS_SORT_BY_FIELDNAME" + return + fi + fi + echo 'refname' +} + # Discovers the path to the git repository taking any '--git-dir=' and # '-C ' options into account and stores it in the $__git_repo_path # variable. @@ -751,7 +776,9 @@ __git_heads () { local pfx="${1-}" cur_="${2-}" sfx="${3-}" - __git for-each-ref --format="${pfx//\%/%%}%(refname:strip=2)$sfx" \ + local sortby=$(__git_get_sort_by_fieldname) + + __git for-each-ref --sort="$sortby" --format="${pfx//\%/%%}%(refname:strip=2)$sfx" \ ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \ "refs/heads/$cur_*" "refs/heads/$cur_*/**" } @@ -765,7 +792,9 @@ __git_remote_heads () { local pfx="${1-}" cur_="${2-}" sfx="${3-}" - __git for-each-ref --format="${pfx//\%/%%}%(refname:strip=2)$sfx" \ + local sortby=$(__git_get_sort_by_fieldname) + + __git for-each-ref --sort="$sortby" --format="${pfx//\%/%%}%(refname:strip=2)$sfx" \ ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \ "refs/remotes/$cur_*" "refs/remotes/$cur_*/**" } @@ -776,7 +805,9 @@ __git_tags () { local pfx="${1-}" cur_="${2-}" sfx="${3-}" - __git for-each-ref --format="${pfx//\%/%%}%(refname:strip=2)$sfx" \ + local sortby=$(__git_get_sort_by_fieldname) + + __git for-each-ref --sort="$sortby" --format="${pfx//\%/%%}%(refname:strip=2)$sfx" \ ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \ "refs/tags/$cur_*" "refs/tags/$cur_*/**" } @@ -818,7 +849,9 @@ __git_dwim_remote_heads () } } ' - __git for-each-ref --format='%(refname)' refs/remotes/ | + local sortby=$(__git_get_sort_by_fieldname) + + __git for-each-ref --sort="$sortby" --format='%(refname)' refs/remotes/ | PFX="$pfx" SFX="$sfx" CUR_="$cur_" \ IGNORE_CASE=${GIT_COMPLETION_IGNORE_CASE+1} \ REMOTES="$(__git_remotes | sort -r)" awk "$awk_script" | @@ -847,6 +880,7 @@ __git_refs () local match="${4-}" local umatch="${4-}" local fer_pfx="${pfx//\%/%%}" # "escape" for-each-ref format specifiers + local sortby=$(__git_get_sort_by_fieldname) __git_find_repo_path dir="$__git_repo_path" @@ -905,7 +939,8 @@ __git_refs () "refs/remotes/$match*" "refs/remotes/$match*/**") ;; esac - __git_dir="$dir" __git for-each-ref --format="$fer_pfx%($format)$sfx" \ + __git_dir="$dir" __git for-each-ref --sort="$sortby" \ + --format="$fer_pfx%($format)$sfx" \ ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \ "${refs[@]}" if [ -n "$track" ]; then @@ -929,7 +964,8 @@ __git_refs () $match*|$umatch*) echo "${pfx}HEAD$sfx" ;; esac local strip="$(__git_count_path_components "refs/remotes/$remote")" - __git for-each-ref --format="$fer_pfx%(refname:strip=$strip)$sfx" \ + __git for-each-ref --sort="$sortby" \ + --format="$fer_pfx%(refname:strip=$strip)$sfx" \ ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \ "refs/remotes/$remote/$match*" \ "refs/remotes/$remote/$match*/**" @@ -2861,7 +2897,8 @@ __git_complete_config_variable_value () remote.*.push) local remote="${varname#remote.}" remote="${remote%.push}" - __gitcomp_nl "$(__git for-each-ref \ + local sortby=$(__git_get_sort_by_fieldname) + __gitcomp_nl "$(__git for-each-ref --sort="$sortby" \ --format='%(refname):%(refname)' refs/heads)" "" "$cur_" return ;; @@ -3983,8 +4020,9 @@ ___git_complete () { local wrapper="__git_wrap${2}" eval "$wrapper () { __git_func_wrap $2 ; }" - complete -o bashdefault -o default -o nospace -F $wrapper $1 2>/dev/null \ - || complete -o default -o nospace -F $wrapper $1 + complete -o bashdefault -o default -o nospace -o nosort \ + -F $wrapper $1 2>/dev/null \ + || complete -o default -o nospace -o nosort -F $wrapper $1 } # Setup the completion for git commands