* [PATCH] completion: new config var to use --sort in for-each-ref
@ 2025-07-27 14:49 Nelson Benitez Leon via GitGitGadget
2025-07-27 15:06 ` Nelson Benítez León
` (2 more replies)
0 siblings, 3 replies; 8+ messages in thread
From: Nelson Benitez Leon via GitGitGadget @ 2025-07-27 14:49 UTC (permalink / raw)
To: git; +Cc: Nelson Benitez Leon, Nelson Benítez León
From: =?UTF-8?q?Nelson=20Ben=C3=ADtez=20Le=C3=B3n?= <nbenitezl@gmail.com>
Currently when completing refs, e.g. by doing "git checkout <TAB>", all
refs are shown in alphabetical order, this is an implicit ordering and
cannot be changed.
This commit will make the sort criteria to now be explicit, mandated by
a new config var which will be used for the --sort=<val> of for-each-ref
This new config var will have a default value of alphabetical order,
so Git's default behaviour remains unchanged.
Also add '-o nosort' to 'complete' to disable its default alphabetical
ordering so our new explicit ordering prevails.
Signed-off-by: Nelson Benítez León <nbenitezl@gmail.com>
---
completion: new config var to use --sort in for-each-ref
Hi, I'm submitting a patch for the Bash completion script, to be able to
change the default implicit alphabetical ordering used when returning
refs e.g. when doing "git checkout "
I wanted the completed refs to be sorted by "recently worked on", I
achieve it by using committer date field in descending order i.e.
--sort="-committerdate" because that shows on top the branches that have
recently been worked on. The completion script does not allow to set a
custom sort order, so we're stuck with the default alphabetical one, so
I'm sending a patch which adds a new config var where the user can set
their desired custom sort criteria.
I've not added tests because I'm not familiar with the test machinery,
hopefully this is still useful.
I'd also like to ask the Git audience about their preference for
changing the default sort value in a future patch:
1. stay the same (alphabetical order)
2. change it to show recently worked on branches first (like me)
I people agree 2. is more useful then we can change it in a follow-up
patch.
Regards,
PD. I previously sent this to the mailing list but resulted in a bad
formatted email because I use Gmail (and I don't want to activate 2FA
authentication just for this) so I'm sending this time through
GitGitGadget and incorporating some fixes from review comments I got,
like adapting commit message to 72 chars wide.
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1946%2Fnbenitez%2Fbash_completion_explicit_sort-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1946/nbenitez/bash_completion_explicit_sort-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1946
contrib/completion/git-completion.bash | 56 +++++++++++++++++++++-----
1 file changed, 47 insertions(+), 9 deletions(-)
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index e3d88b06721..59964a8056e 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<TAB>").
+#
+# 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=<path>' and
# '-C <path>' 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
base-commit: e4ef0485fd78fcb05866ea78df35796b904e4a8e
--
gitgitgadget
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH] completion: new config var to use --sort in for-each-ref
2025-07-27 14:49 [PATCH] completion: new config var to use --sort in for-each-ref Nelson Benitez Leon via GitGitGadget
@ 2025-07-27 15:06 ` Nelson Benítez León
2025-07-27 22:21 ` Junio C Hamano
2025-07-28 17:13 ` SZEDER Gábor
2 siblings, 0 replies; 8+ messages in thread
From: Nelson Benítez León @ 2025-07-27 15:06 UTC (permalink / raw)
To: Nelson Benitez Leon via GitGitGadget; +Cc: git
> Hi, I'm submitting a patch for the Bash completion script, to be able to
> change the default implicit alphabetical ordering used when returning
> refs e.g. when doing "git checkout "
that should be "git checkout <TAB>" (got removed by Github apparently).
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] completion: new config var to use --sort in for-each-ref
2025-07-27 14:49 [PATCH] completion: new config var to use --sort in for-each-ref Nelson Benitez Leon via GitGitGadget
2025-07-27 15:06 ` Nelson Benítez León
@ 2025-07-27 22:21 ` Junio C Hamano
2025-07-28 17:13 ` SZEDER Gábor
2 siblings, 0 replies; 8+ messages in thread
From: Junio C Hamano @ 2025-07-27 22:21 UTC (permalink / raw)
To: Nelson Benitez Leon; +Cc: git, Nelson Benitez Leon via GitGitGadget
"Nelson Benitez Leon via GitGitGadget" <gitgitgadget@gmail.com>
writes:
> From: =?UTF-8?q?Nelson=20Ben=C3=ADtez=20Le=C3=B3n?= <nbenitezl@gmail.com>
>
> Currently when completing refs, e.g. by doing "git checkout <TAB>", all
> refs are shown in alphabetical order, this is an implicit ordering and
> cannot be changed.
>
> This commit will make the sort criteria to now be explicit, mandated by
> a new config var which will be used for the --sort=<val> of for-each-ref
>
> This new config var will have a default value of alphabetical order,
> so Git's default behaviour remains unchanged.
>
> Also add '-o nosort' to 'complete' to disable its default alphabetical
> ordering so our new explicit ordering prevails.
The usual way to compose a log message of this project is to
- Give an observation on how the current system works in the
present tense (so no need to say "Currently X is Y", or
"Previously X was Y" to describe the state before your change;
just "X is Y" is enough), and discuss what you perceive as a
problem in it.
- Propose a solution (optional---often, problem description
trivially leads to an obvious solution in reader's minds).
- Give commands to somebody editing the codebase to "make it so".
There is no place to say "this commit will"; instead, give order
to the code.
in this order.
> Signed-off-by: Nelson Benítez León <nbenitezl@gmail.com>
> ---
> I've not added tests because I'm not familiar with the test machinery,
> hopefully this is still useful.
Tests are required part of a new feature to become part of Git
codebase. Otherwise you cannot notice if somebody else later
changes the code further and accidentally breaks your feature.
You can of course ask for help (i.e. "I'm not familiar with the test
machinery so I am not writing any" is *not* acceptable. "I'm not
familiar with the test machinery; among those who may find this new
feature worthwhile to do, could somebody lend me a hand?" is very
good).
> ... and I don't want to activate 2FA
> authentication just for this ...
You could do OAuth these days, I hear?
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] completion: new config var to use --sort in for-each-ref
2025-07-27 14:49 [PATCH] completion: new config var to use --sort in for-each-ref Nelson Benitez Leon via GitGitGadget
2025-07-27 15:06 ` Nelson Benítez León
2025-07-27 22:21 ` Junio C Hamano
@ 2025-07-28 17:13 ` SZEDER Gábor
2 siblings, 0 replies; 8+ messages in thread
From: SZEDER Gábor @ 2025-07-28 17:13 UTC (permalink / raw)
To: Nelson Benitez Leon via GitGitGadget; +Cc: git, Nelson Benitez Leon
On Sun, Jul 27, 2025 at 02:49:33PM +0000, Nelson Benitez Leon via GitGitGadget wrote:
> From: =?UTF-8?q?Nelson=20Ben=C3=ADtez=20Le=C3=B3n?= <nbenitezl@gmail.com>
>
> Currently when completing refs, e.g. by doing "git checkout <TAB>", all
> refs are shown in alphabetical order, this is an implicit ordering and
> cannot be changed.
>
> This commit will make the sort criteria to now be explicit, mandated by
> a new config var which will be used for the --sort=<val> of for-each-ref
But why would you want to use any other ordering?!
> This new config var will have a default value of alphabetical order,
> so Git's default behaviour remains unchanged.
>
> Also add '-o nosort' to 'complete' to disable its default alphabetical
> ordering so our new explicit ordering prevails.
>
> Signed-off-by: Nelson Benítez León <nbenitezl@gmail.com>
> ---
> completion: new config var to use --sort in for-each-ref
>
> Hi, I'm submitting a patch for the Bash completion script, to be able to
> change the default implicit alphabetical ordering used when returning
> refs e.g. when doing "git checkout "
>
> I wanted the completed refs to be sorted by "recently worked on", I
> achieve it by using committer date field in descending order i.e.
> --sort="-committerdate" because that shows on top the branches that have
> recently been worked on.
Ah, that's why :)
This would be a good addition to the log message, and perhaps a
sufficient justification for the proposed change.
> The completion script does not allow to set a
> custom sort order, so we're stuck with the default alphabetical one, so
> I'm sending a patch which adds a new config var where the user can set
> their desired custom sort criteria.
>
> I've not added tests because I'm not familiar with the test machinery,
> hopefully this is still useful.
>
> I'd also like to ask the Git audience about their preference for
> changing the default sort value in a future patch:
>
> 1. stay the same (alphabetical order)
> 2. change it to show recently worked on branches first (like me)
>
> I people agree 2. is more useful then we can change it in a follow-up
> patch.
>
> Regards,
>
> PD. I previously sent this to the mailing list but resulted in a bad
> formatted email because I use Gmail (and I don't want to activate 2FA
> authentication just for this) so I'm sending this time through
> GitGitGadget and incorporating some fixes from review comments I got,
> like adapting commit message to 72 chars wide.
>
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1946%2Fnbenitez%2Fbash_completion_explicit_sort-v1
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1946/nbenitez/bash_completion_explicit_sort-v1
> Pull-Request: https://github.com/gitgitgadget/git/pull/1946
>
> contrib/completion/git-completion.bash | 56 +++++++++++++++++++++-----
> 1 file changed, 47 insertions(+), 9 deletions(-)
>
> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
> index e3d88b06721..59964a8056e 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<TAB>").
> +#
> +# 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
This approach allows only one sort key to be specified, although 'git
for-each-ref' supports more by accepting multiple --sort options.
> 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'
Printing the sort key to the function's stdout requires that it's
called in a command substitution. Forking a subshell for a command
substitution is very expensive on Windows, therefore we should try to
avoid that in new helper functions, if possible.
Please consider returning the sort key in a particular variable that
is specified as local in the function's callers. See __git_decode()
for an example.
> +}
> +
> # Discovers the path to the git repository taking any '--git-dir=<path>' and
> # '-C <path>' 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
This is problematic, because it turns off sorting for all completion
invocations, but in many cases we do need Bash to do the sorting for
us:
- Subcommands and --options still hard-coded in the completion
script are usually listed in arbitrary order.
- Subcommands and --options listed programmatically by the
parse-options machinery are listed in the order they are specified
in the C source files (which tends to be the order that makes most
sense for the help output).
- Some completion functions list possible completion words from
multiple sources.
I'm afraid that any change that leaves these cases unsorted is
unacceptable.
> }
>
> # Setup the completion for git commands
>
> base-commit: e4ef0485fd78fcb05866ea78df35796b904e4a8e
> --
> gitgitgadget
>
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH] completion: new config var to use --sort in for-each-ref
@ 2025-06-08 16:21 Nelson Benítez León
2025-06-27 19:15 ` D. Ben Knoble
2025-06-27 19:47 ` Kristoffer Haugsbakk
0 siblings, 2 replies; 8+ messages in thread
From: Nelson Benítez León @ 2025-06-08 16:21 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Nbenitez
[-- Attachment #1: Type: text/plain, Size: 802 bytes --]
Hi, I'm attaching a patch for the Bash completion script, to be able
to change the default implicit alphabetical ordering used when
returning refs e.g. when doing "git checkout <TAB>"
I wanted the completed refs to be ordered descending by committer date
i.e. --sort="-committerdate" because that shows on top the branches
I've been recently working on. The completion script didn't allow to
set a custom ordering from the default alphabetical one, so I'm
sending a patch which adds a new config var where the user can set
their desired custom ordering.
I've not added tests because I'm not familiar with the test machinery,
hopefully this is still useful.
Regards,
PD. I send from Gmail web interface because git send-email for Gmail
requires 2-factor authentication and I chose not to enable it.
[-- Attachment #2: 0001-completion-new-config-var-to-use-sort-in-for-each-re.patch --]
[-- Type: text/x-patch, Size: 7962 bytes --]
From 77a02e68481024e10414595730c613450b7d38e8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nelson=20Ben=C3=ADtez=20Le=C3=B3n?= <nbenitezl@gmail.com>
Date: Sun, 8 Jun 2025 15:41:10 +0100
Subject: [PATCH] completion: new config var to use --sort in for-each-ref
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Previously when completing refs eg. "git checkout <TAB>"
all refs were alphabetically ordered, this was an
implicit ordering and could not be changed.
This commit adds a new config var to allow setting
a custom ordering, the conf value will be used
for the --sort=<val> of for-each-ref.
When a custom ordering is not set then alphabetical
default is kept, but this time is explicit as we
pass --sort='refname'
This commit also adds '-o nosort' to 'complete'
to disable its default alphabetical ordering so
our custom ordering prevails.
Signed-off-by: Nelson Benítez León <nbenitezl@gmail.com>
---
contrib/completion/git-completion.bash | 56 +++++++++++++++++++++-----
1 file changed, 47 insertions(+), 9 deletions(-)
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index e3d88b067..59964a805 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -77,18 +77,43 @@
#
# GIT_COMPLETION_IGNORE_CASE
#
# 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>").
+#
+# 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=<path>' and
# '-C <path>' options into account and stores it in the $__git_repo_path
# variable.
__git_find_repo_path ()
{
if [ -n "${__git_repo_path-}" ]; then
@@ -748,13 +773,15 @@ __git_complete_index_file ()
# unset or empty).
# 3: A suffix to be appended to each listed branch (optional).
__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_*/**"
}
# Lists branches from remote repositories.
# 1: A prefix to be added to each listed branch (optional).
@@ -762,24 +789,28 @@ __git_heads ()
# 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" \
+ 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_*/**"
}
# Lists tags from the local repository.
# Accepts the same positional parameters as __git_heads() above.
__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_*/**"
}
# List unique branches from refs/remotes used for 'git checkout' and 'git
# switch' tracking DWIMery.
@@ -815,13 +846,15 @@ __git_dwim_remote_heads ()
print ENVIRON["PFX"] branch ENVIRON["SFX"]
break
}
}
}
'
- __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" |
sort | uniq -u
}
@@ -844,12 +877,13 @@ __git_refs ()
local list_refs_from=path remote="${1-}"
local format refs
local pfx="${3-}" cur_="${4-$cur}" sfx="${5-}"
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"
if [ -z "$remote" ]; then
if [ -z "$dir" ]; then
@@ -902,13 +936,14 @@ __git_refs ()
format="refname:strip=2"
refs=("refs/tags/$match*" "refs/tags/$match*/**"
"refs/heads/$match*" "refs/heads/$match*/**"
"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
__git_dwim_remote_heads "$pfx" "$match" "$sfx"
fi
return
@@ -926,13 +961,14 @@ __git_refs ()
*)
if [ "$list_refs_from" = remote ]; then
case "HEAD" in
$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*/**"
else
local query_symref
case "HEAD" in
@@ -2858,13 +2894,14 @@ __git_complete_config_variable_value ()
__gitcomp_nl "$(__git_refs_remotes "$remote")" "" "$cur_"
return
;;
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
;;
pull.twohead|pull.octopus)
__git_compute_merge_strategies
__gitcomp "$__git_merge_strategies" "" "$cur_"
@@ -3980,14 +4017,15 @@ __git_func_wrap ()
}
___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
# 1: command or alias
# 2: function to call (e.g. `git`, `gitk`, `git_fetch`)
__git_complete ()
--
2.49.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH] completion: new config var to use --sort in for-each-ref
2025-06-08 16:21 Nelson Benítez León
@ 2025-06-27 19:15 ` D. Ben Knoble
2025-06-27 19:47 ` Kristoffer Haugsbakk
1 sibling, 0 replies; 8+ messages in thread
From: D. Ben Knoble @ 2025-06-27 19:15 UTC (permalink / raw)
To: Nelson Benítez León; +Cc: git, Junio C Hamano
On Sun, Jun 8, 2025 at 12:22 PM Nelson Benítez León <nbenitezl@gmail.com> wrote:
>
> Hi, I'm attaching a patch for the Bash completion script, to be able
> to change the default implicit alphabetical ordering used when
> returning refs e.g. when doing "git checkout <TAB>"
I keep meaning to come back and review this, and then I see that the
patch is an attached file and not sent over plaintext (which was
necessary because the GMail web client breaks patches). Perhaps you
could submit your patch via GitGitGadget
(https://gitgitgadget.github.io/) so that submission is in the
expected shape for this project?
>
> I wanted the completed refs to be ordered descending by committer date
> i.e. --sort="-committerdate" because that shows on top the branches
> I've been recently working on. The completion script didn't allow to
> set a custom ordering from the default alphabetical one, so I'm
> sending a patch which adds a new config var where the user can set
> their desired custom ordering.
>
> I've not added tests because I'm not familiar with the test machinery,
> hopefully this is still useful.
>
> Regards,
>
> PD. I send from Gmail web interface because git send-email for Gmail
> requires 2-factor authentication and I chose not to enable it.
--
D. Ben Knoble
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] completion: new config var to use --sort in for-each-ref
2025-06-08 16:21 Nelson Benítez León
2025-06-27 19:15 ` D. Ben Knoble
@ 2025-06-27 19:47 ` Kristoffer Haugsbakk
2025-06-27 19:53 ` D. Ben Knoble
1 sibling, 1 reply; 8+ messages in thread
From: Kristoffer Haugsbakk @ 2025-06-27 19:47 UTC (permalink / raw)
To: Nelson Benítez León, git; +Cc: Junio C Hamano
> Previously when completing refs eg. "git checkout <TAB>"
> all refs were alphabetically ordered, this was an
> implicit ordering and could not be changed.
Style-wise, the commit message is supposed to discuss the current
behavior (without the patch) in the present tense and what this patch
does in the imperative mood. Like you are commanding the code to
change.[1] Something like:
When completing refs, e.g. "git checkout <TAB>", all refs are
alphabetically ordered; this is an implicit ordering and cannot be
changed.
This also gels with the general pattern of:
1. Introduce the current behavior
2. The problem it causes (maybe merged with (1))
3. What to do to fix it
> Previously when completing refs eg. "git checkout <TAB>"
s/eg./e.g./
Maybe also some commas like
..., e.g. "git checkout <TAB>", ...
Or maybe it should be “i.e.”?
> This commit adds a new config var to allow setting
> a custom ordering, the conf value will be used
> for the --sort=<val> of for-each-ref.
>
> When a custom ordering is not set then alphabetical
> default is kept, but this time is explicit as we
> pass --sort='refname'
>
> This commit also adds '-o nosort' to 'complete'
> to disable its default alphabetical ordering so
> our custom ordering prevails.
Super nitpick: the paragraphs could be wrapped closer to 72 characters/
columns.[2][3] This alternative (slightly modified) is closer
to that yet not very uneven, still.
Previously when completing refs eg. "git checkout <TAB>" all refs
were alphabetically ordered, this was an implicit ordering and could
not be changed.
This commit adds a new config var to allow setting a custom ordering,
the conf value will be used for the --sort=<val> of for-each-ref.
When a custom ordering is not set then alphabetical default is kept,
but this time is explicit as we pass --sort='refname'
This commit also adds '-o nosort' to 'complete' to disable its default
alphabetical ordering so our custom ordering prevails.
> This commit also adds '-o nosort' to 'complete'
“This commit” should be replaced with just the imperative style
“Also add”.[1]
† 1: See Documentation/SubmittingPatches, “imperative-mood”
🔗 2: https://lore.kernel.org/git/CAPig+cT1VfY8QiUvrrV3-obTBP1439b6iwaebJtGwML5MScnQA@mail.gmail.com/
--
cheers
Kristoffer Haugsbakk
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] completion: new config var to use --sort in for-each-ref
2025-06-27 19:47 ` Kristoffer Haugsbakk
@ 2025-06-27 19:53 ` D. Ben Knoble
0 siblings, 0 replies; 8+ messages in thread
From: D. Ben Knoble @ 2025-06-27 19:53 UTC (permalink / raw)
To: Kristoffer Haugsbakk; +Cc: Nelson Benítez León, git, Junio C Hamano
On Fri, Jun 27, 2025 at 3:48 PM Kristoffer Haugsbakk
<kristofferhaugsbakk@fastmail.com> wrote:
>
> > Previously when completing refs eg. "git checkout <TAB>"
> > all refs were alphabetically ordered, this was an
> > implicit ordering and could not be changed.
>
> Style-wise, the commit message is supposed to discuss the current
> behavior (without the patch) in the present tense and what this patch
> does in the imperative mood. Like you are commanding the code to
> change.[1] Something like:
>
> When completing refs, e.g. "git checkout <TAB>", all refs are
> alphabetically ordered; this is an implicit ordering and cannot be
> changed.
>
> This also gels with the general pattern of:
>
> 1. Introduce the current behavior
> 2. The problem it causes (maybe merged with (1))
> 3. What to do to fix it
>
> > Previously when completing refs eg. "git checkout <TAB>"
>
> s/eg./e.g./
>
> Maybe also some commas like
>
> ..., e.g. "git checkout <TAB>", ...
>
> Or maybe it should be “i.e.”?
Alright, I'll take the bait:
- "e.g." and "i.e." are typically offset by commas, e.g., like this
- since there are other ways to complete refs, using "e.g." (roughly
"for example") is better than "i.e." (roughly, "that is") here
>
> > This commit adds a new config var to allow setting
> > a custom ordering, the conf value will be used
> > for the --sort=<val> of for-each-ref.
> >
> > When a custom ordering is not set then alphabetical
> > default is kept, but this time is explicit as we
> > pass --sort='refname'
> >
> > This commit also adds '-o nosort' to 'complete'
> > to disable its default alphabetical ordering so
> > our custom ordering prevails.
>
> Super nitpick: the paragraphs could be wrapped closer to 72 characters/
> columns.[2][3] This alternative (slightly modified) is closer
> to that yet not very uneven, still.
>
> Previously when completing refs eg. "git checkout <TAB>" all refs
> were alphabetically ordered, this was an implicit ordering and could
> not be changed.
>
> This commit adds a new config var to allow setting a custom ordering,
> the conf value will be used for the --sort=<val> of for-each-ref.
>
> When a custom ordering is not set then alphabetical default is kept,
> but this time is explicit as we pass --sort='refname'
>
> This commit also adds '-o nosort' to 'complete' to disable its default
> alphabetical ordering so our custom ordering prevails.
>
> > This commit also adds '-o nosort' to 'complete'
>
> “This commit” should be replaced with just the imperative style
> “Also add”.[1]
>
> † 1: See Documentation/SubmittingPatches, “imperative-mood”
> 🔗 2: https://lore.kernel.org/git/CAPig+cT1VfY8QiUvrrV3-obTBP1439b6iwaebJtGwML5MScnQA@mail.gmail.com/
>
> --
> cheers
>
> Kristoffer Haugsbakk
>
All great suggestions, thanks.
--
D. Ben Knoble
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2025-07-28 17:13 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-07-27 14:49 [PATCH] completion: new config var to use --sort in for-each-ref Nelson Benitez Leon via GitGitGadget
2025-07-27 15:06 ` Nelson Benítez León
2025-07-27 22:21 ` Junio C Hamano
2025-07-28 17:13 ` SZEDER Gábor
-- strict thread matches above, loose matches on Subject: below --
2025-06-08 16:21 Nelson Benítez León
2025-06-27 19:15 ` D. Ben Knoble
2025-06-27 19:47 ` Kristoffer Haugsbakk
2025-06-27 19:53 ` D. Ben Knoble
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).