Merge branch 'ta/config-add-to-empty-or-true-fix'
authorJunio C Hamano <gitster@pobox.com>
Fri, 19 Sep 2014 18:38:40 +0000 (11:38 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 19 Sep 2014 18:38:40 +0000 (11:38 -0700)
"git config --add section.var val" used to lose existing
section.var whose value was an empty string.

* ta/config-add-to-empty-or-true-fix:
config: avoid a funny sentinel value "a^"
make config --add behave correctly for empty and NULL values

1  2 
builtin/config.c
cache.h
config.c
diff --combined builtin/config.c
index aba71355f864202932e02bddb804d2fa3c0783d8,bf1aa6b2e4e61784f3d6af3bf1ba2eaafbfbca3e..37305e93e937ade83072501df6b5d07033ee89d3
@@@ -395,6 -395,19 +395,6 @@@ static int urlmatch_collect_fn(const ch
        return 0;
  }
  
 -static char *dup_downcase(const char *string)
 -{
 -      char *result;
 -      size_t len, i;
 -
 -      len = strlen(string);
 -      result = xmalloc(len + 1);
 -      for (i = 0; i < len; i++)
 -              result[i] = tolower(string[i]);
 -      result[i] = '\0';
 -      return result;
 -}
 -
  static int get_urlmatch(const char *var, const char *url)
  {
        char *section_tail;
        if (!url_normalize(url, &config.url))
                die("%s", config.url.err);
  
 -      config.section = dup_downcase(var);
 +      config.section = xstrdup_tolower(var);
        section_tail = strchr(config.section, '.');
        if (section_tail) {
                *section_tail = '\0';
        return 0;
  }
  
 +static char *default_user_config(void)
 +{
 +      struct strbuf buf = STRBUF_INIT;
 +      strbuf_addf(&buf,
 +                  _("# This is Git's per-user configuration file.\n"
 +                    "[core]\n"
 +                    "# Please adapt and uncomment the following lines:\n"
 +                    "#        user = %s\n"
 +                    "#        email = %s\n"),
 +                  ident_default_name(),
 +                  ident_default_email());
 +      return strbuf_detach(&buf, NULL);
 +}
 +
  int cmd_config(int argc, const char **argv, const char *prefix)
  {
        int nongit = !startup_info->have_repository;
                }
        }
        else if (actions == ACTION_EDIT) {
 +              const char *config_file = given_config_source.file ?
 +                      given_config_source.file : git_path("config");
                check_argc(argc, 0, 0);
                if (!given_config_source.file && nongit)
                        die("not in a git directory");
                if (given_config_source.blob)
                        die("editing blobs is not supported");
                git_config(git_default_config, NULL);
 -              launch_editor(given_config_source.file ?
 -                            given_config_source.file : git_path("config"),
 -                            NULL, NULL);
 +              if (use_global_config) {
 +                      int fd = open(config_file, O_CREAT | O_EXCL | O_WRONLY, 0666);
 +                      if (fd) {
 +                              char *content = default_user_config();
 +                              write_str_in_full(fd, content);
 +                              free(content);
 +                              close(fd);
 +                      }
 +                      else if (errno != EEXIST)
 +                              die_errno(_("cannot create configuration file %s"), config_file);
 +              }
 +              launch_editor(config_file, NULL, NULL);
        }
        else if (actions == ACTION_SET) {
                int ret;
                check_argc(argc, 2, 2);
                value = normalize_value(argv[0], argv[1]);
                return git_config_set_multivar_in_file(given_config_source.file,
-                                                      argv[0], value, "^$", 0);
+                                                      argv[0], value,
+                                                      CONFIG_REGEX_NONE, 0);
        }
        else if (actions == ACTION_REPLACE_ALL) {
                check_write();
diff --combined cache.h
index db4ccd1cf7575dfc31b6c7e6720f31529d9edc02,8356168bb5db9c5043ea48462386b1cf278e8d3d..af590d5077d534503263dfe0ee80a69374c27053
+++ b/cache.h
@@@ -7,8 -7,6 +7,8 @@@
  #include "advice.h"
  #include "gettext.h"
  #include "convert.h"
 +#include "trace.h"
 +#include "string-list.h"
  
  #include SHA1_HEADER
  #ifndef git_SHA_CTX
@@@ -76,21 -74,6 +76,21 @@@ unsigned long git_deflate_bound(git_zst
  #define S_IFGITLINK   0160000
  #define S_ISGITLINK(m)        (((m) & S_IFMT) == S_IFGITLINK)
  
 +/*
 + * Some mode bits are also used internally for computations.
 + *
 + * They *must* not overlap with any valid modes, and they *must* not be emitted
 + * to outside world - i.e. appear on disk or network. In other words, it's just
 + * temporary fields, which we internally use, but they have to stay in-house.
 + *
 + * ( such approach is valid, as standard S_IF* fits into 16 bits, and in Git
 + *   codebase mode is `unsigned int` which is assumed to be at least 32 bits )
 + */
 +
 +/* used internally in tree-diff */
 +#define S_DIFFTREE_IFXMIN_NEQ 0x80000000
 +
 +
  /*
   * Intensive research over the course of many years has shown that
   * port 9418 is totally unused by anything else. Or
@@@ -152,7 -135,6 +152,7 @@@ struct cache_entry 
        unsigned int ce_mode;
        unsigned int ce_flags;
        unsigned int ce_namelen;
 +      unsigned int index;     /* for link extension */
        unsigned char sha1[20];
        char name[FLEX_ARRAY]; /* more */
  };
  #define CE_STAGESHIFT 12
  
  /*
 - * Range 0xFFFF0000 in ce_flags is divided into
 + * Range 0xFFFF0FFF in ce_flags is divided into
   * two parts: in-memory flags and on-disk ones.
   * Flags in CE_EXTENDED_FLAGS will get saved on-disk
   * if you want to save a new flag, add it in
  /* used to temporarily mark paths matched by pathspecs */
  #define CE_MATCHED           (1 << 26)
  
 +#define CE_UPDATE_IN_BASE    (1 << 27)
 +#define CE_STRIP_NAME        (1 << 28)
 +
  /*
   * Extended on-disk flags
   */
@@@ -289,22 -268,12 +289,22 @@@ static inline unsigned int canon_mode(u
  
  #define cache_entry_size(len) (offsetof(struct cache_entry,name) + (len) + 1)
  
 +#define SOMETHING_CHANGED     (1 << 0) /* unclassified changes go here */
 +#define CE_ENTRY_CHANGED      (1 << 1)
 +#define CE_ENTRY_REMOVED      (1 << 2)
 +#define CE_ENTRY_ADDED                (1 << 3)
 +#define RESOLVE_UNDO_CHANGED  (1 << 4)
 +#define CACHE_TREE_CHANGED    (1 << 5)
 +#define SPLIT_INDEX_ORDERED   (1 << 6)
 +
 +struct split_index;
  struct index_state {
        struct cache_entry **cache;
        unsigned int version;
        unsigned int cache_nr, cache_alloc, cache_changed;
        struct string_list *resolve_undo;
        struct cache_tree *cache_tree;
 +      struct split_index *split_index;
        struct cache_time timestamp;
        unsigned name_hash_initialized : 1,
                 initialized : 1;
@@@ -333,6 -302,7 +333,6 @@@ extern void free_name_hash(struct index
  #define read_cache_preload(pathspec) read_index_preload(&the_index, (pathspec))
  #define is_cache_unborn() is_index_unborn(&the_index)
  #define read_cache_unmerged() read_index_unmerged(&the_index)
 -#define write_cache(newfd, cache, entries) write_index(&the_index, (newfd))
  #define discard_cache() discard_index(&the_index)
  #define unmerged_cache() unmerged_index(&the_index)
  #define cache_name_pos(name, namelen) index_name_pos(&the_index,(name),(namelen))
@@@ -487,17 -457,12 +487,17 @@@ extern int daemonize(void)
        } while (0)
  
  /* Initialize and use the cache information */
 +struct lock_file;
  extern int read_index(struct index_state *);
  extern int read_index_preload(struct index_state *, const struct pathspec *pathspec);
 +extern int do_read_index(struct index_state *istate, const char *path,
 +                       int must_exist); /* for testting only! */
  extern int read_index_from(struct index_state *, const char *path);
  extern int is_index_unborn(struct index_state *);
  extern int read_index_unmerged(struct index_state *);
 -extern int write_index(struct index_state *, int newfd);
 +#define COMMIT_LOCK           (1 << 0)
 +#define CLOSE_LOCK            (1 << 1)
 +extern int write_locked_index(struct index_state *, struct lock_file *lock, unsigned flags);
  extern int discard_index(struct index_state *);
  extern int unmerged_index(const struct index_state *);
  extern int verify_path(const char *path);
@@@ -509,7 -474,6 +509,7 @@@ extern int index_name_pos(const struct 
  #define ADD_CACHE_SKIP_DFCHECK 4      /* Ok to skip DF conflict checks */
  #define ADD_CACHE_JUST_APPEND 8               /* Append only; tree.c::read_tree() */
  #define ADD_CACHE_NEW_ONLY 16         /* Do not replace existing ones */
 +#define ADD_CACHE_KEEP_CACHE_TREE 32  /* Do not invalidate cache-tree */
  extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option);
  extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name);
  extern int remove_index_entry_at(struct index_state *, int pos);
@@@ -580,16 -544,14 +580,16 @@@ struct lock_file 
  #define LOCK_DIE_ON_ERROR 1
  #define LOCK_NODEREF 2
  extern int unable_to_lock_error(const char *path, int err);
 +extern void unable_to_lock_message(const char *path, int err,
 +                                 struct strbuf *buf);
  extern NORETURN void unable_to_lock_index_die(const char *path, int err);
  extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
  extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
  extern int commit_lock_file(struct lock_file *);
 +extern int reopen_lock_file(struct lock_file *);
  extern void update_index_if_able(struct index_state *, struct lock_file *);
  
  extern int hold_locked_index(struct lock_file *, int);
 -extern int commit_locked_index(struct lock_file *);
  extern void set_alternate_index_output(const char *);
  extern int close_lock_file(struct lock_file *);
  extern void rollback_lock_file(struct lock_file *);
@@@ -641,7 -603,6 +641,7 @@@ extern int precomposed_unicode
   * that is subject to stripspace.
   */
  extern char comment_line_char;
 +extern int auto_comment_line_char;
  
  enum branch_track {
        BRANCH_TRACK_UNSPECIFIED = -1,
@@@ -849,6 -810,7 +849,6 @@@ int normalize_path_copy(char *dst, cons
  int longest_ancestor_length(const char *path, struct string_list *prefixes);
  char *strip_path_suffix(const char *path, const char *suffix);
  int daemon_avoid_alias(const char *path);
 -int offset_1st_component(const char *path);
  
  /* object replacement */
  #define LOOKUP_REPLACE_OBJECT 1
@@@ -1000,7 -962,7 +1000,7 @@@ extern int read_ref(const char *refname
   * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,
   * give up and return NULL.
   *
 - * errno is sometimes set on errors, but not always.
 + * errno is set to something meaningful on error.
   */
  extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int reading, int *flag);
  extern char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag);
@@@ -1022,7 -984,7 +1022,7 @@@ extern int validate_headref(const char 
  
  extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
  extern int df_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
 -extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2);
 +extern int name_compare(const char *name1, size_t len1, const char *name2, size_t len2);
  extern int cache_name_stage_compare(const char *name1, int len1, int stage1, const char *name2, int len2, int stage2);
  
  extern void *read_object_with_reference(const unsigned char *sha1,
@@@ -1039,7 -1001,6 +1039,7 @@@ enum date_mode 
        DATE_SHORT,
        DATE_LOCAL,
        DATE_ISO8601,
 +      DATE_ISO8601_STRICT,
        DATE_RFC2822,
        DATE_RAW
  };
  const char *show_date(unsigned long time, int timezone, enum date_mode mode);
  void show_date_relative(unsigned long time, int tz, const struct timeval *now,
                        struct strbuf *timebuf);
 -int parse_date(const char *date, char *buf, int bufsize);
 +int parse_date(const char *date, struct strbuf *out);
  int parse_date_basic(const char *date, unsigned long *timestamp, int *offset);
  int parse_expiry_date(const char *date, unsigned long *timestamp);
 -void datestamp(char *buf, int bufsize);
 +void datestamp(struct strbuf *out);
  #define approxidate(s) approxidate_careful((s), NULL)
  unsigned long approxidate_careful(const char *, int *);
  unsigned long approxidate_relative(const char *date, const struct timeval *now);
@@@ -1064,7 -1025,6 +1064,7 @@@ extern const char *git_author_info(int)
  extern const char *git_committer_info(int);
  extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int);
  extern const char *fmt_name(const char *name, const char *email);
 +extern const char *ident_default_name(void);
  extern const char *ident_default_email(void);
  extern const char *git_editor(void);
  extern const char *git_pager(int stdout_is_tty);
@@@ -1086,13 -1046,6 +1086,13 @@@ struct ident_split 
   */
  extern int split_ident_line(struct ident_split *, const char *, int);
  
 +/*
 + * Like show_date, but pull the timestamp and tz parameters from
 + * the ident_split. It will also sanity-check the values and produce
 + * a well-known sentinel date if they appear bogus.
 + */
 +const char *show_ident_date(const struct ident_split *id, enum date_mode mode);
 +
  /*
   * Compare split idents for equality or strict ordering. Note that we
   * compare only the ident part of the line, ignoring any timestamp.
  extern int ident_cmp(const struct ident_split *, const struct ident_split *);
  
  struct checkout {
 +      struct index_state *istate;
        const char *base_dir;
        int base_dir_len;
        unsigned force:1,
  extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
  
  struct cache_def {
 -      char path[PATH_MAX + 1];
 -      int len;
 +      struct strbuf path;
        int flags;
        int track_flags;
        int prefix_len_stat_func;
  };
 +#define CACHE_DEF_INIT { STRBUF_INIT, 0, 0, 0 }
 +static inline void cache_def_clear(struct cache_def *cache)
 +{
 +      strbuf_release(&cache->path);
 +}
  
  extern int has_symlink_leading_path(const char *name, int len);
  extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int);
@@@ -1285,6 -1233,8 +1285,8 @@@ extern int update_server_info(int)
  #define CONFIG_INVALID_PATTERN 6
  #define CONFIG_GENERIC_ERROR 7
  
+ #define CONFIG_REGEX_NONE ((void *)1)
  struct git_config_source {
        unsigned int use_stdin:1;
        const char *file;
@@@ -1298,7 -1248,7 +1300,7 @@@ extern int git_config_from_buf(config_f
                               const char *buf, size_t len, void *data);
  extern void git_config_push_parameter(const char *text);
  extern int git_config_from_parameters(config_fn_t fn, void *data);
 -extern int git_config(config_fn_t fn, void *);
 +extern void git_config(config_fn_t fn, void *);
  extern int git_config_with_options(config_fn_t fn, void *,
                                   struct git_config_source *config_source,
                                   int respect_includes);
@@@ -1324,8 -1274,8 +1326,8 @@@ extern int check_repository_format_vers
  extern int git_env_bool(const char *, int);
  extern int git_config_system(void);
  extern int config_error_nonbool(const char *);
 -#if defined(__GNUC__) && ! defined(__clang__)
 -#define config_error_nonbool(s) (config_error_nonbool(s), -1)
 +#if defined(__GNUC__)
 +#define config_error_nonbool(s) (config_error_nonbool(s), const_error())
  #endif
  extern const char *get_log_output_encoding(void);
  extern const char *get_commit_output_encoding(void);
@@@ -1355,69 -1305,6 +1357,69 @@@ extern int parse_config_key(const char 
                            const char **subsection, int *subsection_len,
                            const char **key);
  
 +struct config_set_element {
 +      struct hashmap_entry ent;
 +      char *key;
 +      struct string_list value_list;
 +};
 +
 +struct configset_list_item {
 +      struct config_set_element *e;
 +      int value_index;
 +};
 +
 +/*
 + * the contents of the list are ordered according to their
 + * position in the config files and order of parsing the files.
 + * (i.e. key-value pair at the last position of .git/config will
 + * be at the last item of the list)
 + */
 +struct configset_list {
 +      struct configset_list_item *items;
 +      unsigned int nr, alloc;
 +};
 +
 +struct config_set {
 +      struct hashmap config_hash;
 +      int hash_initialized;
 +      struct configset_list list;
 +};
 +
 +extern void git_configset_init(struct config_set *cs);
 +extern int git_configset_add_file(struct config_set *cs, const char *filename);
 +extern int git_configset_get_value(struct config_set *cs, const char *key, const char **value);
 +extern const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key);
 +extern void git_configset_clear(struct config_set *cs);
 +extern int git_configset_get_string_const(struct config_set *cs, const char *key, const char **dest);
 +extern int git_configset_get_string(struct config_set *cs, const char *key, char **dest);
 +extern int git_configset_get_int(struct config_set *cs, const char *key, int *dest);
 +extern int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest);
 +extern int git_configset_get_bool(struct config_set *cs, const char *key, int *dest);
 +extern int git_configset_get_bool_or_int(struct config_set *cs, const char *key, int *is_bool, int *dest);
 +extern int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest);
 +extern int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest);
 +
 +extern int git_config_get_value(const char *key, const char **value);
 +extern const struct string_list *git_config_get_value_multi(const char *key);
 +extern void git_config_clear(void);
 +extern void git_config_iter(config_fn_t fn, void *data);
 +extern int git_config_get_string_const(const char *key, const char **dest);
 +extern int git_config_get_string(const char *key, char **dest);
 +extern int git_config_get_int(const char *key, int *dest);
 +extern int git_config_get_ulong(const char *key, unsigned long *dest);
 +extern int git_config_get_bool(const char *key, int *dest);
 +extern int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest);
 +extern int git_config_get_maybe_bool(const char *key, int *dest);
 +extern int git_config_get_pathname(const char *key, const char **dest);
 +
 +struct key_value_info {
 +      const char *filename;
 +      int linenr;
 +};
 +
 +extern NORETURN void git_die_config(const char *key, const char *err, ...) __attribute__((format(printf, 2, 3)));
 +extern NORETURN void git_die_config_linenr(const char *key, const char *filename, int linenr);
 +
  extern int committer_ident_sufficiently_given(void);
  extern int author_ident_sufficiently_given(void);
  
@@@ -1471,7 -1358,17 +1473,7 @@@ extern void *alloc_object_node(void)
  extern void alloc_report(void);
  extern unsigned int alloc_commit_index(void);
  
 -/* trace.c */
 -__attribute__((format (printf, 1, 2)))
 -extern void trace_printf(const char *format, ...);
 -__attribute__((format (printf, 2, 3)))
 -extern void trace_argv_printf(const char **argv, const char *format, ...);
 -extern void trace_repo_setup(const char *prefix);
 -extern int trace_want(const char *key);
 -__attribute__((format (printf, 2, 3)))
 -extern void trace_printf_key(const char *key, const char *fmt, ...);
 -extern void trace_strbuf(const char *key, const struct strbuf *buf);
 -
 +/* pkt-line.c */
  void packet_trace_identity(const char *prog);
  
  /* add */
diff --combined config.c
index 179b681af207ee173a806f1c2dc6346abcc39f1e,2e709bf93cdfc4951c99812b69f4d08d8c0c1e93..a677eb6c599ddb39f1b4bb7ac44a59bc167fb3a7
+++ b/config.c
@@@ -9,8 -9,6 +9,8 @@@
  #include "exec_cmd.h"
  #include "strbuf.h"
  #include "quote.h"
 +#include "hashmap.h"
 +#include "string-list.h"
  
  struct config_source {
        struct config_source *prev;
@@@ -39,13 -37,6 +39,13 @@@ static struct config_source *cf
  
  static int zlib_compression_seen;
  
 +/*
 + * Default config_set that contains key-value pairs from the usual set of config
 + * config files (i.e repo specific .git/config, user wide ~/.gitconfig, XDG
 + * config file and the global /etc/gitconfig)
 + */
 +static struct config_set the_config_set;
 +
  static int config_file_fgetc(struct config_source *conf)
  {
        return fgetc(conf->u.file);
@@@ -136,6 -127,7 +136,6 @@@ static int handle_path_include(const ch
  int git_config_include(const char *var, const char *value, void *data)
  {
        struct config_include_data *inc = data;
 -      const char *type;
        int ret;
  
        /*
        if (ret < 0)
                return ret;
  
 -      type = skip_prefix(var, "include.");
 -      if (!type)
 -              return ret;
 -
 -      if (!strcmp(type, "path"))
 +      if (!strcmp(var, "include.path"))
                ret = handle_path_include(value, inc);
        return ret;
  }
  
 -static void lowercase(char *p)
 -{
 -      for (; *p; p++)
 -              *p = tolower(*p);
 -}
 -
  void git_config_push_parameter(const char *text)
  {
        struct strbuf env = STRBUF_INIT;
  int git_config_parse_parameter(const char *text,
                               config_fn_t fn, void *data)
  {
 +      const char *value;
        struct strbuf **pair;
 +
        pair = strbuf_split_str(text, '=', 2);
        if (!pair[0])
                return error("bogus config parameter: %s", text);
 -      if (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=')
 +
 +      if (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=') {
                strbuf_setlen(pair[0], pair[0]->len - 1);
 +              value = pair[1] ? pair[1]->buf : "";
 +      } else {
 +              value = NULL;
 +      }
 +
        strbuf_trim(pair[0]);
        if (!pair[0]->len) {
                strbuf_list_free(pair);
                return error("bogus config parameter: %s", text);
        }
 -      lowercase(pair[0]->buf);
 -      if (fn(pair[0]->buf, pair[1] ? pair[1]->buf : NULL, data) < 0) {
 +      strbuf_tolower(pair[0]);
 +      if (fn(pair[0]->buf, value, data) < 0) {
                strbuf_list_free(pair);
                return -1;
        }
@@@ -242,7 -236,6 +242,7 @@@ static int get_next_char(void
                cf->linenr++;
        if (c == EOF) {
                cf->eof = 1;
 +              cf->linenr++;
                c = '\n';
        }
        return c;
@@@ -318,7 -311,6 +318,7 @@@ static int get_value(config_fn_t fn, vo
  {
        int c;
        char *value;
 +      int ret;
  
        /* Get the full name */
        for (;;) {
                if (!value)
                        return -1;
        }
 -      return fn(name->buf, value, data);
 +      /*
 +       * We already consumed the \n, but we need linenr to point to
 +       * the line we just parsed during the call to fn to get
 +       * accurate line number in error messages.
 +       */
 +      cf->linenr--;
 +      ret = fn(name->buf, value, data);
 +      cf->linenr++;
 +      return ret;
  }
  
  static int get_extended_base_var(struct strbuf *name, int c)
@@@ -465,9 -449,9 +465,9 @@@ static int git_parse_source(config_fn_
                        break;
        }
        if (cf->die_on_error)
 -              die("bad config file line %d in %s", cf->linenr, cf->name);
 +              die(_("bad config file line %d in %s"), cf->linenr, cf->name);
        else
 -              return error("bad config file line %d in %s", cf->linenr, cf->name);
 +              return error(_("bad config file line %d in %s"), cf->linenr, cf->name);
  }
  
  static int parse_unit_factor(const char *end, uintmax_t *val)
@@@ -583,9 -567,9 +583,9 @@@ static void die_bad_number(const char *
                value = "";
  
        if (cf && cf->name)
 -              die("bad numeric config value '%s' for '%s' in %s: %s",
 +              die(_("bad numeric config value '%s' for '%s' in %s: %s"),
                    value, name, cf->name, reason);
 -      die("bad numeric config value '%s' for '%s': %s", value, name, reason);
 +      die(_("bad numeric config value '%s' for '%s': %s"), value, name, reason);
  }
  
  int git_config_int(const char *name, const char *value)
@@@ -670,7 -654,7 +670,7 @@@ int git_config_pathname(const char **de
                return config_error_nonbool(var);
        *dest = expand_user_path(value);
        if (!*dest)
 -              die("Failed to expand user dir in: '%s'", value);
 +              die(_("failed to expand user dir in: '%s'"), value);
        return 0;
  }
  
@@@ -748,7 -732,7 +748,7 @@@ static int git_default_core_config(cons
                if (level == -1)
                        level = Z_DEFAULT_COMPRESSION;
                else if (level < 0 || level > Z_BEST_COMPRESSION)
 -                      die("bad zlib compression level %d", level);
 +                      die(_("bad zlib compression level %d"), level);
                zlib_compression_level = level;
                zlib_compression_seen = 1;
                return 0;
                if (level == -1)
                        level = Z_DEFAULT_COMPRESSION;
                else if (level < 0 || level > Z_BEST_COMPRESSION)
 -                      die("bad zlib compression level %d", level);
 +                      die(_("bad zlib compression level %d"), level);
                core_compression_level = level;
                core_compression_seen = 1;
                if (!zlib_compression_seen)
                return git_config_string(&editor_program, var, value);
  
        if (!strcmp(var, "core.commentchar")) {
 -              const char *comment;
 -              int ret = git_config_string(&comment, var, value);
 -              if (!ret)
 -                      comment_line_char = comment[0];
 -              return ret;
 +              if (!value)
 +                      return config_error_nonbool(var);
 +              else if (!strcasecmp(value, "auto"))
 +                      auto_comment_line_char = 1;
 +              else if (value[0] && !value[1]) {
 +                      comment_line_char = value[0];
 +                      auto_comment_line_char = 0;
 +              } else
 +                      return error("core.commentChar should only be one character");
 +              return 0;
        }
  
        if (!strcmp(var, "core.askpass"))
                else if (!strcmp(value, "link"))
                        object_creation_mode = OBJECT_CREATION_USES_HARDLINKS;
                else
 -                      die("Invalid mode for object creation: %s", value);
 +                      die(_("invalid mode for object creation: %s"), value);
                return 0;
        }
  
@@@ -1181,7 -1160,7 +1181,7 @@@ int git_config_early(config_fn_t fn, vo
  
        switch (git_config_from_parameters(fn, data)) {
        case -1: /* error */
 -              die("unable to parse command-line config");
 +              die(_("unable to parse command-line config"));
                break;
        case 0: /* found nothing */
                break;
@@@ -1228,365 -1207,9 +1228,365 @@@ int git_config_with_options(config_fn_
        return ret;
  }
  
 -int git_config(config_fn_t fn, void *data)
 +static void git_config_raw(config_fn_t fn, void *data)
 +{
 +      if (git_config_with_options(fn, data, NULL, 1) < 0)
 +              /*
 +               * git_config_with_options() normally returns only
 +               * positive values, as most errors are fatal, and
 +               * non-fatal potential errors are guarded by "if"
 +               * statements that are entered only when no error is
 +               * possible.
 +               *
 +               * If we ever encounter a non-fatal error, it means
 +               * something went really wrong and we should stop
 +               * immediately.
 +               */
 +              die(_("unknown error occured while reading the configuration files"));
 +}
 +
 +static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
 +{
 +      int i, value_index;
 +      struct string_list *values;
 +      struct config_set_element *entry;
 +      struct configset_list *list = &cs->list;
 +      struct key_value_info *kv_info;
 +
 +      for (i = 0; i < list->nr; i++) {
 +              entry = list->items[i].e;
 +              value_index = list->items[i].value_index;
 +              values = &entry->value_list;
 +              if (fn(entry->key, values->items[value_index].string, data) < 0) {
 +                      kv_info = values->items[value_index].util;
 +                      git_die_config_linenr(entry->key, kv_info->filename, kv_info->linenr);
 +              }
 +      }
 +}
 +
 +static void git_config_check_init(void);
 +
 +void git_config(config_fn_t fn, void *data)
 +{
 +      git_config_check_init();
 +      configset_iter(&the_config_set, fn, data);
 +}
 +
 +static struct config_set_element *configset_find_element(struct config_set *cs, const char *key)
 +{
 +      struct config_set_element k;
 +      struct config_set_element *found_entry;
 +      char *normalized_key;
 +      int ret;
 +      /*
 +       * `key` may come from the user, so normalize it before using it
 +       * for querying entries from the hashmap.
 +       */
 +      ret = git_config_parse_key(key, &normalized_key, NULL);
 +
 +      if (ret)
 +              return NULL;
 +
 +      hashmap_entry_init(&k, strhash(normalized_key));
 +      k.key = normalized_key;
 +      found_entry = hashmap_get(&cs->config_hash, &k, NULL);
 +      free(normalized_key);
 +      return found_entry;
 +}
 +
 +static int configset_add_value(struct config_set *cs, const char *key, const char *value)
  {
 -      return git_config_with_options(fn, data, NULL, 1);
 +      struct config_set_element *e;
 +      struct string_list_item *si;
 +      struct configset_list_item *l_item;
 +      struct key_value_info *kv_info = xmalloc(sizeof(*kv_info));
 +
 +      e = configset_find_element(cs, key);
 +      /*
 +       * Since the keys are being fed by git_config*() callback mechanism, they
 +       * are already normalized. So simply add them without any further munging.
 +       */
 +      if (!e) {
 +              e = xmalloc(sizeof(*e));
 +              hashmap_entry_init(e, strhash(key));
 +              e->key = xstrdup(key);
 +              string_list_init(&e->value_list, 1);
 +              hashmap_add(&cs->config_hash, e);
 +      }
 +      si = string_list_append_nodup(&e->value_list, value ? xstrdup(value) : NULL);
 +
 +      ALLOC_GROW(cs->list.items, cs->list.nr + 1, cs->list.alloc);
 +      l_item = &cs->list.items[cs->list.nr++];
 +      l_item->e = e;
 +      l_item->value_index = e->value_list.nr - 1;
 +
 +      if (cf) {
 +              kv_info->filename = strintern(cf->name);
 +              kv_info->linenr = cf->linenr;
 +      } else {
 +              /* for values read from `git_config_from_parameters()` */
 +              kv_info->filename = NULL;
 +              kv_info->linenr = -1;
 +      }
 +      si->util = kv_info;
 +
 +      return 0;
 +}
 +
 +static int config_set_element_cmp(const struct config_set_element *e1,
 +                               const struct config_set_element *e2, const void *unused)
 +{
 +      return strcmp(e1->key, e2->key);
 +}
 +
 +void git_configset_init(struct config_set *cs)
 +{
 +      hashmap_init(&cs->config_hash, (hashmap_cmp_fn)config_set_element_cmp, 0);
 +      cs->hash_initialized = 1;
 +      cs->list.nr = 0;
 +      cs->list.alloc = 0;
 +      cs->list.items = NULL;
 +}
 +
 +void git_configset_clear(struct config_set *cs)
 +{
 +      struct config_set_element *entry;
 +      struct hashmap_iter iter;
 +      if (!cs->hash_initialized)
 +              return;
 +
 +      hashmap_iter_init(&cs->config_hash, &iter);
 +      while ((entry = hashmap_iter_next(&iter))) {
 +              free(entry->key);
 +              string_list_clear(&entry->value_list, 1);
 +      }
 +      hashmap_free(&cs->config_hash, 1);
 +      cs->hash_initialized = 0;
 +      free(cs->list.items);
 +      cs->list.nr = 0;
 +      cs->list.alloc = 0;
 +      cs->list.items = NULL;
 +}
 +
 +static int config_set_callback(const char *key, const char *value, void *cb)
 +{
 +      struct config_set *cs = cb;
 +      configset_add_value(cs, key, value);
 +      return 0;
 +}
 +
 +int git_configset_add_file(struct config_set *cs, const char *filename)
 +{
 +      return git_config_from_file(config_set_callback, filename, cs);
 +}
 +
 +int git_configset_get_value(struct config_set *cs, const char *key, const char **value)
 +{
 +      const struct string_list *values = NULL;
 +      /*
 +       * Follows "last one wins" semantic, i.e., if there are multiple matches for the
 +       * queried key in the files of the configset, the value returned will be the last
 +       * value in the value list for that key.
 +       */
 +      values = git_configset_get_value_multi(cs, key);
 +
 +      if (!values)
 +              return 1;
 +      assert(values->nr > 0);
 +      *value = values->items[values->nr - 1].string;
 +      return 0;
 +}
 +
 +const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key)
 +{
 +      struct config_set_element *e = configset_find_element(cs, key);
 +      return e ? &e->value_list : NULL;
 +}
 +
 +int git_configset_get_string_const(struct config_set *cs, const char *key, const char **dest)
 +{
 +      const char *value;
 +      if (!git_configset_get_value(cs, key, &value))
 +              return git_config_string(dest, key, value);
 +      else
 +              return 1;
 +}
 +
 +int git_configset_get_string(struct config_set *cs, const char *key, char **dest)
 +{
 +      return git_configset_get_string_const(cs, key, (const char **)dest);
 +}
 +
 +int git_configset_get_int(struct config_set *cs, const char *key, int *dest)
 +{
 +      const char *value;
 +      if (!git_configset_get_value(cs, key, &value)) {
 +              *dest = git_config_int(key, value);
 +              return 0;
 +      } else
 +              return 1;
 +}
 +
 +int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest)
 +{
 +      const char *value;
 +      if (!git_configset_get_value(cs, key, &value)) {
 +              *dest = git_config_ulong(key, value);
 +              return 0;
 +      } else
 +              return 1;
 +}
 +
 +int git_configset_get_bool(struct config_set *cs, const char *key, int *dest)
 +{
 +      const char *value;
 +      if (!git_configset_get_value(cs, key, &value)) {
 +              *dest = git_config_bool(key, value);
 +              return 0;
 +      } else
 +              return 1;
 +}
 +
 +int git_configset_get_bool_or_int(struct config_set *cs, const char *key,
 +                              int *is_bool, int *dest)
 +{
 +      const char *value;
 +      if (!git_configset_get_value(cs, key, &value)) {
 +              *dest = git_config_bool_or_int(key, value, is_bool);
 +              return 0;
 +      } else
 +              return 1;
 +}
 +
 +int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest)
 +{
 +      const char *value;
 +      if (!git_configset_get_value(cs, key, &value)) {
 +              *dest = git_config_maybe_bool(key, value);
 +              if (*dest == -1)
 +                      return -1;
 +              return 0;
 +      } else
 +              return 1;
 +}
 +
 +int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest)
 +{
 +      const char *value;
 +      if (!git_configset_get_value(cs, key, &value))
 +              return git_config_pathname(dest, key, value);
 +      else
 +              return 1;
 +}
 +
 +static void git_config_check_init(void)
 +{
 +      if (the_config_set.hash_initialized)
 +              return;
 +      git_configset_init(&the_config_set);
 +      git_config_raw(config_set_callback, &the_config_set);
 +}
 +
 +void git_config_clear(void)
 +{
 +      if (!the_config_set.hash_initialized)
 +              return;
 +      git_configset_clear(&the_config_set);
 +}
 +
 +int git_config_get_value(const char *key, const char **value)
 +{
 +      git_config_check_init();
 +      return git_configset_get_value(&the_config_set, key, value);
 +}
 +
 +const struct string_list *git_config_get_value_multi(const char *key)
 +{
 +      git_config_check_init();
 +      return git_configset_get_value_multi(&the_config_set, key);
 +}
 +
 +int git_config_get_string_const(const char *key, const char **dest)
 +{
 +      int ret;
 +      git_config_check_init();
 +      ret = git_configset_get_string_const(&the_config_set, key, dest);
 +      if (ret < 0)
 +              git_die_config(key, NULL);
 +      return ret;
 +}
 +
 +int git_config_get_string(const char *key, char **dest)
 +{
 +      git_config_check_init();
 +      return git_config_get_string_const(key, (const char **)dest);
 +}
 +
 +int git_config_get_int(const char *key, int *dest)
 +{
 +      git_config_check_init();
 +      return git_configset_get_int(&the_config_set, key, dest);
 +}
 +
 +int git_config_get_ulong(const char *key, unsigned long *dest)
 +{
 +      git_config_check_init();
 +      return git_configset_get_ulong(&the_config_set, key, dest);
 +}
 +
 +int git_config_get_bool(const char *key, int *dest)
 +{
 +      git_config_check_init();
 +      return git_configset_get_bool(&the_config_set, key, dest);
 +}
 +
 +int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest)
 +{
 +      git_config_check_init();
 +      return git_configset_get_bool_or_int(&the_config_set, key, is_bool, dest);
 +}
 +
 +int git_config_get_maybe_bool(const char *key, int *dest)
 +{
 +      git_config_check_init();
 +      return git_configset_get_maybe_bool(&the_config_set, key, dest);
 +}
 +
 +int git_config_get_pathname(const char *key, const char **dest)
 +{
 +      int ret;
 +      git_config_check_init();
 +      ret = git_configset_get_pathname(&the_config_set, key, dest);
 +      if (ret < 0)
 +              git_die_config(key, NULL);
 +      return ret;
 +}
 +
 +NORETURN
 +void git_die_config_linenr(const char *key, const char *filename, int linenr)
 +{
 +      if (!filename)
 +              die(_("unable to parse '%s' from command-line config"), key);
 +      else
 +              die(_("bad config variable '%s' in file '%s' at line %d"),
 +                  key, filename, linenr);
 +}
 +
 +NORETURN __attribute__((format(printf, 2, 3)))
 +void git_die_config(const char *key, const char *err, ...)
 +{
 +      const struct string_list *values;
 +      struct key_value_info *kv_info;
 +
 +      if (err) {
 +              va_list params;
 +              va_start(params, err);
 +              vreportf("error: ", err, params);
 +              va_end(params);
 +      }
 +      values = git_config_get_value_multi(key);
 +      kv_info = values->items[values->nr - 1].util;
 +      git_die_config_linenr(key, kv_info->filename, kv_info->linenr);
  }
  
  /*
@@@ -1607,10 -1230,15 +1607,15 @@@ static struct 
  
  static int matches(const char *key, const char *value)
  {
-       return !strcmp(key, store.key) &&
-               (store.value_regex == NULL ||
-                (store.do_not_match ^
-                 !regexec(store.value_regex, value, 0, NULL, 0)));
+       if (strcmp(key, store.key))
+               return 0; /* not ours */
+       if (!store.value_regex)
+               return 1; /* always matches */
+       if (store.value_regex == CONFIG_REGEX_NONE)
+               return 0; /* never matches */
+       return store.do_not_match ^
+               (value && !regexec(store.value_regex, value, 0, NULL, 0));
  }
  
  static int store_aux(const char *key, const char *value, void *cb)
        case KEY_SEEN:
                if (matches(key, value)) {
                        if (store.seen == 1 && store.multi_replace == 0) {
 -                              warning("%s has multiple values", key);
 +                              warning(_("%s has multiple values"), key);
                        }
  
                        ALLOC_GROW(store.offset, store.seen + 1,
@@@ -1872,6 -1500,8 +1877,8 @@@ out_free_ret_1
  /*
   * If value==NULL, unset in (remove from) config,
   * if value_regex!=NULL, disregard key/value pairs where value does not match.
+  * if value_regex==CONFIG_REGEX_NONE, do not match any existing values
+  *     (only add a new one)
   * if multi_replace==0, nothing, or only one matching key/value is replaced,
   *     else all matching key/values (regardless how many) are removed,
   *     before the new pair is written.
@@@ -1955,6 -1585,8 +1962,8 @@@ int git_config_set_multivar_in_file(con
  
                if (value_regex == NULL)
                        store.value_regex = NULL;
+               else if (value_regex == CONFIG_REGEX_NONE)
+                       store.value_regex = CONFIG_REGEX_NONE;
                else {
                        if (value_regex[0] == '!') {
                                store.do_not_match = 1;
                if (git_config_from_file(store_aux, config_filename, NULL)) {
                        error("invalid config file %s", config_filename);
                        free(store.key);
-                       if (store.value_regex != NULL) {
+                       if (store.value_regex != NULL &&
+                           store.value_regex != CONFIG_REGEX_NONE) {
                                regfree(store.value_regex);
                                free(store.value_regex);
                        }
                }
  
                free(store.key);
-               if (store.value_regex != NULL) {
+               if (store.value_regex != NULL &&
+                   store.value_regex != CONFIG_REGEX_NONE) {
                        regfree(store.value_regex);
                        free(store.value_regex);
                }
                        MAP_PRIVATE, in_fd, 0);
                close(in_fd);
  
 +              if (chmod(lock->filename, st.st_mode & 07777) < 0) {
 +                      error("chmod on %s failed: %s",
 +                              lock->filename, strerror(errno));
 +                      ret = CONFIG_NO_WRITE;
 +                      goto out_free;
 +              }
 +
                if (store.seen == 0)
                        store.seen = 1;
  
        lock = NULL;
        ret = 0;
  
 +      /* Invalidate the config cache */
 +      git_config_clear();
 +
  out_free:
        if (lock)
                rollback_lock_file(lock);
@@@ -2171,7 -1795,6 +2182,7 @@@ int git_config_rename_section_in_file(c
        int out_fd;
        char buf[1024];
        FILE *config_file;
 +      struct stat st;
  
        if (new_name && !section_name_is_ok(new_name)) {
                ret = error("invalid section name: %s", new_name);
                goto unlock_and_out;
        }
  
 +      fstat(fileno(config_file), &st);
 +
 +      if (chmod(lock->filename, st.st_mode & 07777) < 0) {
 +              ret = error("chmod on %s failed: %s",
 +                              lock->filename, strerror(errno));
 +              goto out;
 +      }
 +
        while (fgets(buf, sizeof(buf), config_file)) {
                int i;
                int length;