public inbox for git@vger.kernel.org
 help / color / mirror / Atom feed
* [RFC] Support UTF-8 characters in Git alias names
@ 2026-02-08 15:30 Jonatan Holmgren
  2026-02-08 16:07 ` D. Ben Knoble
                   ` (12 more replies)
  0 siblings, 13 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-08 15:30 UTC (permalink / raw)
  To: git

Hi Git developers,

I'd like to propose adding support for UTF-8 characters in Git alias 
names to better support non-English speaking users as these are 
currently restricted to [a-zA-Z0-9-] which prevents users from creating 
aliases in languages with other characters. Shell aliases do not solve 
this as the best you could do is "git-förgrena".


This would allow users to set aliases such as:

     git config alias.förgrena branch # Swedish
     git config alias.分支 branch # Chinese

etc.

The restriction comes from iskeychar() in config.c:526-529:

     static inline int iskeychar(int c)
     {
	return isalnum(c) || c == '-';
     }

The function in question validates all Git config keys, not just 
aliases. The git_config_parse_key() function (same file, 543-598) 
enforces this validation and uses tolower(), which only works for ASCII.

I think the best approach is to support UTF-8 specifically for alias.* 
variables, which would mean modifying the git_config_parse_key() fn to 
allow UTF-8 bytes and make non-ascii aliases case-sensitive to avoid 
complex locale-dependent case folding.

The main pain point would be making sure all platforms handle this 
nicely, esp since mac uses NFD and not NFC Unicode.

Before implementing this, I'd like to hear:

1. Is this a feature the project would like?
2. Is my implementation approach reasonable?
3. What concerns should be addressed in said design?
4. Any compat requirements I should be aware of?

Thank you so much for your consideration!

Jonatan Holmgren

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [RFC] Support UTF-8 characters in Git alias names
  2026-02-08 15:30 [RFC] Support UTF-8 characters in Git alias names Jonatan Holmgren
@ 2026-02-08 16:07 ` D. Ben Knoble
  2026-02-08 23:21 ` brian m. carlson
                   ` (11 subsequent siblings)
  12 siblings, 0 replies; 88+ messages in thread
From: D. Ben Knoble @ 2026-02-08 16:07 UTC (permalink / raw)
  To: Jonatan Holmgren; +Cc: git

On Sun, Feb 8, 2026 at 10:37 AM Jonatan Holmgren <jonatan@jontes.page> wrote:
>
> Hi Git developers,
>
> I'd like to propose adding support for UTF-8 characters in Git alias
> names to better support non-English speaking users as these are
> currently restricted to [a-zA-Z0-9-] which prevents users from creating
> aliases in languages with other characters. Shell aliases do not solve
> this as the best you could do is "git-förgrena".
>
>
> This would allow users to set aliases such as:
>
>      git config alias.förgrena branch # Swedish
>      git config alias.分支 branch # Chinese
>
> etc.
>
> The restriction comes from iskeychar() in config.c:526-529:
>
>      static inline int iskeychar(int c)
>      {
>         return isalnum(c) || c == '-';
>      }
>
> The function in question validates all Git config keys, not just
> aliases. The git_config_parse_key() function (same file, 543-598)
> enforces this validation and uses tolower(), which only works for ASCII.

In particular, this comes from sane-ctype.h and ctype.c, which I could
not easily rip out of the build :/

It looks like Git's ctype definitions are ASCII-specific even though
isalnum(3) on my system is documented as using the locale, so I was
hoping that if I could build Git without it's own ctype.c I could test
your aliases. Bummer.

It looks like this goes back to 4546738b58 (Unlocalized isspace and
friends, 2005-10-13). I don't have an amlog note for it, but searching
all of lore turned up
<https://lore.kernel.org/all/Pine.LNX.4.64.0510130838240.15297@g5.osdl.org/>.
In the same thread, [1] suggests we'd have to use more complex logic
to parse UTF-8 config, right?

[1]: https://lore.kernel.org/all/434E8650.7060604@zytor.com/

Anyway, my opinion (not that it holds much weight) is that user-facing
parts of Git ought to support international languages. (If the `diff`
line for git-apply(1) has to be ASCII, that's probably fine [2]; it
should definitely be parsed as a byte-stream.)

[2]: https://lore.kernel.org/all/7vachd6hdx.fsf@assigned-by-dhcp.cox.net/

-- 
D. Ben Knoble

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [RFC] Support UTF-8 characters in Git alias names
  2026-02-08 15:30 [RFC] Support UTF-8 characters in Git alias names Jonatan Holmgren
  2026-02-08 16:07 ` D. Ben Knoble
@ 2026-02-08 23:21 ` brian m. carlson
  2026-02-09 14:55   ` Junio C Hamano
  2026-02-09  7:36 ` Jeff King
                   ` (10 subsequent siblings)
  12 siblings, 1 reply; 88+ messages in thread
From: brian m. carlson @ 2026-02-08 23:21 UTC (permalink / raw)
  To: Jonatan Holmgren; +Cc: git

[-- Attachment #1: Type: text/plain, Size: 1865 bytes --]

On 2026-02-08 at 15:30:02, Jonatan Holmgren wrote:
> I think the best approach is to support UTF-8 specifically for alias.*
> variables, which would mean modifying the git_config_parse_key() fn to allow
> UTF-8 bytes and make non-ascii aliases case-sensitive to avoid complex
> locale-dependent case folding.

Yes, I don't think anyone should be relying on case folding for aliases.
Not doing case folding also avoids locale problems with Turkic
languages.

Our mailmap also doesn't do case-folding for non-ASCII characters,
so there is some precedent, although I wish we were consistent by not
doing case folding at all.

> The main pain point would be making sure all platforms handle this nicely,
> esp since mac uses NFD and not NFC Unicode.

I don't think this affects command-line arguments, though, which are
just bytes.  Since running `git paramétrer` would just be passing
`paramétrer` as a command-line argument, then it should pass that value
as an unnormalized UTF-8 byte string.  It would only be normalized if
Git invoked `git-paramétrer` as a binary or script.

I don't think we have any Unicode normalization code at all in Git,
though, so if you want a quality implementation, that may be a thing we
need.

> Before implementing this, I'd like to hear:
> 
> 1. Is this a feature the project would like?

I think this would be useful.  I don't personally plan to use it, but I
can imagine a lot of other people would, and in general I'm in favour of
better i18n and l10n support.

> 2. Is my implementation approach reasonable?
> 3. What concerns should be addressed in said design?

As I said, Unicode normalization may be a thing you want to support
here.  Not having it isn't a complete dealbreaker, but it would prevent
hard-to-debug breakage.
-- 
brian m. carlson (they/them)
Toronto, Ontario, CA

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 262 bytes --]

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [RFC] Support UTF-8 characters in Git alias names
  2026-02-08 15:30 [RFC] Support UTF-8 characters in Git alias names Jonatan Holmgren
  2026-02-08 16:07 ` D. Ben Knoble
  2026-02-08 23:21 ` brian m. carlson
@ 2026-02-09  7:36 ` Jeff King
  2026-02-09 13:59   ` Theodore Tso
  2026-02-09 22:01 ` [PATCH v1] alias: support UTF-8 characters via subsection syntax Jonatan Holmgren
                   ` (9 subsequent siblings)
  12 siblings, 1 reply; 88+ messages in thread
From: Jeff King @ 2026-02-09  7:36 UTC (permalink / raw)
  To: Jonatan Holmgren; +Cc: git

On Sun, Feb 08, 2026 at 04:30:02PM +0100, Jonatan Holmgren wrote:

> I think the best approach is to support UTF-8 specifically for alias.*
> variables, which would mean modifying the git_config_parse_key() fn to allow
> UTF-8 bytes and make non-ascii aliases case-sensitive to avoid complex
> locale-dependent case folding.
> 
> The main pain point would be making sure all platforms handle this nicely,
> esp since mac uses NFD and not NFC Unicode.
> 
> Before implementing this, I'd like to hear:
> 
> 1. Is this a feature the project would like?
> 2. Is my implementation approach reasonable?
> 3. What concerns should be addressed in said design?
> 4. Any compat requirements I should be aware of?

I think supporting non-ascii aliases is a good goal.

However, I'm not sure that special-casing the parsing of alias config
keys is the best direction. Since it's a syntactic change, the special
case would have to be understand by all code that reads or writes
config, not just git_config_parse_key(). And then you'd potentially run
into problems with older versions of Git, or alternate implementations
(of which there are several).

Plus it doesn't solve all of the issues. E.g., should we allow new
characters like "_" (for a potential "git foo_bar")? That is doable, but
what about "." (for "git foo.bar")? I think that introduces new
ambiguities into the syntax.

Taking a step back, I think the root of the issue is that the schema for
alias keys is poorly designed. Git's config syntax allows for three
levels: section, subsection, and key. The section and key fields are
restricted to alnum and dash, but the subsection is designed to be
unrestricted (modulo NUL bytes).

And that's why we have:

  [branch "foo/bar"]
  remote = origin

for example, because branch names don't follow the same syntax rules as
config keys. And it's the same issue here: the alias.* schema is trying
to use one syntax (alnum config keys) to store another (command names).
They _usually_ overlap, but not always. The pager.* config has the same
problem.

We've discussed this before, e.g., in:

  https://lore.kernel.org/git/20150206124528.GA18859@inner.h.apk.li/

There the immediate problem was that "git foo_bar" caused an error
message. We hacked around it by suppressing the error, but it was still
impossible to add an alias or pager config. We knew that was a
limitation, but punted until somebody came along who actually cared
about making it work. Now you get to be that somebody. ;)

So what I'd propose instead is introducing a new schema like:

  - setting "alias.foo.command" to "bar" would alias "git foo" to "bar";
    this should work for any command name, as it is just a byte stream

  - a given command subsection is matched verbatim. So alias.foo.command
    matches "git foo" but not "git Foo". Likewise, we do not do any
    normalization. You put what you want into your config, and it should
    match the command you invoke. This is perhaps less friendly, but it
    punts on any normalization or case-folding that we have to do, and
    matches how the rest of Git works (paths are likewise streams of
    bytes, and it is mostly up to the user to use them consistently).

  - leave "alias.foo" as a historical synonym for "alias.foo.command",
    so that existing config continues working

  - optionally add new keys within alias.foo.* sections. For example, we
    could allow alias.foo.help to provide text shown during "git help
    foo". For the most part that could come later, so I'm just
    illustrating possible eventual directions that the new schema would
    allow. But it might be worth pondering a little now to avoid
    painting ourselves into a corner. E.g., you could imagine a schema
    where alias.foo.shell is set to "true" instead of sticking a "!" at
    the front of the value of alias.foo.command. I don't know if that's
    a good idea or not, but if we were going to do stuff like that, we'd
    want to decide now before setting the alias.foo.command behavior in
    stone.

  - likewise, optionally do the same for pager.*

I hacked together some illustrative code below. Note that we do use
strcasecmp() currently to match command names (which kind of makes
sense, since if you had "alias.Foo" in your config, the parser would
downcase it to "alias.foo"). So probably that historical code should
continue to behave like that, but the new "alias.Foo.command" should be
more verbatim (the patch below just feeds them both to strcasecmp).

-Peff

---
diff --git a/alias.c b/alias.c
index 1a1a141a0a..44bdde58af 100644
--- a/alias.c
+++ b/alias.c
@@ -17,19 +17,30 @@ static int config_alias_cb(const char *key, const char *value,
 			   const struct config_context *ctx UNUSED, void *d)
 {
 	struct config_alias_data *data = d;
-	const char *p;
+	const char *cmd, *p;
+	size_t cmd_len;
 
-	if (!skip_prefix(key, "alias.", &p))
+	if (parse_config_key(key, "alias", &cmd, &cmd_len, &p) < 0)
 		return 0;
 
+	if (cmd) {
+		/* The only 3-level key we understand is alias.*.command */
+		if (strcmp(p, "command"))
+			return 0;
+	} else {
+		/* alias.foo is the same as alias.foo.command */
+		cmd = p;
+		cmd_len = strlen(p);
+	}
+
 	if (data->alias) {
-		if (!strcasecmp(p, data->alias)) {
+		if (!strncasecmp(cmd, data->alias, cmd_len)) {
 			FREE_AND_NULL(data->v);
 			return git_config_string(&data->v,
 						 key, value);
 		}
 	} else if (data->list) {
-		string_list_append(data->list, p);
+		string_list_append_nodup(data->list, xmemdupz(cmd, cmd_len));
 	}
 
 	return 0;

^ permalink raw reply related	[flat|nested] 88+ messages in thread

* Re: [RFC] Support UTF-8 characters in Git alias names
  2026-02-09  7:36 ` Jeff King
@ 2026-02-09 13:59   ` Theodore Tso
  0 siblings, 0 replies; 88+ messages in thread
From: Theodore Tso @ 2026-02-09 13:59 UTC (permalink / raw)
  To: Jeff King; +Cc: Jonatan Holmgren, git

I would just note that Unicode normalization can be *tricky*.  The
fact that ext4 case folding would do Unicode normaliation was
sufficient to trigger a CVE that resulted a security vulnerability in
git. Of course, who would be insane enough to use git on a case
folded file system (aside from someone running git instead Termux on
an Android device, that is....)?

So I'd suggest that git folks think very carefully before trying to
dive into the insanity which is Unicode.  It might be a very strong
nice-to-have for certain user communities, but it's also a mess.

	     	 	      		   - Ted

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [RFC] Support UTF-8 characters in Git alias names
  2026-02-08 23:21 ` brian m. carlson
@ 2026-02-09 14:55   ` Junio C Hamano
  2026-02-09 15:19     ` Jonatan Holmgren
  2026-02-09 22:40     ` brian m. carlson
  0 siblings, 2 replies; 88+ messages in thread
From: Junio C Hamano @ 2026-02-09 14:55 UTC (permalink / raw)
  To: brian m. carlson; +Cc: Jonatan Holmgren, git

"brian m. carlson" <sandals@crustytoothpaste.net> writes:

> I don't think we have any Unicode normalization code at all in Git,
> though, so if you want a quality implementation, that may be a thing we
> need.

Isn't NKC/NKD a macOS-only issue in practice?  Anything on the
command line "git" potty and "git-blah" built-in commands receive
goes through precompose_argv_prefix() to be normalized on that
platform.

>> Before implementing this, I'd like to hear:
>> 
>> 1. Is this a feature the project would like?
>
> I think this would be useful.  I don't personally plan to use it, but I
> can imagine a lot of other people would, and in general I'm in favour of
> better i18n and l10n support.

I am not fundamentally against this, as long as such an addition
does not introduce unnecessary bugs and ambiguities.  IOW, do not
force me to read bug reports in this area after it is done.

>> 2. Is my implementation approach reasonable?
>> 3. What concerns should be addressed in said design?
>
> As I said, Unicode normalization may be a thing you want to support
> here.  Not having it isn't a complete dealbreaker, but it would prevent
> hard-to-debug breakage.

A buggy normalization implementation would be also a source of
unnecessary bugs.  We cannot have and eat that cake so easily ;-).

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [RFC] Support UTF-8 characters in Git alias names
  2026-02-09 14:55   ` Junio C Hamano
@ 2026-02-09 15:19     ` Jonatan Holmgren
  2026-02-09 17:59       ` Junio C Hamano
  2026-02-09 22:40     ` brian m. carlson
  1 sibling, 1 reply; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-09 15:19 UTC (permalink / raw)
  To: Junio C Hamano, brian m. carlson; +Cc: git

Thanks for chiming in!

 > Isn't NKC/NKD a macOS-only issue in practice?  Anything on the
 > command line "git" potty and "git-blah" built-in commands receive
 > goes through precompose_argv_prefix() to be normalized on that
 > platform.

If we use Jeff's proposed alias.*.{keyname} approach with literal byte
matching macOS should already handle the normalization at the argv level 
before Git even sees it, correct? I'm not very familiar with how macOS 
handles this.

 > I am not fundamentally against this, as long as such an addition
 > does not introduce unnecessary bugs and ambiguities.  IOW, do not
 > force me to read bug reports in this area after it is done.

Understood. Jeff's subsection approach seems safest, it uses existing
config infrastructure that already supports arbitrary bytes in
subsections. This would enable

     [alias "förgrena"]
         command = branch

as an alternative to (not a replacement for) the current ASCII-only:

     [alias]
         forgrena = branch

Is this sound?

Jonatan

On 2026-02-09 15:55, Junio C Hamano wrote:
> "brian m. carlson"<sandals@crustytoothpaste.net> writes:
> 
>> I don't think we have any Unicode normalization code at all in Git,
>> though, so if you want a quality implementation, that may be a thing we
>> need.
> Isn't NKC/NKD a macOS-only issue in practice?  Anything on the
> command line "git" potty and "git-blah" built-in commands receive
> goes through precompose_argv_prefix() to be normalized on that
> platform.
> 
>>> Before implementing this, I'd like to hear:
>>>
>>> 1. Is this a feature the project would like?
>> I think this would be useful.  I don't personally plan to use it, but I
>> can imagine a lot of other people would, and in general I'm in favour of
>> better i18n and l10n support.
> I am not fundamentally against this, as long as such an addition
> does not introduce unnecessary bugs and ambiguities.  IOW, do not
> force me to read bug reports in this area after it is done.
> 
>>> 2. Is my implementation approach reasonable?
>>> 3. What concerns should be addressed in said design?
>> As I said, Unicode normalization may be a thing you want to support
>> here.  Not having it isn't a complete dealbreaker, but it would prevent
>> hard-to-debug breakage.
> A buggy normalization implementation would be also a source of
> unnecessary bugs.  We cannot have and eat that cake so easily 😉.


^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [RFC] Support UTF-8 characters in Git alias names
  2026-02-09 15:19     ` Jonatan Holmgren
@ 2026-02-09 17:59       ` Junio C Hamano
  0 siblings, 0 replies; 88+ messages in thread
From: Junio C Hamano @ 2026-02-09 17:59 UTC (permalink / raw)
  To: Jonatan Holmgren; +Cc: brian m. carlson, git

Jonatan Holmgren <jonatan@jontes.page> writes:

> Thanks for chiming in!
>
>  > Isn't NKC/NKD a macOS-only issue in practice?  Anything on the
>  > command line "git" potty and "git-blah" built-in commands receive
>  > goes through precompose_argv_prefix() to be normalized on that
>  > platform.
>
> If we use Jeff's proposed alias.*.{keyname} approach with literal byte
> matching macOS should already handle the normalization at the argv level 
> before Git even sees it, correct? I'm not very familiar with how macOS 
> handles this.

I may have phrased it poorly, but the above (with Peff's syntax
change, which I think is independently a good thing to do) is
exactly what I meant.  That way, we do not have to worry about
normalization ourselves at all in this area.

Thanks.

^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v1] alias: support UTF-8 characters via subsection syntax
  2026-02-08 15:30 [RFC] Support UTF-8 characters in Git alias names Jonatan Holmgren
                   ` (2 preceding siblings ...)
  2026-02-09  7:36 ` Jeff King
@ 2026-02-09 22:01 ` Jonatan Holmgren
  2026-02-10  7:44   ` Jeff King
                     ` (2 more replies)
  2026-02-10 18:31 ` [PATCH v2 0/2] support UTF-8 in alias names Jonatan Holmgren
                   ` (8 subsequent siblings)
  12 siblings, 3 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-09 22:01 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

Git aliases are currently restricted to ASCII characters due to
config key syntax limitations. This prevents non-English speakers
from creating aliases in their native languages.

Add support for UTF-8 alias names using config subsections:

    [alias "förgrena"]
        command = branch

The subsection name is matched verbatim (case-sensitive), while the
existing flat syntax (alias.name) remains case-insensitive for
backward compatibility. This approach uses existing config
infrastructure and avoids complex Unicode normalization.

Suggested-by: Jeff King <peff@peff.net>
Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 Documentation/RelNotes/2.54.0.adoc |  8 +++++
 Documentation/config/alias.adoc    | 53 +++++++++++++++++++++++++-----
 alias.c                            | 38 +++++++++++++++++----
 t/t0014-alias-utf8.sh              | 44 +++++++++++++++++++++++++
 t/t0014-alias.sh                   | 32 ++++++++++++++++++
 5 files changed, 161 insertions(+), 14 deletions(-)
 create mode 100755 t/t0014-alias-utf8.sh

diff --git a/Documentation/RelNotes/2.54.0.adoc b/Documentation/RelNotes/2.54.0.adoc
index 20c660d82a..8cb00a21bb 100644
--- a/Documentation/RelNotes/2.54.0.adoc
+++ b/Documentation/RelNotes/2.54.0.adoc
@@ -7,6 +7,14 @@ UI, Workflows & Features
  * "git add -p" and friends note what the current status of the hunk
    being shown is.
 
+ * Git aliases now support UTF-8 characters in alias names through
+   subsection syntax: `[alias "name"] command = value`. This enables
+   aliases in non-English languages. The flat syntax continues
+   to work for backward compatibility.
+
+ * The new subsection syntax uses case-sensitive matching and
+   the flat syntax remains case-insensitive for backward compatibility.
+
 
 Performance, Internal Implementation, Development Support etc.
 --------------------------------------------------------------
diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc
index 80ce17d2de..feba1e2022 100644
--- a/Documentation/config/alias.adoc
+++ b/Documentation/config/alias.adoc
@@ -1,12 +1,49 @@
 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. **Simple syntax** (case-insensitive): `alias.name = value`
+2. **Subsection syntax** (recommended for UTF-8/special characters):
+   `[alias "name"]` with `command = value`
+
+The subsection syntax allows alias names containing UTF-8 characters,
+spaces, or special characters, as the subsection preserves the exact
+bytes including case.
+--
++
+Examples:
++
+----
+# Simple syntax (ASCII names)
+[alias]
+    co = checkout
+    st = status
+
+# Subsection syntax (UTF-8 and special characters)
+[alias "hämta"]
+    command = fetch
+[alias "gömma"]
+    command = stash
+[alias "my alias with whitespace"]
+    command = "status --short"
+----
++
+After defining these, you can run `git co`, `git hämta`, `git gömma`,
+and `git "my alias with whitespace"` (note the quotes for aliases with spaces).
++
+**Note:** The flat syntax `alias.name` remains case-insensitive for
+backward compatibility. The new subsection syntax is case-sensitive:
+`[alias "Foo"]` and `[alias "foo"]` are different aliases.
++
+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.
 +
 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 1a1a141a0a..f0c5f12fdd 100644
--- a/alias.c
+++ b/alias.c
@@ -17,19 +17,45 @@ static int config_alias_cb(const char *key, const char *value,
 			   const struct config_context *ctx UNUSED, void *d)
 {
 	struct config_alias_data *data = d;
-	const char *p;
+	const char *cmd, *subkey;
+	size_t cmd_len;
+	int is_subsection;
 
-	if (!skip_prefix(key, "alias.", &p))
+	/* Use parse_config_key() to handle both 2-level and 3-level keys */
+	if (parse_config_key(key, "alias", &cmd, &cmd_len, &subkey) < 0)
 		return 0;
 
+	/*
+	 * Support two syntaxes:
+	 * 1. alias.name = value (simple, 2-level key)
+	 * 2. [alias "name"] command = value (new, 3-level key)
+	 */
+	if (cmd) {
+		if (strcmp(subkey, "command"))
+			return 0;
+		is_subsection = 1;
+	} else {
+		cmd = subkey;
+		cmd_len = strlen(cmd);
+		is_subsection = 0;
+	}
+
 	if (data->alias) {
-		if (!strcasecmp(p, data->alias)) {
+		int match;
+		if (is_subsection) {
+			match = (strlen(data->alias) == cmd_len &&
+				 !strncmp(data->alias, cmd, cmd_len));
+		} else {
+			match = (strlen(data->alias) == cmd_len &&
+				 !strncasecmp(data->alias, cmd, cmd_len));
+		}
+
+		if (match) {
 			FREE_AND_NULL(data->v);
-			return git_config_string(&data->v,
-						 key, value);
+			return git_config_string(&data->v, key, value);
 		}
 	} else if (data->list) {
-		string_list_append(data->list, p);
+		string_list_append_nodup(data->list, xmemdupz(cmd, cmd_len));
 	}
 
 	return 0;
diff --git a/t/t0014-alias-utf8.sh b/t/t0014-alias-utf8.sh
new file mode 100755
index 0000000000..d55b5a7213
--- /dev/null
+++ b/t/t0014-alias-utf8.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+test_description='UTF-8 support in git aliases via subsection syntax'
+
+. ./test-lib.sh
+
+# Skip if filesystem/locale doesn't support UTF-8
+test_lazy_prereq UTF8_LOCALE '
+	test_have_prereq !MINGW &&
+	test_set_prereq UTF8_LOCALE
+'
+
+test_expect_success 'setup test repository' '
+	git init &&
+	test_commit initial
+'
+
+test_expect_success UTF8_LOCALE 'setup UTF-8 aliases' '
+	git config alias."förgrena".command branch &&
+	git config alias."分支".command "branch --list" &&
+	git config alias."test name".command status
+'
+
+test_expect_success UTF8_LOCALE 'UTF-8 alias with Swedish characters' '
+	git förgrena >output &&
+	test_grep -E "^(\* )?(main|master)" output
+'
+
+test_expect_success UTF8_LOCALE 'UTF-8 alias with CJK characters' '
+	git 分支 >output &&
+	test_grep -E "^(\* )?(main|master)" output
+'
+
+test_expect_success UTF8_LOCALE 'alias with spaces in name' '
+	git "test name" >output &&
+	test_grep "On branch" output
+'
+
+test_expect_success 'list UTF-8 aliases' '
+	git config --get-regexp "^alias\\..*\\.command" >output &&
+	test_line_count -ge 3 output
+'
+
+test_done
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 07a53e7366..b19a8a5061 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -111,5 +111,37 @@ test_expect_success 'cannot alias-shadow a sample of regular builtins' '
 		cannot_alias_regular_builtin "$cmd" || return 1
 	done
 '
+test_expect_success 'flat syntax still works' '
+	git config alias.testlegacy status &&
+	git testlegacy >output &&
+	test_grep "On branch" output
+'
+
+test_expect_success 'new subsection syntax works' '
+	git config alias.testnew.command status &&
+	git testnew >output &&
+	test_grep "On branch" output
+'
+
+test_expect_success 'subsection syntax only accepts command key' '
+	git config alias.invalid.notcommand "value" &&
+	test_must_fail git invalid 2>error &&
+	test_grep -i "not a git command" error
+'
+
+test_expect_success 'simple syntax is case-insensitive' '
+	git config alias.LegacyCase status &&
+	git legacycase >output 2>&1 &&
+	test_grep "On branch" output
+'
+
+test_expect_success 'subsection syntax is case-sensitive' '
+	test_commit case-test &&
+	git config alias.SubCase.command "log --oneline" &&
+	git config alias.subcase.command status &&
+	git SubCase >upper.out 2>&1 &&
+	git subcase >lower.out 2>&1 &&
+	! test_cmp upper.out lower.out
+'
 
 test_done
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* Re: [RFC] Support UTF-8 characters in Git alias names
  2026-02-09 14:55   ` Junio C Hamano
  2026-02-09 15:19     ` Jonatan Holmgren
@ 2026-02-09 22:40     ` brian m. carlson
  2026-02-09 23:14       ` Junio C Hamano
  1 sibling, 1 reply; 88+ messages in thread
From: brian m. carlson @ 2026-02-09 22:40 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Jonatan Holmgren, git

[-- Attachment #1: Type: text/plain, Size: 1942 bytes --]

On 2026-02-09 at 14:55:51, Junio C Hamano wrote:
> "brian m. carlson" <sandals@crustytoothpaste.net> writes:
> 
> > I don't think we have any Unicode normalization code at all in Git,
> > though, so if you want a quality implementation, that may be a thing we
> > need.
> 
> Isn't NKC/NKD a macOS-only issue in practice?  Anything on the
> command line "git" potty and "git-blah" built-in commands receive
> goes through precompose_argv_prefix() to be normalized on that
> platform.

Normalization is not a macOS-only issue.  Many accented characters can
be written in multiple ways, one composed and one decomposed.  If the
alias in the file is composed and what's on the command line is
decomposed, they will not match bytewise even though they are logically
and graphically identical.

For instance, here is the word for "where" in French, first composed,
then decomposed:

où
où

The former is U+006F U+00F9 and the latter is U+006F U+0075 U+0300.
Obviously, if I write one of those in my config file and the other on
the command line, I intended to execute the same alias, but they are not
bytewise identical unless both are normalized identically.

This is why many websites don't accept Unicode in passwords: because
logging in on different systems can produce different sequences and they
must be properly normalized to avoid hard-to-reproduce problems.

There are also canonical (NFC and NFD) and compatibility (NFKC and NFKD)
normalizations.  For instance, a Greek question mark looks like an
English semicolon.  Canonical normalizations preserve this distinction,
but compatibility ones do not.

I'll note that the Mac-native normalizations do not match any standard
Unicode normalizations for any version, so we'd need separate
normalization code.  I also don't think UTF-8-MAC is available on all
versions of libiconv, either.
-- 
brian m. carlson (they/them)
Toronto, Ontario, CA

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 262 bytes --]

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [RFC] Support UTF-8 characters in Git alias names
  2026-02-09 22:40     ` brian m. carlson
@ 2026-02-09 23:14       ` Junio C Hamano
  2026-02-10  0:45         ` Ben Knoble
  0 siblings, 1 reply; 88+ messages in thread
From: Junio C Hamano @ 2026-02-09 23:14 UTC (permalink / raw)
  To: brian m. carlson; +Cc: Jonatan Holmgren, git

"brian m. carlson" <sandals@crustytoothpaste.net> writes:

> On 2026-02-09 at 14:55:51, Junio C Hamano wrote:
>> "brian m. carlson" <sandals@crustytoothpaste.net> writes:
>> 
>> > I don't think we have any Unicode normalization code at all in Git,
>> > though, so if you want a quality implementation, that may be a thing we
>> > need.
>> 
>> Isn't NKC/NKD a macOS-only issue in practice?  Anything on the
>> command line "git" potty and "git-blah" built-in commands receive
>> goes through precompose_argv_prefix() to be normalized on that
>> platform.
>
> Normalization is not a macOS-only issue.  Many accented characters can
> be written in multiple ways,...

Yup, but that wasn't what I brought up macOS for.  No sane person
would write the same string in multiple ways on purpose and
everybody would want to stick to one, so that byte-for-byte
comparison can decide paths they created in the filesystem can be
matched with a list of paths they added in .gitignore, for example.
And for that everybody uses normalization form C, no?

But the macOS makes it harder to stick to a single way when it
involves filesystem entities; the pathname you gave to a new file
with your creat/open(2) may be normalized in macOS specific way when
it comes back from readdir(2).  Ahd for that glitch, we massage the
strings we got from the command line and readdir(), which are in the
normalization form D, into normalization form C.

I think the suggestion here is to assume that the users are doing
the right thing and treat alias names (which eventually end up being
pathname components) as bytes, and everything should be happy, even
on macOS.

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [RFC] Support UTF-8 characters in Git alias names
  2026-02-09 23:14       ` Junio C Hamano
@ 2026-02-10  0:45         ` Ben Knoble
  2026-02-10  1:04           ` Junio C Hamano
  0 siblings, 1 reply; 88+ messages in thread
From: Ben Knoble @ 2026-02-10  0:45 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: brian m. carlson, Jonatan Holmgren, git


> 
> Le 9 févr. 2026 à 18:14, Junio C Hamano <gitster@pobox.com> a écrit :
> 
> "brian m. carlson" <sandals@crustytoothpaste.net> writes:
> 
>>> On 2026-02-09 at 14:55:51, Junio C Hamano wrote:
>>> "brian m. carlson" <sandals@crustytoothpaste.net> writes:
>>> 
>>>> I don't think we have any Unicode normalization code at all in Git,
>>>> though, so if you want a quality implementation, that may be a thing we
>>>> need.
>>> 
>>> Isn't NKC/NKD a macOS-only issue in practice?  Anything on the
>>> command line "git" potty and "git-blah" built-in commands receive
>>> goes through precompose_argv_prefix() to be normalized on that
>>> platform.
>> 
>> Normalization is not a macOS-only issue.  Many accented characters can
>> be written in multiple ways,...
> 
> Yup, but that wasn't what I brought up macOS for.  No sane person
> would write the same string in multiple ways on purpose and
> everybody would want to stick to one, so that byte-for-byte
> comparison can decide paths they created in the filesystem can be
> matched with a list of paths they added in .gitignore, for example.
> And for that everybody uses normalization form C, no?
> 
> But the macOS makes it harder to stick to a single way when it
> involves filesystem entities; the pathname you gave to a new file
> with your creat/open(2) may be normalized in macOS specific way when
> it comes back from readdir(2).  Ahd for that glitch, we massage the
> strings we got from the command line and readdir(), which are in the
> normalization form D, into normalization form C.
> 
> I think the suggestion here is to assume that the users are doing
> the right thing and treat alias names (which eventually end up being
> pathname components) as bytes, and everything should be happy, even
> on macOS.

In what way do alias names end up being pathname components? Or did you mean to insert « treated like » (as in, normalized as command arguments)?

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [RFC] Support UTF-8 characters in Git alias names
  2026-02-10  0:45         ` Ben Knoble
@ 2026-02-10  1:04           ` Junio C Hamano
  2026-02-10  6:59             ` Jeff King
  0 siblings, 1 reply; 88+ messages in thread
From: Junio C Hamano @ 2026-02-10  1:04 UTC (permalink / raw)
  To: Ben Knoble; +Cc: brian m. carlson, Jonatan Holmgren, git

Ben Knoble <ben.knoble@gmail.com> writes:

> In what way do alias names end up being pathname components? Or
> did you mean to insert « treated like » (as in, normalized as
> command arguments)?

Ah, they don't.  I somehow was confusing those custom commands you
would throw into your ~/bin as "git-something" ;-).

But you're correct to point out that for a command line "git
something", "something" would go through the same "undo macOS NFD
for all argv[] elements".


^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [RFC] Support UTF-8 characters in Git alias names
  2026-02-10  1:04           ` Junio C Hamano
@ 2026-02-10  6:59             ` Jeff King
  0 siblings, 0 replies; 88+ messages in thread
From: Jeff King @ 2026-02-10  6:59 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Ben Knoble, brian m. carlson, Jonatan Holmgren, git

On Mon, Feb 09, 2026 at 05:04:18PM -0800, Junio C Hamano wrote:

> Ben Knoble <ben.knoble@gmail.com> writes:
> 
> > In what way do alias names end up being pathname components? Or
> > did you mean to insert « treated like » (as in, normalized as
> > command arguments)?
> 
> Ah, they don't.  I somehow was confusing those custom commands you
> would throw into your ~/bin as "git-something" ;-).
> 
> But you're correct to point out that for a command line "git
> something", "something" would go through the same "undo macOS NFD
> for all argv[] elements".

It's not an issue for aliases, but pager.* (and a hypothetical
pager.*.command) does care about the on-disk representation of program
names (and that it matches what is fed from the command-line).

-Peff

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v1] alias: support UTF-8 characters via subsection syntax
  2026-02-09 22:01 ` [PATCH v1] alias: support UTF-8 characters via subsection syntax Jonatan Holmgren
@ 2026-02-10  7:44   ` Jeff King
  2026-02-10  8:30   ` Torsten Bögershausen
  2026-02-10 16:35   ` Junio C Hamano
  2 siblings, 0 replies; 88+ messages in thread
From: Jeff King @ 2026-02-10  7:44 UTC (permalink / raw)
  To: Jonatan Holmgren; +Cc: git, gitster, D . Ben Knoble, brian m . carlson

On Mon, Feb 09, 2026 at 11:01:15PM +0100, Jonatan Holmgren wrote:

> Git aliases are currently restricted to ASCII characters due to
> config key syntax limitations. This prevents non-English speakers
> from creating aliases in their native languages.
> 
> Add support for UTF-8 alias names using config subsections:
> 
>     [alias "förgrena"]
>         command = branch
> 
> The subsection name is matched verbatim (case-sensitive), while the
> existing flat syntax (alias.name) remains case-insensitive for
> backward compatibility. This approach uses existing config
> infrastructure and avoids complex Unicode normalization.

Thanks, this mostly looks quite good. I have a few comments below that
range from a few small bugs to project-specific gotchas to
style/readability suggestions.

> diff --git a/Documentation/RelNotes/2.54.0.adoc b/Documentation/RelNotes/2.54.0.adoc
> [...]

Usually patches do not fill out their own release notes, as it would
create many conflicts when merging topics in arbitrary order. Instead,
the maintainer generally writes up a blurb that goes in the merge
commits and is eventually placed in the release notes by script.
Suggesting a blurb does save work for the maintainer, but it should go
in the cover letter (so for a single patch, after the "---" lines).

There's a bit on this in Documentation/SubmittingPatches, under the
section "the-topic-summary".

> diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc
> index 80ce17d2de..feba1e2022 100644
> --- a/Documentation/config/alias.adoc
> +++ b/Documentation/config/alias.adoca

Yay, I was happy to see nice comprehensive documentation here. And you
do not seem to have gotten caught by any of the (many) formatting
gotchas.

> +After defining these, you can run `git co`, `git hämta`, `git gömma`,
> +and `git "my alias with whitespace"` (note the quotes for aliases with spaces).

The use of literal backticks here makes sense, and if you haven't disabled it
with a build-time knob, they should be shown in bold. Curiously in my
build of the docs this doesn't quite work, and I get "git h" in bold,
but "ämta" unbolded. Weird, and presumably a bug in asciidoc (the
generated docbook xml shows the same thing, as does html). I get the
same with asciidoctor, too. I'm not sure if it is worth working around
it (or even how we would do so).

> diff --git a/alias.c b/alias.c
> index 1a1a141a0a..f0c5f12fdd 100644
> --- a/alias.c
> +++ b/alias.c
> @@ -17,19 +17,45 @@ static int config_alias_cb(const char *key, const char *value,
>  			   const struct config_context *ctx UNUSED, void *d)
>  {
>  	struct config_alias_data *data = d;
> -	const char *p;
> +	const char *cmd, *subkey;
> +	size_t cmd_len;
> +	int is_subsection;
>  
> -	if (!skip_prefix(key, "alias.", &p))
> +	/* Use parse_config_key() to handle both 2-level and 3-level keys */
> +	if (parse_config_key(key, "alias", &cmd, &cmd_len, &subkey) < 0)
>  		return 0;
>  
> +	/*
> +	 * Support two syntaxes:
> +	 * 1. alias.name = value (simple, 2-level key)
> +	 * 2. [alias "name"] command = value (new, 3-level key)
> +	 */
> +	if (cmd) {
> +		if (strcmp(subkey, "command"))
> +			return 0;
> +		is_subsection = 1;
> +	} else {
> +		cmd = subkey;
> +		cmd_len = strlen(cmd);
> +		is_subsection = 0;
> +	}

OK, so versus what I showed earlier, we are keeping an is_subsection
flag in order to differentiate the cases. And then we use that below...

>  	if (data->alias) {
> -		if (!strcasecmp(p, data->alias)) {
> +		int match;
> +		if (is_subsection) {
> +			match = (strlen(data->alias) == cmd_len &&
> +				 !strncmp(data->alias, cmd, cmd_len));
> +		} else {
> +			match = (strlen(data->alias) == cmd_len &&
> +				 !strncasecmp(data->alias, cmd, cmd_len));
> +		}

...to decide whether to be case-insensitive or not. Makes sense.

I was going to suggest some simplifications here: if is_subsection is
false, then we know that cmd is a NUL-terminated string, and we could
just use strcasecmp() as before. And in fact, with that conditional we
do not need the "cmd" aliasing at all. Which also means we don't need a
separate flag to distinguish the cases.

You could just write:

  if (cmd) {
	match = (strlen(data->alias) == cmd_len &&
	         !strncmp(data->alias, cmd, cmd_len));
  } else {
	match = !strcasecmp(data->alias, subkey);
  }

But I guess we do use the redirection in the listing path later anyway:

>  	} else if (data->list) {
> -		string_list_append(data->list, p);
> +		string_list_append_nodup(data->list, xmemdupz(cmd, cmd_len));
>  	}

So maybe it is better as-is. I dunno. I have a feeling this would all
need even more refactoring even if we ever added any more alias.foo.*
config entries. So we can probably just leave it until then for more
rearranging.


One thing I did find a little funny is the use of the word "subkey" for
one variable. Usually we just call this the "key", but that name is
annoyingly already taken by the function parameter (well before your
patch). Usually we call that "var" instead. I don't know if it's worth
changing or not. It's probably only confusing to me because I've stared
at so many other Git config callback functions.

> diff --git a/t/t0014-alias-utf8.sh b/t/t0014-alias-utf8.sh

Is there any reason to add a new script here, and not just put these in
the existing t0014? If you were bailing from the script when the prereq
was not met, we'd need a separate script. But since you are using
per-test prereqs, they can just exist alongside the other alias tests.

And also, you can't have two scripts with the same number. ;) Running
"make test" should complain about that (I'll assume you ran your tests,
but manually as ./t0014).

> +# Skip if filesystem/locale doesn't support UTF-8
> +test_lazy_prereq UTF8_LOCALE '
> +	test_have_prereq !MINGW &&
> +	test_set_prereq UTF8_LOCALE
> +'

This is a slight mis-use of set_prereq. You should just need to return
success from the lazy-prereq function, and it will be set automatically.
So more like:

  test_lazy_prereq UTF8_LOCALE '
	test_have_prereq !MINGW
  '

At which point I wonder if there is much value in having UTF8_LOCALE at
all, and not just putting "!MINGW" in each of the tests.

Beyond that, my larger question is: what are we asking of the platform
and why does MINGW not work? We do not care about actual utf8 filesystem
support, since these are just aliases. As long as the config file can
store them and we can pass them on the command-line, that should be
enough.

> +test_expect_success UTF8_LOCALE 'setup UTF-8 aliases' '
> +	git config alias."förgrena".command branch &&
> +	git config alias."分支".command "branch --list" &&
> +	git config alias."test name".command status
> +'

Usually we'd just define these in the tests that use them, which keeps
each individual test a bit more self-contained. So more like:

  test_expect_success 'UTF-8 alias with Swedish characters' '
	test_config alias."förgrena".command branch &&
	git förgrena >output &&
	...
  '

But we use them in the listing test, too, so they are each used twice. I
dunno. It might make sense for the listing test to just define its own
expected set.

> +test_expect_success UTF8_LOCALE 'UTF-8 alias with Swedish characters' '
> +	git förgrena >output &&
> +	test_grep -E "^(\* )?(main|master)" output
> +'

So I see we're aliasing "branch" here, which gives us this complicated
regex for checking the output. Would something like "!echo ran my alias"
make the test easier to understand?

> +test_expect_success 'list UTF-8 aliases' '
> +	git config --get-regexp "^alias\\..*\\.command" >output &&
> +	test_line_count -ge 3 output
> +'

This one needs the UTF8_LOCALE prereq, too (since we'd only have set up
those aliases if we had it).

Is doing --get-regexp here all that interesting? It's not testing your
new code at all, and would have passed already. I expected us to look at
the output of "git help -a" to see that the entries are listed.

And trying that with your patch:

  ./git -c alias.föo.command=branch help -a

shows some possible issues:

  - we show it as "föo.command"

  - the alignment with other aliases is not quite right. Probably it is
    using a byte-count instead of utf8_strwidth()

I was puzzled that we would not parse it as föo correctly, since your
patch modified the listing code. It looks like help.c has its own
separate parser for this. Yuck. :( I think this also means that "git
help föo" will fail.

So probably we want a preparatory patch to teach help.c to rely on
list_aliases() and alias_lookup() from alias.[ch].

> +test_expect_success 'flat syntax still works' '
> +	git config alias.testlegacy status &&
> +	git testlegacy >output &&
> +	test_grep "On branch" output
> +'

This probably is covered elsewhere already, but OK. :)

> +test_expect_success 'new subsection syntax works' '
> +	git config alias.testnew.command status &&
> +	git testnew >output &&
> +	test_grep "On branch" output
> +'

Nice basic test without utf8. Good.

> +test_expect_success 'subsection syntax only accepts command key' '
> +	git config alias.invalid.notcommand "value" &&
> +	test_must_fail git invalid 2>error &&
> +	test_grep -i "not a git command" error
> +'

Good thinking.

> +test_expect_success 'simple syntax is case-insensitive' '
> +	git config alias.LegacyCase status &&
> +	git legacycase >output 2>&1 &&
> +	test_grep "On branch" output
> +'
> +
> +test_expect_success 'subsection syntax is case-sensitive' '
> +	test_commit case-test &&
> +	git config alias.SubCase.command "log --oneline" &&
> +	git config alias.subcase.command status &&
> +	git SubCase >upper.out 2>&1 &&
> +	git subcase >lower.out 2>&1 &&
> +	! test_cmp upper.out lower.out
> +'

And these are both very nice to see.

-Peff

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v1] alias: support UTF-8 characters via subsection syntax
  2026-02-09 22:01 ` [PATCH v1] alias: support UTF-8 characters via subsection syntax Jonatan Holmgren
  2026-02-10  7:44   ` Jeff King
@ 2026-02-10  8:30   ` Torsten Bögershausen
  2026-02-10 16:35   ` Junio C Hamano
  2 siblings, 0 replies; 88+ messages in thread
From: Torsten Bögershausen @ 2026-02-10  8:30 UTC (permalink / raw)
  To: Jonatan Holmgren; +Cc: git, peff, gitster, D . Ben Knoble, brian m . carlson

[]
Thanks for working on this.
Trying to review it: Is there a chance to split it into
2 workpackages/commits/ ?

For me it seems as if the introduction of the new subsection is
one big thing. With documentation, code review, tests.

And handling UTF-8 is another big thing, opening questions like
Do we need to set a locale ?
Does mingw handle UTF-8 on the command line, or why is it exlcuded ?

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v1] alias: support UTF-8 characters via subsection syntax
  2026-02-09 22:01 ` [PATCH v1] alias: support UTF-8 characters via subsection syntax Jonatan Holmgren
  2026-02-10  7:44   ` Jeff King
  2026-02-10  8:30   ` Torsten Bögershausen
@ 2026-02-10 16:35   ` Junio C Hamano
  2 siblings, 0 replies; 88+ messages in thread
From: Junio C Hamano @ 2026-02-10 16:35 UTC (permalink / raw)
  To: Jonatan Holmgren; +Cc: git, peff, D . Ben Knoble, brian m . carlson

Jonatan Holmgren <jonatan@jontes.page> writes:

> diff --git a/Documentation/RelNotes/2.54.0.adoc b/Documentation/RelNotes/2.54.0.adoc
> index 20c660d82a..8cb00a21bb 100644
> --- a/Documentation/RelNotes/2.54.0.adoc
> +++ b/Documentation/RelNotes/2.54.0.adoc
> @@ -7,6 +7,14 @@ UI, Workflows & Features
>   * "git add -p" and friends note what the current status of the hunk
>     being shown is.
>  
> + * Git aliases now support UTF-8 characters in alias names through
> +   subsection syntax: `[alias "name"] command = value`. This enables
> +   aliases in non-English languages. The flat syntax continues
> +   to work for backward compatibility.
> +
> + * The new subsection syntax uses case-sensitive matching and
> +   the flat syntax remains case-insensitive for backward compatibility.

Thanks for summrizing the topic well.  "The flat syntax" is a new
phrase to us, I think.  Do we have a term to call two-level
configuration variables in contrast to threee-level ones defined in
the glossary?  I've seen phrases like two- and three- level names to
distinguish them in the past, but that is no way "official".

"git config --help" has Syntax section that calls "alias" a
"section", and "foo" a "subsection", and "command" a "variable name"
in "alias.foo.command".  Perhaps "the alias definition without
subsection, e.g., "[alias] co = checkout", continues to work", or
something, perhaps?

In addition to what Peff already mentioned, we would not want the
second bullet point; the fact that the traditional two-level config
is still supported is very much worth mentioning, which is already
done in the previous point.  So all it remains in this bullet point
is that this topic did not change anything in the three-level
configuration case, which is not noteworty.


> +# Simple syntax (ASCII names)
> +[alias]
> +    co = checkout
> +    st = status

It is somewhat misleading to call this "ASCII" as it is stricter
than that (e.g., there are '.' and other ASCII characters that you
cannot have in the name).  

    "Limited to alphanumeric and '-' letters (the same limitation as
    configuration variable names), case insensitive"

or something.


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v2 0/2] support UTF-8 in alias names
  2026-02-08 15:30 [RFC] Support UTF-8 characters in Git alias names Jonatan Holmgren
                   ` (3 preceding siblings ...)
  2026-02-09 22:01 ` [PATCH v1] alias: support UTF-8 characters via subsection syntax Jonatan Holmgren
@ 2026-02-10 18:31 ` Jonatan Holmgren
  2026-02-10 18:31   ` [PATCH v2 1/2] help: use list_aliases() for alias listing and lookup Jonatan Holmgren
  2026-02-10 18:31   ` [PATCH v2 2/2] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
  2026-02-10 22:27 ` [PATCH 0/3] support UTF-8 in alias names Jonatan Holmgren
                   ` (7 subsequent siblings)
  12 siblings, 2 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-10 18:31 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

Hi all,

Thanks for the feedback on my first patch! This iteration (to my understanding) 
addresses all the points raised by Jeff, Junio, and Brian.

Relevant since RFC/v1:
- Split into two commits: a preparatory refactoring and the main feature
- Removed release notes from commits 
  (suggested blurb below, first time doing this, bear with me :))
- Fixed documentation to use "without subsection" terminology instead of
  "flat syntax"
- Consolidated help.c to use list_aliases() from alias.c, eliminating
  duplicate parsing logic
- Added utf8_strwidth() for proper column alignment in help output
- Improved test coverage with case-sensitivity tests, subsection validation,
  and help listing verification
- Tests now use test_config helper and simpler output verification

The implementation follows Peff's suggestion to use config subsections
rather than modifying the config key syntax. This allows arbitrary bytes
in alias names while maintaining backward compatibility.

Suggested release note blurb:

 * Git aliases now support UTF-8 characters and special characters
   in alias names through subsection syntax: `[alias "name"] command = value`.
   This enables aliases in non-English languages. The traditional syntax
   (without subsection, e.g., `[alias] co = checkout`) continues to work.


Jonatan Holmgren (2):
  help: use list_aliases() for alias listing and lookup
  alias: support non-alphanumeric names via subsection syntax

 Documentation/config/alias.adoc | 44 +++++++++++++++++++++-----
 alias.c                         | 43 ++++++++++++++++++++++----
 help.c                          | 36 ++++++++++-----------
 t/t0014-alias.sh                | 55 +++++++++++++++++++++++++++++++++
 4 files changed, 144 insertions(+), 34 deletions(-)

-- 
2.53.0


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v2 1/2] help: use list_aliases() for alias listing and lookup
  2026-02-10 18:31 ` [PATCH v2 0/2] support UTF-8 in alias names Jonatan Holmgren
@ 2026-02-10 18:31   ` Jonatan Holmgren
  2026-02-10 19:27     ` Junio C Hamano
  2026-02-10 18:31   ` [PATCH v2 2/2] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
  1 sibling, 1 reply; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-10 18:31 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

help.c has its own get_alias() config callback that duplicates the
parsing logic in alias.c. Consolidate by teaching list_aliases() to
also store the alias values (via the string_list util field), then
use it in list_all_cmds_help_aliases() instead of the private
callback.

While at it, switch git_unknown_cmd_config() from skip_prefix() to
parse_config_key() for alias parsing, which properly handles the
config key structure and prepares for multi-level alias config keys
in a subsequent commit.

No functional change intended.

Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 alias.c |  4 +++-
 help.c  | 26 ++++++++------------------
 2 files changed, 11 insertions(+), 19 deletions(-)

diff --git a/alias.c b/alias.c
index 1a1a141a0a..c66a6095bb 100644
--- a/alias.c
+++ b/alias.c
@@ -29,7 +29,9 @@ static int config_alias_cb(const char *key, const char *value,
 						 key, value);
 		}
 	} else if (data->list) {
-		string_list_append(data->list, p);
+		if (value)
+			string_list_append(data->list, p)->util =
+				xstrdup(value);
 	}
 
 	return 0;
diff --git a/help.c b/help.c
index fefd811f7a..a450d57987 100644
--- a/help.c
+++ b/help.c
@@ -20,6 +20,7 @@
 #include "prompt.h"
 #include "fsmonitor-ipc.h"
 #include "repository.h"
+#include "alias.h"
 
 #ifndef NO_CURL
 #include "git-curl-compat.h" /* For LIBCURL_VERSION only */
@@ -468,20 +469,6 @@ void list_developer_interfaces_help(void)
 	putchar('\n');
 }
 
-static int get_alias(const char *var, const char *value,
-		     const struct config_context *ctx UNUSED, void *data)
-{
-	struct string_list *list = data;
-
-	if (skip_prefix(var, "alias.", &var)) {
-		if (!value)
-			return config_error_nonbool(var);
-		string_list_append(list, var)->util = xstrdup(value);
-	}
-
-	return 0;
-}
-
 static void list_all_cmds_help_external_commands(void)
 {
 	struct string_list others = STRING_LIST_INIT_DUP;
@@ -501,7 +488,7 @@ static void list_all_cmds_help_aliases(int longest)
 	struct cmdname_help *aliases;
 	int i;
 
-	repo_config(the_repository, get_alias, &alias_list);
+	list_aliases(&alias_list);
 	string_list_sort(&alias_list);
 
 	for (i = 0; i < alias_list.nr; i++) {
@@ -586,7 +573,8 @@ static int git_unknown_cmd_config(const char *var, const char *value,
 				  void *cb)
 {
 	struct help_unknown_cmd_config *cfg = cb;
-	const char *p;
+	const char *subsection, *key;
+	size_t subsection_len;
 
 	if (!strcmp(var, "help.autocorrect")) {
 		int v = parse_autocorrect(value);
@@ -601,8 +589,10 @@ static int git_unknown_cmd_config(const char *var, const char *value,
 	}
 
 	/* Also use aliases for command lookup */
-	if (skip_prefix(var, "alias.", &p))
-		add_cmdname(&cfg->aliases, p, strlen(p));
+	if (!parse_config_key(var, "alias", &subsection, &subsection_len, &key)) {
+		if (!subsection)
+			add_cmdname(&cfg->aliases, key, strlen(key));
+	}
 
 	return 0;
 }
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* [PATCH v2 2/2] alias: support non-alphanumeric names via subsection syntax
  2026-02-10 18:31 ` [PATCH v2 0/2] support UTF-8 in alias names Jonatan Holmgren
  2026-02-10 18:31   ` [PATCH v2 1/2] help: use list_aliases() for alias listing and lookup Jonatan Holmgren
@ 2026-02-10 18:31   ` Jonatan Holmgren
  2026-02-10 19:47     ` Junio C Hamano
                       ` (2 more replies)
  1 sibling, 3 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-10 18:31 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

Git alias names are limited to alphanumeric characters and dashes
because config variable names are validated by iskeychar(). This
prevents non-English speakers from creating aliases in their native
languages.

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-alphanumeric alias names display correctly.

Suggested-by: Jeff King <peff@peff.net>
Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 Documentation/config/alias.adoc | 44 +++++++++++++++++++++-----
 alias.c                         | 45 ++++++++++++++++++++++-----
 help.c                          | 12 +++++--
 t/t0014-alias.sh                | 55 +++++++++++++++++++++++++++++++++
 4 files changed, 137 insertions(+), 19 deletions(-)

diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc
index 80ce17d2de..17a548cd64 100644
--- a/Documentation/config/alias.adoc
+++ b/Documentation/config/alias.adoc
@@ -1,12 +1,40 @@
 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 is limited to alphanumeric characters and `-` (the same
+   limitation as configuration variable names), and is matched
+   case-insensitively.
+2. With a subsection, e.g., `[alias "name"] command = value`. The
+   alias name can contain any characters including UTF-8, and is
+   matched case-sensitively as raw bytes.
+--
++
+Examples:
++
+----
+# Without subsection
+[alias]
+    co = checkout
+    st = status
+
+# With subsection (allows UTF-8 and special characters)
+[alias "hämta"]
+    command = fetch
+[alias "gömma"]
+    command = stash
+----
++
+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.
 +
 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 c66a6095bb..cfd313ce5d 100644
--- a/alias.c
+++ b/alias.c
@@ -17,21 +17,50 @@ static int config_alias_cb(const char *key, const char *value,
 			   const struct config_context *ctx UNUSED, void *d)
 {
 	struct config_alias_data *data = d;
-	const char *p;
+	const char *subsection, *subkey;
+	size_t subsection_len;
 
-	if (!skip_prefix(key, "alias.", &p))
+	if (parse_config_key(key, "alias", &subsection, &subsection_len,
+			     &subkey) < 0)
 		return 0;
 
+	/*
+	 * Two config syntaxes:
+	 * - alias.name = value        (without subsection, case-insensitive)
+	 * - [alias "name"]
+	 *       command = value       (with subsection, case-sensitive)
+	 */
+	if (subsection) {
+		if (strcmp(subkey, "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, subkey);
+
+		if (match) {
 			FREE_AND_NULL(data->v);
-			return git_config_string(&data->v,
-						 key, value);
+			return git_config_string(&data->v, key, value);
 		}
 	} else if (data->list) {
-		if (value)
-			string_list_append(data->list, p)->util =
-				xstrdup(value);
+		struct string_list_item *item;
+
+		if (!value)
+			return 0;
+
+		if (subsection)
+			item = string_list_append_nodup(data->list,
+				xmemdupz(subsection, subsection_len));
+		else
+			item = string_list_append(data->list, subkey);
+		item->util = xstrdup(value);
 	}
 
 	return 0;
diff --git a/help.c b/help.c
index a450d57987..16abfe7bf3 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;
 	}
@@ -590,8 +591,13 @@ 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) {
+			if (!strcmp(key, "command"))
+				add_cmdname(&cfg->aliases, subsection,
+					    subsection_len);
+		} else {
 			add_cmdname(&cfg->aliases, key, strlen(key));
+		}
 	}
 
 	return 0;
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 07a53e7366..66631ad40f 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -112,4 +112,59 @@ test_expect_success 'cannot alias-shadow a sample of regular builtins' '
 	done
 '
 
+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 '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" &&
+	test_config alias."分支".command "!echo test" &&
+	test_config alias.regular "!echo test" &&
+	git help -a >output &&
+	test_grep "förgrena" output &&
+	test_grep "分支" output &&
+	test_grep "regular" output
+'
+
 test_done
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 1/2] help: use list_aliases() for alias listing and lookup
  2026-02-10 18:31   ` [PATCH v2 1/2] help: use list_aliases() for alias listing and lookup Jonatan Holmgren
@ 2026-02-10 19:27     ` Junio C Hamano
  0 siblings, 0 replies; 88+ messages in thread
From: Junio C Hamano @ 2026-02-10 19:27 UTC (permalink / raw)
  To: Jonatan Holmgren; +Cc: git, peff, D . Ben Knoble, brian m . carlson

Jonatan Holmgren <jonatan@jontes.page> writes:

> help.c has its own get_alias() config callback that duplicates the
> parsing logic in alias.c. Consolidate by teaching list_aliases() to
> also store the alias values (via the string_list util field), then
> use it in list_all_cmds_help_aliases() instead of the private
> callback.
>
> While at it, switch git_unknown_cmd_config() from skip_prefix() to
> parse_config_key() for alias parsing, which properly handles the
> config key structure and prepares for multi-level alias config keys
> in a subsequent commit.
>
> No functional change intended.
>
> Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
> ---
>  alias.c |  4 +++-
>  help.c  | 26 ++++++++------------------
>  2 files changed, 11 insertions(+), 19 deletions(-)
>
> diff --git a/alias.c b/alias.c
> index 1a1a141a0a..c66a6095bb 100644
> --- a/alias.c
> +++ b/alias.c
> @@ -29,7 +29,9 @@ static int config_alias_cb(const char *key, const char *value,
>  						 key, value);
>  		}
>  	} else if (data->list) {
> -		string_list_append(data->list, p);
> +		if (value)
> +			string_list_append(data->list, p)->util =
> +				xstrdup(value);
>  	}

If !value, the original still added p to data->list, but the updated
code discards p when value is not there.  Is that an intended change?

If not,

	} else if (data->list) {
		struct string_list_item *item;

                item = string_list_append(data->list, p);
		if (value)
			item->util = xstrdup(value);
	}

perhaps.

> -static int get_alias(const char *var, const char *value,
> -		     const struct config_context *ctx UNUSED, void *data)
> -{
> -	struct string_list *list = data;
> -
> -	if (skip_prefix(var, "alias.", &var)) {
> -		if (!value)
> -			return config_error_nonbool(var);
> -		string_list_append(list, var)->util = xstrdup(value);
> -	}
> -
> -	return 0;
> -}
> -

A value-less 

	[alias]
		foo

used to get an configuuration error with a friendly message from
help.c:get_alias(), which was removed.  The config_alias_cb() called
by alias.c:list_aliases() either silently ignores foo altogether
(the posted patch) or creates an entry for 'foo' but leaves its
expansion to NULL (the above "silent ignore fix").  Either way,
there needs some new code to compensate for the loss of the error
detection somehow.

> @@ -501,7 +488,7 @@ static void list_all_cmds_help_aliases(int longest)
>  	struct cmdname_help *aliases;
>  	int i;
>  
> -	repo_config(the_repository, get_alias, &alias_list);
> +	list_aliases(&alias_list);
>  	string_list_sort(&alias_list);
>  
>  	for (i = 0; i < alias_list.nr; i++) {

OK.

> @@ -586,7 +573,8 @@ static int git_unknown_cmd_config(const char *var, const char *value,
>  				  void *cb)
>  {
>  	struct help_unknown_cmd_config *cfg = cb;
> -	const char *p;
> +	const char *subsection, *key;
> +	size_t subsection_len;
>  
>  	if (!strcmp(var, "help.autocorrect")) {
>  		int v = parse_autocorrect(value);
> @@ -601,8 +589,10 @@ static int git_unknown_cmd_config(const char *var, const char *value,
>  	}
>  
>  	/* Also use aliases for command lookup */
> -	if (skip_prefix(var, "alias.", &p))
> -		add_cmdname(&cfg->aliases, p, strlen(p));
> +	if (!parse_config_key(var, "alias", &subsection, &subsection_len, &key)) {
> +		if (!subsection)
> +			add_cmdname(&cfg->aliases, key, strlen(key));
> +	}
>  
>  	return 0;
>  }

Arguably, the last two hunks are about preparing for three-level
alias.*.command support.

It is a bit unfortunate that with

    [alias "foo"]
	command = !date
	bar = !echo bar

in your configuration, 

    $ git foo.command
    $ git foo.bar

used to invoke the alias 'foo.command' and 'foo.bar' just fine, but
now with these two preparatory hunks, it no longer is the case and
they are silently ignored.  With the next patch, 'git foo' starts
working in place for 'git foo.command', but 'git foo.bar' has become
forever inaccessible.  I wonder if we want to warn about foo.bar if
not foo.command, or if it is too much?  It is conceivable that we
may add variables like alias.*.help so it may not be a great idea to
warn on anything alias.<subsection>.<key> where <key> is not "command"

Perhaps we can claim that we are fixing a bug that allowed aliases
with a dot in its name by mistake?  I dunno.  No matter what we
claim here, some people will be hit by this behaviour change and
complain about a regression X-<.




^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 2/2] alias: support non-alphanumeric names via subsection syntax
  2026-02-10 18:31   ` [PATCH v2 2/2] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
@ 2026-02-10 19:47     ` Junio C Hamano
  2026-02-10 22:29       ` Jonatan Holmgren
  2026-02-23  9:29     ` Kristoffer Haugsbakk
  2026-02-24 10:27     ` Patrick Steinhardt
  2 siblings, 1 reply; 88+ messages in thread
From: Junio C Hamano @ 2026-02-10 19:47 UTC (permalink / raw)
  To: Jonatan Holmgren; +Cc: git, peff, D . Ben Knoble, brian m . carlson

Jonatan Holmgren <jonatan@jontes.page> writes:

> Git alias names are limited to alphanumeric characters and dashes
> because config variable names are validated by iskeychar(). This

"ASCII alphanumeric", perhaps, as accented alphabet characters are
still alphanumeric ;-).  "because aliases are implemented as config
variable names" is probably an explanation that talks to readers in
terms closer to them (iskeychar() function is an implementation detail).

> prevents non-English speakers from creating aliases in their native
> languages.

True.

> 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-alphanumeric alias names display correctly.

Either move the last two hunks from [1/2] to this step, or make it a
separate patch [1.5/2] between this and the other steps, and explain
it as a change that breaks compatibility in a way that hopefully
would not affect anybody in practice.  The alias configuration
parser used to be overly loose and took "alias.<subsection>.<key>"
as defining an alias "<subsection>.<key>"; that change tightens the
parser and alias.<subsection>.<key> are silently ignored.  This step
then take alias.<subsection>.command to be defining a new-style
alias that can be invoked as "<subsection>", which is case sensitive
and is not limited to ASCII alphanumeric and dashes.

> Suggested-by: Jeff King <peff@peff.net>
> Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
> ---
>  Documentation/config/alias.adoc | 44 +++++++++++++++++++++-----
>  alias.c                         | 45 ++++++++++++++++++++++-----
>  help.c                          | 12 +++++--
>  t/t0014-alias.sh                | 55 +++++++++++++++++++++++++++++++++
>  4 files changed, 137 insertions(+), 19 deletions(-)
>
> diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc
> index 80ce17d2de..17a548cd64 100644
> --- a/Documentation/config/alias.adoc
> +++ b/Documentation/config/alias.adoc
> @@ -1,12 +1,40 @@
>  alias.*::
> +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 is limited to alphanumeric characters and `-` (the same
> +   limitation as configuration variable names), and is matched
> +   case-insensitively.
> +2. With a subsection, e.g., `[alias "name"] command = value`. The
> +   alias name can contain any characters including UTF-8, and is
> +   matched case-sensitively as raw bytes.
> +--
> ++
> +Examples:
> ++
> +----
> +# Without subsection
> +[alias]
> +    co = checkout
> +    st = status
> +
> +# With subsection (allows UTF-8 and special characters)
> +[alias "hämta"]
> +    command = fetch
> +[alias "gömma"]
> +    command = stash
> +----
> ++
> +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.
>  +
>  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 c66a6095bb..cfd313ce5d 100644
> --- a/alias.c
> +++ b/alias.c
> @@ -17,21 +17,50 @@ static int config_alias_cb(const char *key, const char *value,
>  			   const struct config_context *ctx UNUSED, void *d)
>  {
>  	struct config_alias_data *data = d;
> -	const char *p;
> +	const char *subsection, *subkey;
> +	size_t subsection_len;
>  
> -	if (!skip_prefix(key, "alias.", &p))
> +	if (parse_config_key(key, "alias", &subsection, &subsection_len,
> +			     &subkey) < 0)
>  		return 0;
>  
> +	/*
> +	 * Two config syntaxes:
> +	 * - alias.name = value        (without subsection, case-insensitive)
> +	 * - [alias "name"]
> +	 *       command = value       (with subsection, case-sensitive)
> +	 */
> +	if (subsection) {
> +		if (strcmp(subkey, "command"))
> +			return 0;

This silently ignores

	[alias "foo"]
		bar = !date

which may or may not be a feature.  If the variable name is "help"
instead of "bar", it certainly is a feature to silently skip it, as
it is not inconceivable that we would add such a variable name in
the future, and because we won't be able to predict the future, not
limiting us to "help" but ignoring anything we do not understand
like this code does may probably be a good thing.  I dunno.

> +	}
> +
>  	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, subkey);
> +
> +		if (match) {
>  			FREE_AND_NULL(data->v);
> -			return git_config_string(&data->v,
> -						 key, value);
> +			return git_config_string(&data->v, key, value);
>  		}
>  	} else if (data->list) {
> -		if (value)
> -			string_list_append(data->list, p)->util =
> -				xstrdup(value);
> +		struct string_list_item *item;
> +
> +		if (!value)
> +			return 0;
> +
> +		if (subsection)
> +			item = string_list_append_nodup(data->list,
> +				xmemdupz(subsection, subsection_len));
> +		else
> +			item = string_list_append(data->list, subkey);
> +		item->util = xstrdup(value);

This still silently ignores

	[alias "foo"]
		command

which is a much more grave problem than ignoring alias.foo.bar in
the earlier part of this function.  We would want to preserve the
existing diagnosis on broken configuration.

> @@ -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;
>  	}
> @@ -590,8 +591,13 @@ 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) {
> +			if (!strcmp(key, "command"))
> +				add_cmdname(&cfg->aliases, subsection,
> +					    subsection_len);
> +		} else {
>  			add_cmdname(&cfg->aliases, key, strlen(key));
> +		}
>  	}

OK.  Alternatively, out of

	[alias "foo"]
		command = !echo foo
		bar = !echo bar

we _could_ list "foo" (a new style alias) and "foo.bar" (an old
style alias that we have been accepting forever by mistake) for
maximum backward compatibility.  I am still undecided if it is a
good idea.

Thanks.

^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH 0/3] support UTF-8 in alias names
  2026-02-08 15:30 [RFC] Support UTF-8 characters in Git alias names Jonatan Holmgren
                   ` (4 preceding siblings ...)
  2026-02-10 18:31 ` [PATCH v2 0/2] support UTF-8 in alias names Jonatan Holmgren
@ 2026-02-10 22:27 ` Jonatan Holmgren
  2026-02-10 22:27   ` [PATCH 1/3] help: use list_aliases() for alias listing Jonatan Holmgren
                     ` (2 more replies)
  2026-02-11 21:18 ` [PATCH v4 0/3] support UTF-8 in alias names Jonatan Holmgren
                   ` (6 subsequent siblings)
  12 siblings, 3 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-10 22:27 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

Hi all,

This v3 addresses all feedback from Junio on v2. Changes since v2:

- Split into three commits: a preparatory refactoring, a compatibility-
  breaking change separated explicitly, and the main feature
- Preserved error checking for value-less alias config (config_error_nonbool)
- Fixed behavior when !value to preserve adding items to list while keeping
  util as NULL
- Improved commit messages to say "ASCII alphanumeric" and better explain
  that aliases are implemented as config variable names
- Tests now properly verify value-less command key handling

The implementation follows Peff's suggestion to use config subsections
rather than modifying the config key syntax. This allows arbitrary bytes
in alias names while maintaining backward compatibility.

Suggested release note blurb:

 * Git aliases now support UTF-8 characters and special characters
   in alias names through subsection syntax: `[alias "name"] command = value`.
   This enables aliases in non-English languages. The traditional syntax
   (without subsection, e.g., `[alias] co = checkout`) continues to work.

Jonatan Holmgren (3):
  help: use list_aliases() for alias listing
  alias: prepare for subsection aliases
  alias: support non-alphanumeric names via subsection syntax

 Documentation/config/alias.adoc | 43 ++++++++++++++++++-----
 alias.c                         | 40 +++++++++++++++++++---
 help.c                          | 39 +++++++++++----------
 t/t0014-alias.sh                | 60 +++++++++++++++++++++++++++++++++
 4 files changed, 150 insertions(+), 32 deletions(-)

-- 
2.53.0

^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH 1/3] help: use list_aliases() for alias listing
  2026-02-10 22:27 ` [PATCH 0/3] support UTF-8 in alias names Jonatan Holmgren
@ 2026-02-10 22:27   ` Jonatan Holmgren
  2026-02-10 23:17     ` Junio C Hamano
  2026-02-10 22:27   ` [PATCH 2/3] alias: prepare for subsection aliases Jonatan Holmgren
  2026-02-10 22:27   ` [PATCH 3/3] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
  2 siblings, 1 reply; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-10 22:27 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

help.c has its own get_alias() config callback that duplicates the
parsing logic in alias.c. Consolidate by teaching list_aliases() to
also store the alias values (via the string_list util field), then
use it in list_all_cmds_help_aliases() instead of the private
callback.

This preserves the existing error checking for value-less alias
definitions by checking in alias.c rather than help.c.

No functional change intended.

Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 alias.c |  9 ++++++++-
 help.c  | 17 ++---------------
 2 files changed, 10 insertions(+), 16 deletions(-)

diff --git a/alias.c b/alias.c
index 1a1a141a0a..1f8e13610c 100644
--- a/alias.c
+++ b/alias.c
@@ -24,12 +24,19 @@ static int config_alias_cb(const char *key, const char *value,
 
 	if (data->alias) {
 		if (!strcasecmp(p, data->alias)) {
+			if (!value)
+				return config_error_nonbool(key);
 			FREE_AND_NULL(data->v);
 			return git_config_string(&data->v,
 						 key, value);
 		}
 	} else if (data->list) {
-		string_list_append(data->list, p);
+		struct string_list_item *item;
+
+		item = string_list_append(data->list, p);
+		if (value)
+			item->util = xstrdup(value);
+		/* if !value, item->util remains NULL but item is still added */
 	}
 
 	return 0;
diff --git a/help.c b/help.c
index fefd811f7a..0bdb7ca10f 100644
--- a/help.c
+++ b/help.c
@@ -20,6 +20,7 @@
 #include "prompt.h"
 #include "fsmonitor-ipc.h"
 #include "repository.h"
+#include "alias.h"
 
 #ifndef NO_CURL
 #include "git-curl-compat.h" /* For LIBCURL_VERSION only */
@@ -468,20 +469,6 @@ void list_developer_interfaces_help(void)
 	putchar('\n');
 }
 
-static int get_alias(const char *var, const char *value,
-		     const struct config_context *ctx UNUSED, void *data)
-{
-	struct string_list *list = data;
-
-	if (skip_prefix(var, "alias.", &var)) {
-		if (!value)
-			return config_error_nonbool(var);
-		string_list_append(list, var)->util = xstrdup(value);
-	}
-
-	return 0;
-}
-
 static void list_all_cmds_help_external_commands(void)
 {
 	struct string_list others = STRING_LIST_INIT_DUP;
@@ -501,7 +488,7 @@ static void list_all_cmds_help_aliases(int longest)
 	struct cmdname_help *aliases;
 	int i;
 
-	repo_config(the_repository, get_alias, &alias_list);
+	list_aliases(&alias_list);
 	string_list_sort(&alias_list);
 
 	for (i = 0; i < alias_list.nr; i++) {
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* [PATCH 2/3] alias: prepare for subsection aliases
  2026-02-10 22:27 ` [PATCH 0/3] support UTF-8 in alias names Jonatan Holmgren
  2026-02-10 22:27   ` [PATCH 1/3] help: use list_aliases() for alias listing Jonatan Holmgren
@ 2026-02-10 22:27   ` Jonatan Holmgren
  2026-02-10 22:27   ` [PATCH 3/3] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
  2 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-10 22:27 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

Switch git_unknown_cmd_config() from skip_prefix() to
parse_config_key() for alias parsing. This properly handles the
three-level config key structure and prepares for the new
alias.*.command subsection syntax in the next commit.

This is a compatibility break: the alias configuration parser used
to be overly permissive and accepted "alias.<subsection>.<key>" as
defining an alias "<subsection>.<key>". With this change,
alias.<subsection>.<key> entries are silently ignored (unless <key>
is "command", which will be given meaning in the next commit).

This behavior was arguably a bug, since config subsections were never
intended to work this way for aliases, and aliases with dots in their
names have never been documented or intentionally supported.

Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 help.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/help.c b/help.c
index 0bdb7ca10f..eccd0c22f8 100644
--- a/help.c
+++ b/help.c
@@ -573,7 +573,8 @@ static int git_unknown_cmd_config(const char *var, const char *value,
 				  void *cb)
 {
 	struct help_unknown_cmd_config *cfg = cb;
-	const char *p;
+	const char *subsection, *key;
+	size_t subsection_len;
 
 	if (!strcmp(var, "help.autocorrect")) {
 		int v = parse_autocorrect(value);
@@ -588,8 +589,11 @@ static int git_unknown_cmd_config(const char *var, const char *value,
 	}
 
 	/* Also use aliases for command lookup */
-	if (skip_prefix(var, "alias.", &p))
-		add_cmdname(&cfg->aliases, p, strlen(p));
+	if (!parse_config_key(var, "alias", &subsection, &subsection_len,
+			      &key)) {
+		if (!subsection)
+			add_cmdname(&cfg->aliases, key, strlen(key));
+	}
 
 	return 0;
 }
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* [PATCH 3/3] alias: support non-alphanumeric names via subsection syntax
  2026-02-10 22:27 ` [PATCH 0/3] support UTF-8 in alias names Jonatan Holmgren
  2026-02-10 22:27   ` [PATCH 1/3] help: use list_aliases() for alias listing Jonatan Holmgren
  2026-02-10 22:27   ` [PATCH 2/3] alias: prepare for subsection aliases Jonatan Holmgren
@ 2026-02-10 22:27   ` Jonatan Holmgren
  2 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-10 22:27 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

Git alias names are limited to ASCII alphanumeric characters and
dashes because aliases are implemented as config variable names, which
are validated by iskeychar(). This prevents non-English speakers from
creating aliases in their native languages.

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 | 43 ++++++++++++++++++-----
 alias.c                         | 39 +++++++++++++++++----
 help.c                          | 14 ++++++--
 t/t0014-alias.sh                | 60 +++++++++++++++++++++++++++++++++
 4 files changed, 138 insertions(+), 18 deletions(-)

diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc
index 80ce17d2de..9184f7cb37 100644
--- a/Documentation/config/alias.adoc
+++ b/Documentation/config/alias.adoc
@@ -1,12 +1,39 @@
 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 is limited to ASCII alphanumeric characters and `-`,
+   and is matched case-insensitively.
+2. With a subsection, e.g., `[alias "name"] command = value`. The
+   alias name can contain any characters including UTF-8, and is
+   matched case-sensitively as raw bytes.
+--
++
+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 "gömma"]
+    command = stash
+----
++
+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.
 +
 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 1f8e13610c..ae1fe1efc6 100644
--- a/alias.c
+++ b/alias.c
@@ -17,15 +17,37 @@ static int config_alias_cb(const char *key, const char *value,
 			   const struct config_context *ctx UNUSED, void *d)
 {
 	struct config_alias_data *data = d;
-	const char *p;
+	const char *subsection, *subkey;
+	size_t subsection_len;
 
-	if (!skip_prefix(key, "alias.", &p))
+	if (parse_config_key(key, "alias", &subsection, &subsection_len,
+			     &subkey) < 0)
 		return 0;
 
+	/*
+	 * Two config syntaxes:
+	 * - alias.name = value   (without subsection, case-insensitive)
+	 * - [alias "name"]
+	 *       command = value  (with subsection, case-sensitive)
+	 */
+	if (subsection) {
+		if (strcmp(subkey, "command"))
+			return 0;
+		if (!value)
+			return config_error_nonbool(key);
+	}
+
 	if (data->alias) {
-		if (!strcasecmp(p, data->alias)) {
-			if (!value)
-				return config_error_nonbool(key);
+		int match;
+
+		if (subsection)
+			match = (strlen(data->alias) == subsection_len &&
+				 !strncmp(data->alias, subsection,
+					  subsection_len));
+		else
+			match = !strcasecmp(data->alias, subkey);
+
+		if (match) {
 			FREE_AND_NULL(data->v);
 			return git_config_string(&data->v,
 						 key, value);
@@ -33,10 +55,13 @@ static int config_alias_cb(const char *key, const char *value,
 	} else if (data->list) {
 		struct string_list_item *item;
 
-		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, subkey);
 		if (value)
 			item->util = xstrdup(value);
-		/* if !value, item->util remains NULL but item is still added */
 	}
 
 	return 0;
diff --git a/help.c b/help.c
index eccd0c22f8..d7c6011780 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 07a53e7366..5d1f7730e6 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -112,4 +112,64 @@ test_expect_success 'cannot alias-shadow a sample of regular builtins' '
 	done
 '
 
+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 | grep "förgrena"
+'
+
 test_done
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 2/2] alias: support non-alphanumeric names via subsection syntax
  2026-02-10 19:47     ` Junio C Hamano
@ 2026-02-10 22:29       ` Jonatan Holmgren
  0 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-10 22:29 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, peff, D . Ben Knoble, brian m . carlson

I broke it out into 3 commits for my version 3 of the patches and think 
I've caught all the bugs in my implementation. Thanks for the patience!

I also took the liberty of breaking
     [alias "foo"]
	bar = !date
as dotted aliases were never a feature (XKCD 1172 workflow hopefully not 
incoming) and it might be useful to be able to define more stuff under 
this new alias syntax other than "command".

Patch incoming.

Best,
Jonatan

On 2026-02-10 20:47, Junio C Hamano wrote:
> Jonatan Holmgren <jonatan@jontes.page> writes:
> 
>> Git alias names are limited to alphanumeric characters and dashes
>> because config variable names are validated by iskeychar(). This
> 
> "ASCII alphanumeric", perhaps, as accented alphabet characters are
> still alphanumeric ;-).  "because aliases are implemented as config
> variable names" is probably an explanation that talks to readers in
> terms closer to them (iskeychar() function is an implementation detail).
> 
>> prevents non-English speakers from creating aliases in their native
>> languages.
> 
> True.
> 
>> 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-alphanumeric alias names display correctly.
> 
> Either move the last two hunks from [1/2] to this step, or make it a
> separate patch [1.5/2] between this and the other steps, and explain
> it as a change that breaks compatibility in a way that hopefully
> would not affect anybody in practice.  The alias configuration
> parser used to be overly loose and took "alias.<subsection>.<key>"
> as defining an alias "<subsection>.<key>"; that change tightens the
> parser and alias.<subsection>.<key> are silently ignored.  This step
> then take alias.<subsection>.command to be defining a new-style
> alias that can be invoked as "<subsection>", which is case sensitive
> and is not limited to ASCII alphanumeric and dashes.
> 
>> Suggested-by: Jeff King <peff@peff.net>
>> Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
>> ---
>>   Documentation/config/alias.adoc | 44 +++++++++++++++++++++-----
>>   alias.c                         | 45 ++++++++++++++++++++++-----
>>   help.c                          | 12 +++++--
>>   t/t0014-alias.sh                | 55 +++++++++++++++++++++++++++++++++
>>   4 files changed, 137 insertions(+), 19 deletions(-)
>>
>> diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc
>> index 80ce17d2de..17a548cd64 100644
>> --- a/Documentation/config/alias.adoc
>> +++ b/Documentation/config/alias.adoc
>> @@ -1,12 +1,40 @@
>>   alias.*::
>> +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 is limited to alphanumeric characters and `-` (the same
>> +   limitation as configuration variable names), and is matched
>> +   case-insensitively.
>> +2. With a subsection, e.g., `[alias "name"] command = value`. The
>> +   alias name can contain any characters including UTF-8, and is
>> +   matched case-sensitively as raw bytes.
>> +--
>> ++
>> +Examples:
>> ++
>> +----
>> +# Without subsection
>> +[alias]
>> +    co = checkout
>> +    st = status
>> +
>> +# With subsection (allows UTF-8 and special characters)
>> +[alias "hämta"]
>> +    command = fetch
>> +[alias "gömma"]
>> +    command = stash
>> +----
>> ++
>> +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.
>>   +
>>   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 c66a6095bb..cfd313ce5d 100644
>> --- a/alias.c
>> +++ b/alias.c
>> @@ -17,21 +17,50 @@ static int config_alias_cb(const char *key, const char *value,
>>   			   const struct config_context *ctx UNUSED, void *d)
>>   {
>>   	struct config_alias_data *data = d;
>> -	const char *p;
>> +	const char *subsection, *subkey;
>> +	size_t subsection_len;
>>   
>> -	if (!skip_prefix(key, "alias.", &p))
>> +	if (parse_config_key(key, "alias", &subsection, &subsection_len,
>> +			     &subkey) < 0)
>>   		return 0;
>>   
>> +	/*
>> +	 * Two config syntaxes:
>> +	 * - alias.name = value        (without subsection, case-insensitive)
>> +	 * - [alias "name"]
>> +	 *       command = value       (with subsection, case-sensitive)
>> +	 */
>> +	if (subsection) {
>> +		if (strcmp(subkey, "command"))
>> +			return 0;
> 
> This silently ignores
> 
> 	[alias "foo"]
> 		bar = !date
> 
> which may or may not be a feature.  If the variable name is "help"
> instead of "bar", it certainly is a feature to silently skip it, as
> it is not inconceivable that we would add such a variable name in
> the future, and because we won't be able to predict the future, not
> limiting us to "help" but ignoring anything we do not understand
> like this code does may probably be a good thing.  I dunno.
> 
>> +	}
>> +
>>   	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, subkey);
>> +
>> +		if (match) {
>>   			FREE_AND_NULL(data->v);
>> -			return git_config_string(&data->v,
>> -						 key, value);
>> +			return git_config_string(&data->v, key, value);
>>   		}
>>   	} else if (data->list) {
>> -		if (value)
>> -			string_list_append(data->list, p)->util =
>> -				xstrdup(value);
>> +		struct string_list_item *item;
>> +
>> +		if (!value)
>> +			return 0;
>> +
>> +		if (subsection)
>> +			item = string_list_append_nodup(data->list,
>> +				xmemdupz(subsection, subsection_len));
>> +		else
>> +			item = string_list_append(data->list, subkey);
>> +		item->util = xstrdup(value);
> 
> This still silently ignores
> 
> 	[alias "foo"]
> 		command
> 
> which is a much more grave problem than ignoring alias.foo.bar in
> the earlier part of this function.  We would want to preserve the
> existing diagnosis on broken configuration.
> 
>> @@ -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;
>>   	}
>> @@ -590,8 +591,13 @@ 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) {
>> +			if (!strcmp(key, "command"))
>> +				add_cmdname(&cfg->aliases, subsection,
>> +					    subsection_len);
>> +		} else {
>>   			add_cmdname(&cfg->aliases, key, strlen(key));
>> +		}
>>   	}
> 
> OK.  Alternatively, out of
> 
> 	[alias "foo"]
> 		command = !echo foo
> 		bar = !echo bar
> 
> we _could_ list "foo" (a new style alias) and "foo.bar" (an old
> style alias that we have been accepting forever by mistake) for
> maximum backward compatibility.  I am still undecided if it is a
> good idea.
> 
> Thanks.


^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH 1/3] help: use list_aliases() for alias listing
  2026-02-10 22:27   ` [PATCH 1/3] help: use list_aliases() for alias listing Jonatan Holmgren
@ 2026-02-10 23:17     ` Junio C Hamano
  0 siblings, 0 replies; 88+ messages in thread
From: Junio C Hamano @ 2026-02-10 23:17 UTC (permalink / raw)
  To: Jonatan Holmgren; +Cc: git, peff, D . Ben Knoble, brian m . carlson

Jonatan Holmgren <jonatan@jontes.page> writes:

>  	if (data->alias) {
>  		if (!strcasecmp(p, data->alias)) {
> +			if (!value)
> +				return config_error_nonbool(key);
>  			FREE_AND_NULL(data->v);
>  			return git_config_string(&data->v,
>  						 key, value);
>  		}

Hmph, git_config_string() would trigger config_error_nonbool()
anyway if your feed value==NULL, so this change looks a noop.

>  	} else if (data->list) {
> -		string_list_append(data->list, p);
> +		struct string_list_item *item;
> +
> +		item = string_list_append(data->list, p);
> +		if (value)
> +			item->util = xstrdup(value);
> +		/* if !value, item->util remains NULL but item is still added */

This side is silent.  We hold onto the value, when available, and
otherwise we just remember the fact that there is a (misconfigured)
alias by keeping the NULL in the .util member.  Presumably it is now
the responsibility of the caller to deal with these entries with
NULL in their .util member?

>  	}
>  
>  	return 0;
> diff --git a/help.c b/help.c
> index fefd811f7a..0bdb7ca10f 100644
> --- a/help.c
> +++ b/help.c
> @@ -20,6 +20,7 @@
>  #include "prompt.h"
>  #include "fsmonitor-ipc.h"
>  #include "repository.h"
> +#include "alias.h"
>  
>  #ifndef NO_CURL
>  #include "git-curl-compat.h" /* For LIBCURL_VERSION only */
> @@ -468,20 +469,6 @@ void list_developer_interfaces_help(void)
>  	putchar('\n');
>  }
>  
> -static int get_alias(const char *var, const char *value,
> -		     const struct config_context *ctx UNUSED, void *data)
> -{
> -	struct string_list *list = data;
> -
> -	if (skip_prefix(var, "alias.", &var)) {
> -		if (!value)
> -			return config_error_nonbool(var);
> -		string_list_append(list, var)->util = xstrdup(value);
> -	}
> -
> -	return 0;
> -}

We used to use this callback when listing aliases, which (1) added
an alias with proper value to the list, and (2) reported a
misconfigured variable without adding it to the list.  So the net
effect was that the user got diagnosis necessary to fix their
configuration file, while the caller did not have to worry about
getting a broken entry appended to the list.

>  static void list_all_cmds_help_external_commands(void)
>  {
>  	struct string_list others = STRING_LIST_INIT_DUP;
> @@ -501,7 +488,7 @@ static void list_all_cmds_help_aliases(int longest)
>  	struct cmdname_help *aliases;
>  	int i;
>  
> -	repo_config(the_repository, get_alias, &alias_list);
> +	list_aliases(&alias_list);

We call exactly the same alias.c:list_aliases(), which does not feed
data->alias at all, so we will take the "else if (data->list)"
codepath there.  Now we have these broken entries in the returned
list.  Because the shared callback did not give any diagnosis message,
it is on up to us to do so, right?

Perhaps in the code that begins in the post-context of this hunk, here...

	for (i = 0; i < alias_list.nr; i++) {
		if (alias_list.items[i].util)
			continue;
		give error equivanent to config_error_nonbool();
		release resources held by alias_list.items[i];
		shift alias_list.items[i+1..alias_list.nr] by one;
		i-- to compensate for the shift of the array;
	}

or something?

Have you considered doing the config_error_nonbool(key) on the
data->list side of the if/else inside alias.c:config_alias_cb(),
just like help.c:get_alias() callback used to do?

I haven't stared at this code as long as you have, so it is very
possible I am missing the reason why that code path wants to be
silent, though.  But if we can do so, then this caller does not have
to worry about having to handle broken entries at all.

Thanks.

>  	string_list_sort(&alias_list);
>  
>  	for (i = 0; i < alias_list.nr; i++) {


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 0/3] support UTF-8 in alias names
  2026-02-08 15:30 [RFC] Support UTF-8 characters in Git alias names Jonatan Holmgren
                   ` (5 preceding siblings ...)
  2026-02-10 22:27 ` [PATCH 0/3] support UTF-8 in alias names Jonatan Holmgren
@ 2026-02-11 21:18 ` Jonatan Holmgren
  2026-02-11 21:18   ` [PATCH v4 1/3] help: use list_aliases() for alias listing Jonatan Holmgren
                     ` (3 more replies)
  2026-02-16 16:15 ` [PATCH v5 0/4] support uTF-8 " Jonatan Holmgren
                   ` (5 subsequent siblings)
  12 siblings, 4 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-11 21:18 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

This series enables UTF-8 and special characters in Git alias names by
using config subsection syntax: `[alias "name"] command = value`. This
allows non-English speakers to create aliases in their native languages
while maintaining backward compatibility with the traditional syntax.

Changes since v3:

- Removed redundant !value check in data->alias branch, as Junio noted
  that git_config_string() already handles this case

- Added config_error_nonbool() in data->list branch so misconfigured
  aliases (without values) are caught immediately instead of silently
  adding broken entries that callers must handle

- Simplified code: consolidated error checking, removed conditional
  item->util assignment in favor of early error return

- Added test case for value-less alias error handling

- Improved test robustness by using intermediate file + test_grep
  instead of piping to grep

Jonatan Holmgren (3):
  help: use list_aliases() for alias listing
  alias: prepare for subsection aliases
  alias: support non-alphanumeric names via subsection syntax

 Documentation/config/alias.adoc | 43 ++++++++++++++++----
 alias.c                         | 38 ++++++++++++++++--
 help.c                          | 39 +++++++++---------
 t/t0014-alias.sh                | 71 +++++++++++++++++++++++++++++++++
 4 files changed, 159 insertions(+), 32 deletions(-)

-- 
2.53.0


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v4 1/3] help: use list_aliases() for alias listing
  2026-02-11 21:18 ` [PATCH v4 0/3] support UTF-8 in alias names Jonatan Holmgren
@ 2026-02-11 21:18   ` Jonatan Holmgren
  2026-02-11 22:29     ` Junio C Hamano
  2026-02-11 21:18   ` [PATCH v4 2/3] alias: prepare for subsection aliases Jonatan Holmgren
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-11 21:18 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

help.c has its own get_alias() config callback that duplicates the
parsing logic in alias.c. Consolidate by teaching list_aliases() to
also store the alias values (via the string_list util field), then
use it in list_all_cmds_help_aliases() instead of the private
callback.

This preserves the existing error checking for value-less alias
definitions by checking in alias.c rather than help.c.

No functional change intended.

Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 alias.c          |  8 +++++++-
 help.c           | 17 ++---------------
 t/t0014-alias.sh | 10 ++++++++++
 3 files changed, 19 insertions(+), 16 deletions(-)

diff --git a/alias.c b/alias.c
index 1a1a141a0a..271acb9bf1 100644
--- a/alias.c
+++ b/alias.c
@@ -29,7 +29,13 @@ static int config_alias_cb(const char *key, const char *value,
 						 key, value);
 		}
 	} else if (data->list) {
-		string_list_append(data->list, p);
+		struct string_list_item *item;
+
+		if (!value)
+			return config_error_nonbool(key);
+
+		item = string_list_append(data->list, p);
+		item->util = xstrdup(value);
 	}
 
 	return 0;
diff --git a/help.c b/help.c
index fefd811f7a..0bdb7ca10f 100644
--- a/help.c
+++ b/help.c
@@ -20,6 +20,7 @@
 #include "prompt.h"
 #include "fsmonitor-ipc.h"
 #include "repository.h"
+#include "alias.h"
 
 #ifndef NO_CURL
 #include "git-curl-compat.h" /* For LIBCURL_VERSION only */
@@ -468,20 +469,6 @@ void list_developer_interfaces_help(void)
 	putchar('\n');
 }
 
-static int get_alias(const char *var, const char *value,
-		     const struct config_context *ctx UNUSED, void *data)
-{
-	struct string_list *list = data;
-
-	if (skip_prefix(var, "alias.", &var)) {
-		if (!value)
-			return config_error_nonbool(var);
-		string_list_append(list, var)->util = xstrdup(value);
-	}
-
-	return 0;
-}
-
 static void list_all_cmds_help_external_commands(void)
 {
 	struct string_list others = STRING_LIST_INIT_DUP;
@@ -501,7 +488,7 @@ static void list_all_cmds_help_aliases(int longest)
 	struct cmdname_help *aliases;
 	int i;
 
-	repo_config(the_repository, get_alias, &alias_list);
+	list_aliases(&alias_list);
 	string_list_sort(&alias_list);
 
 	for (i = 0; i < alias_list.nr; i++) {
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 07a53e7366..a13d2be8ca 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -112,4 +112,14 @@ test_expect_success 'cannot alias-shadow a sample of regular builtins' '
 	done
 '
 
+test_expect_success 'alias without value reports error' '
+	test_when_finished "git config --unset alias.noval" &&
+	cat >>.git/config <<-\EOF &&
+	[alias]
+		noval
+	EOF
+	test_must_fail git noval 2>error &&
+	test_grep "alias.noval" error
+'
+
 test_done
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* [PATCH v4 2/3] alias: prepare for subsection aliases
  2026-02-11 21:18 ` [PATCH v4 0/3] support UTF-8 in alias names Jonatan Holmgren
  2026-02-11 21:18   ` [PATCH v4 1/3] help: use list_aliases() for alias listing Jonatan Holmgren
@ 2026-02-11 21:18   ` Jonatan Holmgren
  2026-02-11 21:53     ` Junio C Hamano
  2026-02-11 21:18   ` [PATCH v4 3/3] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
  2026-02-12 10:27   ` [PATCH v4 0/3] support UTF-8 in alias names Torsten Bögershausen
  3 siblings, 1 reply; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-11 21:18 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

Switch git_unknown_cmd_config() from skip_prefix() to
parse_config_key() for alias parsing. This properly handles the
three-level config key structure and prepares for the new
alias.*.command subsection syntax in the next commit.

This is a compatibility break: the alias configuration parser used
to be overly permissive and accepted "alias.<subsection>.<key>" as
defining an alias "<subsection>.<key>". With this change,
alias.<subsection>.<key> entries are silently ignored (unless <key>
is "command", which will be given meaning in the next commit).

This behavior was arguably a bug, since config subsections were never
intended to work this way for aliases, and aliases with dots in their
names have never been documented or intentionally supported.

Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 help.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/help.c b/help.c
index 0bdb7ca10f..eccd0c22f8 100644
--- a/help.c
+++ b/help.c
@@ -573,7 +573,8 @@ static int git_unknown_cmd_config(const char *var, const char *value,
 				  void *cb)
 {
 	struct help_unknown_cmd_config *cfg = cb;
-	const char *p;
+	const char *subsection, *key;
+	size_t subsection_len;
 
 	if (!strcmp(var, "help.autocorrect")) {
 		int v = parse_autocorrect(value);
@@ -588,8 +589,11 @@ static int git_unknown_cmd_config(const char *var, const char *value,
 	}
 
 	/* Also use aliases for command lookup */
-	if (skip_prefix(var, "alias.", &p))
-		add_cmdname(&cfg->aliases, p, strlen(p));
+	if (!parse_config_key(var, "alias", &subsection, &subsection_len,
+			      &key)) {
+		if (!subsection)
+			add_cmdname(&cfg->aliases, key, strlen(key));
+	}
 
 	return 0;
 }
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* [PATCH v4 3/3] alias: support non-alphanumeric names via subsection syntax
  2026-02-11 21:18 ` [PATCH v4 0/3] support UTF-8 in alias names Jonatan Holmgren
  2026-02-11 21:18   ` [PATCH v4 1/3] help: use list_aliases() for alias listing Jonatan Holmgren
  2026-02-11 21:18   ` [PATCH v4 2/3] alias: prepare for subsection aliases Jonatan Holmgren
@ 2026-02-11 21:18   ` Jonatan Holmgren
  2026-02-11 22:28     ` Junio C Hamano
                       ` (2 more replies)
  2026-02-12 10:27   ` [PATCH v4 0/3] support UTF-8 in alias names Torsten Bögershausen
  3 siblings, 3 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-11 21:18 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

Git alias names are limited to ASCII alphanumeric characters and
dashes because aliases are implemented as config variable names.
This prevents non-English speakers from creating aliases in their
native languages.

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 | 43 ++++++++++++++++++-----
 alias.c                         | 32 ++++++++++++++---
 help.c                          | 14 ++++++--
 t/t0014-alias.sh                | 61 +++++++++++++++++++++++++++++++++
 4 files changed, 135 insertions(+), 15 deletions(-)

diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc
index 80ce17d2de..9184f7cb37 100644
--- a/Documentation/config/alias.adoc
+++ b/Documentation/config/alias.adoc
@@ -1,12 +1,39 @@
 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 is limited to ASCII alphanumeric characters and `-`,
+   and is matched case-insensitively.
+2. With a subsection, e.g., `[alias "name"] command = value`. The
+   alias name can contain any characters including UTF-8, and is
+   matched case-sensitively as raw bytes.
+--
++
+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 "gömma"]
+    command = stash
+----
++
+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.
 +
 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..896d0f80a4 100644
--- a/alias.c
+++ b/alias.c
@@ -17,13 +17,33 @@ static int config_alias_cb(const char *key, const char *value,
 			   const struct config_context *ctx UNUSED, void *d)
 {
 	struct config_alias_data *data = d;
-	const char *p;
+	const char *subsection, *subkey;
+	size_t subsection_len;
 
-	if (!skip_prefix(key, "alias.", &p))
+	if (parse_config_key(key, "alias", &subsection, &subsection_len,
+			     &subkey) < 0)
+		return 0;
+
+	/*
+	 * Two config syntaxes:
+	 * - alias.name = value   (without subsection, case-insensitive)
+	 * - [alias "name"]
+	 *       command = value  (with subsection, case-sensitive)
+	 */
+	if (subsection && strcmp(subkey, "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, subkey);
+
+		if (match) {
 			FREE_AND_NULL(data->v);
 			return git_config_string(&data->v,
 						 key, value);
@@ -34,7 +54,11 @@ static int config_alias_cb(const char *key, const char *value,
 		if (!value)
 			return config_error_nonbool(key);
 
-		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, subkey);
 		item->util = xstrdup(value);
 	}
 
diff --git a/help.c b/help.c
index eccd0c22f8..d7c6011780 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


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* Re: [PATCH v4 2/3] alias: prepare for subsection aliases
  2026-02-11 21:18   ` [PATCH v4 2/3] alias: prepare for subsection aliases Jonatan Holmgren
@ 2026-02-11 21:53     ` Junio C Hamano
  0 siblings, 0 replies; 88+ messages in thread
From: Junio C Hamano @ 2026-02-11 21:53 UTC (permalink / raw)
  To: Jonatan Holmgren; +Cc: git, peff, D . Ben Knoble, brian m . carlson

Jonatan Holmgren <jonatan@jontes.page> writes:

> Switch git_unknown_cmd_config() from skip_prefix() to
> parse_config_key() for alias parsing. This properly handles the
> three-level config key structure and prepares for the new
> alias.*.command subsection syntax in the next commit.
>
> This is a compatibility break: the alias configuration parser used
> to be overly permissive and accepted "alias.<subsection>.<key>" as
> defining an alias "<subsection>.<key>". With this change,
> alias.<subsection>.<key> entries are silently ignored (unless <key>
> is "command", which will be given meaning in the next commit).
>
> This behavior was arguably a bug, since config subsections were never
> intended to work this way for aliases, and aliases with dots in their
> names have never been documented or intentionally supported.
>
> Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
> ---
>  help.c | 10 +++++++---
>  1 file changed, 7 insertions(+), 3 deletions(-)

Very well explained.

>
> diff --git a/help.c b/help.c
> index 0bdb7ca10f..eccd0c22f8 100644
> --- a/help.c
> +++ b/help.c
> @@ -573,7 +573,8 @@ static int git_unknown_cmd_config(const char *var, const char *value,
>  				  void *cb)
>  {
>  	struct help_unknown_cmd_config *cfg = cb;
> -	const char *p;
> +	const char *subsection, *key;
> +	size_t subsection_len;
>  
>  	if (!strcmp(var, "help.autocorrect")) {
>  		int v = parse_autocorrect(value);
> @@ -588,8 +589,11 @@ static int git_unknown_cmd_config(const char *var, const char *value,
>  	}
>  
>  	/* Also use aliases for command lookup */
> -	if (skip_prefix(var, "alias.", &p))
> -		add_cmdname(&cfg->aliases, p, strlen(p));
> +	if (!parse_config_key(var, "alias", &subsection, &subsection_len,
> +			      &key)) {
> +		if (!subsection)
> +			add_cmdname(&cfg->aliases, key, strlen(key));
> +	}
>  
>  	return 0;
>  }

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v4 3/3] alias: support non-alphanumeric names via subsection syntax
  2026-02-11 21:18   ` [PATCH v4 3/3] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
@ 2026-02-11 22:28     ` Junio C Hamano
  2026-02-12 11:16     ` Richard Kerry
  2026-02-12 18:52     ` Jonatan Holmgren
  2 siblings, 0 replies; 88+ messages in thread
From: Junio C Hamano @ 2026-02-11 22:28 UTC (permalink / raw)
  To: Jonatan Holmgren; +Cc: git, peff, D . Ben Knoble, brian m . carlson

Jonatan Holmgren <jonatan@jontes.page> writes:

>  alias.*::
> +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 is limited to ASCII alphanumeric characters and `-`,
> +   and is matched case-insensitively.

OK.  It is obvious to us that the "alias name" in the example is "co";
is it obvious enough for our first-time readers, or would we want to
do something like

	The alias name ("co", in this example) is limited to ...

to be extra clear, I wonder.

> +2. With a subsection, e.g., `[alias "name"] command = value`. The
> +   alias name can contain any characters including UTF-8, and is
> +   matched case-sensitively as raw bytes.

Unlike the previous example that is more realistic, this uses <name>
and <value> placeholders, with `command` that MUST be given verbatim
by the users.  Is that obvious enough to our first-time readers?

Thinking aloud.  How does it look with placeholder filled with
concrete values?

	... e.g., `[alias "co"] command = checkout`.  The alias name
	("co", in this example) can contain any characters ...

This does not look too bad to me.

We do not allow newlines or NULs in the subsection.  NULs may be too
obvious, but newlines might be worth mentioning.  I dunno.

> +--
> ++
> +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 "gömma"]
> +    command = stash
> +----

Good examples, even though I do not read Swedish ;-).

> +E.g. after defining `alias.last = cat-file commit HEAD`, the invocation
> +`git last` is equivalent to `git cat-file commit HEAD`.

This is not a new problem (it is an inherited text from before your
change), but I've always found this

	alias.last = cat-file commit HEAD

a poor thing to give to our users, as it does not match anything
they practically can use.  It is different from the valid command
line arguments to define the alias, which is

	$ git config set alias.last "cat-file commit HEAD"

and it is different from the way the result appears in the
configuration file, which is

	[alias] last = cat-file commit HEAD

Also, since the sentences are moved around, I am not sure that the
beginning "E.g." still fits there very well.  Taking them all
together, how about

    With a Git alias defined, e.g.,

	$ git config set alias.last "cat-file commit HEAD"

    you can run `git last` and it invokes `git cat-file commit
    HEAD`.

> diff --git a/alias.c b/alias.c
> index 271acb9bf1..896d0f80a4 100644
> --- a/alias.c
> +++ b/alias.c
> @@ -17,13 +17,33 @@ static int config_alias_cb(const char *key, const char *value,
>  			   const struct config_context *ctx UNUSED, void *d)
>  {
>  	struct config_alias_data *data = d;
> -	const char *p;
> +	const char *subsection, *subkey;
> +	size_t subsection_len;

"subkey" is a confusing name for a variable.

The Synatx section in "git config --help" documentation says that a
configuration file consists of "sections and variables", and a
section can further be divided into subsections.

config.c seems to use "key" as a synonym for "variable" above, and
that is very understandable, because "section.subsection.variable"
or "section.variable" as a whole is what the users and documentation
calls a "configuration variable", and to avoid overloading the two
meanings on the same word "variable", we'd better use a different
name for that last-level thing.

Taken together, in

	[alias] co = checkout
	[alias "ci"] command = commit

it would be the best to call the parts like so:

	section: "alias"
	subsection: "ci"
	key: "co" and "command"

> -	if (!skip_prefix(key, "alias.", &p))
> +	if (parse_config_key(key, "alias", &subsection, &subsection_len,
> +			     &subkey) < 0)
> +		return 0;
> +
> +	/*
> +	 * Two config syntaxes:
> +	 * - alias.name = value   (without subsection, case-insensitive)
> +	 * - [alias "name"]
> +	 *       command = value  (with subsection, case-sensitive)
> +	 */
> +	if (subsection && strcmp(subkey, "command"))
>  		return 0;

OK.  We ignore alias.*.variable where variable is not "command".

>  	if (data->alias) {

When the caller is querying one specific alias ...

> +		int match;
> +
> +		if (subsection)
> +			match = (strlen(data->alias) == subsection_len &&
> +				 !strncmp(data->alias, subsection,
> +					  subsection_len));

... we pick either the one that literally matches the subsection
part (we have already verified that the key is "command"), or ...

> +		else
> +			match = !strcasecmp(data->alias, subkey);

... for a two-level variable, the one that matches variable name
case insensitively.  And when we see hit, ...

> +		if (match) {
>  			FREE_AND_NULL(data->v);
>  			return git_config_string(&data->v,
>  						 key, value);

... we report it to the caller.  Otherwise, when we are listing ...

> @@ -34,7 +54,11 @@ static int config_alias_cb(const char *key, const char *value,
>  		if (!value)
>  			return config_error_nonbool(key);
>  
> -		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, subkey);

... the alias name we create differs between the two- and
three-level names, but otherwise the handling is the same between
the two kinds.

>  		item->util = xstrdup(value);
>  	}

All makes sense.

> diff --git a/help.c b/help.c
> index eccd0c22f8..d7c6011780 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;

Looks very good.


^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v4 1/3] help: use list_aliases() for alias listing
  2026-02-11 21:18   ` [PATCH v4 1/3] help: use list_aliases() for alias listing Jonatan Holmgren
@ 2026-02-11 22:29     ` Junio C Hamano
  0 siblings, 0 replies; 88+ messages in thread
From: Junio C Hamano @ 2026-02-11 22:29 UTC (permalink / raw)
  To: Jonatan Holmgren; +Cc: git, peff, D . Ben Knoble, brian m . carlson

Jonatan Holmgren <jonatan@jontes.page> writes:

> help.c has its own get_alias() config callback that duplicates the
> parsing logic in alias.c. Consolidate by teaching list_aliases() to
> also store the alias values (via the string_list util field), then
> use it in list_all_cmds_help_aliases() instead of the private
> callback.
>
> This preserves the existing error checking for value-less alias
> definitions by checking in alias.c rather than help.c.
>
> No functional change intended.
>
> Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
> ---
>  alias.c          |  8 +++++++-
>  help.c           | 17 ++---------------
>  t/t0014-alias.sh | 10 ++++++++++
>  3 files changed, 19 insertions(+), 16 deletions(-)

Looks good.  There is a small functional change not on the
help.c:get_alias() side (i.e., "git help --all") but on the
alias.c:list_aliases() side.  "git --list-cmds=alias", used by
command line completion, used to include such a broken alias, but it
no longer does (and gets an error).

I think it is fine to call it a bugfix ;-)


> diff --git a/alias.c b/alias.c
> index 1a1a141a0a..271acb9bf1 100644
> --- a/alias.c
> +++ b/alias.c
> @@ -29,7 +29,13 @@ static int config_alias_cb(const char *key, const char *value,
>  						 key, value);
>  		}
>  	} else if (data->list) {
> -		string_list_append(data->list, p);
> +		struct string_list_item *item;
> +
> +		if (!value)
> +			return config_error_nonbool(key);
> +
> +		item = string_list_append(data->list, p);
> +		item->util = xstrdup(value);
>  	}
>  
>  	return 0;
> diff --git a/help.c b/help.c
> index fefd811f7a..0bdb7ca10f 100644
> --- a/help.c
> +++ b/help.c
> @@ -20,6 +20,7 @@
>  #include "prompt.h"
>  #include "fsmonitor-ipc.h"
>  #include "repository.h"
> +#include "alias.h"
>  
>  #ifndef NO_CURL
>  #include "git-curl-compat.h" /* For LIBCURL_VERSION only */
> @@ -468,20 +469,6 @@ void list_developer_interfaces_help(void)
>  	putchar('\n');
>  }
>  
> -static int get_alias(const char *var, const char *value,
> -		     const struct config_context *ctx UNUSED, void *data)
> -{
> -	struct string_list *list = data;
> -
> -	if (skip_prefix(var, "alias.", &var)) {
> -		if (!value)
> -			return config_error_nonbool(var);
> -		string_list_append(list, var)->util = xstrdup(value);
> -	}
> -
> -	return 0;
> -}
> -
>  static void list_all_cmds_help_external_commands(void)
>  {
>  	struct string_list others = STRING_LIST_INIT_DUP;
> @@ -501,7 +488,7 @@ static void list_all_cmds_help_aliases(int longest)
>  	struct cmdname_help *aliases;
>  	int i;
>  
> -	repo_config(the_repository, get_alias, &alias_list);
> +	list_aliases(&alias_list);
>  	string_list_sort(&alias_list);
>  
>  	for (i = 0; i < alias_list.nr; i++) {
> diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
> index 07a53e7366..a13d2be8ca 100755
> --- a/t/t0014-alias.sh
> +++ b/t/t0014-alias.sh
> @@ -112,4 +112,14 @@ test_expect_success 'cannot alias-shadow a sample of regular builtins' '
>  	done
>  '
>  
> +test_expect_success 'alias without value reports error' '
> +	test_when_finished "git config --unset alias.noval" &&
> +	cat >>.git/config <<-\EOF &&
> +	[alias]
> +		noval
> +	EOF
> +	test_must_fail git noval 2>error &&
> +	test_grep "alias.noval" error
> +'
> +
>  test_done

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v4 0/3] support UTF-8 in alias names
  2026-02-11 21:18 ` [PATCH v4 0/3] support UTF-8 in alias names Jonatan Holmgren
                     ` (2 preceding siblings ...)
  2026-02-11 21:18   ` [PATCH v4 3/3] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
@ 2026-02-12 10:27   ` Torsten Bögershausen
  2026-02-12 15:35     ` Jonatan Holmgren
  3 siblings, 1 reply; 88+ messages in thread
From: Torsten Bögershausen @ 2026-02-12 10:27 UTC (permalink / raw)
  To: Jonatan Holmgren; +Cc: git, peff, gitster, D . Ben Knoble, brian m . carlson

On Wed, Feb 11, 2026 at 10:18:07PM +0100, Jonatan Holmgren wrote:
[]
Is it only me who only sees the header message, the 0/3, and not
the patches themselves, the 1/3..3/3 ?

^ permalink raw reply	[flat|nested] 88+ messages in thread

* RE: [PATCH v4 3/3] alias: support non-alphanumeric names via subsection syntax
  2026-02-11 21:18   ` [PATCH v4 3/3] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
  2026-02-11 22:28     ` Junio C Hamano
@ 2026-02-12 11:16     ` Richard Kerry
  2026-02-12 15:34       ` Jonatan Holmgren
  2026-02-12 18:52     ` Jonatan Holmgren
  2 siblings, 1 reply; 88+ messages in thread
From: Richard Kerry @ 2026-02-12 11:16 UTC (permalink / raw)
  To: git@vger.kernel.org
  Cc: peff@peff.net, Jonatan Holmgren, gitster@pobox.com,
	D . Ben Knoble, brian m . carlson

> -----Original Message-----
> From: Jonatan Holmgren <jonatan@jontes.page>
> Sent: 11 February 2026 21:18
> 
> Git alias names are limited to ASCII alphanumeric characters and dashes because
> aliases are implemented as config variable names.
> This prevents non-English speakers from creating aliases in their native languages.

I think that is overly specific as a use case and should probably be more like:

This prevents aliases being created in languages using characters outside that range.

1.  It isn't specific to non-English speakers - as an English speaker I may wish to create an alias in a foreign language requiring characters outside that range.
2.  There are other languages which can be expressed entirely using characters in this range, so their speakers are not inconvenienced by the current implementation (I believe Dutch is one such language).

Regards,
Richard.



^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v4 3/3] alias: support non-alphanumeric names via subsection syntax
  2026-02-12 11:16     ` Richard Kerry
@ 2026-02-12 15:34       ` Jonatan Holmgren
  0 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-12 15:34 UTC (permalink / raw)
  To: Richard Kerry, git@vger.kernel.org
  Cc: peff@peff.net, gitster@pobox.com, D . Ben Knoble,
	brian m . carlson

Hi Richard,

Good point regarding the distinction between the human and the character 
set.

I did actually consider that distinction when writing it, but I 
struggled to find a phrasing that didn't feel clunky, which is why I 
settled on the version I sent. However, I agree that your suggestion is 
more technically accurate.

I'll adopt your wording in the next version. Thanks for the review!

Best,
Jonatan

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v4 0/3] support UTF-8 in alias names
  2026-02-12 10:27   ` [PATCH v4 0/3] support UTF-8 in alias names Torsten Bögershausen
@ 2026-02-12 15:35     ` Jonatan Holmgren
  0 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-12 15:35 UTC (permalink / raw)
  To: Torsten Bögershausen
  Cc: git, peff, gitster, D . Ben Knoble, brian m . carlson

Hello!

You should to see the patch from the public inbox at
https://lore.kernel.org/git/20260209135917.GD27241@macsyma.lan/T/#mfb25a7f945ceebb6c8619680cac46c990e4ea112

Best,
Jonatan

On 2026-02-12 11:27, Torsten Bögershausen wrote:
> On Wed, Feb 11, 2026 at 10:18:07PM +0100, Jonatan Holmgren wrote:
> []
> Is it only me who only sees the header message, the 0/3, and not
> the patches themselves, the 1/3..3/3 ?


^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v4 3/3] alias: support non-alphanumeric names via subsection syntax
  2026-02-11 21:18   ` [PATCH v4 3/3] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
  2026-02-11 22:28     ` Junio C Hamano
  2026-02-12 11:16     ` Richard Kerry
@ 2026-02-12 18:52     ` Jonatan Holmgren
  2 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-12 18:52 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson

I have discovered that shell completion suggests the "<alias>.command" 
which is a bug. Can't believe I didn't test that. Fixing in revision 5.

Jonatan

^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 0/4] support uTF-8 in alias names
  2026-02-08 15:30 [RFC] Support UTF-8 characters in Git alias names Jonatan Holmgren
                   ` (6 preceding siblings ...)
  2026-02-11 21:18 ` [PATCH v4 0/3] support UTF-8 in alias names Jonatan Holmgren
@ 2026-02-16 16:15 ` Jonatan Holmgren
  2026-02-16 16:15   ` [PATCH v5 1/4] help: use list_aliases() for alias listing Jonatan Holmgren
                     ` (3 more replies)
  2026-02-18 14:52 ` [PATCH v6 0/4] support UTF-8 in alias names Jonatan Holmgren
                   ` (4 subsequent siblings)
  12 siblings, 4 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-16 16:15 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

Heya!

This series enables UTF-8 and special characters in Git alias names
by using config subsection syntax: [alias "name"] command = value.

I have since last time:
* Changed "native speakers" to "languages outside English range" (thanks Richard!)
* Fixed shell completion in zsh
* Improved docs slightly with feedback from Junio. Also changed out one of the examples with one with spaces to demonstrate that feature.

Thanks for your time.

Jonatan Holmgren (4):
  help: use list_aliases() for alias listing
  alias: prepare for subsection aliases
  alias: support non-alphanumeric names via subsection syntax
  completion: fix zsh alias listing for subsection aliases

 Documentation/config/alias.adoc       | 50 ++++++++++++++++---
 alias.c                               | 42 +++++++++++++---
 builtin/help.c                        | 15 ++++++
 contrib/completion/git-completion.zsh |  2 +-
 help.c                                | 39 +++++++--------
 t/t0014-alias.sh                      | 71 +++++++++++++++++++++++++++
 6 files changed, 184 insertions(+), 35 deletions(-)

-- 
2.53.0.83.g660bbd62ee.dirty


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v5 1/4] help: use list_aliases() for alias listing
  2026-02-16 16:15 ` [PATCH v5 0/4] support uTF-8 " Jonatan Holmgren
@ 2026-02-16 16:15   ` Jonatan Holmgren
  2026-02-16 16:15   ` [PATCH v5 2/4] alias: prepare for subsection aliases Jonatan Holmgren
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-16 16:15 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

help.c has its own get_alias() config callback that duplicates the
parsing logic in alias.c. Consolidate by teaching list_aliases() to
also store the alias values (via the string_list util field), then
use it in list_all_cmds_help_aliases() instead of the private
callback.

This preserves the existing error checking for value-less alias
definitions by checking in alias.c rather than help.c.

No functional change intended.

Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 alias.c          |  8 +++++++-
 help.c           | 17 ++---------------
 t/t0014-alias.sh | 10 ++++++++++
 3 files changed, 19 insertions(+), 16 deletions(-)

diff --git a/alias.c b/alias.c
index 1a1a141a0a..271acb9bf1 100644
--- a/alias.c
+++ b/alias.c
@@ -29,7 +29,13 @@ static int config_alias_cb(const char *key, const char *value,
 						 key, value);
 		}
 	} else if (data->list) {
-		string_list_append(data->list, p);
+		struct string_list_item *item;
+
+		if (!value)
+			return config_error_nonbool(key);
+
+		item = string_list_append(data->list, p);
+		item->util = xstrdup(value);
 	}
 
 	return 0;
diff --git a/help.c b/help.c
index 08b5c60204..5b1b320d02 100644
--- a/help.c
+++ b/help.c
@@ -20,6 +20,7 @@
 #include "prompt.h"
 #include "fsmonitor-ipc.h"
 #include "repository.h"
+#include "alias.h"
 
 #ifndef NO_CURL
 #include "git-curl-compat.h" /* For LIBCURL_VERSION only */
@@ -468,20 +469,6 @@ void list_developer_interfaces_help(void)
 	putchar('\n');
 }
 
-static int get_alias(const char *var, const char *value,
-		     const struct config_context *ctx UNUSED, void *data)
-{
-	struct string_list *list = data;
-
-	if (skip_prefix(var, "alias.", &var)) {
-		if (!value)
-			return config_error_nonbool(var);
-		string_list_append(list, var)->util = xstrdup(value);
-	}
-
-	return 0;
-}
-
 static void list_all_cmds_help_external_commands(void)
 {
 	struct string_list others = STRING_LIST_INIT_DUP;
@@ -501,7 +488,7 @@ static void list_all_cmds_help_aliases(int longest)
 	struct cmdname_help *aliases;
 	int i;
 
-	repo_config(the_repository, get_alias, &alias_list);
+	list_aliases(&alias_list);
 	string_list_sort(&alias_list);
 
 	for (i = 0; i < alias_list.nr; i++) {
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 07a53e7366..a13d2be8ca 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -112,4 +112,14 @@ test_expect_success 'cannot alias-shadow a sample of regular builtins' '
 	done
 '
 
+test_expect_success 'alias without value reports error' '
+	test_when_finished "git config --unset alias.noval" &&
+	cat >>.git/config <<-\EOF &&
+	[alias]
+		noval
+	EOF
+	test_must_fail git noval 2>error &&
+	test_grep "alias.noval" error
+'
+
 test_done
-- 
2.53.0.83.g660bbd62ee.dirty


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* [PATCH v5 2/4] alias: prepare for subsection aliases
  2026-02-16 16:15 ` [PATCH v5 0/4] support uTF-8 " Jonatan Holmgren
  2026-02-16 16:15   ` [PATCH v5 1/4] help: use list_aliases() for alias listing Jonatan Holmgren
@ 2026-02-16 16:15   ` Jonatan Holmgren
  2026-02-16 16:15   ` [PATCH v5 3/4] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
  2026-02-16 16:15   ` [PATCH v5 4/4] completion: fix zsh alias listing for subsection aliases Jonatan Holmgren
  3 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-16 16:15 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

Switch git_unknown_cmd_config() from skip_prefix() to
parse_config_key() for alias parsing. This properly handles the
three-level config key structure and prepares for the new
alias.*.command subsection syntax in the next commit.

This is a compatibility break: the alias configuration parser used
to be overly permissive and accepted "alias.<subsection>.<key>" as
defining an alias "<subsection>.<key>". With this change,
alias.<subsection>.<key> entries are silently ignored (unless <key>
is "command", which will be given meaning in the next commit).

This behavior was arguably a bug, since config subsections were never
intended to work this way for aliases, and aliases with dots in their
names have never been documented or intentionally supported.

Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 help.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/help.c b/help.c
index 5b1b320d02..691af219bf 100644
--- a/help.c
+++ b/help.c
@@ -573,7 +573,8 @@ static int git_unknown_cmd_config(const char *var, const char *value,
 				  void *cb)
 {
 	struct help_unknown_cmd_config *cfg = cb;
-	const char *p;
+	const char *subsection, *key;
+	size_t subsection_len;
 
 	if (!strcmp(var, "help.autocorrect")) {
 		int v = parse_autocorrect(value);
@@ -588,8 +589,11 @@ static int git_unknown_cmd_config(const char *var, const char *value,
 	}
 
 	/* Also use aliases for command lookup */
-	if (skip_prefix(var, "alias.", &p))
-		add_cmdname(&cfg->aliases, p, strlen(p));
+	if (!parse_config_key(var, "alias", &subsection, &subsection_len,
+			      &key)) {
+		if (!subsection)
+			add_cmdname(&cfg->aliases, key, strlen(key));
+	}
 
 	return 0;
 }
-- 
2.53.0.83.g660bbd62ee.dirty


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* [PATCH v5 3/4] alias: support non-alphanumeric names via subsection syntax
  2026-02-16 16:15 ` [PATCH v5 0/4] support uTF-8 " Jonatan Holmgren
  2026-02-16 16:15   ` [PATCH v5 1/4] help: use list_aliases() for alias listing Jonatan Holmgren
  2026-02-16 16:15   ` [PATCH v5 2/4] alias: prepare for subsection aliases Jonatan Holmgren
@ 2026-02-16 16:15   ` Jonatan Holmgren
  2026-02-16 16:15   ` [PATCH v5 4/4] completion: fix zsh alias listing for subsection aliases Jonatan Holmgren
  3 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-16 16:15 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

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.83.g660bbd62ee.dirty


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* [PATCH v5 4/4] completion: fix zsh alias listing for subsection aliases
  2026-02-16 16:15 ` [PATCH v5 0/4] support uTF-8 " Jonatan Holmgren
                     ` (2 preceding siblings ...)
  2026-02-16 16:15   ` [PATCH v5 3/4] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
@ 2026-02-16 16:15   ` Jonatan Holmgren
  2026-02-16 18:32     ` D. Ben Knoble
  2026-02-17 20:01     ` Junio C Hamano
  3 siblings, 2 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-16 16:15 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

The zsh completion function __git_zsh_cmd_alias() uses 'git config
--get-regexp' to enumerate aliases and then strips the "alias." prefix
from each key. For subsection-style aliases (alias.name.command), this
leaves "name.command" as the completion candidate instead of just
"name".

The bash completion does not have this problem because it goes through
'git --list-cmds=alias', which calls list_aliases() in C and already
handles both alias syntaxes correctly. However, zsh needs both the
alias name and its value for descriptive completion, which
--list-cmds=alias does not provide.

Add a hidden --aliases-for-completion option to 'git help', following
the existing --config-for-completion pattern. It outputs NUL-separated
"name\nvalue" pairs using list_aliases(), which correctly resolves both
the traditional (alias.name) and subsection (alias.name.command)
formats. Update __git_zsh_cmd_alias() to use it.
---
 builtin/help.c                        | 15 +++++++++++++++
 contrib/completion/git-completion.zsh |  2 +-
 2 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/builtin/help.c b/builtin/help.c
index c09cbc8912..f02308a391 100644
--- a/builtin/help.c
+++ b/builtin/help.c
@@ -54,6 +54,7 @@ static enum help_action {
 	HELP_ACTION_DEVELOPER_INTERFACES,
 	HELP_ACTION_CONFIG_FOR_COMPLETION,
 	HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION,
+	HELP_ACTION_ALIASES_FOR_COMPLETION,
 } cmd_mode;
 
 static char *html_path;
@@ -90,6 +91,8 @@ static struct option builtin_help_options[] = {
 		    HELP_ACTION_CONFIG_FOR_COMPLETION, PARSE_OPT_HIDDEN),
 	OPT_CMDMODE_F(0, "config-sections-for-completion", &cmd_mode, "",
 		    HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, PARSE_OPT_HIDDEN),
+	OPT_CMDMODE_F(0, "aliases-for-completion", &cmd_mode, "",
+		    HELP_ACTION_ALIASES_FOR_COMPLETION, PARSE_OPT_HIDDEN),
 
 	OPT_END(),
 };
@@ -691,6 +694,18 @@ int cmd_help(int argc,
 			       help_format);
 		list_config_help(SHOW_CONFIG_SECTIONS);
 		return 0;
+	case HELP_ACTION_ALIASES_FOR_COMPLETION: {
+		struct string_list alias_list = STRING_LIST_INIT_DUP;
+		opt_mode_usage(argc, "--aliases-for-completion",
+			       help_format);
+		list_aliases(&alias_list);
+		string_list_sort(&alias_list);
+		for (size_t i = 0; i < alias_list.nr; i++)
+			printf("%s%c%s%c", alias_list.items[i].string, '\n',
+			       (char *)alias_list.items[i].util, '\0');
+		string_list_clear(&alias_list, 1);
+		return 0;
+	}
 	case HELP_ACTION_CONFIG:
 		opt_mode_usage(argc, "--config", help_format);
 		setup_pager(the_repository);
diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh
index f5877bd7a1..c32186a977 100644
--- a/contrib/completion/git-completion.zsh
+++ b/contrib/completion/git-completion.zsh
@@ -202,7 +202,7 @@ __git_zsh_cmd_common ()
 __git_zsh_cmd_alias ()
 {
 	local -a list
-	list=(${${(0)"$(git config -z --get-regexp '^alias\.*')"}#alias.})
+	list=(${(0)"$(git help --aliases-for-completion)"})
 	list=(${(f)"$(printf "%s:alias for '%s'\n" ${(f@)list})"})
 	_describe -t alias-commands 'aliases' list && _ret=0
 }
-- 
2.53.0.83.g660bbd62ee.dirty


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* Re: [PATCH v5 4/4] completion: fix zsh alias listing for subsection aliases
  2026-02-16 16:15   ` [PATCH v5 4/4] completion: fix zsh alias listing for subsection aliases Jonatan Holmgren
@ 2026-02-16 18:32     ` D. Ben Knoble
  2026-02-17 20:01     ` Junio C Hamano
  1 sibling, 0 replies; 88+ messages in thread
From: D. Ben Knoble @ 2026-02-16 18:32 UTC (permalink / raw)
  To: Jonatan Holmgren; +Cc: git, peff, gitster, brian m . carlson

On Mon, Feb 16, 2026 at 11:15 AM Jonatan Holmgren <jonatan@jontes.page> wrote:
>
> The zsh completion function __git_zsh_cmd_alias() uses 'git config
> --get-regexp' to enumerate aliases and then strips the "alias." prefix
> from each key. For subsection-style aliases (alias.name.command), this
> leaves "name.command" as the completion candidate instead of just
> "name".
>
> The bash completion does not have this problem because it goes through
> 'git --list-cmds=alias', which calls list_aliases() in C and already
> handles both alias syntaxes correctly. However, zsh needs both the
> alias name and its value for descriptive completion, which
> --list-cmds=alias does not provide.
>
> Add a hidden --aliases-for-completion option to 'git help', following
> the existing --config-for-completion pattern. It outputs NUL-separated
> "name\nvalue" pairs using list_aliases(), which correctly resolves both
> the traditional (alias.name) and subsection (alias.name.command)
> formats. Update __git_zsh_cmd_alias() to use it.

An alternative would to be to post-process and turn
"alias.name.command" into "name", but this solution is easier to parse
(won't mix 2 formats).

> ---
>  builtin/help.c                        | 15 +++++++++++++++
>  contrib/completion/git-completion.zsh |  2 +-
>  2 files changed, 16 insertions(+), 1 deletion(-)
>
> diff --git a/builtin/help.c b/builtin/help.c
> index c09cbc8912..f02308a391 100644
> --- a/builtin/help.c
> +++ b/builtin/help.c
> @@ -54,6 +54,7 @@ static enum help_action {
>         HELP_ACTION_DEVELOPER_INTERFACES,
>         HELP_ACTION_CONFIG_FOR_COMPLETION,
>         HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION,
> +       HELP_ACTION_ALIASES_FOR_COMPLETION,
>  } cmd_mode;
>
>  static char *html_path;
> @@ -90,6 +91,8 @@ static struct option builtin_help_options[] = {
>                     HELP_ACTION_CONFIG_FOR_COMPLETION, PARSE_OPT_HIDDEN),
>         OPT_CMDMODE_F(0, "config-sections-for-completion", &cmd_mode, "",
>                     HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, PARSE_OPT_HIDDEN),
> +       OPT_CMDMODE_F(0, "aliases-for-completion", &cmd_mode, "",
> +                   HELP_ACTION_ALIASES_FOR_COMPLETION, PARSE_OPT_HIDDEN),
>
>         OPT_END(),
>  };
> @@ -691,6 +694,18 @@ int cmd_help(int argc,
>                                help_format);
>                 list_config_help(SHOW_CONFIG_SECTIONS);
>                 return 0;
> +       case HELP_ACTION_ALIASES_FOR_COMPLETION: {
> +               struct string_list alias_list = STRING_LIST_INIT_DUP;
> +               opt_mode_usage(argc, "--aliases-for-completion",
> +                              help_format);

style nit: I _think_ this line could be unwrapped?

> +               list_aliases(&alias_list);
> +               string_list_sort(&alias_list);

It may not matter much since lists of aliases are probably not
humongous, but do we need to sort? I don't _think_ "git
--list-cmds=alias" does, for example, though I'm not sure about "git
config --get-regexp" that's being replaced. Leaving it in the config
order could even be a feature, so I can put easier-to-complete aliases
earlier in my config?

> +               for (size_t i = 0; i < alias_list.nr; i++)
> +                       printf("%s%c%s%c", alias_list.items[i].string, '\n',
> +                              (char *)alias_list.items[i].util, '\0');
> +               string_list_clear(&alias_list, 1);
> +               return 0;
> +       }
>         case HELP_ACTION_CONFIG:
>                 opt_mode_usage(argc, "--config", help_format);
>                 setup_pager(the_repository);


> diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh
> index f5877bd7a1..c32186a977 100644
> --- a/contrib/completion/git-completion.zsh
> +++ b/contrib/completion/git-completion.zsh
> @@ -202,7 +202,7 @@ __git_zsh_cmd_common ()
>  __git_zsh_cmd_alias ()
>  {
>         local -a list
> -       list=(${${(0)"$(git config -z --get-regexp '^alias\.*')"}#alias.})
> +       list=(${(0)"$(git help --aliases-for-completion)"})
>         list=(${(f)"$(printf "%s:alias for '%s'\n" ${(f@)list})"})
>         _describe -t alias-commands 'aliases' list && _ret=0
>  }
> --
> 2.53.0.83.g660bbd62ee.dirty

The Zsh hunk looks straightforwardly correct, especially since the
output format of the new help-mode is unchanged, thanks.

-- 
D. Ben Knoble

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v5 4/4] completion: fix zsh alias listing for subsection aliases
  2026-02-16 16:15   ` [PATCH v5 4/4] completion: fix zsh alias listing for subsection aliases Jonatan Holmgren
  2026-02-16 18:32     ` D. Ben Knoble
@ 2026-02-17 20:01     ` Junio C Hamano
  1 sibling, 0 replies; 88+ messages in thread
From: Junio C Hamano @ 2026-02-17 20:01 UTC (permalink / raw)
  To: Jonatan Holmgren; +Cc: git, peff, D . Ben Knoble, brian m . carlson

Jonatan Holmgren <jonatan@jontes.page> writes:

> The zsh completion function __git_zsh_cmd_alias() uses 'git config
> --get-regexp' to enumerate aliases and then strips the "alias." prefix
> from each key. For subsection-style aliases (alias.name.command), this
> leaves "name.command" as the completion candidate instead of just
> "name".
>
> The bash completion does not have this problem because it goes through
> 'git --list-cmds=alias', which calls list_aliases() in C and already
> handles both alias syntaxes correctly. However, zsh needs both the
> alias name and its value for descriptive completion, which
> --list-cmds=alias does not provide.

OK.  It is a natural question to ask why it is insufficient to use
what bash side happily uses, and the above gives a good explanation.

> Add a hidden --aliases-for-completion option to 'git help', following
> the existing --config-for-completion pattern. It outputs NUL-separated
> "name\nvalue" pairs using list_aliases(), which correctly resolves both
> the traditional (alias.name) and subsection (alias.name.command)
> formats. Update __git_zsh_cmd_alias() to use it.

Seeing that "--config-for-completion" is used for bash, I wonder if
it would be a good follow-up topic to rewrite the bash completion to
also use "--aliases-for-completion" (and possibly drop the need to
support "git --list-cmds=alias" command).  But that is clearly
outside the scope of this topic.

> ---

Missing sign-off.

>  builtin/help.c                        | 15 +++++++++++++++
>  contrib/completion/git-completion.zsh |  2 +-
>  2 files changed, 16 insertions(+), 1 deletion(-)
>
> diff --git a/builtin/help.c b/builtin/help.c
> index c09cbc8912..f02308a391 100644
> --- a/builtin/help.c
> +++ b/builtin/help.c
> @@ -54,6 +54,7 @@ static enum help_action {
>  	HELP_ACTION_DEVELOPER_INTERFACES,
>  	HELP_ACTION_CONFIG_FOR_COMPLETION,
>  	HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION,
> +	HELP_ACTION_ALIASES_FOR_COMPLETION,
>  } cmd_mode;
>  
>  static char *html_path;
> @@ -90,6 +91,8 @@ static struct option builtin_help_options[] = {
>  		    HELP_ACTION_CONFIG_FOR_COMPLETION, PARSE_OPT_HIDDEN),
>  	OPT_CMDMODE_F(0, "config-sections-for-completion", &cmd_mode, "",
>  		    HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, PARSE_OPT_HIDDEN),
> +	OPT_CMDMODE_F(0, "aliases-for-completion", &cmd_mode, "",
> +		    HELP_ACTION_ALIASES_FOR_COMPLETION, PARSE_OPT_HIDDEN),
>  
>  	OPT_END(),
>  };
> @@ -691,6 +694,18 @@ int cmd_help(int argc,
>  			       help_format);
>  		list_config_help(SHOW_CONFIG_SECTIONS);
>  		return 0;
> +	case HELP_ACTION_ALIASES_FOR_COMPLETION: {
> +		struct string_list alias_list = STRING_LIST_INIT_DUP;
> +		opt_mode_usage(argc, "--aliases-for-completion",
> +			       help_format);
> +		list_aliases(&alias_list);
> +		string_list_sort(&alias_list);
> +		for (size_t i = 0; i < alias_list.nr; i++)
> +			printf("%s%c%s%c", alias_list.items[i].string, '\n',
> +			       (char *)alias_list.items[i].util, '\0');
> +		string_list_clear(&alias_list, 1);
> +		return 0;
> +	}
>  	case HELP_ACTION_CONFIG:
>  		opt_mode_usage(argc, "--config", help_format);
>  		setup_pager(the_repository);
> diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh
> index f5877bd7a1..c32186a977 100644
> --- a/contrib/completion/git-completion.zsh
> +++ b/contrib/completion/git-completion.zsh
> @@ -202,7 +202,7 @@ __git_zsh_cmd_common ()
>  __git_zsh_cmd_alias ()
>  {
>  	local -a list
> -	list=(${${(0)"$(git config -z --get-regexp '^alias\.*')"}#alias.})
> +	list=(${(0)"$(git help --aliases-for-completion)"})
>  	list=(${(f)"$(printf "%s:alias for '%s'\n" ${(f@)list})"})
>  	_describe -t alias-commands 'aliases' list && _ret=0
>  }

^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v6 0/4] support UTF-8 in alias names
  2026-02-08 15:30 [RFC] Support UTF-8 characters in Git alias names Jonatan Holmgren
                   ` (7 preceding siblings ...)
  2026-02-16 16:15 ` [PATCH v5 0/4] support uTF-8 " Jonatan Holmgren
@ 2026-02-18 14:52 ` Jonatan Holmgren
  2026-02-18 14:52   ` [PATCH v6 1/4] help: use list_aliases() for alias listing Jonatan Holmgren
                     ` (3 more replies)
  2026-02-18 21:57 ` [PATCH v7 0/4] support UTF-8 in alias names Jonatan Holmgren
                   ` (3 subsequent siblings)
  12 siblings, 4 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-18 14:52 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

This series enables UTF-8 and special characters in Git alias names
by using config subsection syntax: [alias "name"] command = value.

I have since last time:
- Unwrapped opt_mode_usage() call in builtin/help.c for consistency
- Removed string_list_sort() to preserve config order as suggested by Ben
- Added missing Signed-off-by trailer to patch 4 (oops)
- Fixed --aliases-for-completion output format to use NUL
  seperators instead of newlines (as per my own commit message and intention)

Thanks again.

Jonatan Holmgren (4):
  help: use list_aliases() for alias listing
  alias: prepare for subsection aliases
  alias: support non-alphanumeric names via subsection syntax
  completion: fix zsh alias listing for subsection aliases

 Documentation/config/alias.adoc       | 50 ++++++++++++++++---
 alias.c                               | 42 +++++++++++++---
 builtin/help.c                        | 13 +++++
 contrib/completion/git-completion.zsh |  2 +-
 help.c                                | 39 +++++++--------
 t/t0014-alias.sh                      | 71 +++++++++++++++++++++++++++
 6 files changed, 182 insertions(+), 35 deletions(-)

-- 
2.53.0.122.g591c997fb5.dirty


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v6 1/4] help: use list_aliases() for alias listing
  2026-02-18 14:52 ` [PATCH v6 0/4] support UTF-8 in alias names Jonatan Holmgren
@ 2026-02-18 14:52   ` Jonatan Holmgren
  2026-02-18 14:52   ` [PATCH v6 2/4] alias: prepare for subsection aliases Jonatan Holmgren
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-18 14:52 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

help.c has its own get_alias() config callback that duplicates the
parsing logic in alias.c. Consolidate by teaching list_aliases() to
also store the alias values (via the string_list util field), then
use it in list_all_cmds_help_aliases() instead of the private
callback.

This preserves the existing error checking for value-less alias
definitions by checking in alias.c rather than help.c.

No functional change intended.

Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 alias.c          |  8 +++++++-
 help.c           | 17 ++---------------
 t/t0014-alias.sh | 10 ++++++++++
 3 files changed, 19 insertions(+), 16 deletions(-)

diff --git a/alias.c b/alias.c
index 1a1a141a0a..271acb9bf1 100644
--- a/alias.c
+++ b/alias.c
@@ -29,7 +29,13 @@ static int config_alias_cb(const char *key, const char *value,
 						 key, value);
 		}
 	} else if (data->list) {
-		string_list_append(data->list, p);
+		struct string_list_item *item;
+
+		if (!value)
+			return config_error_nonbool(key);
+
+		item = string_list_append(data->list, p);
+		item->util = xstrdup(value);
 	}
 
 	return 0;
diff --git a/help.c b/help.c
index 08b5c60204..5b1b320d02 100644
--- a/help.c
+++ b/help.c
@@ -20,6 +20,7 @@
 #include "prompt.h"
 #include "fsmonitor-ipc.h"
 #include "repository.h"
+#include "alias.h"
 
 #ifndef NO_CURL
 #include "git-curl-compat.h" /* For LIBCURL_VERSION only */
@@ -468,20 +469,6 @@ void list_developer_interfaces_help(void)
 	putchar('\n');
 }
 
-static int get_alias(const char *var, const char *value,
-		     const struct config_context *ctx UNUSED, void *data)
-{
-	struct string_list *list = data;
-
-	if (skip_prefix(var, "alias.", &var)) {
-		if (!value)
-			return config_error_nonbool(var);
-		string_list_append(list, var)->util = xstrdup(value);
-	}
-
-	return 0;
-}
-
 static void list_all_cmds_help_external_commands(void)
 {
 	struct string_list others = STRING_LIST_INIT_DUP;
@@ -501,7 +488,7 @@ static void list_all_cmds_help_aliases(int longest)
 	struct cmdname_help *aliases;
 	int i;
 
-	repo_config(the_repository, get_alias, &alias_list);
+	list_aliases(&alias_list);
 	string_list_sort(&alias_list);
 
 	for (i = 0; i < alias_list.nr; i++) {
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 07a53e7366..a13d2be8ca 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -112,4 +112,14 @@ test_expect_success 'cannot alias-shadow a sample of regular builtins' '
 	done
 '
 
+test_expect_success 'alias without value reports error' '
+	test_when_finished "git config --unset alias.noval" &&
+	cat >>.git/config <<-\EOF &&
+	[alias]
+		noval
+	EOF
+	test_must_fail git noval 2>error &&
+	test_grep "alias.noval" error
+'
+
 test_done
-- 
2.53.0.122.g591c997fb5.dirty


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* [PATCH v6 2/4] alias: prepare for subsection aliases
  2026-02-18 14:52 ` [PATCH v6 0/4] support UTF-8 in alias names Jonatan Holmgren
  2026-02-18 14:52   ` [PATCH v6 1/4] help: use list_aliases() for alias listing Jonatan Holmgren
@ 2026-02-18 14:52   ` Jonatan Holmgren
  2026-02-18 16:21     ` Kristoffer Haugsbakk
  2026-02-18 14:52   ` [PATCH v6 3/4] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
  2026-02-18 14:52   ` [PATCH v6 4/4] completion: fix zsh alias listing for subsection aliases Jonatan Holmgren
  3 siblings, 1 reply; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-18 14:52 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

Switch git_unknown_cmd_config() from skip_prefix() to
parse_config_key() for alias parsing. This properly handles the
three-level config key structure and prepares for the new
alias.*.command subsection syntax in the next commit.

This is a compatibility break: the alias configuration parser used
to be overly permissive and accepted "alias.<subsection>.<key>" as
defining an alias "<subsection>.<key>". With this change,
alias.<subsection>.<key> entries are silently ignored (unless <key>
is "command", which will be given meaning in the next commit).

This behavior was arguably a bug, since config subsections were never
intended to work this way for aliases, and aliases with dots in their
names have never been documented or intentionally supported.

Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 help.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/help.c b/help.c
index 5b1b320d02..691af219bf 100644
--- a/help.c
+++ b/help.c
@@ -573,7 +573,8 @@ static int git_unknown_cmd_config(const char *var, const char *value,
 				  void *cb)
 {
 	struct help_unknown_cmd_config *cfg = cb;
-	const char *p;
+	const char *subsection, *key;
+	size_t subsection_len;
 
 	if (!strcmp(var, "help.autocorrect")) {
 		int v = parse_autocorrect(value);
@@ -588,8 +589,11 @@ static int git_unknown_cmd_config(const char *var, const char *value,
 	}
 
 	/* Also use aliases for command lookup */
-	if (skip_prefix(var, "alias.", &p))
-		add_cmdname(&cfg->aliases, p, strlen(p));
+	if (!parse_config_key(var, "alias", &subsection, &subsection_len,
+			      &key)) {
+		if (!subsection)
+			add_cmdname(&cfg->aliases, key, strlen(key));
+	}
 
 	return 0;
 }
-- 
2.53.0.122.g591c997fb5.dirty


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* [PATCH v6 3/4] alias: support non-alphanumeric names via subsection syntax
  2026-02-18 14:52 ` [PATCH v6 0/4] support UTF-8 in alias names Jonatan Holmgren
  2026-02-18 14:52   ` [PATCH v6 1/4] help: use list_aliases() for alias listing Jonatan Holmgren
  2026-02-18 14:52   ` [PATCH v6 2/4] alias: prepare for subsection aliases Jonatan Holmgren
@ 2026-02-18 14:52   ` Jonatan Holmgren
  2026-02-18 14:52   ` [PATCH v6 4/4] completion: fix zsh alias listing for subsection aliases Jonatan Holmgren
  3 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-18 14:52 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

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.g591c997fb5.dirty


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* [PATCH v6 4/4] completion: fix zsh alias listing for subsection aliases
  2026-02-18 14:52 ` [PATCH v6 0/4] support UTF-8 in alias names Jonatan Holmgren
                     ` (2 preceding siblings ...)
  2026-02-18 14:52   ` [PATCH v6 3/4] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
@ 2026-02-18 14:52   ` Jonatan Holmgren
  3 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-18 14:52 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

The zsh completion function __git_zsh_cmd_alias() uses 'git config
--get-regexp' to enumerate aliases and then strips the "alias." prefix
from each key. For subsection-style aliases (alias.name.command), this
leaves "name.command" as the completion candidate instead of just
"name".

The bash completion does not have this problem because it goes through
'git --list-cmds=alias', which calls list_aliases() in C and already
handles both alias syntaxes correctly. However, zsh needs both the
alias name and its value for descriptive completion, which
--list-cmds=alias does not provide.

Add a hidden --aliases-for-completion option to 'git help', following
the existing --config-for-completion pattern. It outputs NUL-separated
"name\nvalue" pairs using list_aliases(), which correctly resolves both
the traditional (alias.name) and subsection (alias.name.command)
formats. Update __git_zsh_cmd_alias() to use it.

Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 builtin/help.c                        | 13 +++++++++++++
 contrib/completion/git-completion.zsh |  2 +-
 2 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/builtin/help.c b/builtin/help.c
index c09cbc8912..2db41d0b55 100644
--- a/builtin/help.c
+++ b/builtin/help.c
@@ -54,6 +54,7 @@ static enum help_action {
 	HELP_ACTION_DEVELOPER_INTERFACES,
 	HELP_ACTION_CONFIG_FOR_COMPLETION,
 	HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION,
+	HELP_ACTION_ALIASES_FOR_COMPLETION,
 } cmd_mode;
 
 static char *html_path;
@@ -90,6 +91,8 @@ static struct option builtin_help_options[] = {
 		    HELP_ACTION_CONFIG_FOR_COMPLETION, PARSE_OPT_HIDDEN),
 	OPT_CMDMODE_F(0, "config-sections-for-completion", &cmd_mode, "",
 		    HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, PARSE_OPT_HIDDEN),
+	OPT_CMDMODE_F(0, "aliases-for-completion", &cmd_mode, "",
+		    HELP_ACTION_ALIASES_FOR_COMPLETION, PARSE_OPT_HIDDEN),
 
 	OPT_END(),
 };
@@ -691,6 +694,16 @@ int cmd_help(int argc,
 			       help_format);
 		list_config_help(SHOW_CONFIG_SECTIONS);
 		return 0;
+	case HELP_ACTION_ALIASES_FOR_COMPLETION: {
+		struct string_list alias_list = STRING_LIST_INIT_DUP;
+		opt_mode_usage(argc, "--aliases-for-completion", help_format);
+		list_aliases(&alias_list);
+		for (size_t i = 0; i < alias_list.nr; i++)
+			printf("%s%c%s%c", alias_list.items[i].string, '\0',
+			       (char *)alias_list.items[i].util, '\0');
+		string_list_clear(&alias_list, 1);
+		return 0;
+	}
 	case HELP_ACTION_CONFIG:
 		opt_mode_usage(argc, "--config", help_format);
 		setup_pager(the_repository);
diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh
index f5877bd7a1..c32186a977 100644
--- a/contrib/completion/git-completion.zsh
+++ b/contrib/completion/git-completion.zsh
@@ -202,7 +202,7 @@ __git_zsh_cmd_common ()
 __git_zsh_cmd_alias ()
 {
 	local -a list
-	list=(${${(0)"$(git config -z --get-regexp '^alias\.*')"}#alias.})
+	list=(${(0)"$(git help --aliases-for-completion)"})
 	list=(${(f)"$(printf "%s:alias for '%s'\n" ${(f@)list})"})
 	_describe -t alias-commands 'aliases' list && _ret=0
 }
-- 
2.53.0.122.g591c997fb5.dirty


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* Re: [PATCH v6 2/4] alias: prepare for subsection aliases
  2026-02-18 14:52   ` [PATCH v6 2/4] alias: prepare for subsection aliases Jonatan Holmgren
@ 2026-02-18 16:21     ` Kristoffer Haugsbakk
  0 siblings, 0 replies; 88+ messages in thread
From: Kristoffer Haugsbakk @ 2026-02-18 16:21 UTC (permalink / raw)
  To: Jonatan Holmgren, git
  Cc: Jeff King, Junio C Hamano, D . Ben Knoble, brian m. carlson

On Wed, Feb 18, 2026, at 15:52, Jonatan Holmgren wrote:
> Switch git_unknown_cmd_config() from skip_prefix() to
> parse_config_key() for alias parsing. This properly handles the
> three-level config key structure and prepares for the new
> alias.*.command subsection syntax in the next commit.
>
> This is a compatibility break: the alias configuration parser used
> to be overly permissive and accepted "alias.<subsection>.<key>" as
> defining an alias "<subsection>.<key>". With this change,
> alias.<subsection>.<key> entries are silently ignored (unless <key>
> is "command", which will be given meaning in the next commit).

Unrelated to this change. I was wondering if it makes sense to use a
trailer for commits that break compatibility? However unrealistic it
might be that the break ends up mattering in practice.

Authors might use different terms and phrases in their commit messages,
like

• Breaking change
• Compatibility break
• Hysterical raisins

And the commit message might be using these terms to describe how it
breaks compatibility or how it *avoids* doing so.

>
> This behavior was arguably a bug, since config subsections were never
> intended to work this way for aliases, and aliases with dots in their
> names have never been documented or intentionally supported.
>
> Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
>[snip]

^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v7 0/4] support UTF-8 in alias names
  2026-02-08 15:30 [RFC] Support UTF-8 characters in Git alias names Jonatan Holmgren
                   ` (8 preceding siblings ...)
  2026-02-18 14:52 ` [PATCH v6 0/4] support UTF-8 in alias names Jonatan Holmgren
@ 2026-02-18 21:57 ` Jonatan Holmgren
  2026-02-18 21:57   ` [PATCH v7 1/4] help: use list_aliases() for alias listing Jonatan Holmgren
                     ` (4 more replies)
  2026-02-24 17:12 ` [PATCH 0/2] Fix small issues in alias subsection handling Jonatan Holmgren
                   ` (2 subsequent siblings)
  12 siblings, 5 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-18 21:57 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

Hi all!

This series adds support for non-alphanumeric (including UTF-8)
characters in alias names by allowing aliases to be defined using
git-config's subsection syntax.

Changes since v6:
Reverted the --aliases-for-completion output format to
use a newline as the field separator between name and value within
each NUL-terminated record (name\nvalue\0). In v6 I changed this
to use NUL for both separators, but this diverges from the
convention established by git config -z, which outputs
key\nvalue\0. Matching that convention is the right call here.

Thanks!

Jonatan Holmgren (4):
  help: use list_aliases() for alias listing
  alias: prepare for subsection aliases
  alias: support non-alphanumeric names via subsection syntax
  completion: fix zsh alias listing for subsection aliases

 Documentation/config/alias.adoc       | 50 ++++++++++++++++---
 alias.c                               | 42 +++++++++++++---
 builtin/help.c                        | 13 +++++
 contrib/completion/git-completion.zsh |  2 +-
 help.c                                | 39 +++++++--------
 t/t0014-alias.sh                      | 71 +++++++++++++++++++++++++++
 6 files changed, 182 insertions(+), 35 deletions(-)

-- 
2.53.0.122.g3abf75d576


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v7 1/4] help: use list_aliases() for alias listing
  2026-02-18 21:57 ` [PATCH v7 0/4] support UTF-8 in alias names Jonatan Holmgren
@ 2026-02-18 21:57   ` Jonatan Holmgren
  2026-02-24 22:19     ` Jacob Keller
  2026-02-24 22:21     ` Jacob Keller
  2026-02-18 21:57   ` [PATCH v7 2/4] alias: prepare for subsection aliases Jonatan Holmgren
                     ` (3 subsequent siblings)
  4 siblings, 2 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-18 21:57 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

help.c has its own get_alias() config callback that duplicates the
parsing logic in alias.c. Consolidate by teaching list_aliases() to
also store the alias values (via the string_list util field), then
use it in list_all_cmds_help_aliases() instead of the private
callback.

This preserves the existing error checking for value-less alias
definitions by checking in alias.c rather than help.c.

No functional change intended.

Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 alias.c          |  8 +++++++-
 help.c           | 17 ++---------------
 t/t0014-alias.sh | 10 ++++++++++
 3 files changed, 19 insertions(+), 16 deletions(-)

diff --git a/alias.c b/alias.c
index 1a1a141a0a..271acb9bf1 100644
--- a/alias.c
+++ b/alias.c
@@ -29,7 +29,13 @@ static int config_alias_cb(const char *key, const char *value,
 						 key, value);
 		}
 	} else if (data->list) {
-		string_list_append(data->list, p);
+		struct string_list_item *item;
+
+		if (!value)
+			return config_error_nonbool(key);
+
+		item = string_list_append(data->list, p);
+		item->util = xstrdup(value);
 	}
 
 	return 0;
diff --git a/help.c b/help.c
index 08b5c60204..5b1b320d02 100644
--- a/help.c
+++ b/help.c
@@ -20,6 +20,7 @@
 #include "prompt.h"
 #include "fsmonitor-ipc.h"
 #include "repository.h"
+#include "alias.h"
 
 #ifndef NO_CURL
 #include "git-curl-compat.h" /* For LIBCURL_VERSION only */
@@ -468,20 +469,6 @@ void list_developer_interfaces_help(void)
 	putchar('\n');
 }
 
-static int get_alias(const char *var, const char *value,
-		     const struct config_context *ctx UNUSED, void *data)
-{
-	struct string_list *list = data;
-
-	if (skip_prefix(var, "alias.", &var)) {
-		if (!value)
-			return config_error_nonbool(var);
-		string_list_append(list, var)->util = xstrdup(value);
-	}
-
-	return 0;
-}
-
 static void list_all_cmds_help_external_commands(void)
 {
 	struct string_list others = STRING_LIST_INIT_DUP;
@@ -501,7 +488,7 @@ static void list_all_cmds_help_aliases(int longest)
 	struct cmdname_help *aliases;
 	int i;
 
-	repo_config(the_repository, get_alias, &alias_list);
+	list_aliases(&alias_list);
 	string_list_sort(&alias_list);
 
 	for (i = 0; i < alias_list.nr; i++) {
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 07a53e7366..a13d2be8ca 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -112,4 +112,14 @@ test_expect_success 'cannot alias-shadow a sample of regular builtins' '
 	done
 '
 
+test_expect_success 'alias without value reports error' '
+	test_when_finished "git config --unset alias.noval" &&
+	cat >>.git/config <<-\EOF &&
+	[alias]
+		noval
+	EOF
+	test_must_fail git noval 2>error &&
+	test_grep "alias.noval" error
+'
+
 test_done
-- 
2.53.0.122.g3abf75d576


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* [PATCH v7 2/4] alias: prepare for subsection aliases
  2026-02-18 21:57 ` [PATCH v7 0/4] support UTF-8 in alias names Jonatan Holmgren
  2026-02-18 21:57   ` [PATCH v7 1/4] help: use list_aliases() for alias listing Jonatan Holmgren
@ 2026-02-18 21:57   ` Jonatan Holmgren
  2026-02-18 21:57   ` [PATCH v7 3/4] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
                     ` (2 subsequent siblings)
  4 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-18 21:57 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

Switch git_unknown_cmd_config() from skip_prefix() to
parse_config_key() for alias parsing. This properly handles the
three-level config key structure and prepares for the new
alias.*.command subsection syntax in the next commit.

This is a compatibility break: the alias configuration parser used
to be overly permissive and accepted "alias.<subsection>.<key>" as
defining an alias "<subsection>.<key>". With this change,
alias.<subsection>.<key> entries are silently ignored (unless <key>
is "command", which will be given meaning in the next commit).

This behavior was arguably a bug, since config subsections were never
intended to work this way for aliases, and aliases with dots in their
names have never been documented or intentionally supported.

Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 help.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/help.c b/help.c
index 5b1b320d02..691af219bf 100644
--- a/help.c
+++ b/help.c
@@ -573,7 +573,8 @@ static int git_unknown_cmd_config(const char *var, const char *value,
 				  void *cb)
 {
 	struct help_unknown_cmd_config *cfg = cb;
-	const char *p;
+	const char *subsection, *key;
+	size_t subsection_len;
 
 	if (!strcmp(var, "help.autocorrect")) {
 		int v = parse_autocorrect(value);
@@ -588,8 +589,11 @@ static int git_unknown_cmd_config(const char *var, const char *value,
 	}
 
 	/* Also use aliases for command lookup */
-	if (skip_prefix(var, "alias.", &p))
-		add_cmdname(&cfg->aliases, p, strlen(p));
+	if (!parse_config_key(var, "alias", &subsection, &subsection_len,
+			      &key)) {
+		if (!subsection)
+			add_cmdname(&cfg->aliases, key, strlen(key));
+	}
 
 	return 0;
 }
-- 
2.53.0.122.g3abf75d576


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* [PATCH v7 3/4] alias: support non-alphanumeric names via subsection syntax
  2026-02-18 21:57 ` [PATCH v7 0/4] support UTF-8 in alias names Jonatan Holmgren
  2026-02-18 21:57   ` [PATCH v7 1/4] help: use list_aliases() for alias listing Jonatan Holmgren
  2026-02-18 21:57   ` [PATCH v7 2/4] alias: prepare for subsection aliases Jonatan Holmgren
@ 2026-02-18 21:57   ` Jonatan Holmgren
  2026-02-24 10:55     ` Kristoffer Haugsbakk
  2026-02-18 21:57   ` [PATCH v7 4/4] completion: fix zsh alias listing for subsection aliases Jonatan Holmgren
  2026-02-19 18:17   ` [PATCH v7 0/4] support UTF-8 in alias names Junio C Hamano
  4 siblings, 1 reply; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-18 21:57 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

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


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* [PATCH v7 4/4] completion: fix zsh alias listing for subsection aliases
  2026-02-18 21:57 ` [PATCH v7 0/4] support UTF-8 in alias names Jonatan Holmgren
                     ` (2 preceding siblings ...)
  2026-02-18 21:57   ` [PATCH v7 3/4] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
@ 2026-02-18 21:57   ` Jonatan Holmgren
  2026-02-19 18:17   ` [PATCH v7 0/4] support UTF-8 in alias names Junio C Hamano
  4 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-18 21:57 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

The zsh completion function __git_zsh_cmd_alias() uses 'git config
--get-regexp' to enumerate aliases and then strips the "alias." prefix
from each key. For subsection-style aliases (alias.name.command), this
leaves "name.command" as the completion candidate instead of just
"name".

The bash completion does not have this problem because it goes through
'git --list-cmds=alias', which calls list_aliases() in C and already
handles both alias syntaxes correctly. However, zsh needs both the
alias name and its value for descriptive completion, which
--list-cmds=alias does not provide.

Add a hidden --aliases-for-completion option to 'git help', following
the existing --config-for-completion pattern. It outputs NUL-separated
"name\nvalue" pairs using list_aliases(), which correctly resolves both
the traditional (alias.name) and subsection (alias.name.command)
formats. Update __git_zsh_cmd_alias() to use it.

Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 builtin/help.c                        | 13 +++++++++++++
 contrib/completion/git-completion.zsh |  2 +-
 2 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/builtin/help.c b/builtin/help.c
index c09cbc8912..86a3d03a9b 100644
--- a/builtin/help.c
+++ b/builtin/help.c
@@ -54,6 +54,7 @@ static enum help_action {
 	HELP_ACTION_DEVELOPER_INTERFACES,
 	HELP_ACTION_CONFIG_FOR_COMPLETION,
 	HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION,
+	HELP_ACTION_ALIASES_FOR_COMPLETION,
 } cmd_mode;
 
 static char *html_path;
@@ -90,6 +91,8 @@ static struct option builtin_help_options[] = {
 		    HELP_ACTION_CONFIG_FOR_COMPLETION, PARSE_OPT_HIDDEN),
 	OPT_CMDMODE_F(0, "config-sections-for-completion", &cmd_mode, "",
 		    HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, PARSE_OPT_HIDDEN),
+	OPT_CMDMODE_F(0, "aliases-for-completion", &cmd_mode, "",
+		    HELP_ACTION_ALIASES_FOR_COMPLETION, PARSE_OPT_HIDDEN),
 
 	OPT_END(),
 };
@@ -691,6 +694,16 @@ int cmd_help(int argc,
 			       help_format);
 		list_config_help(SHOW_CONFIG_SECTIONS);
 		return 0;
+	case HELP_ACTION_ALIASES_FOR_COMPLETION: {
+		struct string_list alias_list = STRING_LIST_INIT_DUP;
+		opt_mode_usage(argc, "--aliases-for-completion", help_format);
+		list_aliases(&alias_list);
+		for (size_t i = 0; i < alias_list.nr; i++)
+			printf("%s%c%s%c", alias_list.items[i].string, '\n',
+			       (char *)alias_list.items[i].util, '\0');
+		string_list_clear(&alias_list, 1);
+		return 0;
+	}
 	case HELP_ACTION_CONFIG:
 		opt_mode_usage(argc, "--config", help_format);
 		setup_pager(the_repository);
diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh
index f5877bd7a1..c32186a977 100644
--- a/contrib/completion/git-completion.zsh
+++ b/contrib/completion/git-completion.zsh
@@ -202,7 +202,7 @@ __git_zsh_cmd_common ()
 __git_zsh_cmd_alias ()
 {
 	local -a list
-	list=(${${(0)"$(git config -z --get-regexp '^alias\.*')"}#alias.})
+	list=(${(0)"$(git help --aliases-for-completion)"})
 	list=(${(f)"$(printf "%s:alias for '%s'\n" ${(f@)list})"})
 	_describe -t alias-commands 'aliases' list && _ret=0
 }
-- 
2.53.0.122.g3abf75d576


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* Re: [PATCH v7 0/4] support UTF-8 in alias names
  2026-02-18 21:57 ` [PATCH v7 0/4] support UTF-8 in alias names Jonatan Holmgren
                     ` (3 preceding siblings ...)
  2026-02-18 21:57   ` [PATCH v7 4/4] completion: fix zsh alias listing for subsection aliases Jonatan Holmgren
@ 2026-02-19 18:17   ` Junio C Hamano
  2026-02-19 18:54     ` Jonatan Holmgren
  4 siblings, 1 reply; 88+ messages in thread
From: Junio C Hamano @ 2026-02-19 18:17 UTC (permalink / raw)
  To: Jonatan Holmgren; +Cc: git, peff, D . Ben Knoble, brian m . carlson

Jonatan Holmgren <jonatan@jontes.page> writes:

> Hi all!
>
> This series adds support for non-alphanumeric (including UTF-8)
> characters in alias names by allowing aliases to be defined using
> git-config's subsection syntax.
>
> Changes since v6:
> Reverted the --aliases-for-completion output format to
> use a newline as the field separator between name and value within
> each NUL-terminated record (name\nvalue\0). In v6 I changed this
> to use NUL for both separators, but this diverges from the
> convention established by git config -z, which outputs
> key\nvalue\0. Matching that convention is the right call here.

OK.  The key cannot have NUL nor LF in it, so either _should_ work,
and using LF in the context of -z somehow makes our eyes feel dirty,
but it is how "git config -z" chooses to work, so I agree that this
change makes sense.

Shall we mark this topic ready for 'next' now?

Will queue.  Thanks.

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v7 0/4] support UTF-8 in alias names
  2026-02-19 18:17   ` [PATCH v7 0/4] support UTF-8 in alias names Junio C Hamano
@ 2026-02-19 18:54     ` Jonatan Holmgren
  0 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-19 18:54 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, peff, D . Ben Knoble, brian m . carlson

Thanks for the review and feedback everyone!  Excited to have my first 
patch land in Git.

Jonatan

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 2/2] alias: support non-alphanumeric names via subsection syntax
  2026-02-10 18:31   ` [PATCH v2 2/2] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
  2026-02-10 19:47     ` Junio C Hamano
@ 2026-02-23  9:29     ` Kristoffer Haugsbakk
  2026-02-23 16:07       ` Kristoffer Haugsbakk
  2026-02-24 10:27     ` Patrick Steinhardt
  2 siblings, 1 reply; 88+ messages in thread
From: Kristoffer Haugsbakk @ 2026-02-23  9:29 UTC (permalink / raw)
  To: Jonatan Holmgren, git
  Cc: Jeff King, Junio C Hamano, D . Ben Knoble, brian m. carlson

On Tue, Feb 10, 2026, at 19:31, Jonatan Holmgren wrote:
> [snip]
>
> Suggested-by: Jeff King <peff@peff.net>
> Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
> ---
>  Documentation/config/alias.adoc | 44 +++++++++++++++++++++-----
>  alias.c                         | 45 ++++++++++++++++++++++-----
>  help.c                          | 12 +++++--
>  t/t0014-alias.sh                | 55 +++++++++++++++++++++++++++++++++
>  4 files changed, 137 insertions(+), 19 deletions(-)
>
> diff --git a/Documentation/config/alias.adoc
> b/Documentation/config/alias.adoc
> index 80ce17d2de..17a548cd64 100644
> --- a/Documentation/config/alias.adoc
> +++ b/Documentation/config/alias.adoc
>[snip]
> +# With subsection (allows UTF-8 and special characters)
> +[alias "hämta"]
> +    command = fetch
> +[alias "gömma"]
> +    command = stash
> +----
> ++
> +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.
>  +
>  Note that the first word of an alias does not necessarily have to be a

The HTML output shows the list continuation character (+).

    + Note that the first word of an alias does not ...

>  command. It can be a command-line option that will be passed into the

And there are two more outside this context which I suspect are knock-ons?

    + If the alias expansion is prefixed ..

    ...

    + * Shell commands will be executed ...

See `Documentation/doc-diff master next`.

• master: 7c02d39f (The 6th batch, 2026-02-20)
• next: 4a7958ca (Sync with 'master', 2026-02-20)

> diff --git a/alias.c b/alias.c
>[snip]

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 2/2] alias: support non-alphanumeric names via subsection syntax
  2026-02-23  9:29     ` Kristoffer Haugsbakk
@ 2026-02-23 16:07       ` Kristoffer Haugsbakk
  2026-02-23 20:22         ` Junio C Hamano
  0 siblings, 1 reply; 88+ messages in thread
From: Kristoffer Haugsbakk @ 2026-02-23 16:07 UTC (permalink / raw)
  To: Jonatan Holmgren, git
  Cc: Jeff King, Junio C Hamano, D . Ben Knoble, brian m. carlson

On Mon, Feb 23, 2026, at 10:29, Kristoffer Haugsbakk wrote:
>>[snip]
>
> The HTML output shows the list continuation character (+).
>
>     + Note that the first word of an alias does not ...
>
>>  command. It can be a command-line option that will be passed into the
>
> And there are two more outside this context which I suspect are knock-ons?
>
>     + If the alias expansion is prefixed ..
>
>     ...
>
>     + * Shell commands will be executed ...
>
> See `Documentation/doc-diff master next`.
>
> • master: 7c02d39f (The 6th batch, 2026-02-20)
> • next: 4a7958ca (Sync with 'master', 2026-02-20)
>
>> diff --git a/alias.c b/alias.c
>>[snip]

Sorry that I just replied to the first hit that I found in my
inbox. This applies to the latest version which is in `next`.

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 2/2] alias: support non-alphanumeric names via subsection syntax
  2026-02-23 16:07       ` Kristoffer Haugsbakk
@ 2026-02-23 20:22         ` Junio C Hamano
  2026-02-23 20:25           ` Kristoffer Haugsbakk
  0 siblings, 1 reply; 88+ messages in thread
From: Junio C Hamano @ 2026-02-23 20:22 UTC (permalink / raw)
  To: Kristoffer Haugsbakk
  Cc: Jonatan Holmgren, git, Jeff King, D . Ben Knoble,
	brian m. carlson

"Kristoffer Haugsbakk" <kristofferhaugsbakk@fastmail.com> writes:

> On Mon, Feb 23, 2026, at 10:29, Kristoffer Haugsbakk wrote:
>>>[snip]
>>
>> The HTML output shows the list continuation character (+).
>>
>>     + Note that the first word of an alias does not ...
>>
>>>  command. It can be a command-line option that will be passed into the
>>
>> And there are two more outside this context which I suspect are knock-ons?
>>
>>     + If the alias expansion is prefixed ..
>>
>>     ...
>>
>>     + * Shell commands will be executed ...
>>
>> See `Documentation/doc-diff master next`.
>>
>> • master: 7c02d39f (The 6th batch, 2026-02-20)
>> • next: 4a7958ca (Sync with 'master', 2026-02-20)
>>
>>> diff --git a/alias.c b/alias.c
>>>[snip]
>
> Sorry that I just replied to the first hit that I found in my
> inbox. This applies to the latest version which is in `next`.

And your fix is the [v7 5/4] <followup-alias-i18n.3e0@msgid.xyz>?

Thanks for being careful.

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 2/2] alias: support non-alphanumeric names via subsection syntax
  2026-02-23 20:22         ` Junio C Hamano
@ 2026-02-23 20:25           ` Kristoffer Haugsbakk
  0 siblings, 0 replies; 88+ messages in thread
From: Kristoffer Haugsbakk @ 2026-02-23 20:25 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Jonatan Holmgren, git, Jeff King, D . Ben Knoble,
	brian m. carlson

On Mon, Feb 23, 2026, at 21:22, Junio C Hamano wrote:
>>[snip]
>> Sorry that I just replied to the first hit that I found in my
>> inbox. This applies to the latest version which is in `next`.
>
> And your fix is the [v7 5/4] <followup-alias-i18n.3e0@msgid.xyz>?
>
> Thanks for being careful.

Yep, I was wondering whether to use In-Reply-To the cover letter but I
didn’t know which approach would be less confusing (freestanding or
reply).

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 2/2] alias: support non-alphanumeric names via subsection syntax
  2026-02-10 18:31   ` [PATCH v2 2/2] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
  2026-02-10 19:47     ` Junio C Hamano
  2026-02-23  9:29     ` Kristoffer Haugsbakk
@ 2026-02-24 10:27     ` Patrick Steinhardt
  2 siblings, 0 replies; 88+ messages in thread
From: Patrick Steinhardt @ 2026-02-24 10:27 UTC (permalink / raw)
  To: Jonatan Holmgren
  Cc: git, peff, gitster, D . Ben Knoble, brian m . carlson,
	Johannes Schindelin

On Tue, Feb 10, 2026 at 07:31:10PM +0100, Jonatan Holmgren wrote:
> Git alias names are limited to alphanumeric characters and dashes
> because config variable names are validated by iskeychar(). This
> prevents non-English speakers from creating aliases in their native
> languages.
> 
> 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-alphanumeric alias names display correctly.

This patch has caused a regression in a somewhat esoteric use case.
Before this patch, you could do the following:

    $ git config set "alias..foobar" "!echo barfoo"
    $ git .foobar
    barfoo

Or, phrased as a test case:

    diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
    index a13d2be8ca..dca50e87e2 100755
    --- a/t/t0014-alias.sh
    +++ b/t/t0014-alias.sh
    @@ -4,6 +4,13 @@ test_description='git command aliasing'

     . ./test-lib.sh

    +test_expect_success 'alias with leading dot' '
    +	test_config_global alias..something "!echo foobar" &&
    +	git .something >actual &&
    +	echo foobar >expect &&
    +	test_cmp expect actual
    +'
    +
     test_expect_success 'nested aliases - internal execution' '
     	git config alias.nested-internal-1 nested-internal-2 &&
     	git config alias.nested-internal-2 status &&

I kind of doubt that this was intentional design, but I know that it is
used e.g. by Dscho in his shears scripts [1]. What this script does is
to create a temporary alias "alias..r" that then gets executed via the
sequencer, and this patch broke this. I happened to discover the
regression as I use shears myself.

Chances are that there are other users out there that rely on the
current behaviour.

Thanks!

Patrick

[1]: https://github.com/git-for-windows/build-extra/blob/a82c8fcb0b8f165c1379c12b0cf914741b8dc8d5/shears.sh

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v7 3/4] alias: support non-alphanumeric names via subsection syntax
  2026-02-18 21:57   ` [PATCH v7 3/4] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
@ 2026-02-24 10:55     ` Kristoffer Haugsbakk
  2026-02-24 14:48       ` Jonatan Holmgren
  0 siblings, 1 reply; 88+ messages in thread
From: Kristoffer Haugsbakk @ 2026-02-24 10:55 UTC (permalink / raw)
  To: Jonatan Holmgren, git
  Cc: Jeff King, Junio C Hamano, D . Ben Knoble, brian m. carlson

On Wed, Feb 18, 2026, at 22:57, Jonatan Holmgren wrote:
> 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
>[snip]
> +# With subsection (allows any characters, including UTF-8)
> +[alias "hämta"]
> +    command = fetch
> +[alias "rätta till"]
> +    command = commit --amend

This is in `next` now so this is a question or note for later.

Is this `rätta till` supposed to have a space in it? Or is there
supposed to be a hyphen? I couldn’t get it to work.

Is the intent to use quotes for the command (in e.g. Bash)?

    ./bin-wrappers/git "rätta till"

> +----
> ++
> +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

This is also a note for later. I think this “To avoid” should now be
moved to a separate paragraph since it’s a different topic.

Sorry that all my feedback is this late! These are just notes for the
list in any case.

> +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.
>[snip]

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v7 3/4] alias: support non-alphanumeric names via subsection syntax
  2026-02-24 10:55     ` Kristoffer Haugsbakk
@ 2026-02-24 14:48       ` Jonatan Holmgren
  2026-02-24 23:23         ` Kristoffer Haugsbakk
  0 siblings, 1 reply; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-24 14:48 UTC (permalink / raw)
  To: Kristoffer Haugsbakk, git
  Cc: Jeff King, Junio C Hamano, D . Ben Knoble, brian m. carlson

> Is this `rätta till` supposed to have a space in it? 
Absolutely, as a demo of space working fine

> Is the intent to use quotes for the command (in e.g. Bash)?
Yup, or "\ " which is what my shell defaults to. So the user would type 
"git rä<TAB>". Does that not work on your machine?

Jonatan

^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH 0/2] Fix small issues in alias subsection handling
  2026-02-08 15:30 [RFC] Support UTF-8 characters in Git alias names Jonatan Holmgren
                   ` (9 preceding siblings ...)
  2026-02-18 21:57 ` [PATCH v7 0/4] support UTF-8 in alias names Jonatan Holmgren
@ 2026-02-24 17:12 ` Jonatan Holmgren
  2026-02-24 17:12   ` [PATCH 1/2] doc: fix list continuation in alias subsection example Jonatan Holmgren
                     ` (2 more replies)
  2026-02-26 20:53 ` [PATCH v2 0/3] " Jonatan Holmgren
  2026-03-03 15:12 ` [PATCH] doc: fix list continuation in alias.adoc Jonatan Holmgren
  12 siblings, 3 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-24 17:12 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

Hello!

I have two small patches related to the handling of alias subsections. 
The first one is a documentation fix for the example showing the equivalence
between alias.last and alias.last.command, which was missing list continuation marks. 
The second patch addresses a compatibility issue where an empty subsection ([alias ""])
was not treated as a plain [alias], 
causing existing entries stored this way to be ignored.

Thanks for considering these patches!

Jonatan Holmgren (2):
  doc: fix list continuation in alias subsection example
  alias: treat empty subsection [alias ""] as plain [alias]

 Documentation/config/alias.adoc |  7 ++++---
 alias.c                         |  4 ++++
 t/t0014-alias.sh                | 14 ++++++++++++++
 3 files changed, 22 insertions(+), 3 deletions(-)

-- 
2.53.0


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH 1/2] doc: fix list continuation in alias subsection example
  2026-02-24 17:12 ` [PATCH 0/2] Fix small issues in alias subsection handling Jonatan Holmgren
@ 2026-02-24 17:12   ` Jonatan Holmgren
  2026-02-24 19:11     ` Junio C Hamano
  2026-02-24 17:12   ` [PATCH 2/2] alias: treat empty subsection [alias ""] as plain [alias] Jonatan Holmgren
  2026-02-26 17:00   ` [PATCH 0/2] Fix small issues in alias subsection handling Junio C Hamano
  2 siblings, 1 reply; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-24 17:12 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

The example showing the equivalence between alias.last and
alias.last.command was missing the list continuation marks (+
between the shell session block and the following prose, leaving
the paragraph detached from the list item in the rendered output.

Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 Documentation/config/alias.adoc | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc
index 115fdbb1e3..7830379f58 100644
--- a/Documentation/config/alias.adoc
+++ b/Documentation/config/alias.adoc
@@ -30,13 +30,14 @@ Examples:
 ----
 +
 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`. 
 
-`git last` is equivalent to `git cat-file commit HEAD`. To avoid
-confusion and troubles with script usage, aliases that
+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.
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* [PATCH 2/2] alias: treat empty subsection [alias ""] as plain [alias]
  2026-02-24 17:12 ` [PATCH 0/2] Fix small issues in alias subsection handling Jonatan Holmgren
  2026-02-24 17:12   ` [PATCH 1/2] doc: fix list continuation in alias subsection example Jonatan Holmgren
@ 2026-02-24 17:12   ` Jonatan Holmgren
  2026-02-26 17:00   ` [PATCH 0/2] Fix small issues in alias subsection handling Junio C Hamano
  2 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-24 17:12 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

When git-config stores a key of the form alias..name, it records
it under an empty subsection ([alias ""]). The new subsection-aware
alias lookup would see a non-NULL but zero-length subsection and
fall into the subsection code path, where it required a "command"
key and thus silently ignored the entry.

Normalize an empty subsection to NULL before any further processing
so that entries stored this way continue to work as plain
case-insensitive aliases, matching the pre-subsection behaviour.

Users who relied on alias..name to create an alias literally named
".name" may want to migrate to subsection syntax, which looks less confusing:

    [alias ".name"]
        command = <value>

Add tests covering both the empty-subsection compatibility case and
the leading-dot alias via the new syntax.

Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 alias.c          |  4 ++++
 t/t0014-alias.sh | 14 ++++++++++++++
 2 files changed, 18 insertions(+)

diff --git a/alias.c b/alias.c
index 0d636278bc..ec9833dd30 100644
--- a/alias.c
+++ b/alias.c
@@ -30,6 +30,10 @@ static int config_alias_cb(const char *var, const char *value,
 	 * - [alias "name"]
 	 *       command = value  (with subsection, case-sensitive)
 	 */
+	/* Treat [alias ""] (empty subsection) the same as plain [alias]. */
+	if (subsection && !subsection_len)
+		subsection = NULL;
+
 	if (subsection && strcmp(key, "command"))
 		return 0;
 
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 34bbdb51c5..68b4903cbf 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -183,4 +183,18 @@ test_expect_success 'subsection aliases listed in help -a' '
 	test_grep "förgrena" output
 '
 
+test_expect_success 'empty subsection treated as no subsection' '
+	test_config "alias..something" "!echo foobar" &&
+	git something >actual &&
+	echo foobar >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'alias with leading dot via subsection syntax' '
+	test_config alias.".something".command "!echo foobar" &&
+	git .something >actual &&
+	echo foobar >expect &&
+	test_cmp expect actual
+'
+
 test_done
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* Re: [PATCH 1/2] doc: fix list continuation in alias subsection example
  2026-02-24 17:12   ` [PATCH 1/2] doc: fix list continuation in alias subsection example Jonatan Holmgren
@ 2026-02-24 19:11     ` Junio C Hamano
  2026-02-24 19:14       ` Kristoffer Haugsbakk
  0 siblings, 1 reply; 88+ messages in thread
From: Junio C Hamano @ 2026-02-24 19:11 UTC (permalink / raw)
  To: Jonatan Holmgren, Kristoffer Haugsbakk
  Cc: git, peff, D . Ben Knoble, brian m . carlson

Jonatan Holmgren <jonatan@jontes.page> writes:

> The example showing the equivalence between alias.last and
> alias.last.command was missing the list continuation marks (+
> between the shell session block and the following prose, leaving
> the paragraph detached from the list item in the rendered output.
>
> Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
> ---
>  Documentation/config/alias.adoc | 7 ++++---
>  1 file changed, 4 insertions(+), 3 deletions(-)

This seems to address the same issue as 0ed2275f (doc: config: fix
list continuation in alias section, 2026-02-23).  Should we treat
this one as a replacement of that one?

> diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc
> index 115fdbb1e3..7830379f58 100644
> --- a/Documentation/config/alias.adoc
> +++ b/Documentation/config/alias.adoc
> @@ -30,13 +30,14 @@ Examples:
>  ----
>  +
>  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`. 
>  
> -`git last` is equivalent to `git cat-file commit HEAD`. To avoid
> -confusion and troubles with script usage, aliases that
> +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.

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH 1/2] doc: fix list continuation in alias subsection example
  2026-02-24 19:11     ` Junio C Hamano
@ 2026-02-24 19:14       ` Kristoffer Haugsbakk
  2026-02-24 20:23         ` Junio C Hamano
  0 siblings, 1 reply; 88+ messages in thread
From: Kristoffer Haugsbakk @ 2026-02-24 19:14 UTC (permalink / raw)
  To: Junio C Hamano, Jonatan Holmgren
  Cc: git, Jeff King, D . Ben Knoble, brian m. carlson

On Tue, Feb 24, 2026, at 20:11, Junio C Hamano wrote:
> Jonatan Holmgren <jonatan@jontes.page> writes:
>
>> The example showing the equivalence between alias.last and
>> alias.last.command was missing the list continuation marks (+
>> between the shell session block and the following prose, leaving
>> the paragraph detached from the list item in the rendered output.
>>
>> Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
>> ---
>>  Documentation/config/alias.adoc | 7 ++++---
>>  1 file changed, 4 insertions(+), 3 deletions(-)
>
> This seems to address the same issue as 0ed2275f (doc: config: fix
> list continuation in alias section, 2026-02-23).  Should we treat
> this one as a replacement of that one?

Yeah I think so.

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH 1/2] doc: fix list continuation in alias subsection example
  2026-02-24 19:14       ` Kristoffer Haugsbakk
@ 2026-02-24 20:23         ` Junio C Hamano
  0 siblings, 0 replies; 88+ messages in thread
From: Junio C Hamano @ 2026-02-24 20:23 UTC (permalink / raw)
  To: Kristoffer Haugsbakk
  Cc: Jonatan Holmgren, git, Jeff King, D . Ben Knoble,
	brian m. carlson

"Kristoffer Haugsbakk" <kristofferhaugsbakk@fastmail.com> writes:

> On Tue, Feb 24, 2026, at 20:11, Junio C Hamano wrote:
>> Jonatan Holmgren <jonatan@jontes.page> writes:
>>
>>> The example showing the equivalence between alias.last and
>>> alias.last.command was missing the list continuation marks (+
>>> between the shell session block and the following prose, leaving
>>> the paragraph detached from the list item in the rendered output.
>>>
>>> Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
>>> ---
>>>  Documentation/config/alias.adoc | 7 ++++---
>>>  1 file changed, 4 insertions(+), 3 deletions(-)
>>
>> This seems to address the same issue as 0ed2275f (doc: config: fix
>> list continuation in alias section, 2026-02-23).  Should we treat
>> this one as a replacement of that one?
>
> Yeah I think so.

Thanks.  Will discard 0ed2275f then.

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v7 1/4] help: use list_aliases() for alias listing
  2026-02-18 21:57   ` [PATCH v7 1/4] help: use list_aliases() for alias listing Jonatan Holmgren
@ 2026-02-24 22:19     ` Jacob Keller
  2026-02-24 22:41       ` Junio C Hamano
  2026-02-24 22:21     ` Jacob Keller
  1 sibling, 1 reply; 88+ messages in thread
From: Jacob Keller @ 2026-02-24 22:19 UTC (permalink / raw)
  To: Jonatan Holmgren, git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson



On 2/18/2026 1:57 PM, Jonatan Holmgren wrote:
> help.c has its own get_alias() config callback that duplicates the
> parsing logic in alias.c. Consolidate by teaching list_aliases() to
> also store the alias values (via the string_list util field), then
> use it in list_all_cmds_help_aliases() instead of the private
> callback.
> 
> This preserves the existing error checking for value-less alias
> definitions by checking in alias.c rather than help.c.
> 
> No functional change intended.
> 
This results in a memory leak with git --list-cmds=alias:

==2244105==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 1453 byte(s) in 37 object(s) allocated from:
     #0 0x7f9e268e0ca0 in strdup (/lib64/libasan.so.8+0xe0ca0) (BuildId: 
25975f766867e9e604dc5a71a8befeaed3301942)
     #1 0x00000115997b in xstrdup ../wrapper.c:43
     #2 0x000000841299 in config_alias_cb ../alias.c:62
     #3 0x00000098df73 in git_config_include ../config.c:429
     #4 0x000000973616 in get_value ../config.c:919
     #5 0x000000973616 in git_parse_source ../config.c:1114
     #6 0x000000973616 in do_config_from ../config.c:1341
     #7 0x000000975f5a in do_config_from_file ../config.c:1370
     #8 0x000000980c9b in git_config_from_file_with_options ../config.c:1393
     #9 0x0000009827fe in do_git_config_sequence ../config.c:1556
     #10 0x0000009827fe in config_with_options ../config.c:1615
     #11 0x00000098313d in read_early_config ../config.c:1670
     #12 0x000000841935 in list_aliases ../alias.c:81
     #13 0x00000080b12f in list_cmds ../git.c:102
     #14 0x00000080b12f in handle_options ../git.c:336
     #15 0x00000080d570 in cmd_main ../git.c:955
     #16 0x00000044a54f in main ../common-main.c:9
     #17 0x7f9e25e105b4 in __libc_start_call_main 
(/lib64/libc.so.6+0x35b4) (BuildId: 
2b5beec0fd24fe9c9f43eddfdd5facf0b8a1b805)
     #18 0x7f9e25e10667 in __libc_start_main@@GLIBC_2.34 
(/lib64/libc.so.6+0x3667) (BuildId: 
2b5beec0fd24fe9c9f43eddfdd5facf0b8a1b805)
     #19 0x00000044c944 in _start 
(/home/jekeller/libexec/git-core/git+0x44c944) (BuildId: 
674cf04ebc1da782eede3c3be79a0c15f372df4c)

SUMMARY: AddressSanitizer: 1453 byte(s) leaked in 37 allocation(s).

This leak occurs because you now copy and store the value of the alias 
in the util element, but the call of list_aliases() in list_cmd() 
doesn't clean these up, since its string_list_clear passes 0 to the 
free_util argument.

The following fixed it for me:

diff --git c/git.c i/git.c
index 744cb6527e06..aeb099ab1162 100644
--- c/git.c
+++ i/git.c
@@ -119,7 +119,7 @@ static int list_cmds(const char *spec)
         }
         for (size_t i = 0; i < list.nr; i++)
                 puts(list.items[i].string);
-       string_list_clear(&list, 0);
+       string_list_clear(&list, 1);
         return 0;
  }



^ permalink raw reply related	[flat|nested] 88+ messages in thread

* Re: [PATCH v7 1/4] help: use list_aliases() for alias listing
  2026-02-18 21:57   ` [PATCH v7 1/4] help: use list_aliases() for alias listing Jonatan Holmgren
  2026-02-24 22:19     ` Jacob Keller
@ 2026-02-24 22:21     ` Jacob Keller
  1 sibling, 0 replies; 88+ messages in thread
From: Jacob Keller @ 2026-02-24 22:21 UTC (permalink / raw)
  To: Jonatan Holmgren, git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson

On 2/18/2026 1:57 PM, Jonatan Holmgren wrote:
> help.c has its own get_alias() config callback that duplicates the
> parsing logic in alias.c. Consolidate by teaching list_aliases() to
> also store the alias values (via the string_list util field), then
> use it in list_all_cmds_help_aliases() instead of the private
> callback.
> 
> This preserves the existing error checking for value-less alias
> definitions by checking in alias.c rather than help.c.
> 
> No functional change intended.
> 
This results in a memory leak with git --list-cmds=alias:

==2244105==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 1453 byte(s) in 37 object(s) allocated from:
     #0 0x7f9e268e0ca0 in strdup (/lib64/libasan.so.8+0xe0ca0) (BuildId: 
25975f766867e9e604dc5a71a8befeaed3301942)
     #1 0x00000115997b in xstrdup ../wrapper.c:43
     #2 0x000000841299 in config_alias_cb ../alias.c:62
     #3 0x00000098df73 in git_config_include ../config.c:429
     #4 0x000000973616 in get_value ../config.c:919
     #5 0x000000973616 in git_parse_source ../config.c:1114
     #6 0x000000973616 in do_config_from ../config.c:1341
     #7 0x000000975f5a in do_config_from_file ../config.c:1370
     #8 0x000000980c9b in git_config_from_file_with_options ../config.c:1393
     #9 0x0000009827fe in do_git_config_sequence ../config.c:1556
     #10 0x0000009827fe in config_with_options ../config.c:1615
     #11 0x00000098313d in read_early_config ../config.c:1670
     #12 0x000000841935 in list_aliases ../alias.c:81
     #13 0x00000080b12f in list_cmds ../git.c:102
     #14 0x00000080b12f in handle_options ../git.c:336
     #15 0x00000080d570 in cmd_main ../git.c:955
     #16 0x00000044a54f in main ../common-main.c:9
     #17 0x7f9e25e105b4 in __libc_start_call_main 
(/lib64/libc.so.6+0x35b4) (BuildId: 
2b5beec0fd24fe9c9f43eddfdd5facf0b8a1b805)
     #18 0x7f9e25e10667 in __libc_start_main@@GLIBC_2.34 
(/lib64/libc.so.6+0x3667) (BuildId: 
2b5beec0fd24fe9c9f43eddfdd5facf0b8a1b805)
     #19 0x00000044c944 in _start 
(/home/jekeller/libexec/git-core/git+0x44c944) (BuildId: 
674cf04ebc1da782eede3c3be79a0c15f372df4c)

SUMMARY: AddressSanitizer: 1453 byte(s) leaked in 37 allocation(s).

This leak occurs because you now copy and store the value of the alias 
in the util element, but the call of list_aliases() in list_cmd() 
doesn't clean these up, since its string_list_clear passes 0 to the 
free_util argument.

The following fixed it for me:

diff --git c/git.c i/git.c
index 744cb6527e06..aeb099ab1162 100644
--- c/git.c
+++ i/git.c
@@ -119,7 +119,7 @@ static int list_cmds(const char *spec)
         }
         for (size_t i = 0; i < list.nr; i++)
                 puts(list.items[i].string);
-       string_list_clear(&list, 0);
+       string_list_clear(&list, 1);
         return 0;
  }



^ permalink raw reply related	[flat|nested] 88+ messages in thread

* Re: [PATCH v7 1/4] help: use list_aliases() for alias listing
  2026-02-24 22:19     ` Jacob Keller
@ 2026-02-24 22:41       ` Junio C Hamano
  2026-02-25 20:45         ` Junio C Hamano
  0 siblings, 1 reply; 88+ messages in thread
From: Junio C Hamano @ 2026-02-24 22:41 UTC (permalink / raw)
  To: Jonatan Holmgren, Jacob Keller
  Cc: git, peff, D . Ben Knoble, brian m . carlson

Jacob Keller <jacob.e.keller@intel.com> writes:

> SUMMARY: AddressSanitizer: 1453 byte(s) leaked in 37 allocation(s).
>
> This leak occurs because you now copy and store the value of the alias 
> in the util element, but the call of list_aliases() in list_cmd() 
> doesn't clean these up, since its string_list_clear passes 0 to the 
> free_util argument.
>
> The following fixed it for me:
>
> diff --git c/git.c i/git.c
> index 744cb6527e06..aeb099ab1162 100644
> --- c/git.c
> +++ i/git.c
> @@ -119,7 +119,7 @@ static int list_cmds(const char *spec)
>          }
>          for (size_t i = 0; i < list.nr; i++)
>                  puts(list.items[i].string);
> -       string_list_clear(&list, 0);
> +       string_list_clear(&list, 1);
>          return 0;
>   }

Thanks. This looks like one of the right things to do.  I checked
all list_*() that are called from the loop in this list_cmds(), and
list_aliases() is the only thing that uses .util member of the
string_list_item instances.

However, we need to be a bit careful with list_cmds_by_config().  It
sorts the list accumulated so far, uses remove_duplicates() on it
without passing free_util=1, so there is also the same kind of leak
there, I suspect, until we adjust the call there.

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v7 3/4] alias: support non-alphanumeric names via subsection syntax
  2026-02-24 14:48       ` Jonatan Holmgren
@ 2026-02-24 23:23         ` Kristoffer Haugsbakk
  0 siblings, 0 replies; 88+ messages in thread
From: Kristoffer Haugsbakk @ 2026-02-24 23:23 UTC (permalink / raw)
  To: Jonatan Holmgren, git
  Cc: Jeff King, Junio C Hamano, D . Ben Knoble, brian m. carlson

On Tue, Feb 24, 2026, at 15:48, Jonatan Holmgren wrote:
>> Is this `rätta till` supposed to have a space in it?
> Absolutely, as a demo of space working fine

Aha, okay. Thanks.

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v7 1/4] help: use list_aliases() for alias listing
  2026-02-24 22:41       ` Junio C Hamano
@ 2026-02-25 20:45         ` Junio C Hamano
  2026-02-26 23:33           ` Jacob Keller
  0 siblings, 1 reply; 88+ messages in thread
From: Junio C Hamano @ 2026-02-25 20:45 UTC (permalink / raw)
  To: Jonatan Holmgren
  Cc: Jacob Keller, git, peff, D . Ben Knoble, brian m . carlson

Junio C Hamano <gitster@pobox.com> writes:

> Jacob Keller <jacob.e.keller@intel.com> writes:
>
>> SUMMARY: AddressSanitizer: 1453 byte(s) leaked in 37 allocation(s).
>>
>> This leak occurs because you now copy and store the value of the alias 
>> in the util element, but the call of list_aliases() in list_cmd() 
>> doesn't clean these up, since its string_list_clear passes 0 to the 
>> free_util argument.
>>
>> The following fixed it for me:
>>
>> diff --git c/git.c i/git.c
>> index 744cb6527e06..aeb099ab1162 100644
>> --- c/git.c
>> +++ i/git.c
>> @@ -119,7 +119,7 @@ static int list_cmds(const char *spec)
>>          }
>>          for (size_t i = 0; i < list.nr; i++)
>>                  puts(list.items[i].string);
>> -       string_list_clear(&list, 0);
>> +       string_list_clear(&list, 1);
>>          return 0;
>>   }
>
> Thanks. This looks like one of the right things to do.  I checked
> all list_*() that are called from the loop in this list_cmds(), and
> list_aliases() is the only thing that uses .util member of the
> string_list_item instances.
>
> However, we need to be a bit careful with list_cmds_by_config().  It
> sorts the list accumulated so far, uses remove_duplicates() on it
> without passing free_util=1, so there is also the same kind of leak
> there, I suspect, until we adjust the call there.

FWIW, here is what I tentatively queued on top of these four
patches.  Hopefully we can have a small and final reroll for these
"Fix small issues in alias" patches and merge them down to 'next'
soonish?

Thanks.


diff --git a/git.c b/git.c
index c5fad56813..b5eb740e83 100644
--- a/git.c
+++ b/git.c
@@ -119,7 +119,7 @@ static int list_cmds(const char *spec)
 	}
 	for (size_t i = 0; i < list.nr; i++)
 		puts(list.items[i].string);
-	string_list_clear(&list, 0);
+	string_list_clear(&list, 1);
 	return 0;
 }
 
diff --git a/help.c b/help.c
index 82fb2eaa3f..725e92a195 100644
--- a/help.c
+++ b/help.c
@@ -423,7 +423,7 @@ void list_cmds_by_config(struct string_list *list)
 		return;
 
 	string_list_sort(list);
-	string_list_remove_duplicates(list, 0);
+	string_list_remove_duplicates(list, 1);
 
 	while (*cmd_list) {
 		struct strbuf sb = STRBUF_INIT;
-- 
2.53.0-514-g5fc6f9e594


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* Re: [PATCH 0/2] Fix small issues in alias subsection handling
  2026-02-24 17:12 ` [PATCH 0/2] Fix small issues in alias subsection handling Jonatan Holmgren
  2026-02-24 17:12   ` [PATCH 1/2] doc: fix list continuation in alias subsection example Jonatan Holmgren
  2026-02-24 17:12   ` [PATCH 2/2] alias: treat empty subsection [alias ""] as plain [alias] Jonatan Holmgren
@ 2026-02-26 17:00   ` Junio C Hamano
  2 siblings, 0 replies; 88+ messages in thread
From: Junio C Hamano @ 2026-02-26 17:00 UTC (permalink / raw)
  To: Jonatan Holmgren; +Cc: git, peff, D . Ben Knoble, brian m . carlson

Jonatan Holmgren <jonatan@jontes.page> writes:

> Hello!
>
> I have two small patches related to the handling of alias subsections. 
> The first one is a documentation fix for the example showing the equivalence
> between alias.last and alias.last.command, which was missing list continuation marks. 
> The second patch addresses a compatibility issue where an empty subsection ([alias ""])
> was not treated as a plain [alias], 
> causing existing entries stored this way to be ignored.
>
> Thanks for considering these patches!
>
> Jonatan Holmgren (2):
>   doc: fix list continuation in alias subsection example
>   alias: treat empty subsection [alias ""] as plain [alias]
>
>  Documentation/config/alias.adoc |  7 ++++---
>  alias.c                         |  4 ++++
>  t/t0014-alias.sh                | 14 ++++++++++++++
>  3 files changed, 22 insertions(+), 3 deletions(-)

Memory leaks have been reported for these patches, e.g.,

  https://lore.kernel.org/git/6953f6f2-22e8-4efb-8169-395e1c52634f@intel.com/

I _think_ the following would be sufficient to plug it, but please
double check and then send in updated version of these two patches
incorporating them.

Thanks.



 git.c  | 2 +-
 help.c | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/git.c b/git.c
index c5fad56813..b5eb740e83 100644
--- a/git.c
+++ b/git.c
@@ -119,7 +119,7 @@ static int list_cmds(const char *spec)
 	}
 	for (size_t i = 0; i < list.nr; i++)
 		puts(list.items[i].string);
-	string_list_clear(&list, 0);
+	string_list_clear(&list, 1);
 	return 0;
 }
 
diff --git a/help.c b/help.c
index 82fb2eaa3f..725e92a195 100644
--- a/help.c
+++ b/help.c
@@ -423,7 +423,7 @@ void list_cmds_by_config(struct string_list *list)
 		return;
 
 	string_list_sort(list);
-	string_list_remove_duplicates(list, 0);
+	string_list_remove_duplicates(list, 1);
 
 	while (*cmd_list) {
 		struct strbuf sb = STRBUF_INIT;
-- 
2.53.0-517-g7bf7f89b4a



^ permalink raw reply related	[flat|nested] 88+ messages in thread

* [PATCH v2 0/3] Fix small issues in alias subsection handling
  2026-02-08 15:30 [RFC] Support UTF-8 characters in Git alias names Jonatan Holmgren
                   ` (10 preceding siblings ...)
  2026-02-24 17:12 ` [PATCH 0/2] Fix small issues in alias subsection handling Jonatan Holmgren
@ 2026-02-26 20:53 ` Jonatan Holmgren
  2026-02-26 20:53   ` [PATCH v2 1/3] doc: fix list continuation in alias subsection example Jonatan Holmgren
                     ` (3 more replies)
  2026-03-03 15:12 ` [PATCH] doc: fix list continuation in alias.adoc Jonatan Holmgren
  12 siblings, 4 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-26 20:53 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

v2: Add patch 3 to fix memory leaks in alias listing reported by
    Jacob Keller.  The leaks were introduced by the jh/alias-i18n
    series (specifically "help: use list_aliases() for alias listing"):
    list_aliases() allocates util pointers but two callers cleared
    the list without freeing them.

    Also fix a stray trailing whitespace in patch 1.

    No changes in patch 2.

Jonatan Holmgren (3):
  doc: fix list continuation in alias subsection example
  alias: treat empty subsection [alias ""] as plain [alias]
  git, help: fix memory leaks in alias listing

 Documentation/config/alias.adoc |  7 ++++---
 alias.c                         |  4 ++++
 git.c                           |  2 +-
 help.c                          |  2 +-
 t/t0014-alias.sh                | 14 ++++++++++++++
 5 files changed, 24 insertions(+), 5 deletions(-)

-- 
2.53.0


^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH v2 1/3] doc: fix list continuation in alias subsection example
  2026-02-26 20:53 ` [PATCH v2 0/3] " Jonatan Holmgren
@ 2026-02-26 20:53   ` Jonatan Holmgren
  2026-03-03  9:41     ` Kristoffer Haugsbakk
  2026-02-26 20:53   ` [PATCH v2 2/3] alias: treat empty subsection [alias ""] as plain [alias] Jonatan Holmgren
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-26 20:53 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

The example showing the equivalence between alias.last and
alias.last.command was missing the list continuation marks (+
between the shell session block and the following prose, leaving
the paragraph detached from the list item in the rendered output.

Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 Documentation/config/alias.adoc | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc
index 115fdbb1e3..26949a0ccb 100644
--- a/Documentation/config/alias.adoc
+++ b/Documentation/config/alias.adoc
@@ -30,13 +30,14 @@ Examples:
 ----
 +
 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`.
 
-`git last` is equivalent to `git cat-file commit HEAD`. To avoid
-confusion and troubles with script usage, aliases that
+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.
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* [PATCH v2 2/3] alias: treat empty subsection [alias ""] as plain [alias]
  2026-02-26 20:53 ` [PATCH v2 0/3] " Jonatan Holmgren
  2026-02-26 20:53   ` [PATCH v2 1/3] doc: fix list continuation in alias subsection example Jonatan Holmgren
@ 2026-02-26 20:53   ` Jonatan Holmgren
  2026-02-26 20:53   ` [PATCH v2 3/3] git, help: fix memory leaks in alias listing Jonatan Holmgren
  2026-02-26 21:08   ` [PATCH v2 0/3] Fix small issues in alias subsection handling Junio C Hamano
  3 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-26 20:53 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

When git-config stores a key of the form alias..name, it records
it under an empty subsection ([alias ""]). The new subsection-aware
alias lookup would see a non-NULL but zero-length subsection and
fall into the subsection code path, where it required a "command"
key and thus silently ignored the entry.

Normalize an empty subsection to NULL before any further processing
so that entries stored this way continue to work as plain
case-insensitive aliases, matching the pre-subsection behaviour.

Users who relied on alias..name to create an alias literally named
".name" may want to migrate to subsection syntax, which looks less confusing:

    [alias ".name"]
        command = <value>

Add tests covering both the empty-subsection compatibility case and
the leading-dot alias via the new syntax.

Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 alias.c          |  4 ++++
 t/t0014-alias.sh | 14 ++++++++++++++
 2 files changed, 18 insertions(+)

diff --git a/alias.c b/alias.c
index 0d636278bc..ec9833dd30 100644
--- a/alias.c
+++ b/alias.c
@@ -30,6 +30,10 @@ static int config_alias_cb(const char *var, const char *value,
 	 * - [alias "name"]
 	 *       command = value  (with subsection, case-sensitive)
 	 */
+	/* Treat [alias ""] (empty subsection) the same as plain [alias]. */
+	if (subsection && !subsection_len)
+		subsection = NULL;
+
 	if (subsection && strcmp(key, "command"))
 		return 0;
 
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 34bbdb51c5..68b4903cbf 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -183,4 +183,18 @@ test_expect_success 'subsection aliases listed in help -a' '
 	test_grep "förgrena" output
 '
 
+test_expect_success 'empty subsection treated as no subsection' '
+	test_config "alias..something" "!echo foobar" &&
+	git something >actual &&
+	echo foobar >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'alias with leading dot via subsection syntax' '
+	test_config alias.".something".command "!echo foobar" &&
+	git .something >actual &&
+	echo foobar >expect &&
+	test_cmp expect actual
+'
+
 test_done
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* [PATCH v2 3/3] git, help: fix memory leaks in alias listing
  2026-02-26 20:53 ` [PATCH v2 0/3] " Jonatan Holmgren
  2026-02-26 20:53   ` [PATCH v2 1/3] doc: fix list continuation in alias subsection example Jonatan Holmgren
  2026-02-26 20:53   ` [PATCH v2 2/3] alias: treat empty subsection [alias ""] as plain [alias] Jonatan Holmgren
@ 2026-02-26 20:53   ` Jonatan Holmgren
  2026-02-26 21:08   ` [PATCH v2 0/3] Fix small issues in alias subsection handling Junio C Hamano
  3 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-02-26 20:53 UTC (permalink / raw)
  To: git
  Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren, Jacob Keller

The list_aliases() function sets the util pointer of each list item to
a heap-allocated copy of the alias command value.  Two callers failed
to free these util pointers:

 - list_cmds() in git.c collects a string list with STRING_LIST_INIT_DUP
   and clears it with string_list_clear(&list, 0), which frees the
   duplicated strings (strdup_strings=1) but not the util pointers.
   Pass free_util=1 to free them.

 - list_cmds_by_config() in help.c calls string_list_sort_u(list, 0) to
   deduplicate the list before processing completion.commands overrides.
   When duplicate entries are removed, the util pointer of each discarded
   item is leaked because free_util=0.  Pass free_util=1 to free them.

Reported-by: Jacob Keller <jacob.e.keller@intel.com>
Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 git.c  | 2 +-
 help.c | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/git.c b/git.c
index 744cb6527e..aeb099ab11 100644
--- a/git.c
+++ b/git.c
@@ -119,7 +119,7 @@ static int list_cmds(const char *spec)
 	}
 	for (size_t i = 0; i < list.nr; i++)
 		puts(list.items[i].string);
-	string_list_clear(&list, 0);
+	string_list_clear(&list, 1);
 	return 0;
 }
 
diff --git a/help.c b/help.c
index 95f576c5c8..3e59d07c37 100644
--- a/help.c
+++ b/help.c
@@ -422,7 +422,7 @@ void list_cmds_by_config(struct string_list *list)
 	if (repo_config_get_string_tmp(the_repository, "completion.commands", &cmd_list))
 		return;
 
-	string_list_sort_u(list, 0);
+	string_list_sort_u(list, 1);
 
 	while (*cmd_list) {
 		struct strbuf sb = STRBUF_INIT;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 0/3] Fix small issues in alias subsection handling
  2026-02-26 20:53 ` [PATCH v2 0/3] " Jonatan Holmgren
                     ` (2 preceding siblings ...)
  2026-02-26 20:53   ` [PATCH v2 3/3] git, help: fix memory leaks in alias listing Jonatan Holmgren
@ 2026-02-26 21:08   ` Junio C Hamano
  3 siblings, 0 replies; 88+ messages in thread
From: Junio C Hamano @ 2026-02-26 21:08 UTC (permalink / raw)
  To: Jonatan Holmgren; +Cc: git, peff, D . Ben Knoble, brian m . carlson

Jonatan Holmgren <jonatan@jontes.page> writes:

> v2: Add patch 3 to fix memory leaks in alias listing reported by
>     Jacob Keller.  The leaks were introduced by the jh/alias-i18n
>     series (specifically "help: use list_aliases() for alias listing"):
>     list_aliases() allocates util pointers but two callers cleared
>     the list without freeing them.
>
>     Also fix a stray trailing whitespace in patch 1.
>
>     No changes in patch 2.

Thanks.  Looking good.

Let's mark the topic for 'next'.

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v7 1/4] help: use list_aliases() for alias listing
  2026-02-25 20:45         ` Junio C Hamano
@ 2026-02-26 23:33           ` Jacob Keller
  0 siblings, 0 replies; 88+ messages in thread
From: Jacob Keller @ 2026-02-26 23:33 UTC (permalink / raw)
  To: Junio C Hamano, Jonatan Holmgren
  Cc: git, peff, D . Ben Knoble, brian m . carlson



On 2/25/2026 12:45 PM, Junio C Hamano wrote:
> Junio C Hamano <gitster@pobox.com> writes:
> 
>> Jacob Keller <jacob.e.keller@intel.com> writes:
>>
>>> SUMMARY: AddressSanitizer: 1453 byte(s) leaked in 37 allocation(s).
>>>
>>> This leak occurs because you now copy and store the value of the alias
>>> in the util element, but the call of list_aliases() in list_cmd()
>>> doesn't clean these up, since its string_list_clear passes 0 to the
>>> free_util argument.
>>>
>>> The following fixed it for me:
>>>
>>> diff --git c/git.c i/git.c
>>> index 744cb6527e06..aeb099ab1162 100644
>>> --- c/git.c
>>> +++ i/git.c
>>> @@ -119,7 +119,7 @@ static int list_cmds(const char *spec)
>>>           }
>>>           for (size_t i = 0; i < list.nr; i++)
>>>                   puts(list.items[i].string);
>>> -       string_list_clear(&list, 0);
>>> +       string_list_clear(&list, 1);
>>>           return 0;
>>>    }
>>
>> Thanks. This looks like one of the right things to do.  I checked
>> all list_*() that are called from the loop in this list_cmds(), and
>> list_aliases() is the only thing that uses .util member of the
>> string_list_item instances.
>>
>> However, we need to be a bit careful with list_cmds_by_config().  It
>> sorts the list accumulated so far, uses remove_duplicates() on it
>> without passing free_util=1, so there is also the same kind of leak
>> there, I suspect, until we adjust the call there.
> 
> FWIW, here is what I tentatively queued on top of these four
> patches.  Hopefully we can have a small and final reroll for these
> "Fix small issues in alias" patches and merge them down to 'next'
> soonish?
> 
> Thanks.
> 
> 
> diff --git a/git.c b/git.c
> index c5fad56813..b5eb740e83 100644
> --- a/git.c
> +++ b/git.c
> @@ -119,7 +119,7 @@ static int list_cmds(const char *spec)
>   	}
>   	for (size_t i = 0; i < list.nr; i++)
>   		puts(list.items[i].string);
> -	string_list_clear(&list, 0);
> +	string_list_clear(&list, 1);
>   	return 0;
>   }
>   
> diff --git a/help.c b/help.c
> index 82fb2eaa3f..725e92a195 100644
> --- a/help.c
> +++ b/help.c
> @@ -423,7 +423,7 @@ void list_cmds_by_config(struct string_list *list)
>   		return;
>   
>   	string_list_sort(list);
> -	string_list_remove_duplicates(list, 0);
> +	string_list_remove_duplicates(list, 1);
>   
>   	while (*cmd_list) {
>   		struct strbuf sb = STRBUF_INIT;

This looks correct to me, and I didn't see anything missing. Thanks!

^ permalink raw reply	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 1/3] doc: fix list continuation in alias subsection example
  2026-02-26 20:53   ` [PATCH v2 1/3] doc: fix list continuation in alias subsection example Jonatan Holmgren
@ 2026-03-03  9:41     ` Kristoffer Haugsbakk
  2026-03-03 15:13       ` [PATCH v2 1/3] doc: fix list continuation in alias subsection example! Jonatan Holmgren
  0 siblings, 1 reply; 88+ messages in thread
From: Kristoffer Haugsbakk @ 2026-03-03  9:41 UTC (permalink / raw)
  To: Jonatan Holmgren, git
  Cc: Jeff King, Junio C Hamano, D . Ben Knoble, brian m. carlson

On Thu, Feb 26, 2026, at 21:53, Jonatan Holmgren wrote:
> The example showing the equivalence between alias.last and
> alias.last.command was missing the list continuation marks (+
> between the shell session block and the following prose, leaving
> the paragraph detached from the list item in the rendered output.
>
> Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
> ---
>  Documentation/config/alias.adoc | 7 ++++---
>  1 file changed, 4 insertions(+), 3 deletions(-)
>
> diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc
> index 115fdbb1e3..26949a0ccb 100644
> --- a/Documentation/config/alias.adoc
> +++ b/Documentation/config/alias.adoc
> @@ -30,13 +30,14 @@ Examples:
>  ----
>  +
>  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`.
>

Missing list continuation.

> -`git last` is equivalent to `git cat-file commit HEAD`. To avoid
> -confusion and troubles with script usage, aliases that
> +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.
> --
> 2.53.0

^ permalink raw reply	[flat|nested] 88+ messages in thread

* [PATCH] doc: fix list continuation in alias.adoc
  2026-02-08 15:30 [RFC] Support UTF-8 characters in Git alias names Jonatan Holmgren
                   ` (11 preceding siblings ...)
  2026-02-26 20:53 ` [PATCH v2 0/3] " Jonatan Holmgren
@ 2026-03-03 15:12 ` Jonatan Holmgren
  12 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-03-03 15:12 UTC (permalink / raw)
  To: git; +Cc: peff, gitster, D . Ben Knoble, brian m . carlson,
	Jonatan Holmgren

Add missing list continuation marks ('+') after code blocks and shell examples
so paragraphs render correctly as part of the preceding list item.

Signed-off-by: Jonatan Holmgren <jonatan@jontes.page>
---
 Documentation/config/alias.adoc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc
index 26949a0ccb..dc6ca0ee08 100644
--- a/Documentation/config/alias.adoc
+++ b/Documentation/config/alias.adoc
@@ -36,7 +36,7 @@ With a Git alias defined, e.g.,
     $ 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
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 88+ messages in thread

* Re: [PATCH v2 1/3] doc: fix list continuation in alias subsection example!
  2026-03-03  9:41     ` Kristoffer Haugsbakk
@ 2026-03-03 15:13       ` Jonatan Holmgren
  0 siblings, 0 replies; 88+ messages in thread
From: Jonatan Holmgren @ 2026-03-03 15:13 UTC (permalink / raw)
  To: Kristoffer Haugsbakk, git
  Cc: Jeff King, Junio C Hamano, D . Ben Knoble, brian m. carlson

> Missing list continuation.

Can't believe I missed that! Good catch.

Thanks!

^ permalink raw reply	[flat|nested] 88+ messages in thread

end of thread, other threads:[~2026-03-03 15:20 UTC | newest]

Thread overview: 88+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-08 15:30 [RFC] Support UTF-8 characters in Git alias names Jonatan Holmgren
2026-02-08 16:07 ` D. Ben Knoble
2026-02-08 23:21 ` brian m. carlson
2026-02-09 14:55   ` Junio C Hamano
2026-02-09 15:19     ` Jonatan Holmgren
2026-02-09 17:59       ` Junio C Hamano
2026-02-09 22:40     ` brian m. carlson
2026-02-09 23:14       ` Junio C Hamano
2026-02-10  0:45         ` Ben Knoble
2026-02-10  1:04           ` Junio C Hamano
2026-02-10  6:59             ` Jeff King
2026-02-09  7:36 ` Jeff King
2026-02-09 13:59   ` Theodore Tso
2026-02-09 22:01 ` [PATCH v1] alias: support UTF-8 characters via subsection syntax Jonatan Holmgren
2026-02-10  7:44   ` Jeff King
2026-02-10  8:30   ` Torsten Bögershausen
2026-02-10 16:35   ` Junio C Hamano
2026-02-10 18:31 ` [PATCH v2 0/2] support UTF-8 in alias names Jonatan Holmgren
2026-02-10 18:31   ` [PATCH v2 1/2] help: use list_aliases() for alias listing and lookup Jonatan Holmgren
2026-02-10 19:27     ` Junio C Hamano
2026-02-10 18:31   ` [PATCH v2 2/2] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
2026-02-10 19:47     ` Junio C Hamano
2026-02-10 22:29       ` Jonatan Holmgren
2026-02-23  9:29     ` Kristoffer Haugsbakk
2026-02-23 16:07       ` Kristoffer Haugsbakk
2026-02-23 20:22         ` Junio C Hamano
2026-02-23 20:25           ` Kristoffer Haugsbakk
2026-02-24 10:27     ` Patrick Steinhardt
2026-02-10 22:27 ` [PATCH 0/3] support UTF-8 in alias names Jonatan Holmgren
2026-02-10 22:27   ` [PATCH 1/3] help: use list_aliases() for alias listing Jonatan Holmgren
2026-02-10 23:17     ` Junio C Hamano
2026-02-10 22:27   ` [PATCH 2/3] alias: prepare for subsection aliases Jonatan Holmgren
2026-02-10 22:27   ` [PATCH 3/3] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
2026-02-11 21:18 ` [PATCH v4 0/3] support UTF-8 in alias names Jonatan Holmgren
2026-02-11 21:18   ` [PATCH v4 1/3] help: use list_aliases() for alias listing Jonatan Holmgren
2026-02-11 22:29     ` Junio C Hamano
2026-02-11 21:18   ` [PATCH v4 2/3] alias: prepare for subsection aliases Jonatan Holmgren
2026-02-11 21:53     ` Junio C Hamano
2026-02-11 21:18   ` [PATCH v4 3/3] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
2026-02-11 22:28     ` Junio C Hamano
2026-02-12 11:16     ` Richard Kerry
2026-02-12 15:34       ` Jonatan Holmgren
2026-02-12 18:52     ` Jonatan Holmgren
2026-02-12 10:27   ` [PATCH v4 0/3] support UTF-8 in alias names Torsten Bögershausen
2026-02-12 15:35     ` Jonatan Holmgren
2026-02-16 16:15 ` [PATCH v5 0/4] support uTF-8 " Jonatan Holmgren
2026-02-16 16:15   ` [PATCH v5 1/4] help: use list_aliases() for alias listing Jonatan Holmgren
2026-02-16 16:15   ` [PATCH v5 2/4] alias: prepare for subsection aliases Jonatan Holmgren
2026-02-16 16:15   ` [PATCH v5 3/4] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
2026-02-16 16:15   ` [PATCH v5 4/4] completion: fix zsh alias listing for subsection aliases Jonatan Holmgren
2026-02-16 18:32     ` D. Ben Knoble
2026-02-17 20:01     ` Junio C Hamano
2026-02-18 14:52 ` [PATCH v6 0/4] support UTF-8 in alias names Jonatan Holmgren
2026-02-18 14:52   ` [PATCH v6 1/4] help: use list_aliases() for alias listing Jonatan Holmgren
2026-02-18 14:52   ` [PATCH v6 2/4] alias: prepare for subsection aliases Jonatan Holmgren
2026-02-18 16:21     ` Kristoffer Haugsbakk
2026-02-18 14:52   ` [PATCH v6 3/4] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
2026-02-18 14:52   ` [PATCH v6 4/4] completion: fix zsh alias listing for subsection aliases Jonatan Holmgren
2026-02-18 21:57 ` [PATCH v7 0/4] support UTF-8 in alias names Jonatan Holmgren
2026-02-18 21:57   ` [PATCH v7 1/4] help: use list_aliases() for alias listing Jonatan Holmgren
2026-02-24 22:19     ` Jacob Keller
2026-02-24 22:41       ` Junio C Hamano
2026-02-25 20:45         ` Junio C Hamano
2026-02-26 23:33           ` Jacob Keller
2026-02-24 22:21     ` Jacob Keller
2026-02-18 21:57   ` [PATCH v7 2/4] alias: prepare for subsection aliases Jonatan Holmgren
2026-02-18 21:57   ` [PATCH v7 3/4] alias: support non-alphanumeric names via subsection syntax Jonatan Holmgren
2026-02-24 10:55     ` Kristoffer Haugsbakk
2026-02-24 14:48       ` Jonatan Holmgren
2026-02-24 23:23         ` Kristoffer Haugsbakk
2026-02-18 21:57   ` [PATCH v7 4/4] completion: fix zsh alias listing for subsection aliases Jonatan Holmgren
2026-02-19 18:17   ` [PATCH v7 0/4] support UTF-8 in alias names Junio C Hamano
2026-02-19 18:54     ` Jonatan Holmgren
2026-02-24 17:12 ` [PATCH 0/2] Fix small issues in alias subsection handling Jonatan Holmgren
2026-02-24 17:12   ` [PATCH 1/2] doc: fix list continuation in alias subsection example Jonatan Holmgren
2026-02-24 19:11     ` Junio C Hamano
2026-02-24 19:14       ` Kristoffer Haugsbakk
2026-02-24 20:23         ` Junio C Hamano
2026-02-24 17:12   ` [PATCH 2/2] alias: treat empty subsection [alias ""] as plain [alias] Jonatan Holmgren
2026-02-26 17:00   ` [PATCH 0/2] Fix small issues in alias subsection handling Junio C Hamano
2026-02-26 20:53 ` [PATCH v2 0/3] " Jonatan Holmgren
2026-02-26 20:53   ` [PATCH v2 1/3] doc: fix list continuation in alias subsection example Jonatan Holmgren
2026-03-03  9:41     ` Kristoffer Haugsbakk
2026-03-03 15:13       ` [PATCH v2 1/3] doc: fix list continuation in alias subsection example! Jonatan Holmgren
2026-02-26 20:53   ` [PATCH v2 2/3] alias: treat empty subsection [alias ""] as plain [alias] Jonatan Holmgren
2026-02-26 20:53   ` [PATCH v2 3/3] git, help: fix memory leaks in alias listing Jonatan Holmgren
2026-02-26 21:08   ` [PATCH v2 0/3] Fix small issues in alias subsection handling Junio C Hamano
2026-03-03 15:12 ` [PATCH] doc: fix list continuation in alias.adoc Jonatan Holmgren

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox