mirror of
https://github.com/git/git.git
synced 2026-02-27 18:29:43 +00:00
Merge branch 'jh/alias-i18n' into jch
Extend the alias configuration syntax to allow aliases using characters outside ASCII alphanumeric (plus '-'). * jh/alias-i18n: completion: fix zsh alias listing for subsection aliases alias: support non-alphanumeric names via subsection syntax alias: prepare for subsection aliases help: use list_aliases() for alias listing
This commit is contained in:
@@ -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
|
||||
|
||||
42
alias.c
42
alias.c
@@ -13,23 +13,53 @@ 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) {
|
||||
string_list_append(data->list, p);
|
||||
struct string_list_item *item;
|
||||
|
||||
if (!value)
|
||||
return config_error_nonbool(var);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
39
help.c
39
help.c
@@ -20,6 +20,8 @@
|
||||
#include "prompt.h"
|
||||
#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 */
|
||||
@@ -107,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);
|
||||
@@ -468,20 +470,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,11 +489,11 @@ 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++) {
|
||||
size_t len = strlen(alias_list.items[i].string);
|
||||
size_t len = utf8_strwidth(alias_list.items[i].string);
|
||||
if (longest < len)
|
||||
longest = len;
|
||||
}
|
||||
@@ -586,7 +574,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 +590,18 @@ 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) {
|
||||
/* [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;
|
||||
}
|
||||
|
||||
@@ -112,4 +112,75 @@ 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_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
|
||||
|
||||
Reference in New Issue
Block a user