Merge branch 'sb/config-write-fix'
authorJunio C Hamano <gitster@pobox.com>
Mon, 20 Aug 2018 19:41:32 +0000 (12:41 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 20 Aug 2018 19:41:32 +0000 (12:41 -0700)
Recent update to "git config" broke updating variable in a
subsection, which has been corrected.

* sb/config-write-fix:
  git-config: document accidental multi-line setting in deprecated syntax
  config: fix case sensitive subsection names on writing
  t1300: document current behavior of setting options

1  2 
Documentation/git-config.txt
config.c
t/t1300-config.sh

@@@ -9,13 -9,13 +9,13 @@@ git-config - Get and set repository or 
  SYNOPSIS
  --------
  [verse]
 -'git config' [<file-option>] [type] [--show-origin] [-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] [--show-origin] [-z|--null] --get name [value_regex]
 -'git config' [<file-option>] [type] [--show-origin] [-z|--null] --get-all name [value_regex]
 -'git config' [<file-option>] [type] [--show-origin] [-z|--null] [--name-only] --get-regexp name_regex [value_regex]
 -'git config' [<file-option>] [type] [-z|--null] --get-urlmatch name URL
 +'git config' [<file-option>] [--type=<type>] [--show-origin] [-z|--null] name [value [value_regex]]
 +'git config' [<file-option>] [--type=<type>] --add name value
 +'git config' [<file-option>] [--type=<type>] --replace-all name value [value_regex]
 +'git config' [<file-option>] [--type=<type>] [--show-origin] [-z|--null] --get name [value_regex]
 +'git config' [<file-option>] [--type=<type>] [--show-origin] [-z|--null] --get-all name [value_regex]
 +'git config' [<file-option>] [--type=<type>] [--show-origin] [-z|--null] [--name-only] --get-regexp name_regex [value_regex]
 +'git config' [<file-option>] [--type=<type>] [-z|--null] --get-urlmatch name URL
  'git config' [<file-option>] --unset name [value_regex]
  'git config' [<file-option>] --unset-all name [value_regex]
  'git config' [<file-option>] --rename-section old_name new_name
@@@ -38,10 -38,12 +38,10 @@@ existing values that match the regexp a
  you want to handle the lines that do *not* match the regex, just
  prepend a single exclamation mark in front (see also <<EXAMPLES>>).
  
 -The type specifier can be either `--int` or `--bool`, to make
 -'git config' ensure that the variable(s) are of the given type and
 -convert the value to the canonical form (simple decimal number for int,
 -a "true" or "false" string for bool), or `--path`, which does some
 -path expansion (see `--path` below).  If no type specifier is passed, no
 -checks or transformations are performed on the value.
 +The `--type=<type>` option instructs 'git config' to ensure that incoming and
 +outgoing values are canonicalize-able under the given <type>.  If no
 +`--type=<type>` is given, no canonicalization will be performed. Callers may
 +unset an existing `--type` specifier with `--no-type`.
  
  When reading, the values are read from the system, global and
  repository local configuration files by default, and options
@@@ -158,43 -160,30 +158,43 @@@ See also <<FILES>>
  --list::
        List all variables set in config file, along with their values.
  
 ---bool::
 -      'git config' will ensure that the output is "true" or "false"
 +--type <type>::
 +  'git config' will ensure that any input or output is valid under the given
 +  type constraint(s), and will canonicalize outgoing values in `<type>`'s
 +  canonical form.
 ++
 +Valid `<type>`'s include:
 ++
 +- 'bool': canonicalize values as either "true" or "false".
 +- 'int': canonicalize values as simple decimal numbers. An optional suffix of
 +  'k', 'm', or 'g' will cause the value to be multiplied by 1024, 1048576, or
 +  1073741824 upon input.
 +- 'bool-or-int': canonicalize according to either 'bool' or 'int', as described
 +  above.
 +- 'path': canonicalize by adding a leading `~` to the value of `$HOME` and
 +  `~user` to the home directory for the specified user. This specifier has no
 +  effect when setting the value (but you can use `git config section.variable
 +  ~/` from the command line to let your shell do the expansion.)
 +- 'expiry-date': canonicalize by converting from a fixed or relative date-string
 +  to a timestamp. This specifier has no effect when setting the value.
 +- 'color': When getting a value, canonicalize by converting to an ANSI color
 +  escape sequence. When setting a value, a sanity-check is performed to ensure
 +  that the given value is canonicalize-able as an ANSI color, but it is written
 +  as-is.
 ++
  
 +--bool::
  --int::
 -      'git config' will ensure that the output is a simple
 -      decimal number.  An optional value suffix of 'k', 'm', or 'g'
 -      in the config file will cause the value to be multiplied
 -      by 1024, 1048576, or 1073741824 prior to output.
 -
  --bool-or-int::
 -      'git config' will ensure that the output matches the format of
 -      either --bool or --int, as described above.
 -
  --path::
 -      `git config` will expand a leading `~` to the value of
 -      `$HOME`, and `~user` to the home directory for the
 -      specified user.  This option has no effect when setting the
 -      value (but you can use `git config section.variable ~/`
 -      from the command line to let your shell do the expansion).
 -
  --expiry-date::
 -      `git config` will ensure that the output is converted from
 -      a fixed or relative date-string to a timestamp. This option
 -      has no effect when setting the value.
 +  Historical options for selecting a type specifier. Prefer instead `--type`,
 +  (see: above).
 +
 +--no-type::
 +  Un-sets the previously set type specifier (if one was previously set). This
 +  option requests that 'git config' not canonicalize the retrieved variable.
 +  `--no-type` has no effect without `--type=<type>` or `--<type>`.
  
  -z::
  --null::
        output it as the ANSI color escape sequence to the standard
        output.  The optional `default` parameter is used instead, if
        there is no color configured for `name`.
 ++
 +`--type=color [--default=<default>]` is preferred over `--get-color`.
  
  -e::
  --edit::
        using `--file`, `--global`, etc) and `on` when searching all
        config files.
  
 +--default <value>::
 +  When using `--get`, and the requested variable is not found, behave as if
 +  <value> were the value assigned to the that variable.
 +
 +CONFIGURATION
 +-------------
 +`pager.config` is only respected when listing configuration, i.e., when
 +using `--list` or any of the `--get-*` which may return multiple results.
 +The default is to use a pager.
 +
  [[FILES]]
  FILES
  -----
@@@ -453,6 -430,27 +453,27 @@@ http.sslverify fals
  
  include::config.txt[]
  
+ BUGS
+ ----
+ When using the deprecated `[section.subsection]` syntax, changing a value
+ will result in adding a multi-line key instead of a change, if the subsection
+ is given with at least one uppercase character. For example when the config
+ looks like
+ --------
+   [section.subsection]
+     key = value1
+ --------
+ and running `git config section.Subsection.key value2` will result in
+ --------
+   [section.subsection]
+     key = value1
+     key = value2
+ --------
  GIT
  ---
  Part of the linkgit:git[1] suite
diff --combined config.c
+++ b/config.c
@@@ -6,19 -6,16 +6,19 @@@
   *
   */
  #include "cache.h"
 +#include "branch.h"
  #include "config.h"
  #include "repository.h"
  #include "lockfile.h"
 -#include "exec_cmd.h"
 +#include "exec-cmd.h"
  #include "strbuf.h"
  #include "quote.h"
  #include "hashmap.h"
  #include "string-list.h"
 +#include "object-store.h"
  #include "utf8.h"
  #include "dir.h"
 +#include "color.h"
  
  struct config_source {
        struct config_source *prev;
        enum config_origin_type origin_type;
        const char *name;
        const char *path;
 -      int die_on_error;
 +      enum config_error_action default_error_action;
        int linenr;
        int eof;
        struct strbuf value;
        struct strbuf var;
+       unsigned subsection_case_sensitive : 1;
  
        int (*do_fgetc)(struct config_source *c);
        int (*do_ungetc)(int c, struct config_source *conf);
@@@ -105,7 -103,7 +106,7 @@@ static int config_buf_ungetc(int c, str
        if (conf->u.buf.pos > 0) {
                conf->u.buf.pos--;
                if (conf->u.buf.buf[conf->u.buf.pos] != c)
 -                      die("BUG: config_buf can only ungetc the same character");
 +                      BUG("config_buf can only ungetc the same character");
                return c;
        }
  
@@@ -118,12 -116,12 +119,12 @@@ static long config_buf_ftell(struct con
  }
  
  #define MAX_INCLUDE_DEPTH 10
 -static const char include_depth_advice[] =
 +static const char include_depth_advice[] = N_(
  "exceeded maximum include depth (%d) while including\n"
  "     %s\n"
  "from\n"
  "     %s\n"
 -"Do you have circular includes?";
 +"Do you have circular includes?");
  static int handle_path_include(const char *path, struct config_include_data *inc)
  {
        int ret = 0;
  
        expanded = expand_user_path(path, 0);
        if (!expanded)
 -              return error("could not expand include path '%s'", path);
 +              return error(_("could not expand include path '%s'"), path);
        path = expanded;
  
        /*
                char *slash;
  
                if (!cf || !cf->path)
 -                      return error("relative config includes must come from files");
 +                      return error(_("relative config includes must come from files"));
  
                slash = find_last_dir_sep(cf->path);
                if (slash)
  
        if (!access_or_die(path, R_OK, 0)) {
                if (++inc->depth > MAX_INCLUDE_DEPTH)
 -                      die(include_depth_advice, MAX_INCLUDE_DEPTH, path,
 +                      die(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,
                            !cf ? "<unknown>" :
                            cf->name ? cf->name :
                            "the command line");
@@@ -192,7 -190,7 +193,7 @@@ static int prepare_include_condition_pa
                strbuf_realpath(&path, cf->path, 1);
                slash = find_last_dir_sep(path.buf);
                if (!slash)
 -                      die("BUG: how is this possible?");
 +                      BUG("how is this possible?");
                strbuf_splice(pat, 0, 1, path.buf, slash - path.buf);
                prefix = slash - path.buf + 1 /* slash */;
        } else if (!is_absolute_path(pat->buf))
@@@ -344,13 -342,13 +345,13 @@@ static int git_config_parse_key_1(cons
  
        if (last_dot == NULL || last_dot == key) {
                if (!quiet)
 -                      error("key does not contain a section: %s", key);
 +                      error(_("key does not contain a section: %s"), key);
                return -CONFIG_NO_SECTION_OR_NAME;
        }
  
        if (!last_dot[1]) {
                if (!quiet)
 -                      error("key does not contain variable name: %s", key);
 +                      error(_("key does not contain variable name: %s"), key);
                return -CONFIG_NO_SECTION_OR_NAME;
        }
  
                        if (!iskeychar(c) ||
                            (i == baselen + 1 && !isalpha(c))) {
                                if (!quiet)
 -                                      error("invalid key: %s", key);
 +                                      error(_("invalid key: %s"), key);
                                goto out_free_ret_1;
                        }
                        c = tolower(c);
                } else if (c == '\n') {
                        if (!quiet)
 -                              error("invalid key (newline): %s", key);
 +                              error(_("invalid key (newline): %s"), key);
                        goto out_free_ret_1;
                }
                if (store_key)
@@@ -416,7 -414,7 +417,7 @@@ int git_config_parse_parameter(const ch
  
        pair = strbuf_split_str(text, '=', 2);
        if (!pair[0])
 -              return error("bogus config parameter: %s", text);
 +              return error(_("bogus config parameter: %s"), text);
  
        if (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=') {
                strbuf_setlen(pair[0], pair[0]->len - 1);
        strbuf_trim(pair[0]);
        if (!pair[0]->len) {
                strbuf_list_free(pair);
 -              return error("bogus config parameter: %s", text);
 +              return error(_("bogus config parameter: %s"), text);
        }
  
        if (git_config_parse_key(pair[0]->buf, &canonical_name, NULL)) {
@@@ -463,7 -461,7 +464,7 @@@ int git_config_from_parameters(config_f
        envw = xstrdup(env);
  
        if (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {
 -              ret = error("bogus format in " CONFIG_DATA_ENVIRONMENT);
 +              ret = error(_("bogus format in %s"), CONFIG_DATA_ENVIRONMENT);
                goto out;
        }
  
@@@ -606,6 -604,7 +607,7 @@@ static int get_value(config_fn_t fn, vo
  
  static int get_extended_base_var(struct strbuf *name, int c)
  {
+       cf->subsection_case_sensitive = 0;
        do {
                if (c == '\n')
                        goto error_incomplete_line;
@@@ -642,6 -641,7 +644,7 @@@ error_incomplete_line
  
  static int get_base_var(struct strbuf *name)
  {
+       cf->subsection_case_sensitive = 1;
        for (;;) {
                int c = get_next_char();
                if (cf->eof)
@@@ -811,21 -811,10 +814,21 @@@ static int git_parse_source(config_fn_
                                      cf->linenr, cf->name);
        }
  
 -      if (cf->die_on_error)
 +      switch (opts && opts->error_action ?
 +              opts->error_action :
 +              cf->default_error_action) {
 +      case CONFIG_ERROR_DIE:
                die("%s", error_msg);
 -      else
 +              break;
 +      case CONFIG_ERROR_ERROR:
                error_return = error("%s", error_msg);
 +              break;
 +      case CONFIG_ERROR_SILENT:
 +              error_return = -1;
 +              break;
 +      case CONFIG_ERROR_UNSET:
 +              BUG("config error action unset");
 +      }
  
        free(error_msg);
        return error_return;
@@@ -934,7 -923,7 +937,7 @@@ int git_parse_ulong(const char *value, 
        return 1;
  }
  
 -static int git_parse_ssize_t(const char *value, ssize_t *ret)
 +int git_parse_ssize_t(const char *value, ssize_t *ret)
  {
        intmax_t tmp;
        if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(ssize_t)))
@@@ -1081,15 -1070,6 +1084,15 @@@ int git_config_expiry_date(timestamp_t 
        return 0;
  }
  
 +int git_config_color(char *dest, const char *var, const char *value)
 +{
 +      if (!value)
 +              return config_error_nonbool(var);
 +      if (color_parse(value, dest) < 0)
 +              return -1;
 +      return 0;
 +}
 +
  static int git_default_core_config(const char *var, const char *value)
  {
        /* This needs a better name */
                else {
                        int abbrev = git_config_int(var, value);
                        if (abbrev < minimum_abbrev || abbrev > 40)
 -                              return error("abbrev length out of range: %d", abbrev);
 +                              return error(_("abbrev length out of range: %d"), abbrev);
                        default_abbrev = abbrev;
                }
                return 0;
        }
  
        if (!strcmp(var, "core.safecrlf")) {
 +              int eol_rndtrp_die;
                if (value && !strcasecmp(value, "warn")) {
 -                      safe_crlf = SAFE_CRLF_WARN;
 +                      global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;
                        return 0;
                }
 -              safe_crlf = git_config_bool(var, value);
 +              eol_rndtrp_die = git_config_bool(var, value);
 +              global_conv_flags_eol = eol_rndtrp_die ?
 +                      CONV_EOL_RNDTRP_DIE : 0;
                return 0;
        }
  
                return 0;
        }
  
 +      if (!strcmp(var, "core.checkroundtripencoding")) {
 +              check_roundtrip_encoding = xstrdup(value);
 +              return 0;
 +      }
 +
        if (!strcmp(var, "core.notesref")) {
                notes_ref_name = xstrdup(value);
                return 0;
                        comment_line_char = value[0];
                        auto_comment_line_char = 0;
                } else
 -                      return error("core.commentChar should only be one character");
 +                      return error(_("core.commentChar should only be one character"));
                return 0;
        }
  
                return 0;
        }
  
 +      if (!strcmp(var, "core.partialclonefilter")) {
 +              return git_config_string(&core_partial_clone_filter_default,
 +                                       var, value);
 +      }
 +
 +      if (!strcmp(var, "core.usereplacerefs")) {
 +              read_replace_refs = git_config_bool(var, value);
 +              return 0;
 +      }
 +
        /* Add other config variables here and to Documentation/config.txt. */
        return 0;
  }
@@@ -1397,7 -1359,7 +1400,7 @@@ static int git_default_branch_config(co
                else if (!strcmp(value, "always"))
                        autorebase = AUTOREBASE_ALWAYS;
                else
 -                      return error("malformed value for %s", var);
 +                      return error(_("malformed value for %s"), var);
                return 0;
        }
  
@@@ -1423,9 -1385,9 +1426,9 @@@ static int git_default_push_config(cons
                else if (!strcmp(value, "current"))
                        push_default = PUSH_DEFAULT_CURRENT;
                else {
 -                      error("malformed value for %s: %s", var, value);
 -                      return error("Must be one of nothing, matching, simple, "
 -                                   "upstream or current.");
 +                      error(_("malformed value for %s: %s"), var, value);
 +                      return error(_("must be one of nothing, matching, simple, "
 +                                     "upstream or current"));
                }
                return 0;
        }
@@@ -1465,7 -1427,7 +1468,7 @@@ int git_default_config(const char *var
        if (starts_with(var, "mailmap."))
                return git_default_mailmap_config(var, value);
  
 -      if (starts_with(var, "advice."))
 +      if (starts_with(var, "advice.") || starts_with(var, "color.advice"))
                return git_default_advice_config(var, value);
  
        if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {
@@@ -1527,21 -1489,17 +1530,21 @@@ static int do_config_from_file(config_f
                void *data, const struct config_options *opts)
  {
        struct config_source top;
 +      int ret;
  
        top.u.file = f;
        top.origin_type = origin_type;
        top.name = name;
        top.path = path;
 -      top.die_on_error = 1;
 +      top.default_error_action = CONFIG_ERROR_DIE;
        top.do_fgetc = config_file_fgetc;
        top.do_ungetc = config_file_ungetc;
        top.do_ftell = config_file_ftell;
  
 -      return do_config_from(&top, fn, data, opts);
 +      flockfile(f);
 +      ret = do_config_from(&top, fn, data, opts);
 +      funlockfile(f);
 +      return ret;
  }
  
  static int git_config_from_stdin(config_fn_t fn, void *data)
@@@ -1559,8 -1517,10 +1562,8 @@@ int git_config_from_file_with_options(c
  
        f = fopen_or_warn(filename, "r");
        if (f) {
 -              flockfile(f);
                ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename,
                                          filename, f, data, opts);
 -              funlockfile(f);
                fclose(f);
        }
        return ret;
@@@ -1571,10 -1531,8 +1574,10 @@@ int git_config_from_file(config_fn_t fn
        return git_config_from_file_with_options(fn, filename, data, NULL);
  }
  
 -int git_config_from_mem(config_fn_t fn, const enum config_origin_type origin_type,
 -                      const char *name, const char *buf, size_t len, void *data)
 +int git_config_from_mem(config_fn_t fn,
 +                      const enum config_origin_type origin_type,
 +                      const char *name, const char *buf, size_t len,
 +                      void *data, const struct config_options *opts)
  {
        struct config_source top;
  
        top.origin_type = origin_type;
        top.name = name;
        top.path = NULL;
 -      top.die_on_error = 0;
 +      top.default_error_action = CONFIG_ERROR_ERROR;
        top.do_fgetc = config_buf_fgetc;
        top.do_ungetc = config_buf_ungetc;
        top.do_ftell = config_buf_ftell;
  
 -      return do_config_from(&top, fn, data, NULL);
 +      return do_config_from(&top, fn, data, opts);
  }
  
  int git_config_from_blob_oid(config_fn_t fn,
        unsigned long size;
        int ret;
  
 -      buf = read_sha1_file(oid->hash, &type, &size);
 +      buf = read_object_file(oid, &type, &size);
        if (!buf)
 -              return error("unable to load config blob object '%s'", name);
 +              return error(_("unable to load config blob object '%s'"), name);
        if (type != OBJ_BLOB) {
                free(buf);
 -              return error("reference '%s' does not point to a blob", name);
 +              return error(_("reference '%s' does not point to a blob"), name);
        }
  
 -      ret = git_config_from_mem(fn, CONFIG_ORIGIN_BLOB, name, buf, size, data);
 +      ret = git_config_from_mem(fn, CONFIG_ORIGIN_BLOB, name, buf, size,
 +                                data, NULL);
        free(buf);
  
        return ret;
@@@ -1624,7 -1581,7 +1627,7 @@@ static int git_config_from_blob_ref(con
        struct object_id oid;
  
        if (get_oid(name, &oid) < 0)
 -              return error("unable to resolve config blob '%s'", name);
 +              return error(_("unable to resolve config blob '%s'"), name);
        return git_config_from_blob_oid(fn, name, &oid, data);
  }
  
@@@ -1654,7 -1611,7 +1657,7 @@@ unsigned long git_env_ulong(const char 
  {
        const char *v = getenv(k);
        if (v && !git_parse_ulong(v, &val))
 -              die("failed to parse %s", k);
 +              die(_("failed to parse %s"), k);
        return val;
  }
  
@@@ -1830,7 -1787,7 +1833,7 @@@ static int configset_add_value(struct c
        l_item->value_index = e->value_list.nr - 1;
  
        if (!cf)
 -              die("BUG: configset_add_value has no source");
 +              BUG("configset_add_value has no source");
        if (cf->name) {
                kv_info->filename = strintern(cf->name);
                kv_info->linenr = cf->linenr;
@@@ -2188,6 -2145,23 +2191,6 @@@ int git_config_get_pathname(const char 
        return repo_config_get_pathname(the_repository, key, dest);
  }
  
 -/*
 - * Note: This function exists solely to maintain backward compatibility with
 - * 'fetch' and 'update_clone' storing configuration in '.gitmodules' and should
 - * NOT be used anywhere else.
 - *
 - * Runs the provided config function on the '.gitmodules' file found in the
 - * working directory.
 - */
 -void config_from_gitmodules(config_fn_t fn, void *data)
 -{
 -      if (the_repository->worktree) {
 -              char *file = repo_worktree_path(the_repository, GITMODULES_FILE);
 -              git_config_from_file(fn, file, data);
 -              free(file);
 -      }
 -}
 -
  int git_config_get_expiry(const char *key, const char **output)
  {
        int ret = git_config_get_string_const(key, output);
@@@ -2332,19 -2306,6 +2335,19 @@@ struct config_store_data 
        unsigned int key_seen:1, section_seen:1, is_keys_section:1;
  };
  
 +static void config_store_data_clear(struct config_store_data *store)
 +{
 +      free(store->key);
 +      if (store->value_regex != NULL &&
 +          store->value_regex != CONFIG_REGEX_NONE) {
 +              regfree(store->value_regex);
 +              free(store->value_regex);
 +      }
 +      free(store->parsed);
 +      free(store->seen);
 +      memset(store, 0, sizeof(*store));
 +}
 +
  static int matches(const char *key, const char *value,
                   const struct config_store_data *store)
  {
@@@ -2370,14 -2331,21 +2373,21 @@@ static int store_aux_event(enum config_
        store->parsed[store->parsed_nr].type = type;
  
        if (type == CONFIG_EVENT_SECTION) {
+               int (*cmpfn)(const char *, const char *, size_t);
                if (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')
 -                      return error("invalid section name '%s'", cf->var.buf);
 +                      return error(_("invalid section name '%s'"), cf->var.buf);
  
+               if (cf->subsection_case_sensitive)
+                       cmpfn = strncasecmp;
+               else
+                       cmpfn = strncmp;
                /* Is this the section we were looking for? */
                store->is_keys_section =
                        store->parsed[store->parsed_nr].is_keys_section =
                        cf->var.len - 1 == store->baselen &&
-                       !strncasecmp(cf->var.buf, store->key, store->baselen);
+                       !cmpfn(cf->var.buf, store->key, store->baselen);
                if (store->is_keys_section) {
                        store->section_seen = 1;
                        ALLOC_GROW(store->seen, store->seen_nr + 1,
@@@ -2427,7 -2395,7 +2437,7 @@@ static int store_aux(const char *key, c
  
  static int write_error(const char *filename)
  {
 -      error("failed to write new configuration file %s", filename);
 +      error(_("failed to write new configuration file %s"), filename);
  
        /* Same error code as "failed to rename". */
        return 4;
@@@ -2678,7 -2646,8 +2688,7 @@@ int git_config_set_multivar_in_file_gen
         */
        fd = hold_lock_file_for_update(&lock, config_filename, 0);
        if (fd < 0) {
 -              error_errno("could not lock config file %s", config_filename);
 -              free(store.key);
 +              error_errno(_("could not lock config file %s"), config_filename);
                ret = CONFIG_NO_LOCK;
                goto out_free;
        }
         */
        in_fd = open(config_filename, O_RDONLY);
        if ( in_fd < 0 ) {
 -              free(store.key);
 -
                if ( ENOENT != errno ) {
 -                      error_errno("opening %s", config_filename);
 +                      error_errno(_("opening %s"), config_filename);
                        ret = CONFIG_INVALID_FILE; /* same as "invalid config file" */
                        goto out_free;
                }
                        goto out_free;
                }
  
 -              store.key = (char *)key;
 +              free(store.key);
 +              store.key = xstrdup(key);
                if (write_section(fd, key, &store) < 0 ||
                    write_pair(fd, key, value, &store) < 0)
                        goto write_err_out;
                        store.value_regex = (regex_t*)xmalloc(sizeof(regex_t));
                        if (regcomp(store.value_regex, value_regex,
                                        REG_EXTENDED)) {
 -                              error("invalid pattern: %s", value_regex);
 -                              free(store.value_regex);
 +                              error(_("invalid pattern: %s"), value_regex);
 +                              FREE_AND_NULL(store.value_regex);
                                ret = CONFIG_INVALID_PATTERN;
                                goto out_free;
                        }
                if (git_config_from_file_with_options(store_aux,
                                                      config_filename,
                                                      &store, &opts)) {
 -                      error("invalid config file %s", config_filename);
 -                      free(store.key);
 -                      if (store.value_regex != NULL &&
 -                          store.value_regex != CONFIG_REGEX_NONE) {
 -                              regfree(store.value_regex);
 -                              free(store.value_regex);
 -                      }
 +                      error(_("invalid config file %s"), config_filename);
                        ret = CONFIG_INVALID_FILE;
                        goto out_free;
                }
  
 -              free(store.key);
 -              if (store.value_regex != NULL &&
 -                  store.value_regex != CONFIG_REGEX_NONE) {
 -                      regfree(store.value_regex);
 -                      free(store.value_regex);
 -              }
 -
                /* if nothing to unset, or too many matches, error out */
                if ((store.seen_nr == 0 && value == NULL) ||
                    (store.seen_nr > 1 && multi_replace == 0)) {
                if (contents == MAP_FAILED) {
                        if (errno == ENODEV && S_ISDIR(st.st_mode))
                                errno = EISDIR;
 -                      error_errno("unable to mmap '%s'", config_filename);
 +                      error_errno(_("unable to mmap '%s'"), config_filename);
                        ret = CONFIG_INVALID_FILE;
                        contents = NULL;
                        goto out_free;
                in_fd = -1;
  
                if (chmod(get_lock_file_path(&lock), st.st_mode & 07777) < 0) {
 -                      error_errno("chmod on %s failed", get_lock_file_path(&lock));
 +                      error_errno(_("chmod on %s failed"), get_lock_file_path(&lock));
                        ret = CONFIG_NO_WRITE;
                        goto out_free;
                }
        }
  
        if (commit_lock_file(&lock) < 0) {
 -              error_errno("could not write config file %s", config_filename);
 +              error_errno(_("could not write config file %s"), config_filename);
                ret = CONFIG_NO_WRITE;
                goto out_free;
        }
@@@ -2884,7 -2867,6 +2894,7 @@@ out_free
                munmap(contents, contents_sz);
        if (in_fd >= 0)
                close(in_fd);
 +      config_store_data_clear(&store);
        return ret;
  
  write_err_out:
@@@ -2993,7 -2975,7 +3003,7 @@@ static int git_config_copy_or_rename_se
        memset(&store, 0, sizeof(store));
  
        if (new_name && !section_name_is_ok(new_name)) {
 -              ret = error("invalid section name: %s", new_name);
 +              ret = error(_("invalid section name: %s"), new_name);
                goto out_no_rollback;
        }
  
  
        out_fd = hold_lock_file_for_update(&lock, config_filename, 0);
        if (out_fd < 0) {
 -              ret = error("could not lock config file %s", config_filename);
 +              ret = error(_("could not lock config file %s"), config_filename);
                goto out;
        }
  
        }
  
        if (chmod(get_lock_file_path(&lock), st.st_mode & 07777) < 0) {
 -              ret = error_errno("chmod on %s failed",
 +              ret = error_errno(_("chmod on %s failed"),
                                  get_lock_file_path(&lock));
                goto out;
        }
        config_file = NULL;
  commit_and_out:
        if (commit_lock_file(&lock) < 0)
 -              ret = error_errno("could not write config file %s",
 +              ret = error_errno(_("could not write config file %s"),
                                  config_filename);
  out:
        if (config_file)
        rollback_lock_file(&lock);
  out_no_rollback:
        free(filename_buf);
 +      config_store_data_clear(&store);
        return ret;
  }
  
@@@ -3160,7 -3141,7 +3170,7 @@@ int git_config_copy_section(const char 
  #undef config_error_nonbool
  int config_error_nonbool(const char *var)
  {
 -      return error("missing value for '%s'", var);
 +      return error(_("missing value for '%s'"), var);
  }
  
  int parse_config_key(const char *var,
@@@ -3207,7 -3188,7 +3217,7 @@@ const char *current_config_origin_type(
        else if(cf)
                type = cf->origin_type;
        else
 -              die("BUG: current_config_origin_type called outside config callback");
 +              BUG("current_config_origin_type called outside config callback");
  
        switch (type) {
        case CONFIG_ORIGIN_BLOB:
        case CONFIG_ORIGIN_CMDLINE:
                return "command line";
        default:
 -              die("BUG: unknown config origin type");
 +              BUG("unknown config origin type");
        }
  }
  
@@@ -3233,7 -3214,7 +3243,7 @@@ const char *current_config_name(void
        else if (cf)
                name = cf->name;
        else
 -              die("BUG: current_config_name called outside config callback");
 +              BUG("current_config_name called outside config callback");
        return name ? name : "";
  }
  
@@@ -3244,16 -3225,3 +3254,16 @@@ enum config_scope current_config_scope(
        else
                return current_parsing_scope;
  }
 +
 +int lookup_config(const char **mapping, int nr_mapping, const char *var)
 +{
 +      int i;
 +
 +      for (i = 0; i < nr_mapping; i++) {
 +              const char *name = mapping[i];
 +
 +              if (name && !strcasecmp(var, name))
 +                      return i;
 +      }
 +      return -1;
 +}
diff --combined t/t1300-config.sh
@@@ -742,7 -742,7 +742,7 @@@ test_expect_success bool 
        do
            git config --bool --get bool.true$i >>result
            git config --bool --get bool.false$i >>result
 -        done &&
 +      done &&
        test_cmp expect result'
  
  test_expect_success 'invalid bool (--get)' '
@@@ -888,7 -888,7 +888,7 @@@ EO
  
  test_expect_success !MINGW 'get --path copes with unset $HOME' '
        (
 -              unset HOME;
 +              sane_unset HOME &&
                test_must_fail git config --get --path path.home \
                        >result 2>msg &&
                git config --get --path path.normal >>result &&
@@@ -916,7 -916,7 +916,7 @@@ test_expect_success 'get --expiry-date
        invalid1 = "abc"
        EOF
        cat >expect <<-EOF &&
 -      $(test-date timestamp $rel)
 +      $(test-tool date timestamp $rel)
        1275666415
        1510441871
        1510348087
        test_must_fail git config --expiry-date date.invalid1
  '
  
 +test_expect_success 'get --type=color' '
 +      rm .git/config &&
 +      git config foo.color "red" &&
 +      git config --get --type=color foo.color >actual.raw &&
 +      test_decode_color <actual.raw >actual &&
 +      echo "<RED>" >expect &&
 +      test_cmp expect actual
 +'
 +
 +cat >expect << EOF
 +[foo]
 +      color = red
 +EOF
 +
 +test_expect_success 'set --type=color' '
 +      rm .git/config &&
 +      git config --type=color foo.color "red" &&
 +      test_cmp expect .git/config
 +'
 +
 +test_expect_success 'get --type=color barfs on non-color' '
 +      echo "[foo]bar=not-a-color" >.git/config &&
 +      test_must_fail git config --get --type=color foo.bar
 +'
 +
 +test_expect_success 'set --type=color barfs on non-color' '
 +      test_must_fail git config --type=color foo.color "not-a-color" 2>error &&
 +      test_i18ngrep "cannot parse color" error
 +'
 +
  cat > expect << EOF
  [quote]
        leading = " test"
@@@ -1218,6 -1188,93 +1218,93 @@@ test_expect_success 'last one wins: thr
        test_cmp expect actual
  '
  
+ test_expect_success 'old-fashioned settings are case insensitive' '
+       test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
+       cat >testConfig_actual <<-EOF &&
+               [V.A]
+               r = value1
+       EOF
+       q_to_tab >testConfig_expect <<-EOF &&
+               [V.A]
+               Qr = value2
+       EOF
+       git config -f testConfig_actual "v.a.r" value2 &&
+       test_cmp testConfig_expect testConfig_actual &&
+       cat >testConfig_actual <<-EOF &&
+               [V.A]
+               r = value1
+       EOF
+       q_to_tab >testConfig_expect <<-EOF &&
+               [V.A]
+               QR = value2
+       EOF
+       git config -f testConfig_actual "V.a.R" value2 &&
+       test_cmp testConfig_expect testConfig_actual &&
+       cat >testConfig_actual <<-EOF &&
+               [V.A]
+               r = value1
+       EOF
+       q_to_tab >testConfig_expect <<-EOF &&
+               [V.A]
+               r = value1
+               Qr = value2
+       EOF
+       git config -f testConfig_actual "V.A.r" value2 &&
+       test_cmp testConfig_expect testConfig_actual &&
+       cat >testConfig_actual <<-EOF &&
+               [V.A]
+               r = value1
+       EOF
+       q_to_tab >testConfig_expect <<-EOF &&
+               [V.A]
+               r = value1
+               Qr = value2
+       EOF
+       git config -f testConfig_actual "v.A.r" value2 &&
+       test_cmp testConfig_expect testConfig_actual
+ '
+ test_expect_success 'setting different case sensitive subsections ' '
+       test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" &&
+       cat >testConfig_actual <<-EOF &&
+               [V "A"]
+               R = v1
+               [K "E"]
+               Y = v1
+               [a "b"]
+               c = v1
+               [d "e"]
+               f = v1
+       EOF
+       q_to_tab >testConfig_expect <<-EOF &&
+               [V "A"]
+               Qr = v2
+               [K "E"]
+               Qy = v2
+               [a "b"]
+               Qc = v2
+               [d "e"]
+               f = v1
+               [d "E"]
+               Qf = v2
+       EOF
+       # exact match
+       git config -f testConfig_actual a.b.c v2 &&
+       # match section and subsection, key is cased differently.
+       git config -f testConfig_actual K.E.y v2 &&
+       # section and key are matched case insensitive, but subsection needs
+       # to match; When writing out new values only the key is adjusted
+       git config -f testConfig_actual v.A.r v2 &&
+       # subsection is not matched:
+       git config -f testConfig_actual d.E.f v2 &&
+       test_cmp testConfig_expect testConfig_actual
+ '
  for VAR in a .a a. a.0b a."b c". a."b c".0d
  do
        test_expect_success "git -c $VAR=VAL rejects invalid '$VAR'" '
@@@ -1238,29 -1295,6 +1325,29 @@@ test_expect_success 'git -c is not conf
        GIT_CONFIG_PARAMETERS="" git -c x.one=1 config --list
  '
  
 +sq="'"
 +test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '
 +      cat >expect <<-\EOF &&
 +      env.one one
 +      env.two two
 +      EOF
 +      GIT_CONFIG_PARAMETERS="${sq}env.one=one${sq} ${sq}env.two=two${sq}" \
 +              git config --get-regexp "env.*" >actual &&
 +      test_cmp expect actual &&
 +
 +      cat >expect <<-EOF &&
 +      env.one one${sq}
 +      env.two two
 +      EOF
 +      GIT_CONFIG_PARAMETERS="${sq}env.one=one${sq}\\$sq$sq$sq ${sq}env.two=two${sq}" \
 +              git config --get-regexp "env.*" >actual &&
 +      test_cmp expect actual &&
 +
 +      test_must_fail env \
 +              GIT_CONFIG_PARAMETERS="${sq}env.one=one${sq}\\$sq ${sq}env.two=two${sq}" \
 +              git config --get-regexp "env.*"
 +'
 +
  test_expect_success 'git config --edit works' '
        git config -f tmp test.value no &&
        echo test.value=yes >expect &&
@@@ -1692,10 -1726,10 +1779,10 @@@ test_expect_success '--show-origin stdi
  '
  
  test_expect_success !MINGW '--show-origin blob' '
 -      cat >expect <<-\EOF &&
 -              blob:a9d9f9e555b5c6f07cbe09d3f06fe3df11e09c08   user.custom=true
 -      EOF
        blob=$(git hash-object -w "$CUSTOM_CONFIG_FILE") &&
 +      cat >expect <<-EOF &&
 +              blob:$blob      user.custom=true
 +      EOF
        git config --blob=$blob --show-origin --list >output &&
        test_cmp expect output
  '
@@@ -1716,69 -1750,6 +1803,69 @@@ test_expect_success '--local requires 
        test_expect_code 128 nongit git config --local foo.bar
  '
  
 +cat >.git/config <<-\EOF &&
 +[core]
 +foo = true
 +number = 10
 +big = 1M
 +EOF
 +
 +test_expect_success 'identical modern --type specifiers are allowed' '
 +      git config --type=int --type=int core.big >actual &&
 +      echo 1048576 >expect &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'identical legacy --type specifiers are allowed' '
 +      git config --int --int core.big >actual &&
 +      echo 1048576 >expect &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'identical mixed --type specifiers are allowed' '
 +      git config --int --type=int core.big >actual &&
 +      echo 1048576 >expect &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'non-identical modern --type specifiers are not allowed' '
 +      test_must_fail git config --type=int --type=bool core.big 2>error &&
 +      test_i18ngrep "only one type at a time" error
 +'
 +
 +test_expect_success 'non-identical legacy --type specifiers are not allowed' '
 +      test_must_fail git config --int --bool core.big 2>error &&
 +      test_i18ngrep "only one type at a time" error
 +'
 +
 +test_expect_success 'non-identical mixed --type specifiers are not allowed' '
 +      test_must_fail git config --type=int --bool core.big 2>error &&
 +      test_i18ngrep "only one type at a time" error
 +'
 +
 +test_expect_success '--type allows valid type specifiers' '
 +      echo "true" >expect &&
 +      git config --type=bool core.foo >actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success '--no-type unsets type specifiers' '
 +      echo "10" >expect &&
 +      git config --type=bool --no-type core.number >actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'unset type specifiers may be reset to conflicting ones' '
 +      echo 1048576 >expect &&
 +      git config --type=bool --no-type --type=int core.big >actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success '--type rejects unknown specifiers' '
 +      test_must_fail git config --type=nonsense core.foo 2>error &&
 +      test_i18ngrep "unrecognized --type argument" error
 +'
 +
  test_expect_success '--replace-all does not invent newlines' '
        q_to_tab >.git/config <<-\EOF &&
        [abc]key