aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/config/alias.adoc50
-rw-r--r--alias.c42
-rw-r--r--builtin/help.c13
-rw-r--r--contrib/completion/git-completion.zsh2
-rw-r--r--help.c39
-rwxr-xr-xt/t0014-alias.sh71
6 files changed, 182 insertions, 35 deletions
diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc
index 80ce17d2de..115fdbb1e3 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 1a1a141a0a..0d636278bc 100644
--- a/alias.c
+++ b/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;
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
}
diff --git a/help.c b/help.c
index 3c36d9c218..82fb2eaa3f 100644
--- a/help.c
+++ b/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);
@@ -469,20 +471,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;
@@ -502,11 +490,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;
}
@@ -587,7 +575,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);
@@ -602,8 +591,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;
}
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 07a53e7366..34bbdb51c5 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -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