Merge branch 'jk/color-and-pager'
authorJunio C Hamano <gitster@pobox.com>
Mon, 29 Aug 2011 04:19:16 +0000 (21:19 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 29 Aug 2011 04:19:16 +0000 (21:19 -0700)
* jk/color-and-pager:
want_color: automatically fallback to color.ui
diff: don't load color config in plumbing
config: refactor get_colorbool function
color: delay auto-color decision until point of use
git_config_colorbool: refactor stdout_is_tty handling
diff: refactor COLOR_DIFF from a flag into an int
setup_pager: set GIT_PAGER_IN_USE
t7006: use test_config helpers
test-lib: add helper functions for config
t7006: modernize calls to unset

Conflicts:
builtin/commit.c
parse-options.c

1  2 
builtin/branch.c
builtin/commit.c
builtin/grep.c
builtin/merge.c
combine-diff.c
diff.c
diff.h
grep.c
parse-options-cb.c
t/test-lib.sh
wt-status.c
diff --combined builtin/branch.c
index 953bc1ca9bca583072d36a0dbd904eaa2afa89c8,73d41700d164081a2224f88a74fe3260ed45c9a9..f7da69c932a6e75f4fc37f063719a08f730f9974
@@@ -71,7 -71,7 +71,7 @@@ static int parse_branch_color_slot(cons
  static int git_branch_config(const char *var, const char *value, void *cb)
  {
        if (!strcmp(var, "color.branch")) {
-               branch_use_color = git_config_colorbool(var, value, -1);
+               branch_use_color = git_config_colorbool(var, value);
                return 0;
        }
        if (!prefixcmp(var, "color.branch.")) {
@@@ -88,7 -88,7 +88,7 @@@
  
  static const char *branch_get_color(enum color_branch ix)
  {
-       if (branch_use_color > 0)
+       if (want_color(branch_use_color))
                return branch_colors[ix];
        return "";
  }
@@@ -613,7 -613,7 +613,7 @@@ static int opt_parse_merge_filter(cons
  int cmd_branch(int argc, const char **argv, const char *prefix)
  {
        int delete = 0, rename = 0, force_create = 0;
 -      int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
 +      int verbose = 0, abbrev = -1, detached = 0;
        int reflog = 0;
        enum branch_track track;
        int kinds = REF_LOCAL_BRANCH;
  
        git_config(git_branch_config, NULL);
  
-       if (branch_use_color == -1)
-               branch_use_color = git_use_color_default;
        track = git_branch_track;
  
        head = resolve_ref("HEAD", head_sha1, 0, NULL);
        if (!!delete + !!rename + !!force_create > 1)
                usage_with_options(builtin_branch_usage, options);
  
 +      if (abbrev == -1)
 +              abbrev = DEFAULT_ABBREV;
 +
        if (delete)
                return delete_branches(argc, argv, delete > 1, kinds);
        else if (argc == 0)
diff --combined builtin/commit.c
index 05029d40d1279b9a92ba22f6826f644ce82f5b7a,9763146b6f224b32e56d3d67b0750b56a989b6cf..cbc9613ec661bc2cef8274cd66efb06b9cab55b6
@@@ -62,6 -62,8 +62,6 @@@ N_("The previous cherry-pick is now emp
  "\n"
  "Otherwise, please use 'git reset'\n");
  
 -static unsigned char head_sha1[20];
 -
  static const char *use_message_buffer;
  static const char commit_editmsg[] = "COMMIT_EDITMSG";
  static struct lock_file index_lock; /* real index */
@@@ -100,7 -102,7 +100,7 @@@ static enum 
  static char *cleanup_arg;
  
  static enum commit_whence whence;
 -static int use_editor = 1, initial_commit, include_status = 1;
 +static int use_editor = 1, include_status = 1;
  static int show_ignored_in_status;
  static const char *only_include_assumed;
  static struct strbuf message;
@@@ -254,10 -256,8 +254,10 @@@ static int list_paths(struct string_lis
                ;
        m = xcalloc(1, i);
  
 -      if (with_tree)
 -              overlay_tree_on_cache(with_tree, prefix);
 +      if (with_tree) {
 +              const char *max_prefix = pathspec_prefix(prefix, pattern);
 +              overlay_tree_on_cache(with_tree, max_prefix);
 +      }
  
        for (i = 0; i < active_nr; i++) {
                struct cache_entry *ce = active_cache[i];
                        item->util = item; /* better a valid pointer than a fake one */
        }
  
 -      return report_path_error(m, pattern, prefix ? strlen(prefix) : 0);
 +      return report_path_error(m, pattern, prefix);
  }
  
  static void add_remove_files(struct string_list *list)
        }
  }
  
 -static void create_base_index(void)
 +static void create_base_index(const struct commit *current_head)
  {
        struct tree *tree;
        struct unpack_trees_options opts;
        struct tree_desc t;
  
 -      if (initial_commit) {
 +      if (!current_head) {
                discard_cache();
                return;
        }
        opts.dst_index = &the_index;
  
        opts.fn = oneway_merge;
 -      tree = parse_tree_indirect(head_sha1);
 +      tree = parse_tree_indirect(current_head->object.sha1);
        if (!tree)
                die(_("failed to unpack HEAD tree object"));
        parse_tree(tree);
@@@ -332,8 -332,7 +332,8 @@@ static void refresh_cache_or_die(int re
                die_resolve_conflict("commit");
  }
  
 -static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status)
 +static char *prepare_index(int argc, const char **argv, const char *prefix,
 +                         const struct commit *current_head, int is_status)
  {
        int fd;
        struct string_list partial;
  
        memset(&partial, 0, sizeof(partial));
        partial.strdup_strings = 1;
 -      if (list_paths(&partial, initial_commit ? NULL : "HEAD", prefix, pathspec))
 +      if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, pathspec))
                exit(1);
  
        discard_cache();
                                                (uintmax_t) getpid()),
                                       LOCK_DIE_ON_ERROR);
  
 -      create_base_index();
 +      create_base_index(current_head);
        add_remove_files(&partial);
        refresh_cache(REFRESH_QUIET);
  
@@@ -517,9 -516,12 +517,9 @@@ static int run_status(FILE *fp, const c
        return s->commitable;
  }
  
 -static int is_a_merge(const unsigned char *sha1)
 +static int is_a_merge(const struct commit *current_head)
  {
 -      struct commit *commit = lookup_commit(sha1);
 -      if (!commit || parse_commit(commit))
 -              die(_("could not parse HEAD commit"));
 -      return !!(commit->parents && commit->parents->next);
 +      return !!(current_head->parents && current_head->parents->next);
  }
  
  static const char sign_off_header[] = "Signed-off-by: ";
@@@ -623,7 -625,6 +623,7 @@@ static char *cut_ident_timestamp_part(c
  }
  
  static int prepare_to_commit(const char *index_file, const char *prefix,
 +                           struct commit *current_head,
                             struct wt_status *s,
                             struct strbuf *author_ident)
  {
         * empty due to conflict resolution, which the user should okay.
         */
        if (!commitable && whence != FROM_MERGE && !allow_empty &&
 -          !(amend && is_a_merge(head_sha1))) {
 +          !(amend && is_a_merge(current_head))) {
                run_status(stdout, index_file, prefix, 0, s);
                if (amend)
                        fputs(_(empty_amend_advice), stderr);
@@@ -1003,7 -1004,6 +1003,7 @@@ static const char *read_commit_message(
  static int parse_and_validate_options(int argc, const char *argv[],
                                      const char * const usage[],
                                      const char *prefix,
 +                                    struct commit *current_head,
                                      struct wt_status *s)
  {
        int f = 0;
        if (!use_editor)
                setenv("GIT_EDITOR", ":", 1);
  
 -      if (get_sha1("HEAD", head_sha1))
 -              initial_commit = 1;
 -
        /* Sanity check options */
 -      if (amend && initial_commit)
 +      if (amend && !current_head)
                die(_("You have nothing to amend."));
        if (amend && whence != FROM_COMMIT)
                die(_("You are in the middle of a %s -- cannot amend."), whence_s());
  }
  
  static int dry_run_commit(int argc, const char **argv, const char *prefix,
 -                        struct wt_status *s)
 +                        const struct commit *current_head, struct wt_status *s)
  {
        int commitable;
        const char *index_file;
  
 -      index_file = prepare_index(argc, argv, prefix, 1);
 +      index_file = prepare_index(argc, argv, prefix, current_head, 1);
        commitable = run_status(stdout, index_file, prefix, 0, s);
        rollback_index_files();
  
@@@ -1141,7 -1144,7 +1141,7 @@@ static int git_status_config(const cha
                return 0;
        }
        if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
-               s->use_color = git_config_colorbool(k, v, -1);
+               s->use_color = git_config_colorbool(k, v);
                return 0;
        }
        if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
@@@ -1234,10 -1237,6 +1234,6 @@@ int cmd_status(int argc, const char **a
  
        if (s.relative_paths)
                s.prefix = prefix;
-       if (s.use_color == -1)
-               s.use_color = git_use_color_default;
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
  
        switch (status_format) {
        case STATUS_FORMAT_SHORT:
        return 0;
  }
  
 -static void print_summary(const char *prefix, const unsigned char *sha1)
 +static void print_summary(const char *prefix, const unsigned char *sha1,
 +                        int initial_commit)
  {
        struct rev_info rev;
        struct commit *commit;
@@@ -1378,13 -1376,12 +1374,13 @@@ int cmd_commit(int argc, const char **a
        struct strbuf author_ident = STRBUF_INIT;
        const char *index_file, *reflog_msg;
        char *nl, *p;
 -      unsigned char commit_sha1[20];
 +      unsigned char sha1[20];
        struct ref_lock *ref_lock;
        struct commit_list *parents = NULL, **pptr = &parents;
        struct stat statbuf;
        int allow_fast_forward = 1;
        struct wt_status s;
 +      struct commit *current_head = NULL;
  
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage_with_options(builtin_commit_usage, builtin_commit_options);
        git_config(git_commit_config, &s);
        determine_whence(&s);
  
-       if (s.use_color == -1)
-               s.use_color = git_use_color_default;
 +      if (get_sha1("HEAD", sha1))
 +              current_head = NULL;
 +      else {
 +              current_head = lookup_commit(sha1);
 +              if (!current_head || parse_commit(current_head))
 +                      die(_("could not parse HEAD commit"));
 +      }
        argc = parse_and_validate_options(argc, argv, builtin_commit_usage,
 -                                        prefix, &s);
 +                                        prefix, current_head, &s);
-       if (dry_run) {
-               if (diff_use_color_default == -1)
-                       diff_use_color_default = git_use_color_default;
+       if (dry_run)
 -              return dry_run_commit(argc, argv, prefix, &s);
 -      index_file = prepare_index(argc, argv, prefix, 0);
 +              return dry_run_commit(argc, argv, prefix, current_head, &s);
-       }
 +      index_file = prepare_index(argc, argv, prefix, current_head, 0);
  
        /* Set up everything for writing the commit object.  This includes
           running hooks, writing the trees, and interacting with the user.  */
 -      if (!prepare_to_commit(index_file, prefix, &s, &author_ident)) {
 +      if (!prepare_to_commit(index_file, prefix,
 +                             current_head, &s, &author_ident)) {
                rollback_index_files();
                return 1;
        }
  
        /* Determine parents */
        reflog_msg = getenv("GIT_REFLOG_ACTION");
 -      if (initial_commit) {
 +      if (!current_head) {
                if (!reflog_msg)
                        reflog_msg = "commit (initial)";
        } else if (amend) {
                struct commit_list *c;
 -              struct commit *commit;
  
                if (!reflog_msg)
                        reflog_msg = "commit (amend)";
 -              commit = lookup_commit(head_sha1);
 -              if (!commit || parse_commit(commit))
 -                      die(_("could not parse HEAD commit"));
 -
 -              for (c = commit->parents; c; c = c->next)
 +              for (c = current_head->parents; c; c = c->next)
                        pptr = &commit_list_insert(c->item, pptr)->next;
        } else if (whence == FROM_MERGE) {
                struct strbuf m = STRBUF_INIT;
  
                if (!reflog_msg)
                        reflog_msg = "commit (merge)";
 -              pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
 +              pptr = &commit_list_insert(current_head, pptr)->next;
                fp = fopen(git_path("MERGE_HEAD"), "r");
                if (fp == NULL)
                        die_errno(_("could not open '%s' for reading"),
                        reflog_msg = (whence == FROM_CHERRY_PICK)
                                        ? "commit (cherry-pick)"
                                        : "commit";
 -              pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
 +              pptr = &commit_list_insert(current_head, pptr)->next;
        }
  
        /* Finally, get the commit message */
                exit(1);
        }
  
 -      if (commit_tree(sb.buf, active_cache_tree->sha1, parents, commit_sha1,
 +      if (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1,
                        author_ident.buf)) {
                rollback_index_files();
                die(_("failed to write commit object"));
        strbuf_release(&author_ident);
  
        ref_lock = lock_any_ref_for_update("HEAD",
 -                                         initial_commit ? NULL : head_sha1,
 +                                         !current_head
 +                                         ? NULL
 +                                         : current_head->object.sha1,
                                           0);
  
        nl = strchr(sb.buf, '\n');
                rollback_index_files();
                die(_("cannot lock HEAD ref"));
        }
 -      if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) {
 +      if (write_ref_sha1(ref_lock, sha1, sb.buf) < 0) {
                rollback_index_files();
                die(_("cannot update HEAD ref"));
        }
                struct notes_rewrite_cfg *cfg;
                cfg = init_copy_notes_for_rewrite("amend");
                if (cfg) {
 -                      copy_note_for_rewrite(cfg, head_sha1, commit_sha1);
 +                      /* we are amending, so current_head is not NULL */
 +                      copy_note_for_rewrite(cfg, current_head->object.sha1, sha1);
                        finish_copy_notes_for_rewrite(cfg);
                }
 -              run_rewrite_hook(head_sha1, commit_sha1);
 +              run_rewrite_hook(current_head->object.sha1, sha1);
        }
        if (!quiet)
 -              print_summary(prefix, commit_sha1);
 +              print_summary(prefix, sha1, !current_head);
  
        return 0;
  }
diff --combined builtin/grep.c
index 1851797540c17791f03e0d2fb86f5463e4e3816e,18522cab75cab30fcacb6b259e5bfe9125e0d9c9..1c359c2671536287d97f2049b27a390d33b51e88
@@@ -93,7 -93,8 +93,7 @@@ static pthread_cond_t cond_write
  /* Signalled when we are finished with everything. */
  static pthread_cond_t cond_result;
  
 -static int print_hunk_marks_between_files;
 -static int printed_something;
 +static int skip_first_line;
  
  static void add_work(enum work_type type, char *name, void *id)
  {
@@@ -159,20 -160,10 +159,20 @@@ static void work_done(struct work_item 
            todo_done = (todo_done+1) % ARRAY_SIZE(todo)) {
                w = &todo[todo_done];
                if (w->out.len) {
 -                      if (print_hunk_marks_between_files && printed_something)
 -                              write_or_die(1, "--\n", 3);
 -                      write_or_die(1, w->out.buf, w->out.len);
 -                      printed_something = 1;
 +                      const char *p = w->out.buf;
 +                      size_t len = w->out.len;
 +
 +                      /* Skip the leading hunk mark of the first file. */
 +                      if (skip_first_line) {
 +                              while (len) {
 +                                      len--;
 +                                      if (*p++ == '\n')
 +                                              break;
 +                              }
 +                              skip_first_line = 0;
 +                      }
 +
 +                      write_or_die(1, p, len);
                }
                free(w->name);
                free(w->identifier);
@@@ -325,7 -316,7 +325,7 @@@ static int grep_config(const char *var
        }
  
        if (!strcmp(var, "color.grep"))
-               opt->color = git_config_colorbool(var, value, -1);
+               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"))
@@@ -822,24 -813,18 +822,24 @@@ int cmd_grep(int argc, const char **arg
                OPT_BOOLEAN('c', "count", &opt.count,
                        "show the number of matches instead of matching lines"),
                OPT__COLOR(&opt.color, "highlight matches"),
 +              OPT_BOOLEAN(0, "break", &opt.file_break,
 +                      "print empty line between matches from different files"),
 +              OPT_BOOLEAN(0, "heading", &opt.heading,
 +                      "show filename only once above matches from same file"),
                OPT_GROUP(""),
 -              OPT_CALLBACK('C', NULL, &opt, "n",
 +              OPT_CALLBACK('C', "context", &opt, "n",
                        "show <n> context lines before and after matches",
                        context_callback),
 -              OPT_INTEGER('B', NULL, &opt.pre_context,
 +              OPT_INTEGER('B', "before-context", &opt.pre_context,
                        "show <n> context lines before matches"),
 -              OPT_INTEGER('A', NULL, &opt.post_context,
 +              OPT_INTEGER('A', "after-context", &opt.post_context,
                        "show <n> context lines after matches"),
                OPT_NUMBER_CALLBACK(&opt, "shortcut for -C NUM",
                        context_callback),
                OPT_BOOLEAN('p', "show-function", &opt.funcname,
                        "show a line with the function name before matches"),
 +              OPT_BOOLEAN('W', "function-context", &opt.funcbody,
 +                      "show the surrounding function"),
                OPT_GROUP(""),
                OPT_CALLBACK('f', NULL, &opt, "file",
                        "read patterns from file", file_callback),
        strcpy(opt.color_sep, GIT_COLOR_CYAN);
        opt.color = -1;
        git_config(grep_config, &opt);
-       if (opt.color == -1)
-               opt.color = git_use_color_default;
  
        /*
         * If there is no -- then the paths must exist in the working
                use_threads = 0;
  
        if (use_threads) {
 -              if (opt.pre_context || opt.post_context)
 -                      print_hunk_marks_between_files = 1;
 +              if (opt.pre_context || opt.post_context || opt.file_break ||
 +                  opt.funcbody)
 +                      skip_first_line = 1;
                start_threads(&opt);
        }
  #else
diff --combined builtin/merge.c
index f32c5c64328086cadb424977f170bfb0c70acbed,b75ae0193cb04e969b9d32adfa8a47da3fd956fd..ab4077f272919fb47e3c5f179dc42fc9baad68bb
@@@ -390,8 -390,6 +390,6 @@@ static void finish(const unsigned char 
                opts.output_format |=
                        DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
                opts.detect_rename = DIFF_DETECT_RENAME;
-               if (diff_use_color_default > 0)
-                       DIFF_OPT_SET(&opts, COLOR_DIFF);
                if (diff_setup_done(&opts) < 0)
                        die(_("diff_setup_done failed"));
                diff_tree_sha1(head, new_head, "", &opts);
@@@ -903,7 -901,7 +901,7 @@@ static int finish_automerge(struct comm
        strbuf_addch(&merge_msg, '\n');
        run_prepare_commit_msg();
        commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
 -      strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
 +      strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
        finish(result_commit, buf.buf);
        strbuf_release(&buf);
        drop_save();
@@@ -1033,10 -1031,6 +1031,6 @@@ int cmd_merge(int argc, const char **ar
  
        git_config(git_merge_config, NULL);
  
-       /* for color.ui */
-       if (diff_use_color_default == -1)
-               diff_use_color_default = git_use_color_default;
        if (branch_mergeoptions)
                parse_branch_merge_options(branch_mergeoptions);
        argc = parse_options(argc, argv, prefix, builtin_merge_options,
diff --combined combine-diff.c
index 178313bc23cc2b4bc434bd30934863056823854f,c588c79351a3b12a52ee5b2ce224b98db9c9d8e0..214014dc645e43ae257e46f1046c8ca0d21d3472
@@@ -702,9 -702,8 +702,8 @@@ static void show_combined_header(struc
        int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
        const char *a_prefix = opt->a_prefix ? opt->a_prefix : "a/";
        const char *b_prefix = opt->b_prefix ? opt->b_prefix : "b/";
-       int use_color = DIFF_OPT_TST(opt, COLOR_DIFF);
-       const char *c_meta = diff_get_color(use_color, DIFF_METAINFO);
-       const char *c_reset = diff_get_color(use_color, DIFF_RESET);
+       const char *c_meta = diff_get_color_opt(opt, DIFF_METAINFO);
+       const char *c_reset = diff_get_color_opt(opt, DIFF_RESET);
        const char *abb;
        int added = 0;
        int deleted = 0;
  }
  
  static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
 -                          int dense, struct rev_info *rev)
 +                          int dense, int working_tree_file,
 +                          struct rev_info *rev)
  {
        struct diff_options *opt = &rev->diffopt;
        unsigned long result_size, cnt, lno;
        struct sline *sline; /* survived lines */
        int mode_differs = 0;
        int i, show_hunks;
 -      int working_tree_file = is_null_sha1(elem->sha1);
        mmfile_t result_file;
        struct userdiff_driver *userdiff;
        struct userdiff_driver *textconv = NULL;
                show_combined_header(elem, num_parent, dense, rev,
                                     mode_differs, 1);
                dump_sline(sline, cnt, num_parent,
-                          DIFF_OPT_TST(opt, COLOR_DIFF), result_deleted);
+                          opt->use_color, result_deleted);
        }
        free(result);
  
@@@ -1028,12 -1027,6 +1027,12 @@@ static void show_raw_diff(struct combin
        write_name_quoted(p->path, stdout, line_termination);
  }
  
 +/*
 + * The result (p->elem) is from the working tree and their
 + * parents are typically from multiple stages during a merge
 + * (i.e. diff-files) or the state in HEAD and in the index
 + * (i.e. diff-index).
 + */
  void show_combined_diff(struct combine_diff_path *p,
                       int num_parent,
                       int dense,
                                  DIFF_FORMAT_NAME_STATUS))
                show_raw_diff(p, num_parent, rev);
        else if (opt->output_format & DIFF_FORMAT_PATCH)
 -              show_patch_diff(p, num_parent, dense, rev);
 +              show_patch_diff(p, num_parent, dense, 1, rev);
 +}
 +
 +static void free_combined_pair(struct diff_filepair *pair)
 +{
 +      free(pair->two);
 +      free(pair);
 +}
 +
 +/*
 + * A combine_diff_path expresses N parents on the LHS against 1 merge
 + * result. Synthesize a diff_filepair that has N entries on the "one"
 + * side and 1 entry on the "two" side.
 + *
 + * In the future, we might want to add more data to combine_diff_path
 + * so that we can fill fields we are ignoring (most notably, size) here,
 + * but currently nobody uses it, so this should suffice for now.
 + */
 +static struct diff_filepair *combined_pair(struct combine_diff_path *p,
 +                                         int num_parent)
 +{
 +      int i;
 +      struct diff_filepair *pair;
 +      struct diff_filespec *pool;
 +
 +      pair = xmalloc(sizeof(*pair));
 +      pool = xcalloc(num_parent + 1, sizeof(struct diff_filespec));
 +      pair->one = pool + 1;
 +      pair->two = pool;
 +
 +      for (i = 0; i < num_parent; i++) {
 +              pair->one[i].path = p->path;
 +              pair->one[i].mode = p->parent[i].mode;
 +              hashcpy(pair->one[i].sha1, p->parent[i].sha1);
 +              pair->one[i].sha1_valid = !is_null_sha1(p->parent[i].sha1);
 +              pair->one[i].has_more_entries = 1;
 +      }
 +      pair->one[num_parent - 1].has_more_entries = 0;
 +
 +      pair->two->path = p->path;
 +      pair->two->mode = p->mode;
 +      hashcpy(pair->two->sha1, p->sha1);
 +      pair->two->sha1_valid = !is_null_sha1(p->sha1);
 +      return pair;
 +}
 +
 +static void handle_combined_callback(struct diff_options *opt,
 +                                   struct combine_diff_path *paths,
 +                                   int num_parent,
 +                                   int num_paths)
 +{
 +      struct combine_diff_path *p;
 +      struct diff_queue_struct q;
 +      int i;
 +
 +      q.queue = xcalloc(num_paths, sizeof(struct diff_filepair *));
 +      q.alloc = num_paths;
 +      q.nr = num_paths;
 +      for (i = 0, p = paths; p; p = p->next) {
 +              if (!p->len)
 +                      continue;
 +              q.queue[i++] = combined_pair(p, num_parent);
 +      }
 +      opt->format_callback(&q, opt, opt->format_callback_data);
 +      for (i = 0; i < num_paths; i++)
 +              free_combined_pair(q.queue[i]);
 +      free(q.queue);
  }
  
  void diff_tree_combined(const unsigned char *sha1,
                else if (opt->output_format &
                         (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT))
                        needsep = 1;
 +              else if (opt->output_format & DIFF_FORMAT_CALLBACK)
 +                      handle_combined_callback(opt, paths, num_parent, num_paths);
 +
                if (opt->output_format & DIFF_FORMAT_PATCH) {
                        if (needsep)
                                putchar(opt->line_termination);
                        for (p = paths; p; p = p->next) {
                                if (p->len)
                                        show_patch_diff(p, num_parent, dense,
 -                                                      rev);
 +                                                      0, rev);
                        }
                }
        }
diff --combined diff.c
index d3d8daec77142bc6a67aacdc2e62ee522ddf12db,052e42adf0af124fc0d6d61328bb8f566ceaeaa7..fcc0078074c364d0a4c2bd75a6d390e517eb7f87
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -137,7 -137,7 +137,7 @@@ static int git_config_rename(const cha
  int git_diff_ui_config(const char *var, const char *value, void *cb)
  {
        if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
-               diff_use_color_default = git_config_colorbool(var, value, -1);
+               diff_use_color_default = git_config_colorbool(var, value);
                return 0;
        }
        if (!strcmp(var, "diff.renames")) {
        if (!strcmp(var, "diff.ignoresubmodules"))
                handle_ignore_submodules_arg(&default_diff_options, value);
  
+       if (git_color_config(var, value, cb) < 0)
+               return -1;
        return git_diff_basic_config(var, value, cb);
  }
  
@@@ -212,7 -215,7 +215,7 @@@ int git_diff_basic_config(const char *v
        if (!prefixcmp(var, "submodule."))
                return parse_submodule_config_option(var, value);
  
-       return git_color_default_config(var, value, cb);
+       return git_default_config(var, value, cb);
  }
  
  static char *quote_two(const char *one, const char *two)
@@@ -583,11 -586,10 +586,10 @@@ static void emit_rewrite_diff(const cha
                              struct diff_options *o)
  {
        int lc_a, lc_b;
-       int color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
        const char *name_a_tab, *name_b_tab;
-       const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO);
-       const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO);
-       const char *reset = diff_get_color(color_diff, DIFF_RESET);
+       const char *metainfo = diff_get_color(o->use_color, DIFF_METAINFO);
+       const char *fraginfo = diff_get_color(o->use_color, DIFF_FRAGINFO);
+       const char *reset = diff_get_color(o->use_color, DIFF_RESET);
        static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
        const char *a_prefix, *b_prefix;
        char *data_one, *data_two;
        size_two = fill_textconv(textconv_two, two, &data_two);
  
        memset(&ecbdata, 0, sizeof(ecbdata));
-       ecbdata.color_diff = color_diff;
+       ecbdata.color_diff = want_color(o->use_color);
        ecbdata.found_changesp = &o->found_changes;
        ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
        ecbdata.opt = o;
@@@ -1004,7 -1006,7 +1006,7 @@@ static void free_diff_words_data(struc
  
  const char *diff_get_color(int diff_use_color, enum color_diff ix)
  {
-       if (diff_use_color)
+       if (want_color(diff_use_color))
                return diff_colors[ix];
        return "";
  }
@@@ -1316,10 -1318,9 +1318,10 @@@ static void show_stats(struct diffstat_
        int i, len, add, del, adds = 0, dels = 0;
        uintmax_t max_change = 0, max_len = 0;
        int total_files = data->nr;
 -      int width, name_width;
 +      int width, name_width, count;
        const char *reset, *add_c, *del_c;
        const char *line_prefix = "";
 +      int extra_shown = 0;
        struct strbuf *msg = NULL;
  
        if (data->nr == 0)
  
        width = options->stat_width ? options->stat_width : 80;
        name_width = options->stat_name_width ? options->stat_name_width : 50;
 +      count = options->stat_count ? options->stat_count : data->nr;
  
        /* Sanity: give at least 5 columns to the graph,
         * but leave at least 10 columns for the name.
        add_c = diff_get_color_opt(options, DIFF_FILE_NEW);
        del_c = diff_get_color_opt(options, DIFF_FILE_OLD);
  
 -      for (i = 0; i < data->nr; i++) {
 +      for (i = 0; (i < count) && (i < data->nr); i++) {
                struct diffstat_file *file = data->files[i];
                uintmax_t change = file->added + file->deleted;
 +              if (!data->files[i]->is_renamed &&
 +                       (change == 0)) {
 +                      count++; /* not shown == room for one more */
 +                      continue;
 +              }
                fill_print_name(file);
                len = strlen(file->print_name);
                if (max_len < len)
                if (max_change < change)
                        max_change = change;
        }
 +      count = i; /* min(count, data->nr) */
  
        /* Compute the width of the graph part;
         * 10 is for one blank at the beginning of the line plus
        else
                width = max_change;
  
 -      for (i = 0; i < data->nr; i++) {
 +      for (i = 0; i < count; i++) {
                const char *prefix = "";
                char *name = data->files[i]->print_name;
                uintmax_t added = data->files[i]->added;
                uintmax_t deleted = data->files[i]->deleted;
                int name_len;
  
 +              if (!data->files[i]->is_renamed &&
 +                       (added + deleted == 0)) {
 +                      total_files--;
 +                      continue;
 +              }
                /*
                 * "scale" the filename
                 */
                        fprintf(options->file, "  Unmerged\n");
                        continue;
                }
 -              else if (!data->files[i]->is_renamed &&
 -                       (added + deleted == 0)) {
 -                      total_files--;
 -                      continue;
 -              }
  
                /*
                 * scale the add/delete
                show_graph(options->file, '-', del, del_c, reset);
                fprintf(options->file, "\n");
        }
 +      for (i = count; i < data->nr; i++) {
 +              uintmax_t added = data->files[i]->added;
 +              uintmax_t deleted = data->files[i]->deleted;
 +              if (!data->files[i]->is_renamed &&
 +                       (added + deleted == 0)) {
 +                      total_files--;
 +                      continue;
 +              }
 +              adds += added;
 +              dels += deleted;
 +              if (!extra_shown)
 +                      fprintf(options->file, "%s ...\n", line_prefix);
 +              extra_shown = 1;
 +      }
        fprintf(options->file, "%s", line_prefix);
        fprintf(options->file,
               " %d files changed, %d insertions(+), %d deletions(-)\n",
@@@ -1808,11 -1788,10 +1810,10 @@@ static int is_conflict_marker(const cha
  static void checkdiff_consume(void *priv, char *line, unsigned long len)
  {
        struct checkdiff_t *data = priv;
-       int color_diff = DIFF_OPT_TST(data->o, COLOR_DIFF);
        int marker_size = data->conflict_marker_size;
-       const char *ws = diff_get_color(color_diff, DIFF_WHITESPACE);
-       const char *reset = diff_get_color(color_diff, DIFF_RESET);
-       const char *set = diff_get_color(color_diff, DIFF_FILE_NEW);
+       const char *ws = diff_get_color(data->o->use_color, DIFF_WHITESPACE);
+       const char *reset = diff_get_color(data->o->use_color, DIFF_RESET);
+       const char *set = diff_get_color(data->o->use_color, DIFF_FILE_NEW);
        char *err;
        char *line_prefix = "";
        struct strbuf *msgbuf;
@@@ -2157,7 -2136,7 +2158,7 @@@ static void builtin_diff(const char *na
                memset(&xecfg, 0, sizeof(xecfg));
                memset(&ecbdata, 0, sizeof(ecbdata));
                ecbdata.label_path = lbl;
-               ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
+               ecbdata.color_diff = want_color(o->use_color);
                ecbdata.found_changesp = &o->found_changes;
                ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
                if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
                                        break;
                                }
                        }
-                       if (DIFF_OPT_TST(o, COLOR_DIFF)) {
+                       if (want_color(o->use_color)) {
                                struct diff_words_style *st = ecbdata.diff_words->style;
                                st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
                                st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
@@@ -2855,7 -2834,7 +2856,7 @@@ static void run_diff_cmd(const char *pg
                 */
                fill_metainfo(msg, name, other, one, two, o, p,
                              &must_show_header,
-                             DIFF_OPT_TST(o, COLOR_DIFF) && !pgm);
+                             want_color(o->use_color) && !pgm);
                xfrm_msg = msg->len ? msg->buf : NULL;
        }
  
@@@ -3021,8 -3000,7 +3022,7 @@@ void diff_setup(struct diff_options *op
  
        options->change = diff_change;
        options->add_remove = diff_addremove;
-       if (diff_use_color_default > 0)
-               DIFF_OPT_SET(options, COLOR_DIFF);
+       options->use_color = diff_use_color_default;
        options->detect_rename = diff_detect_rename_default;
  
        if (diff_no_prefix) {
@@@ -3230,7 -3208,6 +3230,7 @@@ static int stat_opt(struct diff_option
        char *end;
        int width = options->stat_width;
        int name_width = options->stat_name_width;
 +      int count = options->stat_count;
        int argcount = 1;
  
        arg += strlen("--stat");
                                name_width = strtoul(av[1], &end, 10);
                                argcount = 2;
                        }
 +              } else if (!prefixcmp(arg, "-count")) {
 +                      arg += strlen("-count");
 +                      if (*arg == '=')
 +                              count = strtoul(arg + 1, &end, 10);
 +                      else if (!*arg && !av[1])
 +                              die("Option '--stat-count' requires a value");
 +                      else if (!*arg) {
 +                              count = strtoul(av[1], &end, 10);
 +                              argcount = 2;
 +                      }
                }
                break;
        case '=':
                width = strtoul(arg+1, &end, 10);
                if (*end == ',')
                        name_width = strtoul(end+1, &end, 10);
 +              if (*end == ',')
 +                      count = strtoul(end+1, &end, 10);
        }
  
        /* Important! This checks all the error cases! */
        options->output_format |= DIFF_FORMAT_DIFFSTAT;
        options->stat_name_width = name_width;
        options->stat_width = width;
 +      options->stat_count = count;
        return argcount;
  }
  
@@@ -3349,7 -3313,7 +3349,7 @@@ int diff_opt_parse(struct diff_options 
        else if (!strcmp(arg, "-s"))
                options->output_format |= DIFF_FORMAT_NO_OUTPUT;
        else if (!prefixcmp(arg, "--stat"))
 -              /* --stat, --stat-width, or --stat-name-width */
 +              /* --stat, --stat-width, --stat-name-width, or --stat-count */
                return stat_opt(options, av);
  
        /* renames options */
                DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
        else if (!strcmp(arg, "--patience"))
                DIFF_XDL_SET(options, PATIENCE_DIFF);
 +      else if (!strcmp(arg, "--histogram"))
 +              DIFF_XDL_SET(options, HISTOGRAM_DIFF);
  
        /* flags options */
        else if (!strcmp(arg, "--binary")) {
        else if (!strcmp(arg, "--follow"))
                DIFF_OPT_SET(options, FOLLOW_RENAMES);
        else if (!strcmp(arg, "--color"))
-               DIFF_OPT_SET(options, COLOR_DIFF);
+               options->use_color = 1;
        else if (!prefixcmp(arg, "--color=")) {
-               int value = git_config_colorbool(NULL, arg+8, -1);
-               if (value == 0)
-                       DIFF_OPT_CLR(options, COLOR_DIFF);
-               else if (value > 0)
-                       DIFF_OPT_SET(options, COLOR_DIFF);
-               else
+               int value = git_config_colorbool(NULL, arg+8);
+               if (value < 0)
                        return error("option `color' expects \"always\", \"auto\", or \"never\"");
+               options->use_color = value;
        }
        else if (!strcmp(arg, "--no-color"))
-               DIFF_OPT_CLR(options, COLOR_DIFF);
+               options->use_color = 0;
        else if (!strcmp(arg, "--color-words")) {
-               DIFF_OPT_SET(options, COLOR_DIFF);
+               options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
        }
        else if (!prefixcmp(arg, "--color-words=")) {
-               DIFF_OPT_SET(options, COLOR_DIFF);
+               options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
                options->word_regex = arg + 14;
        }
                if (!strcmp(type, "plain"))
                        options->word_diff = DIFF_WORDS_PLAIN;
                else if (!strcmp(type, "color")) {
-                       DIFF_OPT_SET(options, COLOR_DIFF);
+                       options->use_color = 1;
                        options->word_diff = DIFF_WORDS_COLOR;
                }
                else if (!strcmp(type, "porcelain"))
diff --combined diff.h
index b920a20f80936ab66f5fe88c950dd35e38e1e856,04a9ad781b54e4beef2987a050b7e6c6f6cd33cf..8c66b59517305546d3e2f66fca652284468e1da1
--- 1/diff.h
--- 2/diff.h
+++ b/diff.h
@@@ -58,7 -58,7 +58,7 @@@ typedef struct strbuf *(*diff_prefix_fn
  #define DIFF_OPT_SILENT_ON_REMOVE    (1 <<  5)
  #define DIFF_OPT_FIND_COPIES_HARDER  (1 <<  6)
  #define DIFF_OPT_FOLLOW_RENAMES      (1 <<  7)
- #define DIFF_OPT_COLOR_DIFF          (1 <<  8)
+ /* (1 <<  8) unused */
  /* (1 <<  9) unused */
  #define DIFF_OPT_HAS_CHANGES         (1 << 10)
  #define DIFF_OPT_QUICK               (1 << 11)
@@@ -101,6 -101,7 +101,7 @@@ struct diff_options 
        const char *single_follow;
        const char *a_prefix, *b_prefix;
        unsigned flags;
+       int use_color;
        int context;
        int interhunkcontext;
        int break_opt;
  
        int stat_width;
        int stat_name_width;
 +      int stat_count;
        const char *word_regex;
        enum diff_words_type word_diff;
  
@@@ -160,7 -160,7 +161,7 @@@ enum color_diff 
  };
  const char *diff_get_color(int diff_use_color, enum color_diff ix);
  #define diff_get_color_opt(o, ix) \
-       diff_get_color(DIFF_OPT_TST((o), COLOR_DIFF), ix)
+       diff_get_color((o)->use_color, ix)
  
  
  extern const char mime_boundary_leader[];
diff --combined grep.c
index 26e8d8ec4cbec60cd5f4e2bef0d99607c7118fb7,e52654b20b51a9f8de5e28159136dbac96f6e889..2dd2a25ad7086698e5bee28442b3cd935444432e
--- 1/grep.c
--- 2/grep.c
+++ b/grep.c
@@@ -430,7 -430,7 +430,7 @@@ static int word_char(char ch
  static void output_color(struct grep_opt *opt, const void *data, size_t size,
                         const char *color)
  {
-       if (opt->color && color && color[0]) {
+       if (want_color(opt->color) && color && color[0]) {
                opt->output(opt, color, strlen(color));
                opt->output(opt, data, size);
                opt->output(opt, GIT_COLOR_RESET, strlen(GIT_COLOR_RESET));
@@@ -721,10 -721,7 +721,10 @@@ static void show_line(struct grep_opt *
        int rest = eol - bol;
        char *line_color = NULL;
  
 -      if (opt->pre_context || opt->post_context) {
 +      if (opt->file_break && opt->last_shown == 0) {
 +              if (opt->show_hunk_mark)
 +                      opt->output(opt, "\n", 1);
 +      } 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);
                        opt->output(opt, "\n", 1);
                }
        }
 +      if (opt->heading && opt->last_shown == 0) {
 +              output_color(opt, name, strlen(name), opt->color_filename);
 +              opt->output(opt, "\n", 1);
 +      }
        opt->last_shown = lno;
  
 -      if (opt->pathname) {
 +      if (!opt->heading && opt->pathname) {
                output_color(opt, name, strlen(name), opt->color_filename);
                output_sep(opt, sign);
        }
@@@ -819,13 -812,10 +819,13 @@@ static void show_funcname_line(struct g
  }
  
  static void show_pre_context(struct grep_opt *opt, const char *name, char *buf,
 -                           char *bol, unsigned lno)
 +                           char *bol, char *end, unsigned lno)
  {
        unsigned cur = lno, from = 1, funcname_lno = 0;
 -      int funcname_needed = opt->funcname;
 +      int funcname_needed = !!opt->funcname;
 +
 +      if (opt->funcbody && !match_funcname(opt, bol, end))
 +              funcname_needed = 2;
  
        if (opt->pre_context < lno)
                from = lno - opt->pre_context;
                from = opt->last_shown + 1;
  
        /* Rewind. */
 -      while (bol > buf && cur > from) {
 +      while (bol > buf &&
 +             cur > (funcname_needed == 2 ? opt->last_shown + 1 : from)) {
                char *eol = --bol;
  
                while (bol > buf && bol[-1] != '\n')
@@@ -946,26 -935,15 +946,26 @@@ static int grep_buffer_1(struct grep_op
        int binary_match_only = 0;
        unsigned count = 0;
        int try_lookahead = 0;
 +      int show_function = 0;
        enum grep_context ctx = GREP_CONTEXT_HEAD;
        xdemitconf_t xecfg;
  
        if (!opt->output)
                opt->output = std_output;
  
 -      if (opt->last_shown && (opt->pre_context || opt->post_context) &&
 -          opt->output == std_output)
 -              opt->show_hunk_mark = 1;
 +      if (opt->pre_context || opt->post_context || opt->file_break ||
 +          opt->funcbody) {
 +              /* Show hunk marks, except for the first file. */
 +              if (opt->last_shown)
 +                      opt->show_hunk_mark = 1;
 +              /*
 +               * If we're using threads then we can't easily identify
 +               * the first file.  Always put hunk marks in that case
 +               * and skip the very first one later in work_done().
 +               */
 +              if (opt->output != std_output)
 +                      opt->show_hunk_mark = 1;
 +      }
        opt->last_shown = 0;
  
        switch (opt->binary) {
                 */
                if (try_lookahead
                    && !(last_hit
 -                       && lno <= last_hit + opt->post_context)
 +                       && (show_function ||
 +                           lno <= last_hit + opt->post_context))
                    && look_ahead(opt, &left, &lno, &bol))
                        break;
                eol = end_of_line(bol, &left);
                        /* Hit at this line.  If we haven't shown the
                         * pre-context lines, we would need to show them.
                         */
 -                      if (opt->pre_context)
 -                              show_pre_context(opt, name, buf, bol, lno);
 +                      if (opt->pre_context || opt->funcbody)
 +                              show_pre_context(opt, name, buf, bol, eol, lno);
                        else if (opt->funcname)
                                show_funcname_line(opt, name, buf, bol, lno);
                        show_line(opt, bol, eol, name, lno, ':');
                        last_hit = lno;
 +                      if (opt->funcbody)
 +                              show_function = 1;
 +                      goto next_line;
                }
 -              else if (last_hit &&
 -                       lno <= last_hit + opt->post_context) {
 +              if (show_function && match_funcname(opt, bol, eol))
 +                      show_function = 0;
 +              if (show_function ||
 +                  (last_hit && lno <= last_hit + opt->post_context)) {
                        /* If the last hit is within the post context,
                         * we need to show this line.
                         */
diff --combined parse-options-cb.c
index c248f667c1cc48075fb278a3309f74c7122502eb,0000000000000000000000000000000000000000..6db0921fc1fde3b5fbf829577bdd019ae95462e0
mode 100644,000000..100644
--- /dev/null
@@@ -1,125 -1,0 +1,125 @@@
-       value = git_config_colorbool(NULL, arg, -1);
 +#include "git-compat-util.h"
 +#include "parse-options.h"
 +#include "cache.h"
 +#include "commit.h"
 +#include "color.h"
 +#include "string-list.h"
 +
 +/*----- some often used options -----*/
 +
 +int parse_opt_abbrev_cb(const struct option *opt, const char *arg, int unset)
 +{
 +      int v;
 +
 +      if (!arg) {
 +              v = unset ? 0 : DEFAULT_ABBREV;
 +      } else {
 +              v = strtol(arg, (char **)&arg, 10);
 +              if (*arg)
 +                      return opterror(opt, "expects a numerical value", 0);
 +              if (v && v < MINIMUM_ABBREV)
 +                      v = MINIMUM_ABBREV;
 +              else if (v > 40)
 +                      v = 40;
 +      }
 +      *(int *)(opt->value) = v;
 +      return 0;
 +}
 +
 +int parse_opt_approxidate_cb(const struct option *opt, const char *arg,
 +                           int unset)
 +{
 +      *(unsigned long *)(opt->value) = approxidate(arg);
 +      return 0;
 +}
 +
 +int parse_opt_color_flag_cb(const struct option *opt, const char *arg,
 +                          int unset)
 +{
 +      int value;
 +
 +      if (!arg)
 +              arg = unset ? "never" : (const char *)opt->defval;
++      value = git_config_colorbool(NULL, arg);
 +      if (value < 0)
 +              return opterror(opt,
 +                      "expects \"always\", \"auto\", or \"never\"", 0);
 +      *(int *)opt->value = value;
 +      return 0;
 +}
 +
 +int parse_opt_verbosity_cb(const struct option *opt, const char *arg,
 +                         int unset)
 +{
 +      int *target = opt->value;
 +
 +      if (unset)
 +              /* --no-quiet, --no-verbose */
 +              *target = 0;
 +      else if (opt->short_name == 'v') {
 +              if (*target >= 0)
 +                      (*target)++;
 +              else
 +                      *target = 1;
 +      } else {
 +              if (*target <= 0)
 +                      (*target)--;
 +              else
 +                      *target = -1;
 +      }
 +      return 0;
 +}
 +
 +int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
 +{
 +      unsigned char sha1[20];
 +      struct commit *commit;
 +
 +      if (!arg)
 +              return -1;
 +      if (get_sha1(arg, sha1))
 +              return error("malformed object name %s", arg);
 +      commit = lookup_commit_reference(sha1);
 +      if (!commit)
 +              return error("no such commit %s", arg);
 +      commit_list_insert(commit, opt->value);
 +      return 0;
 +}
 +
 +int parse_opt_tertiary(const struct option *opt, const char *arg, int unset)
 +{
 +      int *target = opt->value;
 +      *target = unset ? 2 : 1;
 +      return 0;
 +}
 +
 +int parse_options_concat(struct option *dst, size_t dst_size, struct option *src)
 +{
 +      int i, j;
 +
 +      for (i = 0; i < dst_size; i++)
 +              if (dst[i].type == OPTION_END)
 +                      break;
 +      for (j = 0; i < dst_size; i++, j++) {
 +              dst[i] = src[j];
 +              if (src[j].type == OPTION_END)
 +                      return 0;
 +      }
 +      return -1;
 +}
 +
 +int parse_opt_string_list(const struct option *opt, const char *arg, int unset)
 +{
 +      struct string_list *v = opt->value;
 +
 +      if (unset) {
 +              string_list_clear(v, 0);
 +              return 0;
 +      }
 +
 +      if (!arg)
 +              return -1;
 +
 +      string_list_append(v, xstrdup(arg));
 +      return 0;
 +}
diff --combined t/test-lib.sh
index e27422217db28f0c6693deb0d9c093ba89a72dda,395bf609ad9892d5c7e2d44ed5d75f6b51114d3d..d7dfc8b0b1939180f16b8245e58465f883e85cbc
@@@ -92,10 -92,6 +92,10 @@@ _x40="$_x05$_x05$_x05$_x05$_x05$_x05$_x
  # Zero SHA-1
  _z40=0000000000000000000000000000000000000000
  
 +# Line feed
 +LF='
 +'
 +
  # Each test should start with something like this, after copyright notices:
  #
  # test_description='Description of this test...
@@@ -361,6 -357,24 +361,24 @@@ test_chmod () 
        git update-index --add "--chmod=$@"
  }
  
+ # Unset a configuration variable, but don't fail if it doesn't exist.
+ test_unconfig () {
+       git config --unset-all "$@"
+       config_status=$?
+       case "$config_status" in
+       5) # ok, nothing to unset
+               config_status=0
+               ;;
+       esac
+       return $config_status
+ }
+ # Set git config, automatically unsetting it after the test is over.
+ test_config () {
+       test_when_finished "test_unconfig '$1'" &&
+       git config "$@"
+ }
  # Use test_set_prereq to tell that a particular prerequisite is available.
  # The prerequisite can later be checked for in two ways:
  #
@@@ -448,26 -462,20 +466,26 @@@ test_debug () 
        test "$debug" = "" || eval "$1"
  }
  
 +test_eval_ () {
 +      # This is a separate function because some tests use
 +      # "return" to end a test_expect_success block early.
 +      eval >&3 2>&4 "$*"
 +}
 +
  test_run_ () {
        test_cleanup=:
        expecting_failure=$2
 -      eval >&3 2>&4 "$1"
 +      test_eval_ "$1"
        eval_ret=$?
  
        if test -z "$immediate" || test $eval_ret = 0 || test -n "$expecting_failure"
        then
 -              eval >&3 2>&4 "$test_cleanup"
 +              test_eval_ "$test_cleanup"
        fi
        if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE"; then
                echo ""
        fi
 -      return 0
 +      return "$eval_ret"
  }
  
  test_skip () {
@@@ -512,7 -520,8 +530,7 @@@ test_expect_failure () 
        if ! test_skip "$@"
        then
                say >&3 "checking known breakage: $2"
 -              test_run_ "$2" expecting_failure
 -              if [ "$?" = 0 -a "$eval_ret" = 0 ]
 +              if test_run_ "$2" expecting_failure
                then
                        test_known_broken_ok_ "$1"
                else
@@@ -530,7 -539,8 +548,7 @@@ test_expect_success () 
        if ! test_skip "$@"
        then
                say >&3 "expecting success: $2"
 -              test_run_ "$2"
 -              if [ "$?" = 0 -a "$eval_ret" = 0 ]
 +              if test_run_ "$2"
                then
                        test_ok_ "$1"
                else
diff --combined wt-status.c
index 02377729c401fa6e041a713a89fe986fa0f562a0,f2016dcdcc657421b95e6adb751d099dd7804f9d..8836a527d0b1980bd4ebdd50b3225f7ce37ccf79
@@@ -26,7 -26,9 +26,9 @@@ static char default_wt_status_colors[][
  
  static const char *color(int slot, struct wt_status *s)
  {
-       const char *c = s->use_color > 0 ? s->color_palette[slot] : "";
+       const char *c = "";
+       if (want_color(s->use_color))
+               c = s->color_palette[slot];
        if (slot == WT_STATUS_ONBRANCH && color_is_nil(c))
                c = s->color_palette[WT_STATUS_HEADER];
        return c;
@@@ -642,7 -644,7 +644,7 @@@ static void wt_status_print_other(struc
        int i;
        struct strbuf buf = STRBUF_INIT;
  
 -      if (!s->untracked.nr)
 +      if (!l->nr)
                return;
  
        wt_status_print_other_header(s, what, how);
@@@ -681,7 -683,7 +683,7 @@@ static void wt_status_print_verbose(str
         * will have checked isatty on stdout).
         */
        if (s->fp != stdout)
-               DIFF_OPT_CLR(&rev.diffopt, COLOR_DIFF);
+               rev.diffopt.use_color = 0;
        run_diff_index(&rev, 1);
  }