mirror of
https://github.com/git/git.git
synced 2026-02-28 18:48:50 +00:00
alias: support non-alphanumeric names via subsection syntax
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>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
committed by
Junio C Hamano
parent
2ad33ea6b5
commit
ac1f12a9de
@@ -1,12 +1,46 @@
|
|||||||
alias.*::
|
alias.*::
|
||||||
Command aliases for the linkgit:git[1] command wrapper - e.g.
|
alias.*.command::
|
||||||
after defining `alias.last = cat-file commit HEAD`, the invocation
|
Command aliases for the linkgit:git[1] command wrapper. Aliases
|
||||||
`git last` is equivalent to `git cat-file commit HEAD`. To avoid
|
can be defined using two syntaxes:
|
||||||
confusion and troubles with script usage, aliases that
|
+
|
||||||
hide existing Git commands are ignored except for deprecated
|
--
|
||||||
commands. Arguments are split by
|
1. Without a subsection, e.g., `[alias] co = checkout`. The alias
|
||||||
spaces, the usual shell quoting and escaping are supported.
|
name ("co" in this example) is
|
||||||
A quote pair or a backslash can be used to quote them.
|
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
|
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
|
command. It can be a command-line option that will be passed into the
|
||||||
|
|||||||
38
alias.c
38
alias.c
@@ -13,28 +13,52 @@ struct config_alias_data {
|
|||||||
struct string_list *list;
|
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)
|
const struct config_context *ctx UNUSED, void *d)
|
||||||
{
|
{
|
||||||
struct config_alias_data *data = 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;
|
return 0;
|
||||||
|
|
||||||
if (data->alias) {
|
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);
|
FREE_AND_NULL(data->v);
|
||||||
return git_config_string(&data->v,
|
return git_config_string(&data->v,
|
||||||
key, value);
|
var, value);
|
||||||
}
|
}
|
||||||
} else if (data->list) {
|
} else if (data->list) {
|
||||||
struct string_list_item *item;
|
struct string_list_item *item;
|
||||||
|
|
||||||
if (!value)
|
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);
|
item->util = xstrdup(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
help.c
14
help.c
@@ -21,6 +21,7 @@
|
|||||||
#include "fsmonitor-ipc.h"
|
#include "fsmonitor-ipc.h"
|
||||||
#include "repository.h"
|
#include "repository.h"
|
||||||
#include "alias.h"
|
#include "alias.h"
|
||||||
|
#include "utf8.h"
|
||||||
|
|
||||||
#ifndef NO_CURL
|
#ifndef NO_CURL
|
||||||
#include "git-curl-compat.h" /* For LIBCURL_VERSION only */
|
#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++) {
|
for (i = 0; cmds[i].name; i++) {
|
||||||
if (cmds[i].category & mask) {
|
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);
|
printf(" %s ", cmds[i].name);
|
||||||
if (longest > len)
|
if (longest > len)
|
||||||
mput_char(' ', longest - len);
|
mput_char(' ', longest - len);
|
||||||
@@ -493,7 +494,7 @@ static void list_all_cmds_help_aliases(int longest)
|
|||||||
string_list_sort(&alias_list);
|
string_list_sort(&alias_list);
|
||||||
|
|
||||||
for (i = 0; i < alias_list.nr; i++) {
|
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)
|
if (longest < len)
|
||||||
longest = len;
|
longest = len;
|
||||||
}
|
}
|
||||||
@@ -592,8 +593,15 @@ static int git_unknown_cmd_config(const char *var, const char *value,
|
|||||||
/* Also use aliases for command lookup */
|
/* Also use aliases for command lookup */
|
||||||
if (!parse_config_key(var, "alias", &subsection, &subsection_len,
|
if (!parse_config_key(var, "alias", &subsection, &subsection_len,
|
||||||
&key)) {
|
&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));
|
add_cmdname(&cfg->aliases, key, strlen(key));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -122,4 +122,65 @@ test_expect_success 'alias without value reports error' '
|
|||||||
test_grep "alias.noval" 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
|
test_done
|
||||||
|
|||||||
Reference in New Issue
Block a user