git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v7 3/4] alias: support non-alphanumeric names via subsection syntax

From
Jonatan Holmgren <jonatan@jontes.page>
Date
Feb 18, 2026, 21:57 UTC
Message-ID
<20260218215737.1181147-4-jonatan@jontes.page>
In-Reply-To
<20260218215737.1181147-1-jonatan@jontes.page>

Git alias names are limited to ASCII alphanumeric characters and dashes because aliases are implemented as config variable names. This prevents aliases being created in languages using characters outside that range.

Add support for arbitrary alias names by using config subsections:
    [alias "förgrena"]
        command = branch

The subsection name is matched as-is (case-sensitive byte comparison), while the existing definition without a subsection (e.g., "[alias] co = checkout") remains case-insensitive for backward compatibility. This uses existing config infrastructure since subsections already support arbitrary bytes, and avoids introducing Unicode normalization.

Also teach the help subsystem about the new syntax so that "git help -a" properly lists subsection aliases and the autocorrect feature can suggest them. Use utf8_strwidth() instead of strlen() for column alignment so that non-ASCII alias names display correctly.

Suggested-by: Jeff King <peff@peff.net>
Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 Documentation/config/alias.adoc | 50 ++++++++++++++++++++++-----
 alias.c                         | 38 ++++++++++++++++----
 help.c                          | 14 ++++++--
 t/t0014-alias.sh                | 61 +++++++++++++++++++++++++++++++++
 4 files changed, 145 insertions(+), 18 deletions(-)
diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc
index 80ce17d2de..09a6499249 100644
--- a/Documentation/config/alias.adoc
+++ b/Documentation/config/alias.adoc
@@ -1,12 +1,46 @@
 alias.*::
-	Command aliases for the linkgit:git[1] command wrapper - e.g.
-	after defining `alias.last = cat-file commit HEAD`, the invocation
-	`git last` is equivalent to `git cat-file commit HEAD`. To avoid
-	confusion and troubles with script usage, aliases that
-	hide existing Git commands are ignored except for deprecated
-	commands.  Arguments are split by
-	spaces, the usual shell quoting and escaping are supported.
-	A quote pair or a backslash can be used to quote them.
+alias.*.command::
+	Command aliases for the linkgit:git[1] command wrapper. Aliases
+	can be defined using two syntaxes:
++
+--
+1. Without a subsection, e.g., `[alias] co = checkout`. The alias
+   name ("co" in this example) is 
+   limited to ASCII alphanumeric characters and `-`,
+   and is matched case-insensitively.
+2. With a subsection, e.g., `[alias "co"] command = checkout`. The
+   alias name can contain any characters (except for newlines and NUL bytes), 
+   including UTF-8, and is matched case-sensitively as raw bytes.
+   You define the action of the alias in the `command`.
+--
++
+Examples:
++
+----
+# Without subsection (ASCII alphanumeric and dash only)
+[alias]
+    co = checkout
+    st = status
+
+# With subsection (allows any characters, including UTF-8)
+[alias "hämta"]
+    command = fetch
+[alias "rätta till"]
+    command = commit --amend
+----
++
+With a Git alias defined, e.g.,
+
+    $ git config --global alias.last "cat-file commit HEAD"
+    # Which is equivalent to
+    $ git config --global alias.last.command "cat-file commit HEAD"
+
+`git last` is equivalent to `git cat-file commit HEAD`. To avoid
+confusion and troubles with script usage, aliases that
+hide existing Git commands are ignored except for deprecated
+commands.  Arguments are split by
+spaces, the usual shell quoting and escaping are supported.
+A quote pair or a backslash can be used to quote them.
 +
 Note that the first word of an alias does not necessarily have to be a
 command. It can be a command-line option that will be passed into the
diff --git a/alias.c b/alias.c
index 271acb9bf1..0d636278bc 100644
--- a/alias.c
+++ b/alias.c
@@ -13,28 +13,52 @@ struct config_alias_data {
 	struct string_list *list;
 };
 
-static int config_alias_cb(const char *key, const char *value,
+static int config_alias_cb(const char *var, const char *value,
 			   const struct config_context *ctx UNUSED, void *d)
 {
 	struct config_alias_data *data = d;
-	const char *p;
+	const char *subsection, *key;
+	size_t subsection_len;
 
-	if (!skip_prefix(key, "alias.", &p))
+	if (parse_config_key(var, "alias", &subsection, &subsection_len,
+			     &key) < 0)
+		return 0;
+
+	/*
+	 * Two config syntaxes:
+	 * - alias.name = value   (without subsection, case-insensitive)
+	 * - [alias "name"]
+	 *       command = value  (with subsection, case-sensitive)
+	 */
+	if (subsection && strcmp(key, "command"))
 		return 0;
 
 	if (data->alias) {
-		if (!strcasecmp(p, data->alias)) {
+		int match;
+
+		if (subsection)
+			match = (strlen(data->alias) == subsection_len &&
+				 !strncmp(data->alias, subsection,
+					  subsection_len));
+		else
+			match = !strcasecmp(data->alias, key);
+
+		if (match) {
 			FREE_AND_NULL(data->v);
 			return git_config_string(&data->v,
-						 key, value);
+						 var, value);
 		}
 	} else if (data->list) {
 		struct string_list_item *item;
 
 		if (!value)
-			return config_error_nonbool(key);
+			return config_error_nonbool(var);
 
-		item = string_list_append(data->list, p);
+		if (subsection)
+			item = string_list_append_nodup(data->list,
+				xmemdupz(subsection, subsection_len));
+		else
+			item = string_list_append(data->list, key);
 		item->util = xstrdup(value);
 	}
 
diff --git a/help.c b/help.c
index 691af219bf..95f576c5c8 100644
--- a/help.c
+++ b/help.c
@@ -21,6 +21,7 @@
 #include "fsmonitor-ipc.h"
 #include "repository.h"
 #include "alias.h"
+#include "utf8.h"
 
 #ifndef NO_CURL
 #include "git-curl-compat.h" /* For LIBCURL_VERSION only */
@@ -108,7 +109,7 @@ static void print_command_list(const struct cmdname_help *cmds,
 
 	for (i = 0; cmds[i].name; i++) {
 		if (cmds[i].category & mask) {
-			size_t len = strlen(cmds[i].name);
+			size_t len = utf8_strwidth(cmds[i].name);
 			printf("   %s   ", cmds[i].name);
 			if (longest > len)
 				mput_char(' ', longest - len);
@@ -492,7 +493,7 @@ static void list_all_cmds_help_aliases(int longest)
 	string_list_sort(&alias_list);
 
 	for (i = 0; i < alias_list.nr; i++) {
-		size_t len = strlen(alias_list.items[i].string);
+		size_t len = utf8_strwidth(alias_list.items[i].string);
 		if (longest < len)
 			longest = len;
 	}
@@ -591,8 +592,15 @@ static int git_unknown_cmd_config(const char *var, const char *value,
 	/* Also use aliases for command lookup */
 	if (!parse_config_key(var, "alias", &subsection, &subsection_len,
 			      &key)) {
-		if (!subsection)
+		if (subsection) {
+			/* [alias "name"] command = value */
+			if (!strcmp(key, "command"))
+				add_cmdname(&cfg->aliases, subsection,
+					    subsection_len);
+		} else {
+			/* alias.name = value */
 			add_cmdname(&cfg->aliases, key, strlen(key));
+		}
 	}
 
 	return 0;
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index a13d2be8ca..34bbdb51c5 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -122,4 +122,65 @@ test_expect_success 'alias without value reports error' '
 	test_grep "alias.noval" error
 '
 
+test_expect_success 'subsection syntax works' '
+	test_config alias.testnew.command "!echo ran-subsection" &&
+	git testnew >output &&
+	test_grep "ran-subsection" output
+'
+
+test_expect_success 'subsection syntax only accepts command key' '
+	test_config alias.invalid.notcommand value &&
+	test_must_fail git invalid 2>error &&
+	test_grep -i "not a git command" error
+'
+
+test_expect_success 'subsection syntax requires value for command' '
+	test_when_finished "git config --remove-section alias.noval" &&
+	cat >>.git/config <<-\EOF &&
+	[alias "noval"]
+		command
+	EOF
+	test_must_fail git noval 2>error &&
+	test_grep "alias.noval.command" error
+'
+
+test_expect_success 'simple syntax is case-insensitive' '
+	test_config alias.LegacyCase "!echo ran-legacy" &&
+	git legacycase >output &&
+	test_grep "ran-legacy" output
+'
+
+test_expect_success 'subsection syntax is case-sensitive' '
+	test_config alias.SubCase.command "!echo ran-upper" &&
+	test_config alias.subcase.command "!echo ran-lower" &&
+	git SubCase >upper.out &&
+	git subcase >lower.out &&
+	test_grep "ran-upper" upper.out &&
+	test_grep "ran-lower" lower.out
+'
+
+test_expect_success 'UTF-8 alias with Swedish characters' '
+	test_config alias."förgrena".command "!echo ran-swedish" &&
+	git förgrena >output &&
+	test_grep "ran-swedish" output
+'
+
+test_expect_success 'UTF-8 alias with CJK characters' '
+	test_config alias."分支".command "!echo ran-cjk" &&
+	git 分支 >output &&
+	test_grep "ran-cjk" output
+'
+
+test_expect_success 'alias with spaces in name' '
+	test_config alias."test name".command "!echo ran-spaces" &&
+	git "test name" >output &&
+	test_grep "ran-spaces" output
+'
+
+test_expect_success 'subsection aliases listed in help -a' '
+	test_config alias."förgrena".command "!echo test" &&
+	git help -a >output &&
+	test_grep "förgrena" output
+'
+
 test_done
-- 
2.53.0.122.g3abf75d576
Previous: Jonatan HolmgrenNext: Kristoffer Haugsbakk
Message 67 of 88 in “[RFC] Support UTF-8 characters in Git alias names”
  1. Jonatan HolmgrenFeb 8, 2026
  2. D. Ben KnobleFeb 8, 2026
  3. brian m. carlsonFeb 8, 2026
  4. Junio C HamanoFeb 9, 2026
  5. Jonatan HolmgrenFeb 9, 2026
  6. Junio C HamanoFeb 9, 2026
  7. brian m. carlsonFeb 9, 2026
  8. Junio C HamanoFeb 9, 2026
  9. Ben KnobleFeb 10, 2026
  10. Junio C HamanoFeb 10, 2026
  11. Jeff KingFeb 10, 2026
  12. Jeff KingFeb 9, 2026
  13. Theodore TsoFeb 9, 2026
  14. alias: support UTF-8 characters via subsection syntaxJonatan Holmgren, Feb 9, 2026
  15. Jeff KingFeb 10, 2026
  16. Torsten BögershausenFeb 10, 2026
  17. Junio C HamanoFeb 10, 2026
  18. 0/2 support UTF-8 in alias namesJonatan Holmgren, Feb 10, 2026
  19. 1/2 help: use list_aliases() for alias listing and lookupJonatan Holmgren, Feb 10, 2026
  20. Junio C HamanoFeb 10, 2026
  21. 2/2 alias: support non-alphanumeric names via subsection syntaxJonatan Holmgren, Feb 10, 2026
  22. Junio C HamanoFeb 10, 2026
  23. Jonatan HolmgrenFeb 10, 2026
  24. Kristoffer HaugsbakkFeb 23, 2026
  25. Kristoffer HaugsbakkFeb 23, 2026
  26. Junio C HamanoFeb 23, 2026
  27. Kristoffer HaugsbakkFeb 23, 2026
  28. Patrick SteinhardtFeb 24, 2026
  29. 0/3 support UTF-8 in alias namesJonatan Holmgren, Feb 10, 2026
  30. 1/3 help: use list_aliases() for alias listingJonatan Holmgren, Feb 10, 2026
  31. Junio C HamanoFeb 10, 2026
  32. 2/3 alias: prepare for subsection aliasesJonatan Holmgren, Feb 10, 2026
  33. 3/3 alias: support non-alphanumeric names via subsection syntaxJonatan Holmgren, Feb 10, 2026
  34. 0/3 support UTF-8 in alias namesJonatan Holmgren, Feb 11, 2026
  35. 2/3 alias: prepare for subsection aliasesJonatan Holmgren, Feb 11, 2026
  36. Junio C HamanoFeb 11, 2026
  37. 1/3 help: use list_aliases() for alias listingJonatan Holmgren, Feb 11, 2026
  38. Junio C HamanoFeb 11, 2026
  39. 3/3 alias: support non-alphanumeric names via subsection syntaxJonatan Holmgren, Feb 11, 2026
  40. Junio C HamanoFeb 11, 2026
  41. Richard KerryFeb 12, 2026
  42. Jonatan HolmgrenFeb 12, 2026
  43. Jonatan HolmgrenFeb 12, 2026
  44. Torsten BögershausenFeb 12, 2026
  45. Jonatan HolmgrenFeb 12, 2026
  46. 0/4 support uTF-8 in alias namesJonatan Holmgren, Feb 16, 2026
  47. 4/4 completion: fix zsh alias listing for subsection aliasesJonatan Holmgren, Feb 16, 2026
  48. D. Ben KnobleFeb 16, 2026
  49. Junio C HamanoFeb 17, 2026
  50. 2/4 alias: prepare for subsection aliasesJonatan Holmgren, Feb 16, 2026
  51. 1/4 help: use list_aliases() for alias listingJonatan Holmgren, Feb 16, 2026
  52. 3/4 alias: support non-alphanumeric names via subsection syntaxJonatan Holmgren, Feb 16, 2026
  53. 0/4 support UTF-8 in alias namesJonatan Holmgren, Feb 18, 2026
  54. 2/4 alias: prepare for subsection aliasesJonatan Holmgren, Feb 18, 2026
  55. Kristoffer HaugsbakkFeb 18, 2026
  56. 1/4 help: use list_aliases() for alias listingJonatan Holmgren, Feb 18, 2026
  57. 4/4 completion: fix zsh alias listing for subsection aliasesJonatan Holmgren, Feb 18, 2026
  58. 3/4 alias: support non-alphanumeric names via subsection syntaxJonatan Holmgren, Feb 18, 2026
  59. 0/4 support UTF-8 in alias namesJonatan Holmgren, Feb 18, 2026
  60. 1/4 help: use list_aliases() for alias listingJonatan Holmgren, Feb 18, 2026
  61. Jacob KellerFeb 24, 2026
  62. Junio C HamanoFeb 24, 2026
  63. Junio C HamanoFeb 25, 2026
  64. Jacob KellerFeb 26, 2026
  65. Jacob KellerFeb 24, 2026
  66. 2/4 alias: prepare for subsection aliasesJonatan Holmgren, Feb 18, 2026
  67. 3/4 alias: support non-alphanumeric names via subsection syntaxJonatan Holmgren, Feb 18, 2026
  68. Kristoffer HaugsbakkFeb 24, 2026
  69. Jonatan HolmgrenFeb 24, 2026
  70. Kristoffer HaugsbakkFeb 24, 2026
  71. 4/4 completion: fix zsh alias listing for subsection aliasesJonatan Holmgren, Feb 18, 2026
  72. Junio C HamanoFeb 19, 2026
  73. Jonatan HolmgrenFeb 19, 2026
  74. 0/2 Fix small issues in alias subsection handlingJonatan Holmgren, Feb 24, 2026
  75. 1/2 doc: fix list continuation in alias subsection exampleJonatan Holmgren, Feb 24, 2026
  76. Junio C HamanoFeb 24, 2026
  77. Kristoffer HaugsbakkFeb 24, 2026
  78. Junio C HamanoFeb 24, 2026
  79. 2/2 alias: treat empty subsection [alias ""] as plain [alias]Jonatan Holmgren, Feb 24, 2026
  80. Junio C HamanoFeb 26, 2026
  81. 0/3 Fix small issues in alias subsection handlingJonatan Holmgren, Feb 26, 2026
  82. 2/3 alias: treat empty subsection [alias ""] as plain [alias]Jonatan Holmgren, Feb 26, 2026
  83. 1/3 doc: fix list continuation in alias subsection exampleJonatan Holmgren, Feb 26, 2026
  84. Kristoffer HaugsbakkMar 3, 2026
  85. Jonatan HolmgrenMar 3, 2026
  86. 3/3 git, help: fix memory leaks in alias listingJonatan Holmgren, Feb 26, 2026
  87. Junio C HamanoFeb 26, 2026
  88. doc: fix list continuation in alias.adocJonatan Holmgren, Mar 3, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.