Skip to content

Commit 874cf0d

Browse files
committed
Merge branch 'jh/alias-i18n' into jh/alias-i18n-fixes
* 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
2 parents 67ad421 + edd8ad1 commit 874cf0d

6 files changed

Lines changed: 182 additions & 35 deletions

File tree

Documentation/config/alias.adoc

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,46 @@
11
alias.*::
2-
Command aliases for the linkgit:git[1] command wrapper - e.g.
3-
after defining `alias.last = cat-file commit HEAD`, the invocation
4-
`git last` is equivalent to `git cat-file commit HEAD`. To avoid
5-
confusion and troubles with script usage, aliases that
6-
hide existing Git commands are ignored except for deprecated
7-
commands. Arguments are split by
8-
spaces, the usual shell quoting and escaping are supported.
9-
A quote pair or a backslash can be used to quote them.
2+
alias.*.command::
3+
Command aliases for the linkgit:git[1] command wrapper. Aliases
4+
can be defined using two syntaxes:
5+
+
6+
--
7+
1. Without a subsection, e.g., `[alias] co = checkout`. The alias
8+
name ("co" in this example) is
9+
limited to ASCII alphanumeric characters and `-`,
10+
and is matched case-insensitively.
11+
2. With a subsection, e.g., `[alias "co"] command = checkout`. The
12+
alias name can contain any characters (except for newlines and NUL bytes),
13+
including UTF-8, and is matched case-sensitively as raw bytes.
14+
You define the action of the alias in the `command`.
15+
--
16+
+
17+
Examples:
18+
+
19+
----
20+
# Without subsection (ASCII alphanumeric and dash only)
21+
[alias]
22+
co = checkout
23+
st = status
24+
25+
# With subsection (allows any characters, including UTF-8)
26+
[alias "hämta"]
27+
command = fetch
28+
[alias "rätta till"]
29+
command = commit --amend
30+
----
31+
+
32+
With a Git alias defined, e.g.,
33+
34+
$ git config --global alias.last "cat-file commit HEAD"
35+
# Which is equivalent to
36+
$ git config --global alias.last.command "cat-file commit HEAD"
37+
38+
`git last` is equivalent to `git cat-file commit HEAD`. To avoid
39+
confusion and troubles with script usage, aliases that
40+
hide existing Git commands are ignored except for deprecated
41+
commands. Arguments are split by
42+
spaces, the usual shell quoting and escaping are supported.
43+
A quote pair or a backslash can be used to quote them.
1044
+
1145
Note that the first word of an alias does not necessarily have to be a
1246
command. It can be a command-line option that will be passed into the

alias.c

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,53 @@ struct config_alias_data {
1313
struct string_list *list;
1414
};
1515

16-
static int config_alias_cb(const char *key, const char *value,
16+
static int config_alias_cb(const char *var, const char *value,
1717
const struct config_context *ctx UNUSED, void *d)
1818
{
1919
struct config_alias_data *data = d;
20-
const char *p;
20+
const char *subsection, *key;
21+
size_t subsection_len;
2122

22-
if (!skip_prefix(key, "alias.", &p))
23+
if (parse_config_key(var, "alias", &subsection, &subsection_len,
24+
&key) < 0)
25+
return 0;
26+
27+
/*
28+
* Two config syntaxes:
29+
* - alias.name = value (without subsection, case-insensitive)
30+
* - [alias "name"]
31+
* command = value (with subsection, case-sensitive)
32+
*/
33+
if (subsection && strcmp(key, "command"))
2334
return 0;
2435

2536
if (data->alias) {
26-
if (!strcasecmp(p, data->alias)) {
37+
int match;
38+
39+
if (subsection)
40+
match = (strlen(data->alias) == subsection_len &&
41+
!strncmp(data->alias, subsection,
42+
subsection_len));
43+
else
44+
match = !strcasecmp(data->alias, key);
45+
46+
if (match) {
2747
FREE_AND_NULL(data->v);
2848
return git_config_string(&data->v,
29-
key, value);
49+
var, value);
3050
}
3151
} else if (data->list) {
32-
string_list_append(data->list, p);
52+
struct string_list_item *item;
53+
54+
if (!value)
55+
return config_error_nonbool(var);
56+
57+
if (subsection)
58+
item = string_list_append_nodup(data->list,
59+
xmemdupz(subsection, subsection_len));
60+
else
61+
item = string_list_append(data->list, key);
62+
item->util = xstrdup(value);
3363
}
3464

3565
return 0;

builtin/help.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ static enum help_action {
5454
HELP_ACTION_DEVELOPER_INTERFACES,
5555
HELP_ACTION_CONFIG_FOR_COMPLETION,
5656
HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION,
57+
HELP_ACTION_ALIASES_FOR_COMPLETION,
5758
} cmd_mode;
5859

5960
static char *html_path;
@@ -90,6 +91,8 @@ static struct option builtin_help_options[] = {
9091
HELP_ACTION_CONFIG_FOR_COMPLETION, PARSE_OPT_HIDDEN),
9192
OPT_CMDMODE_F(0, "config-sections-for-completion", &cmd_mode, "",
9293
HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, PARSE_OPT_HIDDEN),
94+
OPT_CMDMODE_F(0, "aliases-for-completion", &cmd_mode, "",
95+
HELP_ACTION_ALIASES_FOR_COMPLETION, PARSE_OPT_HIDDEN),
9396

9497
OPT_END(),
9598
};
@@ -691,6 +694,16 @@ int cmd_help(int argc,
691694
help_format);
692695
list_config_help(SHOW_CONFIG_SECTIONS);
693696
return 0;
697+
case HELP_ACTION_ALIASES_FOR_COMPLETION: {
698+
struct string_list alias_list = STRING_LIST_INIT_DUP;
699+
opt_mode_usage(argc, "--aliases-for-completion", help_format);
700+
list_aliases(&alias_list);
701+
for (size_t i = 0; i < alias_list.nr; i++)
702+
printf("%s%c%s%c", alias_list.items[i].string, '\n',
703+
(char *)alias_list.items[i].util, '\0');
704+
string_list_clear(&alias_list, 1);
705+
return 0;
706+
}
694707
case HELP_ACTION_CONFIG:
695708
opt_mode_usage(argc, "--config", help_format);
696709
setup_pager(the_repository);

contrib/completion/git-completion.zsh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ __git_zsh_cmd_common ()
202202
__git_zsh_cmd_alias ()
203203
{
204204
local -a list
205-
list=(${${(0)"$(git config -z --get-regexp '^alias\.*')"}#alias.})
205+
list=(${(0)"$(git help --aliases-for-completion)"})
206206
list=(${(f)"$(printf "%s:alias for '%s'\n" ${(f@)list})"})
207207
_describe -t alias-commands 'aliases' list && _ret=0
208208
}

help.c

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
#include "prompt.h"
2121
#include "fsmonitor-ipc.h"
2222
#include "repository.h"
23+
#include "alias.h"
24+
#include "utf8.h"
2325

2426
#ifndef NO_CURL
2527
#include "git-curl-compat.h" /* For LIBCURL_VERSION only */
@@ -107,7 +109,7 @@ static void print_command_list(const struct cmdname_help *cmds,
107109

108110
for (i = 0; cmds[i].name; i++) {
109111
if (cmds[i].category & mask) {
110-
size_t len = strlen(cmds[i].name);
112+
size_t len = utf8_strwidth(cmds[i].name);
111113
printf(" %s ", cmds[i].name);
112114
if (longest > len)
113115
mput_char(' ', longest - len);
@@ -469,20 +471,6 @@ void list_developer_interfaces_help(void)
469471
putchar('\n');
470472
}
471473

472-
static int get_alias(const char *var, const char *value,
473-
const struct config_context *ctx UNUSED, void *data)
474-
{
475-
struct string_list *list = data;
476-
477-
if (skip_prefix(var, "alias.", &var)) {
478-
if (!value)
479-
return config_error_nonbool(var);
480-
string_list_append(list, var)->util = xstrdup(value);
481-
}
482-
483-
return 0;
484-
}
485-
486474
static void list_all_cmds_help_external_commands(void)
487475
{
488476
struct string_list others = STRING_LIST_INIT_DUP;
@@ -502,11 +490,11 @@ static void list_all_cmds_help_aliases(int longest)
502490
struct cmdname_help *aliases;
503491
int i;
504492

505-
repo_config(the_repository, get_alias, &alias_list);
493+
list_aliases(&alias_list);
506494
string_list_sort(&alias_list);
507495

508496
for (i = 0; i < alias_list.nr; i++) {
509-
size_t len = strlen(alias_list.items[i].string);
497+
size_t len = utf8_strwidth(alias_list.items[i].string);
510498
if (longest < len)
511499
longest = len;
512500
}
@@ -587,7 +575,8 @@ static int git_unknown_cmd_config(const char *var, const char *value,
587575
void *cb)
588576
{
589577
struct help_unknown_cmd_config *cfg = cb;
590-
const char *p;
578+
const char *subsection, *key;
579+
size_t subsection_len;
591580

592581
if (!strcmp(var, "help.autocorrect")) {
593582
int v = parse_autocorrect(value);
@@ -602,8 +591,18 @@ static int git_unknown_cmd_config(const char *var, const char *value,
602591
}
603592

604593
/* Also use aliases for command lookup */
605-
if (skip_prefix(var, "alias.", &p))
606-
add_cmdname(&cfg->aliases, p, strlen(p));
594+
if (!parse_config_key(var, "alias", &subsection, &subsection_len,
595+
&key)) {
596+
if (subsection) {
597+
/* [alias "name"] command = value */
598+
if (!strcmp(key, "command"))
599+
add_cmdname(&cfg->aliases, subsection,
600+
subsection_len);
601+
} else {
602+
/* alias.name = value */
603+
add_cmdname(&cfg->aliases, key, strlen(key));
604+
}
605+
}
607606

608607
return 0;
609608
}

t/t0014-alias.sh

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,75 @@ test_expect_success 'cannot alias-shadow a sample of regular builtins' '
112112
done
113113
'
114114

115+
test_expect_success 'alias without value reports error' '
116+
test_when_finished "git config --unset alias.noval" &&
117+
cat >>.git/config <<-\EOF &&
118+
[alias]
119+
noval
120+
EOF
121+
test_must_fail git noval 2>error &&
122+
test_grep "alias.noval" error
123+
'
124+
125+
test_expect_success 'subsection syntax works' '
126+
test_config alias.testnew.command "!echo ran-subsection" &&
127+
git testnew >output &&
128+
test_grep "ran-subsection" output
129+
'
130+
131+
test_expect_success 'subsection syntax only accepts command key' '
132+
test_config alias.invalid.notcommand value &&
133+
test_must_fail git invalid 2>error &&
134+
test_grep -i "not a git command" error
135+
'
136+
137+
test_expect_success 'subsection syntax requires value for command' '
138+
test_when_finished "git config --remove-section alias.noval" &&
139+
cat >>.git/config <<-\EOF &&
140+
[alias "noval"]
141+
command
142+
EOF
143+
test_must_fail git noval 2>error &&
144+
test_grep "alias.noval.command" error
145+
'
146+
147+
test_expect_success 'simple syntax is case-insensitive' '
148+
test_config alias.LegacyCase "!echo ran-legacy" &&
149+
git legacycase >output &&
150+
test_grep "ran-legacy" output
151+
'
152+
153+
test_expect_success 'subsection syntax is case-sensitive' '
154+
test_config alias.SubCase.command "!echo ran-upper" &&
155+
test_config alias.subcase.command "!echo ran-lower" &&
156+
git SubCase >upper.out &&
157+
git subcase >lower.out &&
158+
test_grep "ran-upper" upper.out &&
159+
test_grep "ran-lower" lower.out
160+
'
161+
162+
test_expect_success 'UTF-8 alias with Swedish characters' '
163+
test_config alias."förgrena".command "!echo ran-swedish" &&
164+
git förgrena >output &&
165+
test_grep "ran-swedish" output
166+
'
167+
168+
test_expect_success 'UTF-8 alias with CJK characters' '
169+
test_config alias."分支".command "!echo ran-cjk" &&
170+
git 分支 >output &&
171+
test_grep "ran-cjk" output
172+
'
173+
174+
test_expect_success 'alias with spaces in name' '
175+
test_config alias."test name".command "!echo ran-spaces" &&
176+
git "test name" >output &&
177+
test_grep "ran-spaces" output
178+
'
179+
180+
test_expect_success 'subsection aliases listed in help -a' '
181+
test_config alias."förgrena".command "!echo test" &&
182+
git help -a >output &&
183+
test_grep "förgrena" output
184+
'
185+
115186
test_done

0 commit comments

Comments
 (0)