Merge branch 'nd/complete-config-vars'
authorJunio C Hamano <gitster@pobox.com>
Mon, 25 Jun 2018 20:22:38 +0000 (13:22 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 25 Jun 2018 20:22:38 +0000 (13:22 -0700)
Continuing with the idea to programatically enumerate various
pieces of data required for command line completion, teach the
codebase to report the list of configuration variables
subcommands care about to help complete them.

* nd/complete-config-vars:
completion: complete general config vars in two steps
log-tree: allow to customize 'grafted' color
completion: support case-insensitive config vars
completion: keep other config var completion in camelCase
completion: drop the hard coded list of config vars
am: move advice.amWorkDir parsing back to advice.c
advice: keep config name in camelCase in advice_config[]
fsck: produce camelCase config key names
help: add --config to list all available config
fsck: factor out msg_id_info[] lazy initialization code
grep: keep all colors in an array
Add and use generic name->id mapping code for color slot parsing

1  2 
Documentation/config.txt
builtin/am.c
builtin/branch.c
builtin/commit.c
config.c
contrib/completion/git-completion.bash
diff.c
fsck.c
grep.c
log-tree.c
diff --combined Documentation/config.txt
index ab641bf5a9984b7ab2dfea787a0db5704f79a448,4bb1b6f9af70dcc7403db6d84b183dcbe6808902..1cc18a828ca63bc489726dc3f1489a5e5e5c8b2b
@@@ -1162,7 -1162,8 +1162,8 @@@ color.diff.<slot>:
  color.decorate.<slot>::
        Use customized color for 'git log --decorate' output.  `<slot>` is one
        of `branch`, `remoteBranch`, `tag`, `stash` or `HEAD` for local
-       branches, remote-tracking branches, tags, stash and HEAD, respectively.
+       branches, remote-tracking branches, tags, stash and HEAD, respectively
+       and `grafted` for grafted commits.
  
  color.grep::
        When set to `always`, always highlight matches.  When `false` (or
@@@ -1251,33 -1252,6 +1252,33 @@@ color.status.<slot>:
        status short-format), or
        `unmerged` (files which have unmerged changes).
  
 +color.blame.repeatedLines::
 +      Use the customized color for the part of git-blame output that
 +      is repeated meta information per line (such as commit id,
 +      author name, date and timezone). Defaults to cyan.
 +
 +color.blame.highlightRecent::
 +      This can be used to color the metadata of a blame line depending
 +      on age of the line.
 ++
 +This setting should be set to a comma-separated list of color and date settings,
 +starting and ending with a color, the dates should be set from oldest to newest.
 +The metadata will be colored given the colors if the the line was introduced
 +before the given timestamp, overwriting older timestamped colors.
 ++
 +Instead of an absolute timestamp relative timestamps work as well, e.g.
 +2.weeks.ago is valid to address anything older than 2 weeks.
 ++
 +It defaults to 'blue,12 month ago,white,1 month ago,red', which colors
 +everything older than one year blue, recent changes between one month and
 +one year old are kept white, and lines introduced within the last month are
 +colored red.
 +
 +blame.coloring::
 +      This determines the coloring scheme to be applied to blame
 +      output. It can be 'repeatedLines', 'highlightRecent',
 +      or 'none' which is the default.
 +
  color.transport::
        A boolean to enable/disable color when pushes are rejected. May be
        set to `always`, `false` (or `never`) or `auto` (or `true`), in which
@@@ -3214,18 -3188,6 +3215,18 @@@ status.displayCommentPrefix:
        behavior of linkgit:git-status[1] in Git 1.8.4 and previous.
        Defaults to false.
  
 +status.renameLimit::
 +      The number of files to consider when performing rename detection
 +      in linkgit:git-status[1] and linkgit:git-commit[1]. Defaults to
 +      the value of diff.renameLimit.
 +
 +status.renames::
 +      Whether and how Git detects renames in linkgit:git-status[1] and
 +      linkgit:git-commit[1] .  If set to "false", rename detection is
 +      disabled. If set to "true", basic rename detection is enabled.
 +      If set to "copies" or "copy", Git will detect copies, as well.
 +      Defaults to the value of diff.renames.
 +
  status.showStash::
        If set to true, linkgit:git-status[1] will display the number of
        entries currently stashed away.
diff --combined builtin/am.c
index 2fc2d1e82c5ead598476964329b555e38de9a495,93130aaa3544a0f7b0aa5154ad0169f5afa31046..6273ea5195bb7f7f2296155753c5e7982d533d66
@@@ -403,11 -403,11 +403,11 @@@ static void am_load(struct am_state *st
        struct strbuf sb = STRBUF_INIT;
  
        if (read_state_file(&sb, state, "next", 1) < 0)
 -              die("BUG: state file 'next' does not exist");
 +              BUG("state file 'next' does not exist");
        state->cur = strtol(sb.buf, NULL, 10);
  
        if (read_state_file(&sb, state, "last", 1) < 0)
 -              die("BUG: state file 'last' does not exist");
 +              BUG("state file 'last' does not exist");
        state->last = strtol(sb.buf, NULL, 10);
  
        if (read_author_script(state) < 0)
@@@ -986,7 -986,7 +986,7 @@@ static int split_mail(struct am_state *
        case PATCH_FORMAT_MBOXRD:
                return split_mail_mbox(state, paths, keep_cr, 1);
        default:
 -              die("BUG: invalid patch_format");
 +              BUG("invalid patch_format");
        }
        return -1;
  }
@@@ -1041,7 -1041,7 +1041,7 @@@ static void am_setup(struct am_state *s
                str = "b";
                break;
        default:
 -              die("BUG: invalid value for state->keep");
 +              BUG("invalid value for state->keep");
        }
  
        write_state_text(state, "keep", str);
                str = "t";
                break;
        default:
 -              die("BUG: invalid value for state->scissors");
 +              BUG("invalid value for state->scissors");
        }
        write_state_text(state, "scissors", str);
  
@@@ -1216,7 -1216,7 +1216,7 @@@ static int parse_mail(struct am_state *
                mi.keep_non_patch_brackets_in_subject = 1;
                break;
        default:
 -              die("BUG: invalid value for state->keep");
 +              BUG("invalid value for state->keep");
        }
  
        if (state->message_id)
                mi.use_scissors = 1;
                break;
        default:
 -              die("BUG: invalid value for state->scissors");
 +              BUG("invalid value for state->scissors");
        }
  
        mi.input = xfopen(mail, "r");
@@@ -1463,7 -1463,7 +1463,7 @@@ static int run_apply(const struct am_st
        int options = 0;
  
        if (init_apply_state(&apply_state, NULL))
 -              die("BUG: init_apply_state() failed");
 +              BUG("init_apply_state() failed");
  
        argv_array_push(&apply_opts, "apply");
        argv_array_pushv(&apply_opts, state->git_apply_opts.argv);
                apply_state.apply_verbosity = verbosity_silent;
  
        if (check_apply_state(&apply_state, force_apply))
 -              die("BUG: check_apply_state() failed");
 +              BUG("check_apply_state() failed");
  
        argv_array_push(&apply_paths, am_path(state, "patch"));
  
@@@ -1542,7 -1542,7 +1542,7 @@@ static int fall_back_threeway(const str
        char *their_tree_name;
  
        if (get_oid("HEAD", &our_tree) < 0)
 -              hashcpy(our_tree.hash, EMPTY_TREE_SHA1_BIN);
 +              oidcpy(&our_tree, the_hash_algo->empty_tree);
  
        if (build_fake_ancestor(state, index_path))
                return error("could not build fake ancestor");
@@@ -1827,15 -1827,11 +1827,11 @@@ static void am_run(struct am_state *sta
                }
  
                if (apply_status) {
-                       int advice_amworkdir = 1;
                        printf_ln(_("Patch failed at %s %.*s"), msgnum(state),
                                linelen(state->msg), state->msg);
  
-                       git_config_get_bool("advice.amworkdir", &advice_amworkdir);
                        if (advice_amworkdir)
-                               printf_ln(_("Use 'git am --show-current-patch' to see the failed patch"));
+                               advise(_("Use 'git am --show-current-patch' to see the failed patch"));
  
                        die_user_resolve(state);
                }
@@@ -2042,7 -2038,7 +2038,7 @@@ static void am_skip(struct am_state *st
        am_rerere_clear();
  
        if (get_oid("HEAD", &head))
 -              hashcpy(head.hash, EMPTY_TREE_SHA1_BIN);
 +              oidcpy(&head, the_hash_algo->empty_tree);
  
        if (clean_index(&head, &head))
                die(_("failed to clean index"));
@@@ -2105,11 -2101,11 +2101,11 @@@ static void am_abort(struct am_state *s
        curr_branch = resolve_refdup("HEAD", 0, &curr_head, NULL);
        has_curr_head = curr_branch && !is_null_oid(&curr_head);
        if (!has_curr_head)
 -              hashcpy(curr_head.hash, EMPTY_TREE_SHA1_BIN);
 +              oidcpy(&curr_head, the_hash_algo->empty_tree);
  
        has_orig_head = !get_oid("ORIG_HEAD", &orig_head);
        if (!has_orig_head)
 -              hashcpy(orig_head.hash, EMPTY_TREE_SHA1_BIN);
 +              oidcpy(&orig_head, the_hash_algo->empty_tree);
  
        clean_index(&curr_head, &orig_head);
  
@@@ -2231,12 -2227,12 +2227,12 @@@ int cmd_am(int argc, const char **argv
                        N_("pass -b flag to git-mailinfo"), KEEP_NON_PATCH),
                OPT_BOOL('m', "message-id", &state.message_id,
                        N_("pass -m flag to git-mailinfo")),
 -              { OPTION_SET_INT, 0, "keep-cr", &keep_cr, NULL,
 -                N_("pass --keep-cr flag to git-mailsplit for mbox format"),
 -                PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1},
 -              { OPTION_SET_INT, 0, "no-keep-cr", &keep_cr, NULL,
 -                N_("do not pass --keep-cr flag to git-mailsplit independent of am.keepcr"),
 -                PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
 +              OPT_SET_INT_F(0, "keep-cr", &keep_cr,
 +                      N_("pass --keep-cr flag to git-mailsplit for mbox format"),
 +                      1, PARSE_OPT_NONEG),
 +              OPT_SET_INT_F(0, "no-keep-cr", &keep_cr,
 +                      N_("do not pass --keep-cr flag to git-mailsplit independent of am.keepcr"),
 +                      0, PARSE_OPT_NONEG),
                OPT_BOOL('c', "scissors", &state.scissors,
                        N_("strip everything before a scissors line")),
                OPT_PASSTHRU_ARGV(0, "whitespace", &state.git_apply_opts, N_("action"),
                ret = show_patch(&state);
                break;
        default:
 -              die("BUG: invalid resume value");
 +              BUG("invalid resume value");
        }
  
        am_state_release(&state);
diff --combined builtin/branch.c
index 5217ba3bdebc2255e95260fdb097166d3617e120,426c20b61d50a31306d37d9b2d80364fe957f9a9..1876ca9e7969019e1db1c97aedcf1064903b80a2
@@@ -22,6 -22,7 +22,7 @@@
  #include "wt-status.h"
  #include "ref-filter.h"
  #include "worktree.h"
+ #include "help.h"
  
  static const char * const builtin_branch_usage[] = {
        N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"),
@@@ -55,25 -56,19 +56,19 @@@ enum color_branch 
        BRANCH_COLOR_UPSTREAM = 5
  };
  
+ static const char *color_branch_slots[] = {
+       [BRANCH_COLOR_RESET]    = "reset",
+       [BRANCH_COLOR_PLAIN]    = "plain",
+       [BRANCH_COLOR_REMOTE]   = "remote",
+       [BRANCH_COLOR_LOCAL]    = "local",
+       [BRANCH_COLOR_CURRENT]  = "current",
+       [BRANCH_COLOR_UPSTREAM] = "upstream",
+ };
  static struct string_list output = STRING_LIST_INIT_DUP;
  static unsigned int colopts;
  
- static int parse_branch_color_slot(const char *slot)
- {
-       if (!strcasecmp(slot, "plain"))
-               return BRANCH_COLOR_PLAIN;
-       if (!strcasecmp(slot, "reset"))
-               return BRANCH_COLOR_RESET;
-       if (!strcasecmp(slot, "remote"))
-               return BRANCH_COLOR_REMOTE;
-       if (!strcasecmp(slot, "local"))
-               return BRANCH_COLOR_LOCAL;
-       if (!strcasecmp(slot, "current"))
-               return BRANCH_COLOR_CURRENT;
-       if (!strcasecmp(slot, "upstream"))
-               return BRANCH_COLOR_UPSTREAM;
-       return -1;
- }
+ define_list_config_array(color_branch_slots);
  
  static int git_branch_config(const char *var, const char *value, void *cb)
  {
@@@ -86,7 -81,7 +81,7 @@@
                return 0;
        }
        if (skip_prefix(var, "color.branch.", &slot_name)) {
-               int slot = parse_branch_color_slot(slot_name);
+               int slot = LOOKUP_CONFIG(color_branch_slots, slot_name);
                if (slot < 0)
                        return 0;
                if (!value)
@@@ -500,7 -495,7 +495,7 @@@ static void copy_or_rename_branch(cons
  
        if (!skip_prefix(oldref.buf, "refs/heads/", &interpreted_oldname) ||
            !skip_prefix(newref.buf, "refs/heads/", &interpreted_newname)) {
 -              die("BUG: expected prefix missing for refs");
 +              BUG("expected prefix missing for refs");
        }
  
        if (copy)
@@@ -592,8 -587,8 +587,8 @@@ int cmd_branch(int argc, const char **a
                OPT__QUIET(&quiet, N_("suppress informational messages")),
                OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
                        BRANCH_TRACK_EXPLICIT),
 -              { OPTION_SET_INT, 0, "set-upstream", &track, NULL, N_("do not use"),
 -                      PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, BRANCH_TRACK_OVERRIDE },
 +              OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
 +                      BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),
                OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
                OPT_BOOL(0, "unset-upstream", &unset_upstream, N_("Unset the upstream info")),
                OPT__COLOR(&branch_use_color, N_("use colored output")),
                 * If no sorting parameter is given then we default to sorting
                 * by 'refname'. This would give us an alphabetically sorted
                 * array with the 'HEAD' ref at the beginning followed by
 -               * local branches 'refs/heads/...' and finally remote-tacking
 +               * local branches 'refs/heads/...' and finally remote-tracking
                 * branches 'refs/remotes/...'.
                 */
                if (!sorting)
diff --combined builtin/commit.c
index a842fea666a33bb2191d10cf452a255754bb44e6,8bb39116145b575eeb74e18d85e79b7c0ab35f85..9bcbb0c25cb44e6230a9d4c1c4564dd2c9eb5acc
@@@ -32,6 -32,7 +32,7 @@@
  #include "column.h"
  #include "sequencer.h"
  #include "mailmap.h"
+ #include "help.h"
  
  static const char * const builtin_commit_usage[] = {
        N_("git commit [<options>] [--] <pathspec>..."),
@@@ -66,6 -67,18 +67,18 @@@ N_("If you wish to skip this commit, us
  "Then \"git cherry-pick --continue\" will resume cherry-picking\n"
  "the remaining commits.\n");
  
+ static const char *color_status_slots[] = {
+       [WT_STATUS_HEADER]        = "header",
+       [WT_STATUS_UPDATED]       = "updated",
+       [WT_STATUS_CHANGED]       = "changed",
+       [WT_STATUS_UNTRACKED]     = "untracked",
+       [WT_STATUS_NOBRANCH]      = "noBranch",
+       [WT_STATUS_UNMERGED]      = "unmerged",
+       [WT_STATUS_LOCAL_BRANCH]  = "localBranch",
+       [WT_STATUS_REMOTE_BRANCH] = "remoteBranch",
+       [WT_STATUS_ONBRANCH]      = "branch",
+ };
  static const char *use_message_buffer;
  static struct lock_file index_lock; /* real index */
  static struct lock_file false_lock; /* used only for partial commits */
@@@ -143,16 -156,6 +156,16 @@@ static int opt_parse_m(const struct opt
        return 0;
  }
  
 +static int opt_parse_rename_score(const struct option *opt, const char *arg, int unset)
 +{
 +      const char **value = opt->value;
 +      if (arg != NULL && *arg == '=')
 +              arg = arg + 1;
 +
 +      *value = arg;
 +      return 0;
 +}
 +
  static void determine_whence(struct wt_status *s)
  {
        if (file_exists(git_path_merge_head()))
@@@ -505,7 -508,7 +518,7 @@@ static int is_a_merge(const struct comm
  static void assert_split_ident(struct ident_split *id, const struct strbuf *buf)
  {
        if (split_ident_line(id, buf->buf, buf->len) || !id->date_begin)
 -              die("BUG: unable to parse our own ident: %s", buf->buf);
 +              BUG("unable to parse our own ident: %s", buf->buf);
  }
  
  static void export_one(const char *var, const char *s, const char *e, int hack)
@@@ -1183,27 -1186,14 +1196,14 @@@ static int dry_run_commit(int argc, con
        return commitable ? 0 : 1;
  }
  
+ define_list_config_array_extra(color_status_slots, {"added"});
  static int parse_status_slot(const char *slot)
  {
-       if (!strcasecmp(slot, "header"))
-               return WT_STATUS_HEADER;
-       if (!strcasecmp(slot, "branch"))
-               return WT_STATUS_ONBRANCH;
-       if (!strcasecmp(slot, "updated") || !strcasecmp(slot, "added"))
+       if (!strcasecmp(slot, "added"))
                return WT_STATUS_UPDATED;
-       if (!strcasecmp(slot, "changed"))
-               return WT_STATUS_CHANGED;
-       if (!strcasecmp(slot, "untracked"))
-               return WT_STATUS_UNTRACKED;
-       if (!strcasecmp(slot, "nobranch"))
-               return WT_STATUS_NOBRANCH;
-       if (!strcasecmp(slot, "unmerged"))
-               return WT_STATUS_UNMERGED;
-       if (!strcasecmp(slot, "localBranch"))
-               return WT_STATUS_LOCAL_BRANCH;
-       if (!strcasecmp(slot, "remoteBranch"))
-               return WT_STATUS_REMOTE_BRANCH;
-       return -1;
+       return LOOKUP_CONFIG(color_status_slots, slot);
  }
  
  static int git_status_config(const char *k, const char *v, void *cb)
                        return error(_("Invalid untracked files mode '%s'"), v);
                return 0;
        }
 +      if (!strcmp(k, "diff.renamelimit")) {
 +              if (s->rename_limit == -1)
 +                      s->rename_limit = git_config_int(k, v);
 +              return 0;
 +      }
 +      if (!strcmp(k, "status.renamelimit")) {
 +              s->rename_limit = git_config_int(k, v);
 +              return 0;
 +      }
 +      if (!strcmp(k, "diff.renames")) {
 +              if (s->detect_rename == -1)
 +                      s->detect_rename = git_config_rename(k, v);
 +              return 0;
 +      }
 +      if (!strcmp(k, "status.renames")) {
 +              s->detect_rename = git_config_rename(k, v);
 +              return 0;
 +      }
        return git_diff_ui_config(k, v, NULL);
  }
  
  int cmd_status(int argc, const char **argv, const char *prefix)
  {
 +      static int no_renames = -1;
 +      static const char *rename_score_arg = (const char *)-1;
        static struct wt_status s;
        int fd;
        struct object_id oid;
                  N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"),
                  PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
                OPT_COLUMN(0, "column", &s.colopts, N_("list untracked files in columns")),
 +              OPT_BOOL(0, "no-renames", &no_renames, N_("do not detect renames")),
 +              { OPTION_CALLBACK, 'M', "find-renames", &rename_score_arg,
 +                N_("n"), N_("detect renames, optionally set similarity index"),
 +                PARSE_OPT_OPTARG, opt_parse_rename_score },
                OPT_END(),
        };
  
        s.ignore_submodule_arg = ignore_submodule_arg;
        s.status_format = status_format;
        s.verbose = verbose;
 +      if (no_renames != -1)
 +              s.detect_rename = !no_renames;
 +      if ((intptr_t)rename_score_arg != -1) {
 +              if (s.detect_rename < DIFF_DETECT_RENAME)
 +                      s.detect_rename = DIFF_DETECT_RENAME;
 +              if (rename_score_arg)
 +                      s.rename_score = parse_rename_score(&rename_score_arg);
 +      }
  
        wt_status_collect(&s);
  
diff --combined config.c
index fbbf0f8e9f2b2ae0a96769108d8a2773f071aec1,d9ff3cb38c1deb4b0c0f2e03a097118129eca2d6..a0a6ae1980d9c95d13bb78422e64850396c1a4d8
+++ b/config.c
@@@ -103,7 -103,7 +103,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;
        }
  
@@@ -190,7 -190,7 +190,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))
@@@ -1814,7 -1814,7 +1814,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;
@@@ -2333,19 -2333,6 +2333,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)
  {
@@@ -2372,7 -2359,7 +2372,7 @@@ static int store_aux_event(enum config_
  
        if (type == CONFIG_EVENT_SECTION) {
                if (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')
 -                      BUG("Invalid section name '%s'", cf->var.buf);
 +                      return error("invalid section name '%s'", cf->var.buf);
  
                /* Is this the section we were looking for? */
                store->is_keys_section =
@@@ -2680,6 -2667,7 +2680,6 @@@ 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);
                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);
                        ret = CONFIG_INVALID_FILE; /* same as "invalid config file" */
                        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;
                        if (regcomp(store.value_regex, value_regex,
                                        REG_EXTENDED)) {
                                error("invalid pattern: %s", value_regex);
 -                              free(store.value_regex);
 +                              FREE_AND_NULL(store.value_regex);
                                ret = CONFIG_INVALID_PATTERN;
                                goto out_free;
                        }
                                                      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);
 -                      }
                        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)) {
@@@ -2885,7 -2887,6 +2885,7 @@@ out_free
                munmap(contents, contents_sz);
        if (in_fd >= 0)
                close(in_fd);
 +      config_store_data_clear(&store);
        return ret;
  
  write_err_out:
@@@ -3126,7 -3127,6 +3126,7 @@@ out
        rollback_lock_file(&lock);
  out_no_rollback:
        free(filename_buf);
 +      config_store_data_clear(&store);
        return ret;
  }
  
@@@ -3208,7 -3208,7 +3208,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");
        }
  }
  
@@@ -3234,7 -3234,7 +3234,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 : "";
  }
  
@@@ -3245,3 -3245,16 +3245,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;
+ }
index dd3e925843acf53c439e504101d46d8606ea5d69,a6f55e856a14fe8d844b8ee419fcb72c8667039a..dc2b04603dddd274433c6fb34582bba0142f52d1
@@@ -94,70 -94,6 +94,70 @@@ __git (
                ${__git_dir:+--git-dir="$__git_dir"} "$@" 2>/dev/null
  }
  
 +# Removes backslash escaping, single quotes and double quotes from a word,
 +# stores the result in the variable $dequoted_word.
 +# 1: The word to dequote.
 +__git_dequote ()
 +{
 +      local rest="$1" len ch
 +
 +      dequoted_word=""
 +
 +      while test -n "$rest"; do
 +              len=${#dequoted_word}
 +              dequoted_word="$dequoted_word${rest%%[\\\'\"]*}"
 +              rest="${rest:$((${#dequoted_word}-$len))}"
 +
 +              case "${rest:0:1}" in
 +              \\)
 +                      ch="${rest:1:1}"
 +                      case "$ch" in
 +                      $'\n')
 +                              ;;
 +                      *)
 +                              dequoted_word="$dequoted_word$ch"
 +                              ;;
 +                      esac
 +                      rest="${rest:2}"
 +                      ;;
 +              \')
 +                      rest="${rest:1}"
 +                      len=${#dequoted_word}
 +                      dequoted_word="$dequoted_word${rest%%\'*}"
 +                      rest="${rest:$((${#dequoted_word}-$len+1))}"
 +                      ;;
 +              \")
 +                      rest="${rest:1}"
 +                      while test -n "$rest" ; do
 +                              len=${#dequoted_word}
 +                              dequoted_word="$dequoted_word${rest%%[\\\"]*}"
 +                              rest="${rest:$((${#dequoted_word}-$len))}"
 +                              case "${rest:0:1}" in
 +                              \\)
 +                                      ch="${rest:1:1}"
 +                                      case "$ch" in
 +                                      \"|\\|\$|\`)
 +                                              dequoted_word="$dequoted_word$ch"
 +                                              ;;
 +                                      $'\n')
 +                                              ;;
 +                                      *)
 +                                              dequoted_word="$dequoted_word\\$ch"
 +                                              ;;
 +                                      esac
 +                                      rest="${rest:2}"
 +                                      ;;
 +                              \")
 +                                      rest="${rest:1}"
 +                                      break
 +                                      ;;
 +                              esac
 +                      done
 +                      ;;
 +              esac
 +      done
 +}
 +
  # The following function is based on code from:
  #
  #   bash_completion - programmable completion functions for bash 3.2+
@@@ -410,24 -346,6 +410,24 @@@ __gitcomp_nl (
        __gitcomp_nl_append "$@"
  }
  
 +# Fills the COMPREPLY array with prefiltered paths without any additional
 +# processing.
 +# Callers must take care of providing only paths that match the current path
 +# to be completed and adding any prefix path components, if necessary.
 +# 1: List of newline-separated matching paths, complete with all prefix
 +#    path componens.
 +__gitcomp_file_direct ()
 +{
 +      local IFS=$'\n'
 +
 +      COMPREPLY=($1)
 +
 +      # use a hack to enable file mode in bash < 4
 +      compopt -o filenames +o nospace 2>/dev/null ||
 +      compgen -f /non-existing-dir/ >/dev/null ||
 +      true
 +}
 +
  # Generates completion reply with compgen from newline-separated possible
  # completion filenames.
  # It accepts 1 to 3 arguments:
@@@ -447,8 -365,7 +447,8 @@@ __gitcomp_file (
  
        # use a hack to enable file mode in bash < 4
        compopt -o filenames +o nospace 2>/dev/null ||
 -      compgen -f /non-existing-dir/ > /dev/null
 +      compgen -f /non-existing-dir/ >/dev/null ||
 +      true
  }
  
  # Execute 'git ls-files', unless the --committable option is specified, in
  __git_ls_files_helper ()
  {
        if [ "$2" == "--committable" ]; then
 -              __git -C "$1" diff-index --name-only --relative HEAD
 +              __git -C "$1" -c core.quotePath=false diff-index \
 +                      --name-only --relative HEAD -- "${3//\\/\\\\}*"
        else
                # NOTE: $2 is not quoted in order to support multiple options
 -              __git -C "$1" ls-files --exclude-standard $2
 +              __git -C "$1" -c core.quotePath=false ls-files \
 +                      --exclude-standard $2 -- "${3//\\/\\\\}*"
        fi
  }
  
  #    If provided, only files within the specified directory are listed.
  #    Sub directories are never recursed.  Path must have a trailing
  #    slash.
 +# 3: List only paths matching this path component (optional).
  __git_index_files ()
  {
 -      local root="${2-.}" file
 +      local root="$2" match="$3"
 +
 +      __git_ls_files_helper "$root" "$1" "$match" |
 +      awk -F / -v pfx="${2//\\/\\\\}" '{
 +              paths[$1] = 1
 +      }
 +      END {
 +              for (p in paths) {
 +                      if (substr(p, 1, 1) != "\"") {
 +                              # No special characters, easy!
 +                              print pfx p
 +                              continue
 +                      }
 +
 +                      # The path is quoted.
 +                      p = dequote(p)
 +                      if (p == "")
 +                              continue
 +
 +                      # Even when a directory name itself does not contain
 +                      # any special characters, it will still be quoted if
 +                      # any of its (stripped) trailing path components do.
 +                      # Because of this we may have seen the same direcory
 +                      # both quoted and unquoted.
 +                      if (p in paths)
 +                              # We have seen the same directory unquoted,
 +                              # skip it.
 +                              continue
 +                      else
 +                              print pfx p
 +              }
 +      }
 +      function dequote(p,    bs_idx, out, esc, esc_idx, dec) {
 +              # Skip opening double quote.
 +              p = substr(p, 2)
 +
 +              # Interpret backslash escape sequences.
 +              while ((bs_idx = index(p, "\\")) != 0) {
 +                      out = out substr(p, 1, bs_idx - 1)
 +                      esc = substr(p, bs_idx + 1, 1)
 +                      p = substr(p, bs_idx + 2)
 +
 +                      if ((esc_idx = index("abtvfr\"\\", esc)) != 0) {
 +                              # C-style one-character escape sequence.
 +                              out = out substr("\a\b\t\v\f\r\"\\",
 +                                               esc_idx, 1)
 +                      } else if (esc == "n") {
 +                              # Uh-oh, a newline character.
 +                              # We cant reliably put a pathname
 +                              # containing a newline into COMPREPLY,
 +                              # and the newline would create a mess.
 +                              # Skip this path.
 +                              return ""
 +                      } else {
 +                              # Must be a \nnn octal value, then.
 +                              dec = esc             * 64 + \
 +                                    substr(p, 1, 1) * 8  + \
 +                                    substr(p, 2, 1)
 +                              out = out sprintf("%c", dec)
 +                              p = substr(p, 3)
 +                      }
 +              }
 +              # Drop closing double quote, if there is one.
 +              # (There isnt any if this is a directory, as it was
 +              # already stripped with the trailing path components.)
 +              if (substr(p, length(p), 1) == "\"")
 +                      out = out substr(p, 1, length(p) - 1)
 +              else
 +                      out = out p
 +
 +              return out
 +      }'
 +}
 +
 +# __git_complete_index_file requires 1 argument:
 +# 1: the options to pass to ls-file
 +#
 +# The exception is --committable, which finds the files appropriate commit.
 +__git_complete_index_file ()
 +{
 +      local dequoted_word pfx="" cur_
 +
 +      __git_dequote "$cur"
 +
 +      case "$dequoted_word" in
 +      ?*/*)
 +              pfx="${dequoted_word%/*}/"
 +              cur_="${dequoted_word##*/}"
 +              ;;
 +      *)
 +              cur_="$dequoted_word"
 +      esac
  
 -      __git_ls_files_helper "$root" "$1" |
 -      cut -f1 -d/ | sort | uniq
 +      __gitcomp_file_direct "$(__git_index_files "$1" "$pfx" "$cur_")"
  }
  
  # Lists branches from the local repository.
@@@ -889,6 -713,26 +889,6 @@@ __git_complete_revlist_file (
        esac
  }
  
 -
 -# __git_complete_index_file requires 1 argument:
 -# 1: the options to pass to ls-file
 -#
 -# The exception is --committable, which finds the files appropriate commit.
 -__git_complete_index_file ()
 -{
 -      local pfx="" cur_="$cur"
 -
 -      case "$cur_" in
 -      ?*/*)
 -              pfx="${cur_%/*}"
 -              cur_="${cur_##*/}"
 -              pfx="${pfx}/"
 -              ;;
 -      esac
 -
 -      __gitcomp_file "$(__git_index_files "$1" ${pfx:+"$pfx"})" "$pfx" "$cur_"
 -}
 -
  __git_complete_file ()
  {
        __git_complete_revlist_file
@@@ -2142,9 -1986,24 +2142,24 @@@ __git_config_get_set_variables (
        __git config $config_file --name-only --list
  }
  
+ __git_config_vars=
+ __git_compute_config_vars ()
+ {
+       test -n "$__git_config_vars" ||
+       __git_config_vars="$(git help --config-for-completion | sort | uniq)"
+ }
  _git_config ()
  {
-       case "$prev" in
+       local varname
+       if [ "${BASH_VERSINFO[0]:-0}" -ge 4 ]; then
+               varname="${prev,,}"
+       else
+               varname="$(echo "$prev" |tr A-Z a-z)"
+       fi
+       case "$varname" in
        branch.*.remote|branch.*.pushremote)
                __gitcomp_nl "$(__git_remotes)"
                return
                ;;
        branch.*.*)
                local pfx="${cur%.*}." cur_="${cur##*.}"
-               __gitcomp "remote pushremote merge mergeoptions rebase" "$pfx" "$cur_"
+               __gitcomp "remote pushRemote merge mergeOptions rebase" "$pfx" "$cur_"
                return
                ;;
        branch.*)
                local pfx="${cur%.*}." cur_="${cur#*.}"
                __gitcomp_direct "$(__git_heads "$pfx" "$cur_" ".")"
-               __gitcomp_nl_append $'autosetupmerge\nautosetuprebase\n' "$pfx" "$cur_"
+               __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_"
                return
                ;;
        guitool.*.*)
                local pfx="${cur%.*}." cur_="${cur##*.}"
                __gitcomp "
-                       argprompt cmd confirm needsfile noconsole norescan
-                       prompt revprompt revunmerged title
+                       argPrompt cmd confirm needsFile noConsole noRescan
+                       prompt revPrompt revUnmerged title
                        " "$pfx" "$cur_"
                return
                ;;
                local pfx="${cur%.*}." cur_="${cur##*.}"
                __gitcomp "
                        url proxy fetch push mirror skipDefaultUpdate
-                       receivepack uploadpack tagopt pushurl
+                       receivepack uploadpack tagOpt pushurl
                        " "$pfx" "$cur_"
                return
                ;;
        remote.*)
                local pfx="${cur%.*}." cur_="${cur#*.}"
                __gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "."
-               __gitcomp_nl_append "pushdefault" "$pfx" "$cur_"
+               __gitcomp_nl_append "pushDefault" "$pfx" "$cur_"
                return
                ;;
        url.*.*)
                __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur_"
                return
                ;;
+       *.*)
+               __git_compute_config_vars
+               __gitcomp "$__git_config_vars"
+               ;;
+       *)
+               __git_compute_config_vars
+               __gitcomp "$(echo "$__git_config_vars" | sed 's/\.[^ ]*/./g')"
        esac
-       __gitcomp "
-               add.ignoreErrors
-               advice.amWorkDir
-               advice.commitBeforeMerge
-               advice.detachedHead
-               advice.implicitIdentity
-               advice.pushAlreadyExists
-               advice.pushFetchFirst
-               advice.pushNeedsForce
-               advice.pushNonFFCurrent
-               advice.pushNonFFMatching
-               advice.pushUpdateRejected
-               advice.resolveConflict
-               advice.rmHints
-               advice.statusHints
-               advice.statusUoption
-               advice.ignoredHook
-               alias.
-               am.keepcr
-               am.threeWay
-               apply.ignorewhitespace
-               apply.whitespace
-               branch.autosetupmerge
-               branch.autosetuprebase
-               browser.
-               clean.requireForce
-               color.branch
-               color.branch.current
-               color.branch.local
-               color.branch.plain
-               color.branch.remote
-               color.decorate.HEAD
-               color.decorate.branch
-               color.decorate.remoteBranch
-               color.decorate.stash
-               color.decorate.tag
-               color.diff
-               color.diff.commit
-               color.diff.frag
-               color.diff.func
-               color.diff.meta
-               color.diff.new
-               color.diff.old
-               color.diff.plain
-               color.diff.whitespace
-               color.grep
-               color.grep.context
-               color.grep.filename
-               color.grep.function
-               color.grep.linenumber
-               color.grep.match
-               color.grep.selected
-               color.grep.separator
-               color.interactive
-               color.interactive.error
-               color.interactive.header
-               color.interactive.help
-               color.interactive.prompt
-               color.pager
-               color.showbranch
-               color.status
-               color.status.added
-               color.status.changed
-               color.status.header
-               color.status.localBranch
-               color.status.nobranch
-               color.status.remoteBranch
-               color.status.unmerged
-               color.status.untracked
-               color.status.updated
-               color.ui
-               commit.cleanup
-               commit.gpgSign
-               commit.status
-               commit.template
-               commit.verbose
-               core.abbrev
-               core.askpass
-               core.attributesfile
-               core.autocrlf
-               core.bare
-               core.bigFileThreshold
-               core.checkStat
-               core.commentChar
-               core.commitGraph
-               core.compression
-               core.createObject
-               core.deltaBaseCacheLimit
-               core.editor
-               core.eol
-               core.excludesfile
-               core.fileMode
-               core.fsyncobjectfiles
-               core.gitProxy
-               core.hideDotFiles
-               core.hooksPath
-               core.ignoreStat
-               core.ignorecase
-               core.logAllRefUpdates
-               core.loosecompression
-               core.notesRef
-               core.packedGitLimit
-               core.packedGitWindowSize
-               core.packedRefsTimeout
-               core.pager
-               core.precomposeUnicode
-               core.preferSymlinkRefs
-               core.preloadindex
-               core.protectHFS
-               core.protectNTFS
-               core.quotepath
-               core.repositoryFormatVersion
-               core.safecrlf
-               core.sharedRepository
-               core.sparseCheckout
-               core.splitIndex
-               core.sshCommand
-               core.symlinks
-               core.trustctime
-               core.untrackedCache
-               core.warnAmbiguousRefs
-               core.whitespace
-               core.worktree
-               credential.helper
-               credential.useHttpPath
-               credential.username
-               credentialCache.ignoreSIGHUP
-               diff.autorefreshindex
-               diff.external
-               diff.ignoreSubmodules
-               diff.mnemonicprefix
-               diff.noprefix
-               diff.renameLimit
-               diff.renames
-               diff.statGraphWidth
-               diff.submodule
-               diff.suppressBlankEmpty
-               diff.tool
-               diff.wordRegex
-               diff.algorithm
-               difftool.
-               difftool.prompt
-               fetch.recurseSubmodules
-               fetch.unpackLimit
-               format.attach
-               format.cc
-               format.coverLetter
-               format.from
-               format.headers
-               format.numbered
-               format.pretty
-               format.signature
-               format.signoff
-               format.subjectprefix
-               format.suffix
-               format.thread
-               format.to
-               gc.
-               gc.aggressiveDepth
-               gc.aggressiveWindow
-               gc.auto
-               gc.autoDetach
-               gc.autopacklimit
-               gc.logExpiry
-               gc.packrefs
-               gc.pruneexpire
-               gc.reflogexpire
-               gc.reflogexpireunreachable
-               gc.rerereresolved
-               gc.rerereunresolved
-               gc.worktreePruneExpire
-               gitcvs.allbinary
-               gitcvs.commitmsgannotation
-               gitcvs.dbTableNamePrefix
-               gitcvs.dbdriver
-               gitcvs.dbname
-               gitcvs.dbpass
-               gitcvs.dbuser
-               gitcvs.enabled
-               gitcvs.logfile
-               gitcvs.usecrlfattr
-               guitool.
-               gui.blamehistoryctx
-               gui.commitmsgwidth
-               gui.copyblamethreshold
-               gui.diffcontext
-               gui.encoding
-               gui.fastcopyblame
-               gui.matchtrackingbranch
-               gui.newbranchtemplate
-               gui.pruneduringfetch
-               gui.spellingdictionary
-               gui.trustmtime
-               help.autocorrect
-               help.browser
-               help.format
-               http.lowSpeedLimit
-               http.lowSpeedTime
-               http.maxRequests
-               http.minSessions
-               http.noEPSV
-               http.postBuffer
-               http.proxy
-               http.sslCipherList
-               http.sslVersion
-               http.sslCAInfo
-               http.sslCAPath
-               http.sslCert
-               http.sslCertPasswordProtected
-               http.sslKey
-               http.sslVerify
-               http.useragent
-               i18n.commitEncoding
-               i18n.logOutputEncoding
-               imap.authMethod
-               imap.folder
-               imap.host
-               imap.pass
-               imap.port
-               imap.preformattedHTML
-               imap.sslverify
-               imap.tunnel
-               imap.user
-               init.templatedir
-               instaweb.browser
-               instaweb.httpd
-               instaweb.local
-               instaweb.modulepath
-               instaweb.port
-               interactive.singlekey
-               log.date
-               log.decorate
-               log.showroot
-               mailmap.file
-               man.
-               man.viewer
-               merge.
-               merge.conflictstyle
-               merge.log
-               merge.renameLimit
-               merge.renormalize
-               merge.stat
-               merge.tool
-               merge.verbosity
-               mergetool.
-               mergetool.keepBackup
-               mergetool.keepTemporaries
-               mergetool.prompt
-               notes.displayRef
-               notes.rewrite.
-               notes.rewrite.amend
-               notes.rewrite.rebase
-               notes.rewriteMode
-               notes.rewriteRef
-               pack.compression
-               pack.deltaCacheLimit
-               pack.deltaCacheSize
-               pack.depth
-               pack.indexVersion
-               pack.packSizeLimit
-               pack.threads
-               pack.window
-               pack.windowMemory
-               pager.
-               pretty.
-               pull.octopus
-               pull.twohead
-               push.default
-               push.followTags
-               rebase.autosquash
-               rebase.stat
-               receive.autogc
-               receive.denyCurrentBranch
-               receive.denyDeleteCurrent
-               receive.denyDeletes
-               receive.denyNonFastForwards
-               receive.fsckObjects
-               receive.unpackLimit
-               receive.updateserverinfo
-               remote.pushdefault
-               remotes.
-               repack.usedeltabaseoffset
-               rerere.autoupdate
-               rerere.enabled
-               sendemail.
-               sendemail.aliasesfile
-               sendemail.aliasfiletype
-               sendemail.bcc
-               sendemail.cc
-               sendemail.cccmd
-               sendemail.chainreplyto
-               sendemail.confirm
-               sendemail.envelopesender
-               sendemail.from
-               sendemail.identity
-               sendemail.multiedit
-               sendemail.signedoffbycc
-               sendemail.smtpdomain
-               sendemail.smtpencryption
-               sendemail.smtppass
-               sendemail.smtpserver
-               sendemail.smtpserveroption
-               sendemail.smtpserverport
-               sendemail.smtpuser
-               sendemail.suppresscc
-               sendemail.suppressfrom
-               sendemail.thread
-               sendemail.to
-               sendemail.tocmd
-               sendemail.validate
-               sendemail.smtpbatchsize
-               sendemail.smtprelogindelay
-               showbranch.default
-               status.relativePaths
-               status.showUntrackedFiles
-               status.submodulesummary
-               submodule.
-               tar.umask
-               transfer.unpackLimit
-               url.
-               user.email
-               user.name
-               user.signingkey
-               web.browser
-               branch. remote.
-       "
  }
  
  _git_remote ()
@@@ -3223,10 -2763,7 +2919,10 @@@ __gitk_main (
        __git_complete_revlist
  }
  
 -if [[ -n ${ZSH_VERSION-} ]]; then
 +if [[ -n ${ZSH_VERSION-} ]] &&
 +   # Don't define these functions when sourced from 'git-completion.zsh',
 +   # it has its own implementations.
 +   [[ -z ${GIT_SOURCING_ZSH_COMPLETION-} ]]; then
        echo "WARNING: this script is deprecated, please see git-completion.zsh" 1>&2
  
        autoload -U +X compinit && compinit
                compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0
        }
  
 +      __gitcomp_file_direct ()
 +      {
 +              emulate -L zsh
 +
 +              local IFS=$'\n'
 +              compset -P '*[=:]'
 +              compadd -Q -f -- ${=1} && _ret=0
 +      }
 +
        __gitcomp_file ()
        {
                emulate -L zsh
diff --combined diff.c
index 136d44b45560d5c9db7e88a1ff22aa6b086108e8,513410d46bfeff9c6341619133f873b898e42198..639eb646b9fa0f07eaeb20a1f0734c177092806d
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -22,6 -22,7 +22,7 @@@
  #include "argv-array.h"
  #include "graph.h"
  #include "packfile.h"
+ #include "help.h"
  
  #ifdef NO_FAST_WORKING_DIRECTORY
  #define FAST_WORKING_DIRECTORY 0
@@@ -69,46 -70,37 +70,37 @@@ static char diff_colors[][COLOR_MAXLEN
        GIT_COLOR_FAINT_ITALIC, /* NEW_MOVED_ALTERNATIVE_DIM */
  };
  
+ static const char *color_diff_slots[] = {
+       [DIFF_CONTEXT]                = "context",
+       [DIFF_METAINFO]               = "meta",
+       [DIFF_FRAGINFO]               = "frag",
+       [DIFF_FILE_OLD]               = "old",
+       [DIFF_FILE_NEW]               = "new",
+       [DIFF_COMMIT]                 = "commit",
+       [DIFF_WHITESPACE]             = "whitespace",
+       [DIFF_FUNCINFO]               = "func",
+       [DIFF_FILE_OLD_MOVED]         = "oldMoved",
+       [DIFF_FILE_OLD_MOVED_ALT]     = "oldMovedAlternative",
+       [DIFF_FILE_OLD_MOVED_DIM]     = "oldMovedDimmed",
+       [DIFF_FILE_OLD_MOVED_ALT_DIM] = "oldMovedAlternativeDimmed",
+       [DIFF_FILE_NEW_MOVED]         = "newMoved",
+       [DIFF_FILE_NEW_MOVED_ALT]     = "newMovedAlternative",
+       [DIFF_FILE_NEW_MOVED_DIM]     = "newMovedDimmed",
+       [DIFF_FILE_NEW_MOVED_ALT_DIM] = "newMovedAlternativeDimmed",
+ };
  static NORETURN void die_want_option(const char *option_name)
  {
        die(_("option '%s' requires a value"), option_name);
  }
  
+ define_list_config_array_extra(color_diff_slots, {"plain"});
  static int parse_diff_color_slot(const char *var)
  {
-       if (!strcasecmp(var, "context") || !strcasecmp(var, "plain"))
+       if (!strcasecmp(var, "plain"))
                return DIFF_CONTEXT;
-       if (!strcasecmp(var, "meta"))
-               return DIFF_METAINFO;
-       if (!strcasecmp(var, "frag"))
-               return DIFF_FRAGINFO;
-       if (!strcasecmp(var, "old"))
-               return DIFF_FILE_OLD;
-       if (!strcasecmp(var, "new"))
-               return DIFF_FILE_NEW;
-       if (!strcasecmp(var, "commit"))
-               return DIFF_COMMIT;
-       if (!strcasecmp(var, "whitespace"))
-               return DIFF_WHITESPACE;
-       if (!strcasecmp(var, "func"))
-               return DIFF_FUNCINFO;
-       if (!strcasecmp(var, "oldmoved"))
-               return DIFF_FILE_OLD_MOVED;
-       if (!strcasecmp(var, "oldmovedalternative"))
-               return DIFF_FILE_OLD_MOVED_ALT;
-       if (!strcasecmp(var, "oldmoveddimmed"))
-               return DIFF_FILE_OLD_MOVED_DIM;
-       if (!strcasecmp(var, "oldmovedalternativedimmed"))
-               return DIFF_FILE_OLD_MOVED_ALT_DIM;
-       if (!strcasecmp(var, "newmoved"))
-               return DIFF_FILE_NEW_MOVED;
-       if (!strcasecmp(var, "newmovedalternative"))
-               return DIFF_FILE_NEW_MOVED_ALT;
-       if (!strcasecmp(var, "newmoveddimmed"))
-               return DIFF_FILE_NEW_MOVED_DIM;
-       if (!strcasecmp(var, "newmovedalternativedimmed"))
-               return DIFF_FILE_NEW_MOVED_ALT_DIM;
-       return -1;
+       return LOOKUP_CONFIG(color_diff_slots, var);
  }
  
  static int parse_dirstat_params(struct diff_options *options, const char *params_string,
@@@ -177,7 -169,7 +169,7 @@@ static int parse_submodule_params(struc
        return 0;
  }
  
 -static int git_config_rename(const char *var, const char *value)
 +int git_config_rename(const char *var, const char *value)
  {
        if (!value)
                return DIFF_DETECT_RENAME;
@@@ -1184,7 -1176,7 +1176,7 @@@ static void emit_diff_symbol_from_struc
                fputs(o->stat_sep, o->file);
                break;
        default:
 -              die("BUG: unknown diff symbol");
 +              BUG("unknown diff symbol");
        }
        strbuf_release(&sb);
  }
@@@ -1343,7 -1335,7 +1335,7 @@@ static struct diff_tempfile *claim_diff
        for (i = 0; i < ARRAY_SIZE(diff_temp); i++)
                if (!diff_temp[i].name)
                        return diff_temp + i;
 -      die("BUG: diff is failing to clean up its tempfiles");
 +      BUG("diff is failing to clean up its tempfiles");
  }
  
  static void remove_tempfile(void)
@@@ -3472,7 -3464,7 +3464,7 @@@ static int reuse_worktree_file(const ch
         * objects however would tend to be slower as they need
         * to be individually opened and inflated.
         */
 -      if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(oid->hash))
 +      if (!FAST_WORKING_DIRECTORY && !want_file && has_object_pack(oid))
                return 0;
  
        /*
@@@ -3841,7 -3833,7 +3833,7 @@@ static const char *diff_abbrev_oid(cons
                if (abbrev < 0)
                        abbrev = FALLBACK_DEFAULT_ABBREV;
                if (abbrev > GIT_SHA1_HEXSZ)
 -                      die("BUG: oid abbreviation out of range: %d", abbrev);
 +                      BUG("oid abbreviation out of range: %d", abbrev);
                if (abbrev)
                        hex[abbrev] = '\0';
                return hex;
@@@ -3898,14 -3890,13 +3890,14 @@@ static void fill_metainfo(struct strbu
                *must_show_header = 0;
        }
        if (one && two && oidcmp(&one->oid, &two->oid)) {
 -              int abbrev = o->flags.full_index ? 40 : DEFAULT_ABBREV;
 +              const unsigned hexsz = the_hash_algo->hexsz;
 +              int abbrev = o->flags.full_index ? hexsz : DEFAULT_ABBREV;
  
                if (o->flags.binary) {
                        mmfile_t mf;
                        if ((!fill_mmfile(&mf, one) && diff_filespec_is_binary(one)) ||
                            (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
 -                              abbrev = 40;
 +                              abbrev = hexsz;
                }
                strbuf_addf(msg, "%s%sindex %s..%s", line_prefix, set,
                            diff_abbrev_oid(&one->oid, abbrev),
@@@ -4140,11 -4131,6 +4132,11 @@@ void diff_setup_done(struct diff_option
                              DIFF_FORMAT_NAME_STATUS |
                              DIFF_FORMAT_CHECKDIFF |
                              DIFF_FORMAT_NO_OUTPUT;
 +      /*
 +       * This must be signed because we're comparing against a potentially
 +       * negative value.
 +       */
 +      const int hexsz = the_hash_algo->hexsz;
  
        if (options->set_default)
                options->set_default(options);
                         */
                        read_cache();
        }
 -      if (40 < options->abbrev)
 -              options->abbrev = 40; /* full */
 +      if (hexsz < options->abbrev)
 +              options->abbrev = hexsz; /* full */
  
        /*
         * It does not make sense to show the first hit we happened
@@@ -4341,7 -4327,7 +4333,7 @@@ static int stat_opt(struct diff_option
        int argcount = 1;
  
        if (!skip_prefix(arg, "--stat", &arg))
 -              die("BUG: stat option does not begin with --stat: %s", arg);
 +              BUG("stat option does not begin with --stat: %s", arg);
        end = (char *)arg;
  
        switch (*arg) {
@@@ -4804,8 -4790,8 +4796,8 @@@ int diff_opt_parse(struct diff_options 
                options->abbrev = strtoul(arg, NULL, 10);
                if (options->abbrev < MINIMUM_ABBREV)
                        options->abbrev = MINIMUM_ABBREV;
 -              else if (40 < options->abbrev)
 -                      options->abbrev = 40;
 +              else if (the_hash_algo->hexsz < options->abbrev)
 +                      options->abbrev = the_hash_algo->hexsz;
        }
        else if ((argcount = parse_long_opt("src-prefix", av, &optarg))) {
                options->a_prefix = optarg;
@@@ -5526,7 -5512,7 +5518,7 @@@ static void diff_flush_patch_all_file_p
        struct diff_queue_struct *q = &diff_queued_diff;
  
        if (WSEH_NEW & WS_RULE_MASK)
 -              die("BUG: WS rules bit mask overlaps with diff symbol flags");
 +              BUG("WS rules bit mask overlaps with diff symbol flags");
  
        if (o->color_moved)
                o->emitted_symbols = &esm;
@@@ -6060,7 -6046,7 +6052,7 @@@ size_t fill_textconv(struct userdiff_dr
        }
  
        if (!driver->textconv)
 -              die("BUG: fill_textconv called with non-textconv driver");
 +              BUG("fill_textconv called with non-textconv driver");
  
        if (driver->textconv_cache && df->oid_valid) {
                *outbuf = notes_cache_get(driver->textconv_cache,
diff --combined fsck.c
index 48e7e36869a7cbcb47e2e2a0401dd1047612e5d7,c2b8974c0e6a1553616093f5120123ce59d3b20d..0b8b20b6c464cdfbbb8afd9685f45e3e2e67adfa
--- 1/fsck.c
--- 2/fsck.c
+++ b/fsck.c
  #include "utf8.h"
  #include "sha1-array.h"
  #include "decorate.h"
 +#include "oidset.h"
 +#include "packfile.h"
 +#include "submodule-config.h"
 +#include "config.h"
+ #include "help.h"
  
 +static struct oidset gitmodules_found = OIDSET_INIT;
 +static struct oidset gitmodules_done = OIDSET_INIT;
 +
  #define FSCK_FATAL -1
  #define FSCK_INFO -2
  
@@@ -51,7 -45,6 +52,7 @@@
        FUNC(MISSING_TAG_ENTRY, ERROR) \
        FUNC(MISSING_TAG_OBJECT, ERROR) \
        FUNC(MISSING_TREE, ERROR) \
 +      FUNC(MISSING_TREE_OBJECT, ERROR) \
        FUNC(MISSING_TYPE, ERROR) \
        FUNC(MISSING_TYPE_ENTRY, ERROR) \
        FUNC(MULTIPLE_AUTHORS, ERROR) \
        FUNC(TREE_NOT_SORTED, ERROR) \
        FUNC(UNKNOWN_TYPE, ERROR) \
        FUNC(ZERO_PADDED_DATE, ERROR) \
 +      FUNC(GITMODULES_MISSING, ERROR) \
 +      FUNC(GITMODULES_BLOB, ERROR) \
 +      FUNC(GITMODULES_PARSE, ERROR) \
 +      FUNC(GITMODULES_NAME, ERROR) \
 +      FUNC(GITMODULES_SYMLINK, ERROR) \
        /* warnings */ \
        FUNC(BAD_FILEMODE, WARN) \
        FUNC(EMPTY_NAME, WARN) \
@@@ -86,37 -74,60 +87,60 @@@ enum fsck_msg_id 
  #undef MSG_ID
  
  #define STR(x) #x
- #define MSG_ID(id, msg_type) { STR(id), NULL, FSCK_##msg_type },
+ #define MSG_ID(id, msg_type) { STR(id), NULL, NULL, FSCK_##msg_type },
  static struct {
        const char *id_string;
        const char *downcased;
+       const char *camelcased;
        int msg_type;
  } msg_id_info[FSCK_MSG_MAX + 1] = {
        FOREACH_MSG_ID(MSG_ID)
-       { NULL, NULL, -1 }
+       { NULL, NULL, NULL, -1 }
  };
  #undef MSG_ID
  
- static int parse_msg_id(const char *text)
+ static void prepare_msg_ids(void)
  {
        int i;
  
-       if (!msg_id_info[0].downcased) {
-               /* convert id_string to lower case, without underscores. */
-               for (i = 0; i < FSCK_MSG_MAX; i++) {
-                       const char *p = msg_id_info[i].id_string;
-                       int len = strlen(p);
-                       char *q = xmalloc(len);
-                       msg_id_info[i].downcased = q;
-                       while (*p)
-                               if (*p == '_')
-                                       p++;
-                               else
-                                       *(q)++ = tolower(*(p)++);
-                       *q = '\0';
+       if (msg_id_info[0].downcased)
+               return;
+       /* convert id_string to lower case, without underscores. */
+       for (i = 0; i < FSCK_MSG_MAX; i++) {
+               const char *p = msg_id_info[i].id_string;
+               int len = strlen(p);
+               char *q = xmalloc(len);
+               msg_id_info[i].downcased = q;
+               while (*p)
+                       if (*p == '_')
+                               p++;
+                       else
+                               *(q)++ = tolower(*(p)++);
+               *q = '\0';
+               p = msg_id_info[i].id_string;
+               q = xmalloc(len);
+               msg_id_info[i].camelcased = q;
+               while (*p) {
+                       if (*p == '_') {
+                               p++;
+                               if (*p)
+                                       *q++ = *p++;
+                       } else {
+                               *q++ = tolower(*p++);
+                       }
                }
+               *q = '\0';
        }
+ }
+ static int parse_msg_id(const char *text)
+ {
+       int i;
+       prepare_msg_ids();
  
        for (i = 0; i < FSCK_MSG_MAX; i++)
                if (!strcmp(text, msg_id_info[i].downcased))
        return -1;
  }
  
+ void list_config_fsck_msg_ids(struct string_list *list, const char *prefix)
+ {
+       int i;
+       prepare_msg_ids();
+       for (i = 0; i < FSCK_MSG_MAX; i++)
+               list_config_item(list, prefix, msg_id_info[i].camelcased);
+ }
  static int fsck_msg_type(enum fsck_msg_id msg_id,
        struct fsck_options *options)
  {
@@@ -576,18 -597,10 +610,18 @@@ static int fsck_tree(struct tree *item
                has_empty_name |= !*name;
                has_dot |= !strcmp(name, ".");
                has_dotdot |= !strcmp(name, "..");
 -              has_dotgit |= (!strcmp(name, ".git") ||
 -                             is_hfs_dotgit(name) ||
 -                             is_ntfs_dotgit(name));
 +              has_dotgit |= is_hfs_dotgit(name) || is_ntfs_dotgit(name);
                has_zero_pad |= *(char *)desc.buffer == '0';
 +
 +              if (is_hfs_dotgitmodules(name) || is_ntfs_dotgitmodules(name)) {
 +                      if (!S_ISLNK(mode))
 +                              oidset_insert(&gitmodules_found, oid);
 +                      else
 +                              retval += report(options, &item->object,
 +                                               FSCK_MSG_GITMODULES_SYMLINK,
 +                                               ".gitmodules is a symbolic link");
 +              }
 +
                if (update_tree_entry_gently(&desc)) {
                        retval += report(options, &item->object, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree");
                        break;
@@@ -734,31 -747,30 +768,31 @@@ static int fsck_ident(const char **iden
  static int fsck_commit_buffer(struct commit *commit, const char *buffer,
        unsigned long size, struct fsck_options *options)
  {
 -      unsigned char tree_sha1[20], sha1[20];
 +      struct object_id tree_oid, oid;
        struct commit_graft *graft;
        unsigned parent_count, parent_line_count = 0, author_count;
        int err;
        const char *buffer_begin = buffer;
 +      const char *p;
  
        if (verify_headers(buffer, size, &commit->object, options))
                return -1;
  
        if (!skip_prefix(buffer, "tree ", &buffer))
                return report(options, &commit->object, FSCK_MSG_MISSING_TREE, "invalid format - expected 'tree' line");
 -      if (get_sha1_hex(buffer, tree_sha1) || buffer[40] != '\n') {
 +      if (parse_oid_hex(buffer, &tree_oid, &p) || *p != '\n') {
                err = report(options, &commit->object, FSCK_MSG_BAD_TREE_SHA1, "invalid 'tree' line format - bad sha1");
                if (err)
                        return err;
        }
 -      buffer += 41;
 +      buffer = p + 1;
        while (skip_prefix(buffer, "parent ", &buffer)) {
 -              if (get_sha1_hex(buffer, sha1) || buffer[40] != '\n') {
 +              if (parse_oid_hex(buffer, &oid, &p) || *p != '\n') {
                        err = report(options, &commit->object, FSCK_MSG_BAD_PARENT_SHA1, "invalid 'parent' line format - bad sha1");
                        if (err)
                                return err;
                }
 -              buffer += 41;
 +              buffer = p + 1;
                parent_line_count++;
        }
        graft = lookup_commit_graft(&commit->object.oid);
        if (err)
                return err;
        if (!get_commit_tree(commit)) {
 -              err = report(options, &commit->object, FSCK_MSG_BAD_TREE, "could not load commit's tree %s", sha1_to_hex(tree_sha1));
 +              err = report(options, &commit->object, FSCK_MSG_BAD_TREE, "could not load commit's tree %s", oid_to_hex(&tree_oid));
                if (err)
                        return err;
        }
@@@ -823,12 -835,11 +857,12 @@@ static int fsck_commit(struct commit *c
  static int fsck_tag_buffer(struct tag *tag, const char *data,
        unsigned long size, struct fsck_options *options)
  {
 -      unsigned char sha1[20];
 +      struct object_id oid;
        int ret = 0;
        const char *buffer;
        char *to_free = NULL, *eol;
        struct strbuf sb = STRBUF_INIT;
 +      const char *p;
  
        if (data)
                buffer = data;
                ret = report(options, &tag->object, FSCK_MSG_MISSING_OBJECT, "invalid format - expected 'object' line");
                goto done;
        }
 -      if (get_sha1_hex(buffer, sha1) || buffer[40] != '\n') {
 +      if (parse_oid_hex(buffer, &oid, &p) || *p != '\n') {
                ret = report(options, &tag->object, FSCK_MSG_BAD_OBJECT_SHA1, "invalid 'object' line format - bad sha1");
                if (ret)
                        goto done;
        }
 -      buffer += 41;
 +      buffer = p + 1;
  
        if (!skip_prefix(buffer, "type ", &buffer)) {
                ret = report(options, &tag->object, FSCK_MSG_MISSING_TYPE_ENTRY, "invalid format - expected 'type' line");
@@@ -926,66 -937,6 +960,66 @@@ static int fsck_tag(struct tag *tag, co
        return fsck_tag_buffer(tag, data, size, options);
  }
  
 +struct fsck_gitmodules_data {
 +      struct object *obj;
 +      struct fsck_options *options;
 +      int ret;
 +};
 +
 +static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata)
 +{
 +      struct fsck_gitmodules_data *data = vdata;
 +      const char *subsection, *key;
 +      int subsection_len;
 +      char *name;
 +
 +      if (parse_config_key(var, "submodule", &subsection, &subsection_len, &key) < 0 ||
 +          !subsection)
 +              return 0;
 +
 +      name = xmemdupz(subsection, subsection_len);
 +      if (check_submodule_name(name) < 0)
 +              data->ret |= report(data->options, data->obj,
 +                                  FSCK_MSG_GITMODULES_NAME,
 +                                  "disallowed submodule name: %s",
 +                                  name);
 +      free(name);
 +
 +      return 0;
 +}
 +
 +static int fsck_blob(struct blob *blob, const char *buf,
 +                   unsigned long size, struct fsck_options *options)
 +{
 +      struct fsck_gitmodules_data data;
 +
 +      if (!oidset_contains(&gitmodules_found, &blob->object.oid))
 +              return 0;
 +      oidset_insert(&gitmodules_done, &blob->object.oid);
 +
 +      if (!buf) {
 +              /*
 +               * A missing buffer here is a sign that the caller found the
 +               * blob too gigantic to load into memory. Let's just consider
 +               * that an error.
 +               */
 +              return report(options, &blob->object,
 +                            FSCK_MSG_GITMODULES_PARSE,
 +                            ".gitmodules too large to parse");
 +      }
 +
 +      data.obj = &blob->object;
 +      data.options = options;
 +      data.ret = 0;
 +      if (git_config_from_mem(fsck_gitmodules_fn, CONFIG_ORIGIN_BLOB,
 +                              ".gitmodules", buf, size, &data))
 +              data.ret |= report(options, &blob->object,
 +                                 FSCK_MSG_GITMODULES_PARSE,
 +                                 "could not parse gitmodules blob");
 +
 +      return data.ret;
 +}
 +
  int fsck_object(struct object *obj, void *data, unsigned long size,
        struct fsck_options *options)
  {
                return report(options, obj, FSCK_MSG_BAD_OBJECT_SHA1, "no valid object to fsck");
  
        if (obj->type == OBJ_BLOB)
 -              return 0;
 +              return fsck_blob((struct blob *)obj, data, size, options);
        if (obj->type == OBJ_TREE)
                return fsck_tree((struct tree *) obj, options);
        if (obj->type == OBJ_COMMIT)
@@@ -1017,53 -968,3 +1051,53 @@@ int fsck_error_function(struct fsck_opt
        error("object %s: %s", describe_object(o, obj), message);
        return 1;
  }
 +
 +int fsck_finish(struct fsck_options *options)
 +{
 +      int ret = 0;
 +      struct oidset_iter iter;
 +      const struct object_id *oid;
 +
 +      oidset_iter_init(&gitmodules_found, &iter);
 +      while ((oid = oidset_iter_next(&iter))) {
 +              struct blob *blob;
 +              enum object_type type;
 +              unsigned long size;
 +              char *buf;
 +
 +              if (oidset_contains(&gitmodules_done, oid))
 +                      continue;
 +
 +              blob = lookup_blob(oid);
 +              if (!blob) {
 +                      struct object *obj = lookup_unknown_object(oid->hash);
 +                      ret |= report(options, obj,
 +                                    FSCK_MSG_GITMODULES_BLOB,
 +                                    "non-blob found at .gitmodules");
 +                      continue;
 +              }
 +
 +              buf = read_object_file(oid, &type, &size);
 +              if (!buf) {
 +                      if (is_promisor_object(&blob->object.oid))
 +                              continue;
 +                      ret |= report(options, &blob->object,
 +                                    FSCK_MSG_GITMODULES_MISSING,
 +                                    "unable to read .gitmodules blob");
 +                      continue;
 +              }
 +
 +              if (type == OBJ_BLOB)
 +                      ret |= fsck_blob(blob, buf, size, options);
 +              else
 +                      ret |= report(options, &blob->object,
 +                                    FSCK_MSG_GITMODULES_BLOB,
 +                                    "non-blob found at .gitmodules");
 +              free(buf);
 +      }
 +
 +
 +      oidset_clear(&gitmodules_found);
 +      oidset_clear(&gitmodules_done);
 +      return ret;
 +}
diff --combined grep.c
index 45ec7e636c004dbd35d4e0d05c243bc1d1479d19,bf71b130c20391beeef7923ed2db71869ddf8c56..7c1b8e2e8ba38f27ee4fe16e39cfe319ab89265f
--- 1/grep.c
--- 2/grep.c
+++ b/grep.c
@@@ -7,12 -7,24 +7,24 @@@
  #include "diffcore.h"
  #include "commit.h"
  #include "quote.h"
+ #include "help.h"
  
  static int grep_source_load(struct grep_source *gs);
  static int grep_source_is_binary(struct grep_source *gs);
  
  static struct grep_opt grep_defaults;
  
+ static const char *color_grep_slots[] = {
+       [GREP_COLOR_CONTEXT]        = "context",
+       [GREP_COLOR_FILENAME]       = "filename",
+       [GREP_COLOR_FUNCTION]       = "function",
+       [GREP_COLOR_LINENO]         = "lineNumber",
+       [GREP_COLOR_MATCH_CONTEXT]  = "matchContext",
+       [GREP_COLOR_MATCH_SELECTED] = "matchSelected",
+       [GREP_COLOR_SELECTED]       = "selected",
+       [GREP_COLOR_SEP]            = "separator",
+ };
  static void std_output(struct grep_opt *opt, const void *buf, size_t size)
  {
        fwrite(buf, size, 1, stdout);
@@@ -42,14 -54,14 +54,14 @@@ void init_grep_defaults(void
        opt->pathname = 1;
        opt->max_depth = -1;
        opt->pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED;
-       color_set(opt->color_context, "");
-       color_set(opt->color_filename, "");
-       color_set(opt->color_function, "");
-       color_set(opt->color_lineno, "");
-       color_set(opt->color_match_context, GIT_COLOR_BOLD_RED);
-       color_set(opt->color_match_selected, GIT_COLOR_BOLD_RED);
-       color_set(opt->color_selected, "");
-       color_set(opt->color_sep, GIT_COLOR_CYAN);
+       color_set(opt->colors[GREP_COLOR_CONTEXT], "");
+       color_set(opt->colors[GREP_COLOR_FILENAME], "");
+       color_set(opt->colors[GREP_COLOR_FUNCTION], "");
+       color_set(opt->colors[GREP_COLOR_LINENO], "");
+       color_set(opt->colors[GREP_COLOR_MATCH_CONTEXT], GIT_COLOR_BOLD_RED);
+       color_set(opt->colors[GREP_COLOR_MATCH_SELECTED], GIT_COLOR_BOLD_RED);
+       color_set(opt->colors[GREP_COLOR_SELECTED], "");
+       color_set(opt->colors[GREP_COLOR_SEP], GIT_COLOR_CYAN);
        opt->color = -1;
        opt->output = std_output;
  }
@@@ -69,6 -81,8 +81,8 @@@ static int parse_pattern_type_arg(cons
        die("bad %s argument: %s", opt, arg);
  }
  
+ define_list_config_array_extra(color_grep_slots, {"match"});
  /*
   * Read the configuration file once and store it in
   * the grep_defaults template.
@@@ -76,7 -90,7 +90,7 @@@
  int grep_config(const char *var, const char *value, void *cb)
  {
        struct grep_opt *opt = &grep_defaults;
-       char *color = NULL;
+       const char *slot;
  
        if (userdiff_config(var, value) < 0)
                return -1;
  
        if (!strcmp(var, "color.grep"))
                opt->color = git_config_colorbool(var, value);
-       else if (!strcmp(var, "color.grep.context"))
-               color = opt->color_context;
-       else if (!strcmp(var, "color.grep.filename"))
-               color = opt->color_filename;
-       else if (!strcmp(var, "color.grep.function"))
-               color = opt->color_function;
-       else if (!strcmp(var, "color.grep.linenumber"))
-               color = opt->color_lineno;
-       else if (!strcmp(var, "color.grep.matchcontext"))
-               color = opt->color_match_context;
-       else if (!strcmp(var, "color.grep.matchselected"))
-               color = opt->color_match_selected;
-       else if (!strcmp(var, "color.grep.selected"))
-               color = opt->color_selected;
-       else if (!strcmp(var, "color.grep.separator"))
-               color = opt->color_sep;
-       else if (!strcmp(var, "color.grep.match")) {
-               int rc = 0;
-               if (!value)
-                       return config_error_nonbool(var);
-               rc |= color_parse(value, opt->color_match_context);
-               rc |= color_parse(value, opt->color_match_selected);
-               return rc;
-       }
-       if (color) {
+       if (!strcmp(var, "color.grep.match")) {
+               if (grep_config("color.grep.matchcontext", value, cb) < 0)
+                       return -1;
+               if (grep_config("color.grep.matchselected", value, cb) < 0)
+                       return -1;
+       } else if (skip_prefix(var, "color.grep.", &slot)) {
+               int i = LOOKUP_CONFIG(color_grep_slots, slot);
+               char *color;
+               if (i < 0)
+                       return -1;
+               color = opt->colors[i];
                if (!value)
                        return config_error_nonbool(var);
                return color_parse(value, color);
  void grep_init(struct grep_opt *opt, const char *prefix)
  {
        struct grep_opt *def = &grep_defaults;
+       int i;
  
        memset(opt, 0, sizeof(*opt));
        opt->prefix = prefix;
        opt->relative = def->relative;
        opt->output = def->output;
  
-       color_set(opt->color_context, def->color_context);
-       color_set(opt->color_filename, def->color_filename);
-       color_set(opt->color_function, def->color_function);
-       color_set(opt->color_lineno, def->color_lineno);
-       color_set(opt->color_match_context, def->color_match_context);
-       color_set(opt->color_match_selected, def->color_match_selected);
-       color_set(opt->color_selected, def->color_selected);
-       color_set(opt->color_sep, def->color_sep);
+       for (i = 0; i < NR_GREP_COLORS; i++)
+               color_set(opt->colors[i], def->colors[i]);
  }
  
  static void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, struct grep_opt *opt)
@@@ -404,7 -399,7 +399,7 @@@ static void compile_pcre1_regexp(struc
                        die("Couldn't allocate PCRE JIT stack");
                pcre_assign_jit_stack(p->pcre1_extra_info, NULL, p->pcre1_jit_stack);
        } else if (p->pcre1_jit_on != 0) {
 -              die("BUG: The pcre1_jit_on variable should be 0 or 1, not %d",
 +              BUG("The pcre1_jit_on variable should be 0 or 1, not %d",
                    p->pcre1_jit_on);
        }
  #endif
@@@ -550,7 -545,7 +545,7 @@@ static void compile_pcre2_pattern(struc
                        die("Couldn't allocate PCRE2 match context");
                pcre2_jit_stack_assign(p->pcre2_match_context, NULL, p->pcre2_jit_stack);
        } else if (p->pcre2_jit_on != 0) {
 -              die("BUG: The pcre2_jit_on variable should be 0 or 1, not %d",
 +              BUG("The pcre2_jit_on variable should be 0 or 1, not %d",
                    p->pcre1_jit_on);
        }
  }
@@@ -636,6 -631,7 +631,6 @@@ static void compile_fixed_regexp(struc
        if (err) {
                char errbuf[1024];
                regerror(err, &p->regexp, errbuf, sizeof(errbuf));
 -              regfree(&p->regexp);
                compile_regexp_failed(p, errbuf);
        }
  }
@@@ -700,6 -696,7 +695,6 @@@ static void compile_regexp(struct grep_
        if (err) {
                char errbuf[1024];
                regerror(err, &p->regexp, errbuf, 1024);
 -              regfree(&p->regexp);
                compile_regexp_failed(p, errbuf);
        }
  }
@@@ -915,10 -912,10 +910,10 @@@ static struct grep_expr *prep_header_pa
  
        for (p = opt->header_list; p; p = p->next) {
                if (p->token != GREP_PATTERN_HEAD)
 -                      die("BUG: a non-header pattern in grep header list.");
 +                      BUG("a non-header pattern in grep header list.");
                if (p->field < GREP_HEADER_FIELD_MIN ||
                    GREP_HEADER_FIELD_MAX <= p->field)
 -                      die("BUG: unknown header field %d", p->field);
 +                      BUG("unknown header field %d", p->field);
                compile_regexp(p, opt);
        }
  
  
                h = compile_pattern_atom(&pp);
                if (!h || pp != p->next)
 -                      die("BUG: malformed header expr");
 +                      BUG("malformed header expr");
                if (!header_group[p->field]) {
                        header_group[p->field] = h;
                        continue;
@@@ -1098,12 -1095,12 +1093,12 @@@ static void output_sep(struct grep_opt 
        if (opt->null_following_name)
                opt->output(opt, "\0", 1);
        else
-               output_color(opt, &sign, 1, opt->color_sep);
+               output_color(opt, &sign, 1, opt->colors[GREP_COLOR_SEP]);
  }
  
  static void show_name(struct grep_opt *opt, const char *name)
  {
-       output_color(opt, name, strlen(name), opt->color_filename);
+       output_color(opt, name, strlen(name), opt->colors[GREP_COLOR_FILENAME]);
        opt->output(opt, opt->null_following_name ? "\0" : "\n", 1);
  }
  
@@@ -1370,28 -1367,28 +1365,28 @@@ static void show_line(struct grep_opt *
        } else if (opt->pre_context || opt->post_context || opt->funcbody) {
                if (opt->last_shown == 0) {
                        if (opt->show_hunk_mark) {
-                               output_color(opt, "--", 2, opt->color_sep);
+                               output_color(opt, "--", 2, opt->colors[GREP_COLOR_SEP]);
                                opt->output(opt, "\n", 1);
                        }
                } else if (lno > opt->last_shown + 1) {
-                       output_color(opt, "--", 2, opt->color_sep);
+                       output_color(opt, "--", 2, opt->colors[GREP_COLOR_SEP]);
                        opt->output(opt, "\n", 1);
                }
        }
        if (opt->heading && opt->last_shown == 0) {
-               output_color(opt, name, strlen(name), opt->color_filename);
+               output_color(opt, name, strlen(name), opt->colors[GREP_COLOR_FILENAME]);
                opt->output(opt, "\n", 1);
        }
        opt->last_shown = lno;
  
        if (!opt->heading && opt->pathname) {
-               output_color(opt, name, strlen(name), opt->color_filename);
+               output_color(opt, name, strlen(name), opt->colors[GREP_COLOR_FILENAME]);
                output_sep(opt, sign);
        }
        if (opt->linenum) {
                char buf[32];
                xsnprintf(buf, sizeof(buf), "%d", lno);
-               output_color(opt, buf, strlen(buf), opt->color_lineno);
+               output_color(opt, buf, strlen(buf), opt->colors[GREP_COLOR_LINENO]);
                output_sep(opt, sign);
        }
        if (opt->color) {
                int eflags = 0;
  
                if (sign == ':')
-                       match_color = opt->color_match_selected;
+                       match_color = opt->colors[GREP_COLOR_MATCH_SELECTED];
                else
-                       match_color = opt->color_match_context;
+                       match_color = opt->colors[GREP_COLOR_MATCH_CONTEXT];
                if (sign == ':')
-                       line_color = opt->color_selected;
+                       line_color = opt->colors[GREP_COLOR_SELECTED];
                else if (sign == '-')
-                       line_color = opt->color_context;
+                       line_color = opt->colors[GREP_COLOR_CONTEXT];
                else if (sign == '=')
-                       line_color = opt->color_function;
+                       line_color = opt->colors[GREP_COLOR_FUNCTION];
                *eol = '\0';
                while (next_match(opt, bol, eol, ctx, &match, eflags)) {
                        if (match.rm_so == match.rm_eo)
@@@ -1650,7 -1647,7 +1645,7 @@@ static int fill_textconv_grep(struct us
                fill_filespec(df, &null_oid, 0, 0100644);
                break;
        default:
 -              die("BUG: attempt to textconv something without a path?");
 +              BUG("attempt to textconv something without a path?");
        }
  
        /*
@@@ -1746,7 -1743,7 +1741,7 @@@ static int grep_source_1(struct grep_op
                case GREP_BINARY_TEXT:
                        break;
                default:
 -                      die("BUG: unknown binary handling mode");
 +                      BUG("unknown binary handling mode");
                }
        }
  
                        if (binary_match_only) {
                                opt->output(opt, "Binary file ", 12);
                                output_color(opt, gs->name, strlen(gs->name),
-                                            opt->color_filename);
+                                            opt->colors[GREP_COLOR_FILENAME]);
                                opt->output(opt, " matches\n", 9);
                                return 1;
                        }
                char buf[32];
                if (opt->pathname) {
                        output_color(opt, gs->name, strlen(gs->name),
-                                    opt->color_filename);
+                                    opt->colors[GREP_COLOR_FILENAME]);
                        output_sep(opt, ':');
                }
                xsnprintf(buf, sizeof(buf), "%u\n", count);
@@@ -2070,7 -2067,7 +2065,7 @@@ static int grep_source_load(struct grep
        case GREP_SOURCE_BUF:
                return gs->buf ? 0 : -1;
        }
 -      die("BUG: invalid grep_source type to load");
 +      BUG("invalid grep_source type to load");
  }
  
  void grep_source_load_driver(struct grep_source *gs)
diff --combined log-tree.c
index 0b97de5e879a9d90d8d62412d1743a37b7915fd3,888c236aa30c03f6ada90e063cbf11b93f4b9c68..d3a43e29cd50e0afb015f1a5efdb82fd195d9c93
@@@ -12,6 -12,7 +12,7 @@@
  #include "gpg-interface.h"
  #include "sequencer.h"
  #include "line-log.h"
+ #include "help.h"
  
  static struct decoration name_decoration = { "object names" };
  static int decoration_loaded;
@@@ -27,6 -28,15 +28,15 @@@ static char decoration_colors[][COLOR_M
        GIT_COLOR_BOLD_BLUE,    /* GRAFTED */
  };
  
+ static const char *color_decorate_slots[] = {
+       [DECORATION_REF_LOCAL]  = "branch",
+       [DECORATION_REF_REMOTE] = "remoteBranch",
+       [DECORATION_REF_TAG]    = "tag",
+       [DECORATION_REF_STASH]  = "stash",
+       [DECORATION_REF_HEAD]   = "HEAD",
+       [DECORATION_GRAFTED]    = "grafted",
+ };
  static const char *decorate_get_color(int decorate_use_color, enum decoration_type ix)
  {
        if (want_color(decorate_use_color))
        return "";
  }
  
- static int parse_decorate_color_slot(const char *slot)
- {
-       /*
-        * We're comparing with 'ignore-case' on
-        * (because config.c sets them all tolower),
-        * but let's match the letters in the literal
-        * string values here with how they are
-        * documented in Documentation/config.txt, for
-        * consistency.
-        *
-        * We love being consistent, don't we?
-        */
-       if (!strcasecmp(slot, "branch"))
-               return DECORATION_REF_LOCAL;
-       if (!strcasecmp(slot, "remoteBranch"))
-               return DECORATION_REF_REMOTE;
-       if (!strcasecmp(slot, "tag"))
-               return DECORATION_REF_TAG;
-       if (!strcasecmp(slot, "stash"))
-               return DECORATION_REF_STASH;
-       if (!strcasecmp(slot, "HEAD"))
-               return DECORATION_REF_HEAD;
-       return -1;
- }
+ define_list_config_array(color_decorate_slots);
  
  int parse_decorate_color_config(const char *var, const char *slot_name, const char *value)
  {
-       int slot = parse_decorate_color_slot(slot_name);
+       int slot = LOOKUP_CONFIG(color_decorate_slots, slot_name);
        if (slot < 0)
                return 0;
        if (!value)
@@@ -295,12 -282,8 +282,12 @@@ void show_decorations(struct rev_info *
  {
        struct strbuf sb = STRBUF_INIT;
  
 -      if (opt->show_source && commit->util)
 -              fprintf(opt->diffopt.file, "\t%s", (char *) commit->util);
 +      if (opt->sources) {
 +              char **slot = revision_sources_peek(opt->sources, commit);
 +
 +              if (slot && *slot)
 +                      fprintf(opt->diffopt.file, "\t%s", *slot);
 +      }
        if (!opt->show_decorations)
                return;
        format_decorations(&sb, commit, opt->diffopt.use_color);
@@@ -391,15 -374,11 +378,15 @@@ void log_write_email_headers(struct rev
                graph_show_oneline(opt->graph);
        }
        if (opt->mime_boundary && maybe_multipart) {
 -              static char subject_buffer[1024];
 -              static char buffer[1024];
 +              static struct strbuf subject_buffer = STRBUF_INIT;
 +              static struct strbuf buffer = STRBUF_INIT;
                struct strbuf filename =  STRBUF_INIT;
                *need_8bit_cte_p = -1; /* NEVER */
 -              snprintf(subject_buffer, sizeof(subject_buffer) - 1,
 +
 +              strbuf_reset(&subject_buffer);
 +              strbuf_reset(&buffer);
 +
 +              strbuf_addf(&subject_buffer,
                         "%s"
                         "MIME-Version: 1.0\n"
                         "Content-Type: multipart/mixed;"
                         extra_headers ? extra_headers : "",
                         mime_boundary_leader, opt->mime_boundary,
                         mime_boundary_leader, opt->mime_boundary);
 -              extra_headers = subject_buffer;
 +              extra_headers = subject_buffer.buf;
  
                if (opt->numbered_files)
                        strbuf_addf(&filename, "%d", opt->nr);
                else
                        fmt_output_commit(&filename, commit, opt);
 -              snprintf(buffer, sizeof(buffer) - 1,
 +              strbuf_addf(&buffer,
                         "\n--%s%s\n"
                         "Content-Type: text/x-patch;"
                         " name=\"%s\"\n"
                         filename.buf,
                         opt->no_inline ? "attachment" : "inline",
                         filename.buf);
 -              opt->diffopt.stat_sep = buffer;
 +              opt->diffopt.stat_sep = buffer.buf;
                strbuf_release(&filename);
        }
        *extra_headers_p = extra_headers;