diff options
-rw-r--r-- | Documentation/git-config.txt | 2 | ||||
-rw-r--r-- | builtin-config.c | 427 | ||||
-rw-r--r-- | config.c | 17 | ||||
-rwxr-xr-x | t/t1300-repo-config.sh | 9 |
4 files changed, 255 insertions, 200 deletions
diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index 82ce89eae8..7131ee3c66 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git config' [<file-option>] [type] [-z|--null] name [value [value_regex]] 'git config' [<file-option>] [type] --add name value -'git config' [<file-option>] [type] --replace-all name [value [value_regex]] +'git config' [<file-option>] [type] --replace-all name value [value_regex] 'git config' [<file-option>] [type] [-z|--null] --get name [value_regex] 'git config' [<file-option>] [type] [-z|--null] --get-all name [value_regex] 'git config' [<file-option>] [type] [-z|--null] --get-regexp name_regex [value_regex] diff --git a/builtin-config.c b/builtin-config.c index d52a057444..d8da72cf20 100644 --- a/builtin-config.c +++ b/builtin-config.c @@ -1,9 +1,12 @@ #include "builtin.h" #include "cache.h" #include "color.h" +#include "parse-options.h" -static const char git_config_set_usage[] = -"git config [ --global | --system | [ -f | --file ] config-file ] [ --bool | --int | --bool-or-int ] [ -z | --null ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list | --get-color var [default] | --get-colorbool name [stdout-is-tty] | --edit | -e ]"; +static const char *const builtin_config_usage[] = { + "git config [options]", + NULL +}; static char *key; static regex_t *key_regexp; @@ -16,7 +19,67 @@ static int seen; static char delim = '='; static char key_delim = ' '; static char term = '\n'; -static enum { T_RAW, T_INT, T_BOOL, T_BOOL_OR_INT } type = T_RAW; + +static int use_global_config, use_system_config; +static const char *given_config_file; +static int actions, types; +static const char *get_color_slot, *get_colorbool_slot; +static int end_null; + +#define ACTION_GET (1<<0) +#define ACTION_GET_ALL (1<<1) +#define ACTION_GET_REGEXP (1<<2) +#define ACTION_REPLACE_ALL (1<<3) +#define ACTION_ADD (1<<4) +#define ACTION_UNSET (1<<5) +#define ACTION_UNSET_ALL (1<<6) +#define ACTION_RENAME_SECTION (1<<7) +#define ACTION_REMOVE_SECTION (1<<8) +#define ACTION_LIST (1<<9) +#define ACTION_EDIT (1<<10) +#define ACTION_SET (1<<11) +#define ACTION_SET_ALL (1<<12) +#define ACTION_GET_COLOR (1<<13) +#define ACTION_GET_COLORBOOL (1<<14) + +#define TYPE_BOOL (1<<0) +#define TYPE_INT (1<<1) +#define TYPE_BOOL_OR_INT (1<<2) + +static struct option builtin_config_options[] = { + OPT_GROUP("Config file location"), + OPT_BOOLEAN(0, "global", &use_global_config, "use global config file"), + OPT_BOOLEAN(0, "system", &use_system_config, "use system config file"), + OPT_STRING('f', "file", &given_config_file, "FILE", "use given config file"), + OPT_GROUP("Action"), + OPT_BIT(0, "get", &actions, "get value: name [value-regex]", ACTION_GET), + OPT_BIT(0, "get-all", &actions, "get all values: key [value-regex]", ACTION_GET_ALL), + OPT_BIT(0, "get-regexp", &actions, "get values for regexp: name-regex [value-regex]", ACTION_GET_REGEXP), + OPT_BIT(0, "replace-all", &actions, "replace all matching variables: name value [value_regex]", ACTION_REPLACE_ALL), + OPT_BIT(0, "add", &actions, "adds a new variable: name value", ACTION_ADD), + OPT_BIT(0, "unset", &actions, "removes a variable: name [value-regex]", ACTION_UNSET), + OPT_BIT(0, "unset-all", &actions, "removes all matches: name [value-regex]", ACTION_UNSET_ALL), + OPT_BIT(0, "rename-section", &actions, "rename section: old-name new-name", ACTION_RENAME_SECTION), + OPT_BIT(0, "remove-section", &actions, "remove a section: name", ACTION_REMOVE_SECTION), + OPT_BIT('l', "list", &actions, "list all", ACTION_LIST), + OPT_BIT('e', "edit", &actions, "opens an editor", ACTION_EDIT), + OPT_STRING(0, "get-color", &get_color_slot, "slot", "find the color configured: [default]"), + OPT_STRING(0, "get-colorbool", &get_colorbool_slot, "slot", "find the color setting: [stdout-is-tty]"), + OPT_GROUP("Type"), + OPT_BIT(0, "bool", &types, "value is \"true\" or \"false\"", TYPE_BOOL), + OPT_BIT(0, "int", &types, "value is decimal number", TYPE_INT), + OPT_BIT(0, "bool-or-int", &types, "value is --bool or --int", TYPE_BOOL_OR_INT), + OPT_GROUP("Other"), + OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"), + OPT_END(), +}; + +static void check_argc(int argc, int min, int max) { + if (argc >= min && argc <= max) + return; + error("wrong number of arguments"); + usage_with_options(builtin_config_usage, builtin_config_options); +} static int show_all_config(const char *key_, const char *value_, void *cb) { @@ -49,11 +112,11 @@ static int show_config(const char *key_, const char *value_, void *cb) } if (seen && !do_all) dup_error = 1; - if (type == T_INT) + if (types == TYPE_INT) sprintf(value, "%d", git_config_int(key_, value_?value_:"")); - else if (type == T_BOOL) + else if (types == TYPE_BOOL) vptr = git_config_bool(key_, value_) ? "true" : "false"; - else if (type == T_BOOL_OR_INT) { + else if (types == TYPE_BOOL_OR_INT) { int is_bool, v; v = git_config_bool_or_int(key_, value_, &is_bool); if (is_bool) @@ -152,18 +215,18 @@ static char *normalize_value(const char *key, const char *value) if (!value) return NULL; - if (type == T_RAW) + if (types == 0) normalized = xstrdup(value); else { normalized = xmalloc(64); - if (type == T_INT) { + if (types == TYPE_INT) { int v = git_config_int(key, value); sprintf(normalized, "%d", v); } - else if (type == T_BOOL) + else if (types == TYPE_BOOL) sprintf(normalized, "%s", git_config_bool(key, value) ? "true" : "false"); - else if (type == T_BOOL_OR_INT) { + else if (types == TYPE_BOOL_OR_INT) { int is_bool, v; v = git_config_bool_or_int(key, value, &is_bool); if (!is_bool) @@ -178,6 +241,7 @@ static char *normalize_value(const char *key, const char *value) static int get_color_found; static const char *get_color_slot; +static const char *get_colorbool_slot; static char parsed_color[COLOR_MAXLEN]; static int git_get_color_config(const char *var, const char *value, void *cb) @@ -191,29 +255,8 @@ static int git_get_color_config(const char *var, const char *value, void *cb) return 0; } -static int get_color(int argc, const char **argv) +static void get_color(const char *def_color) { - /* - * grab the color setting for the given slot from the configuration, - * or parse the default value if missing, and return ANSI color - * escape sequence. - * - * e.g. - * git config --get-color color.diff.whitespace "blue reverse" - */ - const char *def_color = NULL; - - switch (argc) { - default: - usage(git_config_set_usage); - case 2: - def_color = argv[1]; - /* fallthru */ - case 1: - get_color_slot = argv[0]; - break; - } - get_color_found = 0; parsed_color[0] = '\0'; git_config(git_get_color_config, NULL); @@ -222,7 +265,6 @@ static int get_color(int argc, const char **argv) color_parse(def_color, "command line", parsed_color); fputs(parsed_color, stdout); - return 0; } static int stdout_is_tty; @@ -231,7 +273,7 @@ static int get_diff_color_found; static int git_get_colorbool_config(const char *var, const char *value, void *cb) { - if (!strcmp(var, get_color_slot)) { + if (!strcmp(var, get_colorbool_slot)) { get_colorbool_found = git_config_colorbool(var, value, stdout_is_tty); } @@ -246,191 +288,188 @@ static int git_get_colorbool_config(const char *var, const char *value, return 0; } -static int get_colorbool(int argc, const char **argv) +static int get_colorbool(int print) { - /* - * git config --get-colorbool <slot> [<stdout-is-tty>] - * - * returns "true" or "false" depending on how <slot> - * is configured. - */ - - if (argc == 2) - stdout_is_tty = git_config_bool("command line", argv[1]); - else if (argc == 1) - stdout_is_tty = isatty(1); - else - usage(git_config_set_usage); get_colorbool_found = -1; get_diff_color_found = -1; - get_color_slot = argv[0]; git_config(git_get_colorbool_config, NULL); if (get_colorbool_found < 0) { - if (!strcmp(get_color_slot, "color.diff")) + if (!strcmp(get_colorbool_slot, "color.diff")) get_colorbool_found = get_diff_color_found; if (get_colorbool_found < 0) get_colorbool_found = git_use_color_default; } - if (argc == 1) { - return get_colorbool_found ? 0 : 1; - } else { + if (print) { printf("%s\n", get_colorbool_found ? "true" : "false"); return 0; - } + } else + return get_colorbool_found ? 0 : 1; } -int cmd_config(int argc, const char **argv, const char *prefix) +int cmd_config(int argc, const char **argv, const char *unused_prefix) { int nongit; char *value; - const char *file = setup_git_directory_gently(&nongit); + const char *prefix = setup_git_directory_gently(&nongit); config_exclusive_filename = getenv(CONFIG_ENVIRONMENT); - while (1 < argc) { - if (!strcmp(argv[1], "--int")) - type = T_INT; - else if (!strcmp(argv[1], "--bool")) - type = T_BOOL; - else if (!strcmp(argv[1], "--bool-or-int")) - type = T_BOOL_OR_INT; - else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l")) { - if (argc != 2) - usage(git_config_set_usage); - if (git_config(show_all_config, NULL) < 0 && - file && errno) - die("unable to read config file %s: %s", file, - strerror(errno)); - return 0; - } - else if (!strcmp(argv[1], "--global")) { - char *home = getenv("HOME"); - if (home) { - char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); - config_exclusive_filename = user_config; - } else { - die("$HOME not set"); - } - } - else if (!strcmp(argv[1], "--system")) - config_exclusive_filename = git_etc_gitconfig(); - else if (!strcmp(argv[1], "--file") || !strcmp(argv[1], "-f")) { - if (argc < 3) - usage(git_config_set_usage); - if (!is_absolute_path(argv[2]) && file) - file = prefix_filename(file, strlen(file), - argv[2]); - else - file = argv[2]; - config_exclusive_filename = file; - argc--; - argv++; - } - else if (!strcmp(argv[1], "--null") || !strcmp(argv[1], "-z")) { - term = '\0'; - delim = '\n'; - key_delim = '\n'; - } - else if (!strcmp(argv[1], "--rename-section")) { - int ret; - if (argc != 4) - usage(git_config_set_usage); - ret = git_config_rename_section(argv[2], argv[3]); - if (ret < 0) - return ret; - if (ret == 0) { - fprintf(stderr, "No such section!\n"); - return 1; - } - return 0; - } - else if (!strcmp(argv[1], "--remove-section")) { - int ret; - if (argc != 3) - usage(git_config_set_usage); - ret = git_config_rename_section(argv[2], NULL); - if (ret < 0) - return ret; - if (ret == 0) { - fprintf(stderr, "No such section!\n"); - return 1; - } - return 0; - } else if (!strcmp(argv[1], "--get-color")) { - return get_color(argc-2, argv+2); - } else if (!strcmp(argv[1], "--get-colorbool")) { - return get_colorbool(argc-2, argv+2); - } else if (!strcmp(argv[1], "--edit") || !strcmp(argv[1], "-e")) { - if (argc != 2) - usage(git_config_set_usage); - git_config(git_default_config, NULL); - launch_editor(config_exclusive_filename ? - config_exclusive_filename : git_path("config"), - NULL, NULL); - return 0; - } else - break; - argc--; - argv++; - } - - switch (argc) { - case 2: - return get_value(argv[1], NULL); - case 3: - if (!strcmp(argv[1], "--unset")) - return git_config_set(argv[2], NULL); - else if (!strcmp(argv[1], "--unset-all")) - return git_config_set_multivar(argv[2], NULL, NULL, 1); - else if (!strcmp(argv[1], "--get")) - return get_value(argv[2], NULL); - else if (!strcmp(argv[1], "--get-all")) { - do_all = 1; - return get_value(argv[2], NULL); - } else if (!strcmp(argv[1], "--get-regexp")) { - show_keys = 1; - use_key_regexp = 1; - do_all = 1; - return get_value(argv[2], NULL); + argc = parse_options(argc, argv, builtin_config_options, builtin_config_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (use_global_config + use_system_config + !!given_config_file > 1) { + error("only one config file at a time."); + usage_with_options(builtin_config_usage, builtin_config_options); + } + + if (use_global_config) { + char *home = getenv("HOME"); + if (home) { + char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); + config_exclusive_filename = user_config; } else { - value = normalize_value(argv[1], argv[2]); - return git_config_set(argv[1], value); + die("$HOME not set"); } - case 4: - if (!strcmp(argv[1], "--unset")) - return git_config_set_multivar(argv[2], NULL, argv[3], 0); - else if (!strcmp(argv[1], "--unset-all")) - return git_config_set_multivar(argv[2], NULL, argv[3], 1); - else if (!strcmp(argv[1], "--get")) - return get_value(argv[2], argv[3]); - else if (!strcmp(argv[1], "--get-all")) { - do_all = 1; - return get_value(argv[2], argv[3]); - } else if (!strcmp(argv[1], "--get-regexp")) { - show_keys = 1; - use_key_regexp = 1; - do_all = 1; - return get_value(argv[2], argv[3]); - } else if (!strcmp(argv[1], "--add")) { - value = normalize_value(argv[2], argv[3]); - return git_config_set_multivar(argv[2], value, "^$", 0); - } else if (!strcmp(argv[1], "--replace-all")) { - value = normalize_value(argv[2], argv[3]); - return git_config_set_multivar(argv[2], value, NULL, 1); - } else { - value = normalize_value(argv[1], argv[2]); - return git_config_set_multivar(argv[1], value, argv[3], 0); + } + else if (use_system_config) + config_exclusive_filename = git_etc_gitconfig(); + else if (given_config_file) { + if (!is_absolute_path(given_config_file) && prefix) + config_exclusive_filename = prefix_filename(prefix, + strlen(prefix), + argv[2]); + else + config_exclusive_filename = given_config_file; + } + + if (end_null) { + term = '\0'; + delim = '\n'; + key_delim = '\n'; + } + + if (HAS_MULTI_BITS(types)) { + error("only one type at a time."); + usage_with_options(builtin_config_usage, builtin_config_options); + } + + if (get_color_slot) + actions |= ACTION_GET_COLOR; + if (get_colorbool_slot) + actions |= ACTION_GET_COLORBOOL; + + if ((get_color_slot || get_colorbool_slot) && types) { + error("--get-color and variable type are incoherent"); + usage_with_options(builtin_config_usage, builtin_config_options); + } + + if (HAS_MULTI_BITS(actions)) { + error("only one action at a time."); + usage_with_options(builtin_config_usage, builtin_config_options); + } + if (actions == 0) + switch (argc) { + case 1: actions = ACTION_GET; break; + case 2: actions = ACTION_SET; break; + case 3: actions = ACTION_SET_ALL; break; + default: + usage_with_options(builtin_config_usage, builtin_config_options); } - case 5: - if (!strcmp(argv[1], "--replace-all")) { - value = normalize_value(argv[2], argv[3]); - return git_config_set_multivar(argv[2], value, argv[4], 1); + + if (actions == ACTION_LIST) { + check_argc(argc, 0, 0); + if (git_config(show_all_config, NULL) < 0) { + if (config_exclusive_filename) + die("unable to read config file %s: %s", + config_exclusive_filename, strerror(errno)); + else + die("error processing config file(s)"); } - case 1: - default: - usage(git_config_set_usage); } + else if (actions == ACTION_EDIT) { + check_argc(argc, 0, 0); + git_config(git_default_config, NULL); + launch_editor(config_exclusive_filename ? + config_exclusive_filename : git_path("config"), + NULL, NULL); + } + else if (actions == ACTION_SET) { + check_argc(argc, 2, 2); + value = normalize_value(argv[0], argv[1]); + return git_config_set(argv[0], value); + } + else if (actions == ACTION_SET_ALL) { + check_argc(argc, 2, 3); + value = normalize_value(argv[0], argv[1]); + return git_config_set_multivar(argv[0], value, argv[2], 0); + } + else if (actions == ACTION_ADD) { + check_argc(argc, 2, 2); + value = normalize_value(argv[0], argv[1]); + return git_config_set_multivar(argv[0], value, "^$", 0); + } + else if (actions == ACTION_REPLACE_ALL) { + check_argc(argc, 2, 3); + value = normalize_value(argv[0], argv[1]); + return git_config_set_multivar(argv[0], value, argv[2], 1); + } + else if (actions == ACTION_GET) { + check_argc(argc, 1, 2); + return get_value(argv[0], argv[1]); + } + else if (actions == ACTION_GET_ALL) { + do_all = 1; + check_argc(argc, 1, 2); + return get_value(argv[0], argv[1]); + } + else if (actions == ACTION_GET_REGEXP) { + show_keys = 1; + use_key_regexp = 1; + do_all = 1; + check_argc(argc, 1, 2); + return get_value(argv[0], argv[1]); + } + else if (actions == ACTION_UNSET) { + check_argc(argc, 1, 2); + if (argc == 2) + return git_config_set_multivar(argv[0], NULL, argv[1], 0); + else + return git_config_set(argv[0], NULL); + } + else if (actions == ACTION_UNSET_ALL) { + check_argc(argc, 1, 2); + return git_config_set_multivar(argv[0], NULL, argv[1], 1); + } + else if (actions == ACTION_RENAME_SECTION) { + int ret; + check_argc(argc, 2, 2); + ret = git_config_rename_section(argv[0], argv[1]); + if (ret < 0) + return ret; + if (ret == 0) + die("No such section!"); + } + else if (actions == ACTION_REMOVE_SECTION) { + int ret; + check_argc(argc, 1, 1); + ret = git_config_rename_section(argv[0], NULL); + if (ret < 0) + return ret; + if (ret == 0) + die("No such section!"); + } + else if (actions == ACTION_GET_COLOR) { + get_color(argv[0]); + } + else if (actions == ACTION_GET_COLORBOOL) { + if (argc == 1) + stdout_is_tty = git_config_bool("command line", argv[0]); + else if (argc == 0) + stdout_is_tty = isatty(1); + return get_colorbool(argc != 0); + } + return 0; } @@ -644,28 +644,37 @@ int git_config_global(void) int git_config(config_fn_t fn, void *data) { - int ret = 0; + int ret = 0, found = 0; char *repo_config = NULL; const char *home = NULL; /* Setting $GIT_CONFIG makes git read _only_ the given config file. */ if (config_exclusive_filename) return git_config_from_file(fn, config_exclusive_filename, data); - if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) + if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) { ret += git_config_from_file(fn, git_etc_gitconfig(), data); + found += 1; + } home = getenv("HOME"); if (git_config_global() && home) { char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); - if (!access(user_config, R_OK)) + if (!access(user_config, R_OK)) { ret += git_config_from_file(fn, user_config, data); + found += 1; + } free(user_config); } repo_config = git_pathdup("config"); - ret += git_config_from_file(fn, repo_config, data); + if (!access(repo_config, R_OK)) { + ret += git_config_from_file(fn, repo_config, data); + found += 1; + } free(repo_config); + if (found == 0) + return -1; return ret; } diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 3c06842d99..9c81e041b9 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -118,7 +118,14 @@ EOF test_expect_success 'multiple unset is correct' 'cmp .git/config expect' -mv .git/config2 .git/config +cp .git/config2 .git/config + +test_expect_success '--replace-all missing value' ' + test_must_fail git config --replace-all beta.haha && + test_cmp .git/config2 .git/config +' + +rm .git/config2 test_expect_success '--replace-all' \ 'git config --replace-all beta.haha gamma' |