{"thread":{"id":"63605","subject":"[PATCH] completion: new config var to use --sort in for-each-ref","startedAt":"2025-06-08T16:22:23Z","lastAt":"2025-06-27T19:53:15Z","messageCount":4,"participants":["Nelson Benítez León","D. Ben Knoble","Kristoffer Haugsbakk"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"519938","messageId":"CAAUd64333fEMHAaNW=-CkfUMN4HDFF2v==HdO0OFnOgm+W+Jog@mail.gmail.com","threadId":"63605","inReplyTo":null,"subject":"[PATCH] completion: new config var to use --sort in for-each-ref","fromName":"Nelson Benítez León","fromEmail":"nbenitezl@gmail.com","sentAt":"2025-06-08T16:21:44Z","receivedAt":"2025-06-08T16:22:23Z","isPatch":true,"sender":{"key":"nbenitezl@gmail.com","avatar":"https://gravatar.com/avatar/43794fe96eb6be6a99b1334207fa0fffa2c6606a64e2abd5ea994ffb65ddeab3?d=mp&s=160"},"body":"Hi, I'm attaching a patch for the Bash completion script, to be able\nto change the default implicit alphabetical ordering used when\nreturning refs e.g. when doing \"git checkout <TAB>\"\n\nI wanted the completed refs to be ordered descending by committer date\ni.e. --sort=\"-committerdate\" because that shows on top the branches\nI've been recently working on. The completion script didn't allow to\nset a custom ordering from the default alphabetical one, so I'm\nsending a patch which adds a new config var where the user can set\ntheir desired custom ordering.\n\nI've not added tests because I'm not familiar with the test machinery,\nhopefully this is still useful.\n\nRegards,\n\nPD. I send from Gmail web interface because git send-email for Gmail\nrequires 2-factor authentication and I chose not to enable it.\n\n\nFrom 77a02e68481024e10414595730c613450b7d38e8 Mon Sep 17 00:00:00 2001\nFrom: =?UTF-8?q?Nelson=20Ben=C3=ADtez=20Le=C3=B3n?= <nbenitezl@gmail.com>\nDate: Sun, 8 Jun 2025 15:41:10 +0100\nSubject: [PATCH] completion: new config var to use --sort in for-each-ref\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n\nPreviously when completing refs eg. \"git checkout <TAB>\"\nall refs were alphabetically ordered, this was an\nimplicit ordering and could not be changed.\n\nThis commit adds a new config var to allow setting\na custom ordering, the conf value will be used\nfor the --sort=<val> of for-each-ref.\n\nWhen a custom ordering is not set then alphabetical\ndefault is kept, but this time is explicit as we\npass --sort='refname'\n\nThis commit also adds '-o nosort' to 'complete'\nto disable its default alphabetical ordering so\nour custom ordering prevails.\n\nSigned-off-by: Nelson Benítez León <nbenitezl@gmail.com>\n---\n contrib/completion/git-completion.bash | 56 +++++++++++++++++++++-----\n 1 file changed, 47 insertions(+), 9 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex e3d88b067..59964a805 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -77,18 +77,43 @@\n #\n #   GIT_COMPLETION_IGNORE_CASE\n #\n #     When set, uses for-each-ref '--ignore-case' to find refs that match\n #     case insensitively, even on systems with case sensitive file systems\n #     (e.g., completing tag name \"FOO\" on \"git checkout f<TAB>\").\n+#\n+#   GIT_COMPLETION_REFS_SORT_BY_FIELDNAME\n+#\n+#     Fieldname string to use for --sort option of for-each-ref. If empty or\n+#     not defined it defaults to \"refname\" which is the same default git uses\n+#     when no --sort option is provided. Some example values:\n+#       '-committerdate' to descending sort by committer date\n+#       '-version:refname' to descending sort by refname interpreted as version\n+#       More info and examples: https://git-scm.com/docs/git-for-each-ref#_field_names\n \n case \"$COMP_WORDBREAKS\" in\n *:*) : great ;;\n *)   COMP_WORDBREAKS=\"$COMP_WORDBREAKS:\"\n esac\n \n+# Reads and validates GIT_COMPLETION_REFS_SORT_BY_FIELDNAME configuration var,\n+# returning the content of it when it's valid, or if not valid or is empty or\n+# not defined, then it returns the documented default i.e. 'refname'.\n+__git_get_sort_by_fieldname ()\n+{\n+\tif [ -n \"${GIT_COMPLETION_REFS_SORT_BY_FIELDNAME-}\" ]; then\n+\t\t# Validate by using a regex pattern which only allows a set\n+\t\t# of characters that may appear in a --sort expression\n+        if [[ \"$GIT_COMPLETION_REFS_SORT_BY_FIELDNAME\" =~ ^[a-zA-Z0-9%:=*(),_\\ -]+$ ]]; then\n+            echo \"$GIT_COMPLETION_REFS_SORT_BY_FIELDNAME\"\n+            return\n+        fi\n+\tfi\n+\techo 'refname'\n+}\n+\n # Discovers the path to the git repository taking any '--git-dir=<path>' and\n # '-C <path>' options into account and stores it in the $__git_repo_path\n # variable.\n __git_find_repo_path ()\n {\n \tif [ -n \"${__git_repo_path-}\" ]; then\n@@ -748,13 +773,15 @@ __git_complete_index_file ()\n #    unset or empty).\n # 3: A suffix to be appended to each listed branch (optional).\n __git_heads ()\n {\n \tlocal pfx=\"${1-}\" cur_=\"${2-}\" sfx=\"${3-}\"\n \n-\t__git for-each-ref --format=\"${pfx//\\%/%%}%(refname:strip=2)$sfx\" \\\n+\tlocal sortby=$(__git_get_sort_by_fieldname)\n+\n+\t__git for-each-ref --sort=\"$sortby\" --format=\"${pfx//\\%/%%}%(refname:strip=2)$sfx\" \\\n \t\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n \t\t\t\"refs/heads/$cur_*\" \"refs/heads/$cur_*/**\"\n }\n \n # Lists branches from remote repositories.\n # 1: A prefix to be added to each listed branch (optional).\n@@ -762,24 +789,28 @@ __git_heads ()\n #    unset or empty).\n # 3: A suffix to be appended to each listed branch (optional).\n __git_remote_heads ()\n {\n \tlocal pfx=\"${1-}\" cur_=\"${2-}\" sfx=\"${3-}\"\n \n-\t__git for-each-ref --format=\"${pfx//\\%/%%}%(refname:strip=2)$sfx\" \\\n+\tlocal sortby=$(__git_get_sort_by_fieldname)\n+\n+\t__git for-each-ref --sort=\"$sortby\" --format=\"${pfx//\\%/%%}%(refname:strip=2)$sfx\" \\\n \t\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n \t\t\t\"refs/remotes/$cur_*\" \"refs/remotes/$cur_*/**\"\n }\n \n # Lists tags from the local repository.\n # Accepts the same positional parameters as __git_heads() above.\n __git_tags ()\n {\n \tlocal pfx=\"${1-}\" cur_=\"${2-}\" sfx=\"${3-}\"\n \n-\t__git for-each-ref --format=\"${pfx//\\%/%%}%(refname:strip=2)$sfx\" \\\n+\tlocal sortby=$(__git_get_sort_by_fieldname)\n+\n+\t__git for-each-ref --sort=\"$sortby\" --format=\"${pfx//\\%/%%}%(refname:strip=2)$sfx\" \\\n \t\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n \t\t\t\"refs/tags/$cur_*\" \"refs/tags/$cur_*/**\"\n }\n \n # List unique branches from refs/remotes used for 'git checkout' and 'git\n # switch' tracking DWIMery.\n@@ -815,13 +846,15 @@ __git_dwim_remote_heads ()\n \t\t\t\tprint ENVIRON[\"PFX\"] branch ENVIRON[\"SFX\"]\n \t\t\t\tbreak\n \t\t\t}\n \t\t}\n \t}\n \t'\n-\t__git for-each-ref --format='%(refname)' refs/remotes/ |\n+\tlocal sortby=$(__git_get_sort_by_fieldname)\n+\n+\t__git for-each-ref --sort=\"$sortby\" --format='%(refname)' refs/remotes/ |\n \t\tPFX=\"$pfx\" SFX=\"$sfx\" CUR_=\"$cur_\" \\\n \t\t\tIGNORE_CASE=${GIT_COMPLETION_IGNORE_CASE+1} \\\n \t\t\tREMOTES=\"$(__git_remotes | sort -r)\" awk \"$awk_script\" |\n \t\tsort | uniq -u\n }\n \n@@ -844,12 +877,13 @@ __git_refs ()\n \tlocal list_refs_from=path remote=\"${1-}\"\n \tlocal format refs\n \tlocal pfx=\"${3-}\" cur_=\"${4-$cur}\" sfx=\"${5-}\"\n \tlocal match=\"${4-}\"\n \tlocal umatch=\"${4-}\"\n \tlocal fer_pfx=\"${pfx//\\%/%%}\" # \"escape\" for-each-ref format specifiers\n+\tlocal sortby=$(__git_get_sort_by_fieldname)\n \n \t__git_find_repo_path\n \tdir=\"$__git_repo_path\"\n \n \tif [ -z \"$remote\" ]; then\n \t\tif [ -z \"$dir\" ]; then\n@@ -902,13 +936,14 @@ __git_refs ()\n \t\t\tformat=\"refname:strip=2\"\n \t\t\trefs=(\"refs/tags/$match*\" \"refs/tags/$match*/**\"\n \t\t\t\t\"refs/heads/$match*\" \"refs/heads/$match*/**\"\n \t\t\t\t\"refs/remotes/$match*\" \"refs/remotes/$match*/**\")\n \t\t\t;;\n \t\tesac\n-\t\t__git_dir=\"$dir\" __git for-each-ref --format=\"$fer_pfx%($format)$sfx\" \\\n+\t\t__git_dir=\"$dir\" __git for-each-ref --sort=\"$sortby\" \\\n+\t\t\t--format=\"$fer_pfx%($format)$sfx\" \\\n \t\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n \t\t\t\"${refs[@]}\"\n \t\tif [ -n \"$track\" ]; then\n \t\t\t__git_dwim_remote_heads \"$pfx\" \"$match\" \"$sfx\"\n \t\tfi\n \t\treturn\n@@ -926,13 +961,14 @@ __git_refs ()\n \t*)\n \t\tif [ \"$list_refs_from\" = remote ]; then\n \t\t\tcase \"HEAD\" in\n \t\t\t$match*|$umatch*)\techo \"${pfx}HEAD$sfx\" ;;\n \t\t\tesac\n \t\t\tlocal strip=\"$(__git_count_path_components \"refs/remotes/$remote\")\"\n-\t\t\t__git for-each-ref --format=\"$fer_pfx%(refname:strip=$strip)$sfx\" \\\n+\t\t\t__git for-each-ref --sort=\"$sortby\" \\\n+\t\t\t\t--format=\"$fer_pfx%(refname:strip=$strip)$sfx\" \\\n \t\t\t\t${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \\\n \t\t\t\t\"refs/remotes/$remote/$match*\" \\\n \t\t\t\t\"refs/remotes/$remote/$match*/**\"\n \t\telse\n \t\t\tlocal query_symref\n \t\t\tcase \"HEAD\" in\n@@ -2858,13 +2894,14 @@ __git_complete_config_variable_value ()\n \t\t__gitcomp_nl \"$(__git_refs_remotes \"$remote\")\" \"\" \"$cur_\"\n \t\treturn\n \t\t;;\n \tremote.*.push)\n \t\tlocal remote=\"${varname#remote.}\"\n \t\tremote=\"${remote%.push}\"\n-\t\t__gitcomp_nl \"$(__git for-each-ref \\\n+\t\tlocal sortby=$(__git_get_sort_by_fieldname)\n+\t\t__gitcomp_nl \"$(__git for-each-ref --sort=\"$sortby\" \\\n \t\t\t--format='%(refname):%(refname)' refs/heads)\" \"\" \"$cur_\"\n \t\treturn\n \t\t;;\n \tpull.twohead|pull.octopus)\n \t\t__git_compute_merge_strategies\n \t\t__gitcomp \"$__git_merge_strategies\" \"\" \"$cur_\"\n@@ -3980,14 +4017,15 @@ __git_func_wrap ()\n }\n \n ___git_complete ()\n {\n \tlocal wrapper=\"__git_wrap${2}\"\n \teval \"$wrapper () { __git_func_wrap $2 ; }\"\n-\tcomplete -o bashdefault -o default -o nospace -F $wrapper $1 2>/dev/null \\\n-\t\t|| complete -o default -o nospace -F $wrapper $1\n+\tcomplete -o bashdefault -o default -o nospace -o nosort \\\n+\t\t-F $wrapper $1 2>/dev/null \\\n+\t\t|| complete -o default -o nospace -o nosort -F $wrapper $1\n }\n \n # Setup the completion for git commands\n # 1: command or alias\n # 2: function to call (e.g. `git`, `gitk`, `git_fetch`)\n __git_complete ()\n-- \n2.49.0\n\n"},{"id":"520822","messageId":"CALnO6CB4voTTQZrg5OjgHOhuVP4_B71f7LuNFeHNYby4kXOu4A@mail.gmail.com","threadId":"63605","inReplyTo":"CAAUd64333fEMHAaNW=-CkfUMN4HDFF2v==HdO0OFnOgm+W+Jog@mail.gmail.com","subject":"Re: [PATCH] completion: new config var to use --sort in for-each-ref","fromName":"D. Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2025-06-27T19:15:58Z","receivedAt":"2025-06-27T19:16:12Z","isPatch":true,"sender":{"key":"ben.knoble@gmail.com","avatar":"https://avatars.githubusercontent.com/u/22802209?v=4"},"body":"On Sun, Jun 8, 2025 at 12:22 PM Nelson Benítez León <nbenitezl@gmail.com> wrote:\n>\n> Hi, I'm attaching a patch for the Bash completion script, to be able\n> to change the default implicit alphabetical ordering used when\n> returning refs e.g. when doing \"git checkout <TAB>\"\n\nI keep meaning to come back and review this, and then I see that the\npatch is an attached file and not sent over plaintext (which was\nnecessary because the GMail web client breaks patches). Perhaps you\ncould submit your patch via GitGitGadget\n(https://gitgitgadget.github.io/) so that submission is in the\nexpected shape for this project?\n\n>\n> I wanted the completed refs to be ordered descending by committer date\n> i.e. --sort=\"-committerdate\" because that shows on top the branches\n> I've been recently working on. The completion script didn't allow to\n> set a custom ordering from the default alphabetical one, so I'm\n> sending a patch which adds a new config var where the user can set\n> their desired custom ordering.\n>\n> I've not added tests because I'm not familiar with the test machinery,\n> hopefully this is still useful.\n>\n> Regards,\n>\n> PD. I send from Gmail web interface because git send-email for Gmail\n> requires 2-factor authentication and I chose not to enable it.\n\n\n\n-- \nD. Ben Knoble\n"},{"id":"520823","messageId":"a3cd775d-99fa-49de-881b-1e8f178f9102@app.fastmail.com","threadId":"63605","inReplyTo":"CAAUd64333fEMHAaNW=-CkfUMN4HDFF2v==HdO0OFnOgm+W+Jog@mail.gmail.com","subject":"Re: [PATCH] completion: new config var to use --sort in for-each-ref","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2025-06-27T19:47:40Z","receivedAt":"2025-06-27T19:48:03Z","isPatch":true,"sender":{"key":"kristofferhaugsbakk@fastmail.com","avatar":null},"body":"> Previously when completing refs eg. \"git checkout <TAB>\"\n> all refs were alphabetically ordered, this was an\n> implicit ordering and could not be changed.\n\nStyle-wise, the commit message is supposed to discuss the current\nbehavior (without the patch) in the present tense and what this patch\ndoes in the imperative mood.  Like you are commanding the code to\nchange.[1]  Something like:\n\n    When completing refs, e.g. \"git checkout <TAB>\", all refs are\n    alphabetically ordered; this is an implicit ordering and cannot be\n    changed.\n\nThis also gels with the general pattern of:\n\n1. Introduce the current behavior\n2. The problem it causes (maybe merged with (1))\n3. What to do to fix it\n\n> Previously when completing refs eg. \"git checkout <TAB>\"\n\ns/eg./e.g./\n\nMaybe also some commas like\n\n    ..., e.g. \"git checkout <TAB>\", ...\n\nOr maybe it should be “i.e.”?\n\n> This commit adds a new config var to allow setting\n> a custom ordering, the conf value will be used\n> for the --sort=<val> of for-each-ref.\n>\n> When a custom ordering is not set then alphabetical\n> default is kept, but this time is explicit as we\n> pass --sort='refname'\n>\n> This commit also adds '-o nosort' to 'complete'\n> to disable its default alphabetical ordering so\n> our custom ordering prevails.\n\nSuper nitpick: the paragraphs could be wrapped closer to 72 characters/\ncolumns.[2][3]  This alternative (slightly modified) is closer\nto that yet not very uneven, still.\n\n    Previously when completing refs eg. \"git checkout <TAB>\" all refs\n    were alphabetically ordered, this was an implicit ordering and could\n    not be changed.\n\n    This commit adds a new config var to allow setting a custom ordering,\n    the conf value will be used for the --sort=<val> of for-each-ref.\n\n    When a custom ordering is not set then alphabetical default is kept,\n    but this time is explicit as we pass --sort='refname'\n\n    This commit also adds '-o nosort' to 'complete' to disable its default\n    alphabetical ordering so our custom ordering prevails.\n\n> This commit also adds '-o nosort' to 'complete'\n\n“This commit” should be replaced with just the imperative style\n“Also add”.[1]\n\n† 1: See Documentation/SubmittingPatches, “imperative-mood”\n🔗 2: https://lore.kernel.org/git/CAPig+cT1VfY8QiUvrrV3-obTBP1439b6iwaebJtGwML5MScnQA@mail.gmail.com/\n\n-- \ncheers \n\nKristoffer Haugsbakk\n"},{"id":"520825","messageId":"CALnO6CB8GWVNPS_eud6QEpzh7M5oQ6Vcm1-Hx5VqP=L1ovtyaw@mail.gmail.com","threadId":"63605","inReplyTo":"a3cd775d-99fa-49de-881b-1e8f178f9102@app.fastmail.com","subject":"Re: [PATCH] completion: new config var to use --sort in for-each-ref","fromName":"D. Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2025-06-27T19:53:02Z","receivedAt":"2025-06-27T19:53:15Z","isPatch":true,"sender":{"key":"ben.knoble@gmail.com","avatar":"https://avatars.githubusercontent.com/u/22802209?v=4"},"body":"On Fri, Jun 27, 2025 at 3:48 PM Kristoffer Haugsbakk\n<kristofferhaugsbakk@fastmail.com> wrote:\n>\n> > Previously when completing refs eg. \"git checkout <TAB>\"\n> > all refs were alphabetically ordered, this was an\n> > implicit ordering and could not be changed.\n>\n> Style-wise, the commit message is supposed to discuss the current\n> behavior (without the patch) in the present tense and what this patch\n> does in the imperative mood.  Like you are commanding the code to\n> change.[1]  Something like:\n>\n>     When completing refs, e.g. \"git checkout <TAB>\", all refs are\n>     alphabetically ordered; this is an implicit ordering and cannot be\n>     changed.\n>\n> This also gels with the general pattern of:\n>\n> 1. Introduce the current behavior\n> 2. The problem it causes (maybe merged with (1))\n> 3. What to do to fix it\n>\n> > Previously when completing refs eg. \"git checkout <TAB>\"\n>\n> s/eg./e.g./\n>\n> Maybe also some commas like\n>\n>     ..., e.g. \"git checkout <TAB>\", ...\n>\n> Or maybe it should be “i.e.”?\n\nAlright, I'll take the bait:\n- \"e.g.\" and \"i.e.\" are typically offset by commas, e.g., like this\n- since there are other ways to complete refs, using \"e.g.\" (roughly\n\"for example\") is better than \"i.e.\" (roughly, \"that is\") here\n\n>\n> > This commit adds a new config var to allow setting\n> > a custom ordering, the conf value will be used\n> > for the --sort=<val> of for-each-ref.\n> >\n> > When a custom ordering is not set then alphabetical\n> > default is kept, but this time is explicit as we\n> > pass --sort='refname'\n> >\n> > This commit also adds '-o nosort' to 'complete'\n> > to disable its default alphabetical ordering so\n> > our custom ordering prevails.\n>\n> Super nitpick: the paragraphs could be wrapped closer to 72 characters/\n> columns.[2][3]  This alternative (slightly modified) is closer\n> to that yet not very uneven, still.\n>\n>     Previously when completing refs eg. \"git checkout <TAB>\" all refs\n>     were alphabetically ordered, this was an implicit ordering and could\n>     not be changed.\n>\n>     This commit adds a new config var to allow setting a custom ordering,\n>     the conf value will be used for the --sort=<val> of for-each-ref.\n>\n>     When a custom ordering is not set then alphabetical default is kept,\n>     but this time is explicit as we pass --sort='refname'\n>\n>     This commit also adds '-o nosort' to 'complete' to disable its default\n>     alphabetical ordering so our custom ordering prevails.\n>\n> > This commit also adds '-o nosort' to 'complete'\n>\n> “This commit” should be replaced with just the imperative style\n> “Also add”.[1]\n>\n> † 1: See Documentation/SubmittingPatches, “imperative-mood”\n> 🔗 2: https://lore.kernel.org/git/CAPig+cT1VfY8QiUvrrV3-obTBP1439b6iwaebJtGwML5MScnQA@mail.gmail.com/\n>\n> --\n> cheers\n>\n> Kristoffer Haugsbakk\n>\n\nAll great suggestions, thanks.\n\n-- \nD. Ben Knoble\n"}]}