Merge branch 'jc/grep-commandline-vs-configuration'
authorJunio C Hamano <gitster@pobox.com>
Thu, 4 Aug 2016 21:39:18 +0000 (14:39 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 4 Aug 2016 21:39:18 +0000 (14:39 -0700)
"git -c grep.patternType=extended log --basic-regexp" misbehaved
because the internal API to access the grep machinery was not
designed well.

* jc/grep-commandline-vs-configuration:
  grep: further simplify setting the pattern type

1  2 
grep.c
grep.h
revision.c
t/t4202-log.sh

diff --combined grep.c
--- 1/grep.c
--- 2/grep.c
+++ b/grep.c
@@@ -4,8 -4,6 +4,8 @@@
  #include "xdiff-interface.h"
  #include "diff.h"
  #include "diffcore.h"
 +#include "commit.h"
 +#include "quote.h"
  
  static int grep_source_load(struct grep_source *gs);
  static int grep_source_is_binary(struct grep_source *gs);
@@@ -33,14 -31,14 +33,14 @@@ void init_grep_defaults(void
        opt->max_depth = -1;
        opt->pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED;
        opt->extended_regexp_option = 0;
 -      strcpy(opt->color_context, "");
 -      strcpy(opt->color_filename, "");
 -      strcpy(opt->color_function, "");
 -      strcpy(opt->color_lineno, "");
 -      strcpy(opt->color_match_context, GIT_COLOR_BOLD_RED);
 -      strcpy(opt->color_match_selected, GIT_COLOR_BOLD_RED);
 -      strcpy(opt->color_selected, "");
 -      strcpy(opt->color_sep, GIT_COLOR_CYAN);
 +      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);
        opt->color = -1;
  }
  
@@@ -153,27 -151,17 +153,17 @@@ void grep_init(struct grep_opt *opt, co
        opt->regflags = def->regflags;
        opt->relative = def->relative;
  
 -      strcpy(opt->color_context, def->color_context);
 -      strcpy(opt->color_filename, def->color_filename);
 -      strcpy(opt->color_function, def->color_function);
 -      strcpy(opt->color_lineno, def->color_lineno);
 -      strcpy(opt->color_match_context, def->color_match_context);
 -      strcpy(opt->color_match_selected, def->color_match_selected);
 -      strcpy(opt->color_selected, def->color_selected);
 -      strcpy(opt->color_sep, def->color_sep);
 +      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);
  }
  
- void grep_commit_pattern_type(enum grep_pattern_type pattern_type, struct grep_opt *opt)
- {
-       if (pattern_type != GREP_PATTERN_TYPE_UNSPECIFIED)
-               grep_set_pattern_type_option(pattern_type, opt);
-       else if (opt->pattern_type_option != GREP_PATTERN_TYPE_UNSPECIFIED)
-               grep_set_pattern_type_option(opt->pattern_type_option, opt);
-       else if (opt->extended_regexp_option)
-               grep_set_pattern_type_option(GREP_PATTERN_TYPE_ERE, opt);
- }
- void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, struct grep_opt *opt)
+ static void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, struct grep_opt *opt)
  {
        switch (pattern_type) {
        case GREP_PATTERN_TYPE_UNSPECIFIED:
        }
  }
  
+ void grep_commit_pattern_type(enum grep_pattern_type pattern_type, struct grep_opt *opt)
+ {
+       if (pattern_type != GREP_PATTERN_TYPE_UNSPECIFIED)
+               grep_set_pattern_type_option(pattern_type, opt);
+       else if (opt->pattern_type_option != GREP_PATTERN_TYPE_UNSPECIFIED)
+               grep_set_pattern_type_option(opt->pattern_type_option, opt);
+       else if (opt->extended_regexp_option)
+               grep_set_pattern_type_option(GREP_PATTERN_TYPE_ERE, opt);
+ }
  static struct grep_pat *create_grep_pat(const char *pat, size_t patlen,
                                        const char *origin, int no,
                                        enum grep_pat_token t,
@@@ -308,9 -306,9 +308,9 @@@ static NORETURN void compile_regexp_fai
        char where[1024];
  
        if (p->no)
 -              sprintf(where, "In '%s' at %d, ", p->origin, p->no);
 +              xsnprintf(where, sizeof(where), "In '%s' at %d, ", p->origin, p->no);
        else if (p->origin)
 -              sprintf(where, "%s, ", p->origin);
 +              xsnprintf(where, sizeof(where), "%s, ", p->origin);
        else
                where[0] = 0;
  
@@@ -324,16 -322,11 +324,16 @@@ static void compile_pcre_regexp(struct 
        int erroffset;
        int options = PCRE_MULTILINE;
  
 -      if (opt->ignore_case)
 +      if (opt->ignore_case) {
 +              if (has_non_ascii(p->pattern))
 +                      p->pcre_tables = pcre_maketables();
                options |= PCRE_CASELESS;
 +      }
 +      if (is_utf8_locale() && has_non_ascii(p->pattern))
 +              options |= PCRE_UTF8;
  
        p->pcre_regexp = pcre_compile(p->pattern, options, &error, &erroffset,
 -                      NULL);
 +                                    p->pcre_tables);
        if (!p->pcre_regexp)
                compile_regexp_failed(p, error);
  
@@@ -367,7 -360,6 +367,7 @@@ static void free_pcre_regexp(struct gre
  {
        pcre_free(p->pcre_regexp);
        pcre_free(p->pcre_extra_info);
 +      pcre_free((void *)p->pcre_tables);
  }
  #else /* !USE_LIBPCRE */
  static void compile_pcre_regexp(struct grep_pat *p, const struct grep_opt *opt)
@@@ -404,68 -396,26 +404,68 @@@ static int is_fixed(const char *s, size
        return 1;
  }
  
 +static void compile_fixed_regexp(struct grep_pat *p, struct grep_opt *opt)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +      int err;
 +      int regflags;
 +
 +      basic_regex_quote_buf(&sb, p->pattern);
 +      regflags = opt->regflags & ~REG_EXTENDED;
 +      if (opt->ignore_case)
 +              regflags |= REG_ICASE;
 +      err = regcomp(&p->regexp, sb.buf, regflags);
 +      if (opt->debug)
 +              fprintf(stderr, "fixed %s\n", sb.buf);
 +      strbuf_release(&sb);
 +      if (err) {
 +              char errbuf[1024];
 +              regerror(err, &p->regexp, errbuf, sizeof(errbuf));
 +              regfree(&p->regexp);
 +              compile_regexp_failed(p, errbuf);
 +      }
 +}
 +
  static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
  {
 +      int icase, ascii_only;
        int err;
  
        p->word_regexp = opt->word_regexp;
        p->ignore_case = opt->ignore_case;
 +      icase          = opt->regflags & REG_ICASE || p->ignore_case;
 +      ascii_only     = !has_non_ascii(p->pattern);
  
 +      /*
 +       * Even when -F (fixed) asks us to do a non-regexp search, we
 +       * may not be able to correctly case-fold when -i
 +       * (ignore-case) is asked (in which case, we'll synthesize a
 +       * regexp to match the pattern that matches regexp special
 +       * characters literally, while ignoring case differences).  On
 +       * the other hand, even without -F, if the pattern does not
 +       * have any regexp special characters and there is no need for
 +       * case-folding search, we can internally turn it into a
 +       * simple string match using kws.  p->fixed tells us if we
 +       * want to use kws.
 +       */
        if (opt->fixed || is_fixed(p->pattern, p->patternlen))
 -              p->fixed = 1;
 +              p->fixed = !icase || ascii_only;
        else
                p->fixed = 0;
  
        if (p->fixed) {
 -              if (opt->regflags & REG_ICASE || p->ignore_case)
 -                      p->kws = kwsalloc(tolower_trans_tbl);
 -              else
 -                      p->kws = kwsalloc(NULL);
 +              p->kws = kwsalloc(icase ? tolower_trans_tbl : NULL);
                kwsincr(p->kws, p->pattern, p->patternlen);
                kwsprep(p->kws);
                return;
 +      } else if (opt->fixed) {
 +              /*
 +               * We come here when the pattern has the non-ascii
 +               * characters we cannot case-fold, and asked to
 +               * ignore-case.
 +               */
 +              compile_fixed_regexp(p, opt);
 +              return;
        }
  
        if (opt->pcre) {
@@@ -1446,17 -1396,9 +1446,17 @@@ static int fill_textconv_grep(struct us
        return 0;
  }
  
 +static int is_empty_line(const char *bol, const char *eol)
 +{
 +      while (bol < eol && isspace(*bol))
 +              bol++;
 +      return bol == eol;
 +}
 +
  static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int collect_hits)
  {
        char *bol;
 +      char *peek_bol = NULL;
        unsigned long left;
        unsigned lno = 1;
        unsigned last_hit = 0;
                                show_function = 1;
                        goto next_line;
                }
 -              if (show_function && match_funcname(opt, gs, bol, eol))
 -                      show_function = 0;
 +              if (show_function && (!peek_bol || peek_bol < bol)) {
 +                      unsigned long peek_left = left;
 +                      char *peek_eol = eol;
 +
 +                      /*
 +                       * Trailing empty lines are not interesting.
 +                       * Peek past them to see if they belong to the
 +                       * body of the current function.
 +                       */
 +                      peek_bol = bol;
 +                      while (is_empty_line(peek_bol, peek_eol)) {
 +                              peek_bol = peek_eol + 1;
 +                              peek_eol = end_of_line(peek_bol, &peek_left);
 +                      }
 +
 +                      if (match_funcname(opt, gs, peek_bol, peek_eol))
 +                              show_function = 0;
 +              }
                if (show_function ||
                    (last_hit && lno <= last_hit + opt->post_context)) {
                        /* If the last hit is within the post context,
@@@ -1806,7 -1732,7 +1806,7 @@@ static int grep_source_load_file(struc
        if (lstat(filename, &st) < 0) {
        err_ret:
                if (errno != ENOENT)
 -                      error(_("'%s': %s"), filename, strerror(errno));
 +                      error_errno(_("failed to stat '%s'"), filename);
                return -1;
        }
        if (!S_ISREG(st.st_mode))
        i = open(filename, O_RDONLY);
        if (i < 0)
                goto err_ret;
 -      data = xmalloc(size + 1);
 +      data = xmallocz(size);
        if (st.st_size != read_in_full(i, data, size)) {
 -              error(_("'%s': short read %s"), filename, strerror(errno));
 +              error_errno(_("'%s': short read"), filename);
                close(i);
                free(data);
                return -1;
        }
        close(i);
 -      data[size] = 0;
  
        gs->buf = data;
        gs->size = size;
diff --combined grep.h
--- 1/grep.h
--- 2/grep.h
+++ b/grep.h
@@@ -48,7 -48,6 +48,7 @@@ struct grep_pat 
        regex_t regexp;
        pcre *pcre_regexp;
        pcre_extra *pcre_extra_info;
 +      const unsigned char *pcre_tables;
        kwset_t kws;
        unsigned fixed:1;
        unsigned ignore_case:1;
@@@ -145,7 -144,6 +145,6 @@@ struct grep_opt 
  extern void init_grep_defaults(void);
  extern int grep_config(const char *var, const char *value, void *);
  extern void grep_init(struct grep_opt *, const char *prefix);
- void grep_set_pattern_type_option(enum grep_pattern_type, struct grep_opt *opt);
  void grep_commit_pattern_type(enum grep_pattern_type, struct grep_opt *opt);
  
  extern void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t);
diff --combined revision.c
  #include "commit-slab.h"
  #include "dir.h"
  #include "cache-tree.h"
 +#include "bisect.h"
  
  volatile show_early_output_fn_t show_early_output;
  
 +static const char *term_bad;
 +static const char *term_good;
 +
  void show_object_with_name(FILE *out, struct object *obj, const char *name)
  {
        const char *p;
  
 -      fprintf(out, "%s ", sha1_to_hex(obj->sha1));
 +      fprintf(out, "%s ", oid_to_hex(&obj->oid));
        for (p = name; *p && *p != '\n'; p++)
                fputc(*p, out);
        fputc('\n', out);
@@@ -50,19 -46,19 +50,19 @@@ static void mark_tree_contents_unintere
        struct name_entry entry;
        struct object *obj = &tree->object;
  
 -      if (!has_sha1_file(obj->sha1))
 +      if (!has_object_file(&obj->oid))
                return;
        if (parse_tree(tree) < 0)
 -              die("bad tree %s", sha1_to_hex(obj->sha1));
 +              die("bad tree %s", oid_to_hex(&obj->oid));
  
        init_tree_desc(&desc, tree->buffer, tree->size);
        while (tree_entry(&desc, &entry)) {
                switch (object_type(entry.mode)) {
                case OBJ_TREE:
 -                      mark_tree_uninteresting(lookup_tree(entry.sha1));
 +                      mark_tree_uninteresting(lookup_tree(entry.oid->hash));
                        break;
                case OBJ_BLOB:
 -                      mark_blob_uninteresting(lookup_blob(entry.sha1));
 +                      mark_blob_uninteresting(lookup_blob(entry.oid->hash));
                        break;
                default:
                        /* Subproject commit - not in this repository */
  
  void mark_tree_uninteresting(struct tree *tree)
  {
 -      struct object *obj = &tree->object;
 +      struct object *obj;
  
        if (!tree)
                return;
 +
 +      obj = &tree->object;
        if (obj->flags & UNINTERESTING)
                return;
        obj->flags |= UNINTERESTING;
@@@ -99,7 -93,10 +99,7 @@@ void mark_parents_uninteresting(struct 
                commit_list_insert(l->item, &parents);
  
        while (parents) {
 -              struct commit *commit = parents->item;
 -              l = parents;
 -              parents = parents->next;
 -              free(l);
 +              struct commit *commit = pop_commit(&parents);
  
                while (commit) {
                        /*
                         * it is popped next time around, we won't be trying
                         * to parse it and get an error.
                         */
 -                      if (!has_sha1_file(commit->object.sha1))
 +                      if (!has_object_file(&commit->object.oid))
                                commit->object.parsed = 1;
  
                        if (commit->object.flags & UNINTERESTING)
@@@ -228,18 -225,19 +228,18 @@@ static struct commit *handle_commit(str
                        add_pending_object(revs, object, tag->tag);
                if (!tag->tagged)
                        die("bad tag");
 -              object = parse_object(tag->tagged->sha1);
 +              object = parse_object(tag->tagged->oid.hash);
                if (!object) {
                        if (flags & UNINTERESTING)
                                return NULL;
 -                      die("bad object %s", sha1_to_hex(tag->tagged->sha1));
 +                      die("bad object %s", oid_to_hex(&tag->tagged->oid));
                }
                object->flags |= flags;
                /*
                 * We'll handle the tagged object by looping or dropping
                 * through to the non-tag handlers below. Do not
 -               * propagate data from the tag's pending entry.
 +               * propagate path data from the tag's pending entry.
                 */
 -              name = "";
                path = NULL;
                mode = 0;
        }
@@@ -307,8 -305,8 +307,8 @@@ static int everybody_uninteresting(stru
                list = list->next;
                if (commit->object.flags & UNINTERESTING)
                        continue;
 -              if (interesting_cache)
 -                      *interesting_cache = commit;
 +
 +              *interesting_cache = commit;
                return 0;
        }
        return 1;
@@@ -455,7 -453,7 +455,7 @@@ static int rev_compare_tree(struct rev_
  
        tree_difference = REV_TREE_SAME;
        DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES);
 -      if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "",
 +      if (diff_tree_sha1(t1->object.oid.hash, t2->object.oid.hash, "",
                           &revs->pruning) < 0)
                return REV_TREE_DIFFERENT;
        return tree_difference;
@@@ -471,7 -469,7 +471,7 @@@ static int rev_same_tree_as_empty(struc
  
        tree_difference = REV_TREE_SAME;
        DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES);
 -      retval = diff_tree_sha1(NULL, t1->object.sha1, "", &revs->pruning);
 +      retval = diff_tree_sha1(NULL, t1->object.oid.hash, "", &revs->pruning);
  
        return retval >= 0 && (tree_difference == REV_TREE_SAME);
  }
@@@ -484,7 -482,7 +484,7 @@@ struct treesame_state 
  static struct treesame_state *initialise_treesame(struct rev_info *revs, struct commit *commit)
  {
        unsigned n = commit_list_count(commit->parents);
 -      struct treesame_state *st = xcalloc(1, sizeof(*st) + n);
 +      struct treesame_state *st = xcalloc(1, st_add(sizeof(*st), n));
        st->nparents = n;
        add_decoration(&revs->treesame, &commit->object, st);
        return st;
@@@ -555,7 -553,7 +555,7 @@@ static unsigned update_treesame(struct 
  
                st = lookup_decoration(&revs->treesame, &commit->object);
                if (!st)
 -                      die("update_treesame %s", sha1_to_hex(commit->object.sha1));
 +                      die("update_treesame %s", oid_to_hex(&commit->object.oid));
                relevant_parents = 0;
                relevant_change = irrelevant_change = 0;
                for (p = commit->parents, n = 0; p; n++, p = p->next) {
@@@ -653,8 -651,8 +653,8 @@@ static void try_to_simplify_commit(stru
                }
                if (parse_commit(p) < 0)
                        die("cannot simplify commit %s (because of %s)",
 -                          sha1_to_hex(commit->object.sha1),
 -                          sha1_to_hex(p->object.sha1));
 +                          oid_to_hex(&commit->object.oid),
 +                          oid_to_hex(&p->object.oid));
                switch (rev_compare_tree(revs, p, commit)) {
                case REV_TREE_SAME:
                        if (!revs->simplify_history || !relevant_commit(p)) {
                                 */
                                if (parse_commit(p) < 0)
                                        die("cannot simplify commit %s (invalid %s)",
 -                                          sha1_to_hex(commit->object.sha1),
 -                                          sha1_to_hex(p->object.sha1));
 +                                          oid_to_hex(&commit->object.oid),
 +                                          oid_to_hex(&p->object.oid));
                                p->parents = NULL;
                        }
                /* fallthrough */
                                irrelevant_change = 1;
                        continue;
                }
 -              die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
 +              die("bad tree compare for commit %s", oid_to_hex(&commit->object.oid));
        }
  
        /*
@@@ -1044,10 -1042,14 +1044,10 @@@ static int limit_list(struct rev_info *
        }
  
        while (list) {
 -              struct commit_list *entry = list;
 -              struct commit *commit = list->item;
 +              struct commit *commit = pop_commit(&list);
                struct object *obj = &commit->object;
                show_early_output_fn_t show;
  
 -              list = list->next;
 -              free(entry);
 -
                if (commit == interesting_cache)
                        interesting_cache = NULL;
  
@@@ -1134,7 -1136,7 +1134,7 @@@ static void add_rev_cmdline_list(struc
  {
        while (commit_list) {
                struct object *object = &commit_list->item->object;
 -              add_rev_cmdline(revs, object, sha1_to_hex(object->sha1),
 +              add_rev_cmdline(revs, object, oid_to_hex(&object->oid),
                                whence, flags);
                commit_list = commit_list->next;
        }
@@@ -1160,8 -1162,7 +1160,8 @@@ int ref_excluded(struct string_list *re
        return 0;
  }
  
 -static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 +static int handle_one_ref(const char *path, const struct object_id *oid,
 +                        int flag, void *cb_data)
  {
        struct all_refs_cb *cb = cb_data;
        struct object *object;
        if (ref_excluded(cb->all_revs->ref_excludes, path))
            return 0;
  
 -      object = get_reference(cb->all_revs, path, sha1, cb->all_flags);
 +      object = get_reference(cb->all_revs, path, oid->hash, cb->all_flags);
        add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags);
 -      add_pending_sha1(cb->all_revs, path, sha1, cb->all_flags);
 +      add_pending_sha1(cb->all_revs, path, oid->hash, cb->all_flags);
        return 0;
  }
  
@@@ -1235,8 -1236,7 +1235,8 @@@ static int handle_one_reflog_ent(unsign
        return 0;
  }
  
 -static int handle_one_reflog(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 +static int handle_one_reflog(const char *path, const struct object_id *oid,
 +                           int flag, void *cb_data)
  {
        struct all_refs_cb *cb = cb_data;
        cb->warned_bad_reflog = 0;
  void add_reflogs_to_pending(struct rev_info *revs, unsigned flags)
  {
        struct all_refs_cb cb;
 +
        cb.all_revs = revs;
        cb.all_flags = flags;
        for_each_reflog(handle_one_reflog, &cb);
@@@ -1323,7 -1322,7 +1323,7 @@@ static int add_parents_only(struct rev_
                        break;
                if (!((struct tag*)it)->tagged)
                        return 0;
 -              hashcpy(sha1, ((struct tag*)it)->tagged->sha1);
 +              hashcpy(sha1, ((struct tag*)it)->tagged->oid.hash);
        }
        if (it->type != OBJ_COMMIT)
                return 0;
@@@ -1356,10 -1355,8 +1356,10 @@@ void init_revisions(struct rev_info *re
        revs->skip_count = -1;
        revs->max_count = -1;
        revs->max_parents = -1;
 +      revs->expand_tabs_in_log = -1;
  
        revs->commit_format = CMIT_FMT_DEFAULT;
 +      revs->expand_tabs_in_log_default = 8;
  
        init_grep_defaults();
        grep_init(&revs->grep_filter, prefix);
@@@ -1382,7 -1379,7 +1382,7 @@@ static void add_pending_commit_list(str
        while (commit_list) {
                struct object *object = &commit_list->item->object;
                object->flags |= flags;
 -              add_pending_object(revs, object, sha1_to_hex(object->sha1));
 +              add_pending_object(revs, object, oid_to_hex(&object->oid));
                commit_list = commit_list->next;
        }
  }
@@@ -1425,7 -1422,7 +1425,7 @@@ static void prepare_show_merge(struct r
                       ce_same_name(ce, active_cache[i+1]))
                        i++;
        }
 -      free_pathspec(&revs->prune_data);
 +      clear_pathspec(&revs->prune_data);
        parse_pathspec(&revs->prune_data, PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,
                       PATHSPEC_PREFER_FULL | PATHSPEC_LITERAL_PATH, "", prune);
        revs->limited = 1;
@@@ -1502,10 -1499,10 +1502,10 @@@ int handle_revision_arg(const char *arg
  
                                a = (a_obj->type == OBJ_COMMIT
                                     ? (struct commit *)a_obj
 -                                   : lookup_commit_reference(a_obj->sha1));
 +                                   : lookup_commit_reference(a_obj->oid.hash));
                                b = (b_obj->type == OBJ_COMMIT
                                     ? (struct commit *)b_obj
 -                                   : lookup_commit_reference(b_obj->sha1));
 +                                   : lookup_commit_reference(b_obj->oid.hash));
                                if (!a || !b)
                                        goto missing;
                                exclude = get_merge_bases(a, b);
@@@ -1581,7 -1578,10 +1581,7 @@@ static void append_prune_data(struct cm
  static void read_pathspec_from_stdin(struct rev_info *revs, struct strbuf *sb,
                                     struct cmdline_pathspec *prune)
  {
 -      while (strbuf_getwholeline(sb, stdin, '\n') != EOF) {
 -              int len = sb->len;
 -              if (len && sb->buf[len - 1] == '\n')
 -                      sb->buf[--len] = '\0';
 +      while (strbuf_getline(sb, stdin) != EOF) {
                ALLOC_GROW(prune->path, prune->nr + 1, prune->alloc);
                prune->path[prune->nr++] = xstrdup(sb->buf);
        }
@@@ -1598,8 -1598,10 +1598,8 @@@ static void read_revisions_from_stdin(s
        warn_on_object_refname_ambiguity = 0;
  
        strbuf_init(&sb, 1000);
 -      while (strbuf_getwholeline(&sb, stdin, '\n') != EOF) {
 +      while (strbuf_getline(&sb, stdin) != EOF) {
                int len = sb.len;
 -              if (len && sb.buf[len - 1] == '\n')
 -                      sb.buf[--len] = '\0';
                if (!len)
                        break;
                if (sb.buf[0] == '-') {
@@@ -1856,23 -1858,12 +1856,23 @@@ static int handle_revision_opt(struct r
                revs->verbose_header = 1;
                revs->pretty_given = 1;
                get_commit_format(arg+9, revs);
 +      } else if (!strcmp(arg, "--expand-tabs")) {
 +              revs->expand_tabs_in_log = 8;
 +      } else if (!strcmp(arg, "--no-expand-tabs")) {
 +              revs->expand_tabs_in_log = 0;
 +      } else if (skip_prefix(arg, "--expand-tabs=", &arg)) {
 +              int val;
 +              if (strtol_i(arg, 10, &val) < 0 || val < 0)
 +                      die("'%s': not a non-negative integer", arg);
 +              revs->expand_tabs_in_log = val;
        } else if (!strcmp(arg, "--show-notes") || !strcmp(arg, "--notes")) {
                revs->show_notes = 1;
                revs->show_notes_given = 1;
                revs->notes_opt.use_default_notes = 1;
        } else if (!strcmp(arg, "--show-signature")) {
                revs->show_signature = 1;
 +      } else if (!strcmp(arg, "--no-show-signature")) {
 +              revs->show_signature = 0;
        } else if (!strcmp(arg, "--show-linear-break") ||
                   starts_with(arg, "--show-linear-break=")) {
                if (starts_with(arg, "--show-linear-break="))
        } else if (!strcmp(arg, "--full-history")) {
                revs->simplify_history = 0;
        } else if (!strcmp(arg, "--relative-date")) {
 -              revs->date_mode = DATE_RELATIVE;
 +              revs->date_mode.type = DATE_RELATIVE;
                revs->date_mode_explicit = 1;
        } else if ((argcount = parse_long_opt("date", argv, &optarg))) {
 -              revs->date_mode = parse_date_format(optarg);
 +              parse_date_format(optarg, &revs->date_mode);
                revs->date_mode_explicit = 1;
                return argcount;
        } else if (!strcmp(arg, "--log-size")) {
        } else if (!strcmp(arg, "--grep-debug")) {
                revs->grep_filter.debug = 1;
        } else if (!strcmp(arg, "--basic-regexp")) {
-               grep_set_pattern_type_option(GREP_PATTERN_TYPE_BRE, &revs->grep_filter);
+               revs->grep_filter.pattern_type_option = GREP_PATTERN_TYPE_BRE;
        } else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) {
-               grep_set_pattern_type_option(GREP_PATTERN_TYPE_ERE, &revs->grep_filter);
+               revs->grep_filter.pattern_type_option = GREP_PATTERN_TYPE_ERE;
        } else if (!strcmp(arg, "--regexp-ignore-case") || !strcmp(arg, "-i")) {
                revs->grep_filter.regflags |= REG_ICASE;
                DIFF_OPT_SET(&revs->diffopt, PICKAXE_IGNORE_CASE);
        } else if (!strcmp(arg, "--fixed-strings") || !strcmp(arg, "-F")) {
-               grep_set_pattern_type_option(GREP_PATTERN_TYPE_FIXED, &revs->grep_filter);
+               revs->grep_filter.pattern_type_option = GREP_PATTERN_TYPE_FIXED;
        } else if (!strcmp(arg, "--perl-regexp")) {
-               grep_set_pattern_type_option(GREP_PATTERN_TYPE_PCRE, &revs->grep_filter);
+               revs->grep_filter.pattern_type_option = GREP_PATTERN_TYPE_PCRE;
        } else if (!strcmp(arg, "--all-match")) {
                revs->grep_filter.all_match = 1;
        } else if (!strcmp(arg, "--invert-grep")) {
        } else if (!strcmp(arg, "--ignore-missing")) {
                revs->ignore_missing = 1;
        } else {
 -              int opts = diff_opt_parse(&revs->diffopt, argv, argc);
 +              int opts = diff_opt_parse(&revs->diffopt, argv, argc, revs->prefix);
                if (!opts)
                        unkv[(*unkc)++] = arg;
                return opts;
@@@ -2026,23 -2017,14 +2026,23 @@@ void parse_revision_opt(struct rev_inf
        ctx->argc -= n;
  }
  
 +static int for_each_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data, const char *term) {
 +      struct strbuf bisect_refs = STRBUF_INIT;
 +      int status;
 +      strbuf_addf(&bisect_refs, "refs/bisect/%s", term);
 +      status = for_each_ref_in_submodule(submodule, bisect_refs.buf, fn, cb_data);
 +      strbuf_release(&bisect_refs);
 +      return status;
 +}
 +
  static int for_each_bad_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data)
  {
 -      return for_each_ref_in_submodule(submodule, "refs/bisect/bad", fn, cb_data);
 +      return for_each_bisect_ref(submodule, fn, cb_data, term_bad);
  }
  
  static int for_each_good_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data)
  {
 -      return for_each_ref_in_submodule(submodule, "refs/bisect/good", fn, cb_data);
 +      return for_each_bisect_ref(submodule, fn, cb_data, term_good);
  }
  
  static int handle_revision_pseudo_opt(const char *submodule,
                handle_refs(submodule, revs, *flags, for_each_branch_ref_submodule);
                clear_ref_exclusion(&revs->ref_excludes);
        } else if (!strcmp(arg, "--bisect")) {
 +              read_bisect_terms(&term_bad, &term_good);
                handle_refs(submodule, revs, *flags, for_each_bad_bisect_ref);
                handle_refs(submodule, revs, *flags ^ (UNINTERESTING | BOTTOM), for_each_good_bisect_ref);
                revs->bisect = 1;
        return 1;
  }
  
 +static void NORETURN diagnose_missing_default(const char *def)
 +{
 +      unsigned char sha1[20];
 +      int flags;
 +      const char *refname;
 +
 +      refname = resolve_ref_unsafe(def, 0, sha1, &flags);
 +      if (!refname || !(flags & REF_ISSYMREF) || (flags & REF_ISBROKEN))
 +              die(_("your current branch appears to be broken"));
 +
 +      skip_prefix(refname, "refs/heads/", &refname);
 +      die(_("your current branch '%s' does not have any commits yet"),
 +          refname);
 +}
 +
  /*
   * Parse revision information, filling in the "rev_info" structure,
   * and removing the used arguments from the argument list.
@@@ -2277,7 -2243,7 +2277,7 @@@ int setup_revisions(int argc, const cha
                struct object *object;
                struct object_context oc;
                if (get_sha1_with_context(revs->def, 0, sha1, &oc))
 -                      die("bad default revision '%s'", revs->def);
 +                      diagnose_missing_default(revs->def);
                object = get_reference(revs, revs->def, sha1, 0);
                add_pending_object_with_mode(revs, object, revs->def, oc.mode);
        }
        if (revs->first_parent_only && revs->bisect)
                die(_("--first-parent is incompatible with --bisect"));
  
 +      if (revs->expand_tabs_in_log < 0)
 +              revs->expand_tabs_in_log = revs->expand_tabs_in_log_default;
 +
        return left;
  }
  
@@@ -2682,7 -2645,10 +2682,7 @@@ static void simplify_merges(struct rev_
                yet_to_do = NULL;
                tail = &yet_to_do;
                while (list) {
 -                      commit = list->item;
 -                      next = list->next;
 -                      free(list);
 -                      list = next;
 +                      commit = pop_commit(&list);
                        tail = simplify_one(revs, commit, tail);
                }
        }
        while (list) {
                struct merge_simplify_state *st;
  
 -              commit = list->item;
 -              next = list->next;
 -              free(list);
 -              list = next;
 +              commit = pop_commit(&list);
                st = locate_simplify_state(revs, commit);
                if (st->simplified == commit)
                        tail = &commit_list_insert(commit, tail)->next;
@@@ -2894,7 -2863,7 +2894,7 @@@ static int commit_match(struct commit *
        if (opt->show_notes) {
                if (!buf.len)
                        strbuf_addstr(&buf, message);
 -              format_display_notes(commit->object.sha1, &buf, encoding, 1);
 +              format_display_notes(commit->object.oid.hash, &buf, encoding, 1);
        }
  
        /*
@@@ -2924,7 -2893,7 +2924,7 @@@ enum commit_action get_commit_action(st
  {
        if (commit->object.flags & SHOWN)
                return commit_ignore;
 -      if (revs->unpacked && has_sha1_pack(commit->object.sha1))
 +      if (revs->unpacked && has_sha1_pack(commit->object.oid.hash))
                return commit_ignore;
        if (revs->show_all)
                return commit_show;
@@@ -3050,7 -3019,7 +3050,7 @@@ static void track_linear(struct rev_inf
                struct commit_list *p;
                for (p = revs->previous_parents; p; p = p->next)
                        if (p->item == NULL || /* first commit */
 -                          !hashcmp(p->item->object.sha1, commit->object.sha1))
 +                          !oidcmp(&p->item->object.oid, &commit->object.oid))
                                break;
                revs->linear = p != NULL;
        }
@@@ -3068,7 -3037,11 +3068,7 @@@ static struct commit *get_revision_1(st
                return NULL;
  
        do {
 -              struct commit_list *entry = revs->commits;
 -              struct commit *commit = entry->item;
 -
 -              revs->commits = entry->next;
 -              free(entry);
 +              struct commit *commit = pop_commit(&revs->commits);
  
                if (revs->reflog_info) {
                        save_parents(revs, commit);
                        if (add_parents_to_list(revs, commit, &revs->commits, NULL) < 0) {
                                if (!revs->ignore_missing_links)
                                        die("Failed to traverse parents of commit %s",
 -                                              sha1_to_hex(commit->object.sha1));
 +                                              oid_to_hex(&commit->object.oid));
                        }
                }
  
                        continue;
                case commit_error:
                        die("Failed to simplify parents of commit %s",
 -                          sha1_to_hex(commit->object.sha1));
 +                          oid_to_hex(&commit->object.oid));
                default:
                        if (revs->track_linear)
                                track_linear(revs, commit);
diff --combined t/t4202-log.sh
@@@ -101,8 -101,8 +101,8 @@@ test_expect_success 'oneline' 
  
  test_expect_success 'diff-filter=A' '
  
 -      git log --pretty="format:%s" --diff-filter=A HEAD > actual &&
 -      git log --pretty="format:%s" --diff-filter A HEAD > actual-separate &&
 +      git log --no-renames --pretty="format:%s" --diff-filter=A HEAD > actual &&
 +      git log --no-renames --pretty="format:%s" --diff-filter A HEAD > actual-separate &&
        printf "fifth\nfourth\nthird\ninitial" > expect &&
        test_cmp expect actual &&
        test_cmp expect actual-separate
@@@ -119,7 -119,7 +119,7 @@@ test_expect_success 'diff-filter=M' 
  
  test_expect_success 'diff-filter=D' '
  
 -      actual=$(git log --pretty="format:%s" --diff-filter=D HEAD) &&
 +      actual=$(git log --no-renames --pretty="format:%s" --diff-filter=D HEAD) &&
        expect=$(echo sixth ; echo third) &&
        verbose test "$actual" = "$expect"
  
@@@ -146,30 -146,7 +146,30 @@@ test_expect_success 'git log --follow' 
        actual=$(git log --follow --pretty="format:%s" ichi) &&
        expect=$(echo third ; echo second ; echo initial) &&
        verbose test "$actual" = "$expect"
 +'
 +
 +test_expect_success 'git config log.follow works like --follow' '
 +      test_config log.follow true &&
 +      actual=$(git log --pretty="format:%s" ichi) &&
 +      expect=$(echo third ; echo second ; echo initial) &&
 +      verbose test "$actual" = "$expect"
 +'
 +
 +test_expect_success 'git config log.follow does not die with multiple paths' '
 +      test_config log.follow true &&
 +      git log --pretty="format:%s" ichi ein
 +'
  
 +test_expect_success 'git config log.follow does not die with no paths' '
 +      test_config log.follow true &&
 +      git log --
 +'
 +
 +test_expect_success 'git config log.follow is overridden by --no-follow' '
 +      test_config log.follow true &&
 +      actual=$(git log --no-follow --pretty="format:%s" ichi) &&
 +      expect="third" &&
 +      verbose test "$actual" = "$expect"
  '
  
  cat > expect << EOF
@@@ -255,6 -232,20 +255,20 @@@ test_expect_success 'log -F -E --grep=<
        test_cmp expect actual
  '
  
+ test_expect_success 'log with grep.patternType configuration' '
+       >expect &&
+       git -c grep.patterntype=fixed \
+       log -1 --pretty=tformat:%s --grep=s.c.nd >actual &&
+       test_cmp expect actual
+ '
+ test_expect_success 'log with grep.patternType configuration and command line' '
+       echo second >expect &&
+       git -c grep.patterntype=fixed \
+       log -1 --pretty=tformat:%s --basic-regexp --grep=s.c.nd >actual &&
+       test_cmp expect actual
+ '
  cat > expect <<EOF
  * Second
  * sixth
@@@ -848,7 -839,7 +862,7 @@@ sanitize_output () 
  }
  
  test_expect_success 'log --graph with diff and stats' '
 -      git log --graph --pretty=short --stat -p >actual &&
 +      git log --no-renames --graph --pretty=short --stat -p >actual &&
        sanitize_output >actual.sanitized <actual &&
        test_i18ncmp expect actual.sanitized
  '
@@@ -860,15 -851,12 +874,15 @@@ test_expect_success 'dotdot is a paren
        test_cmp expect actual
  '
  
 -test_expect_success GPG 'log --graph --show-signature' '
 +test_expect_success GPG 'setup signed branch' '
        test_when_finished "git reset --hard && git checkout master" &&
        git checkout -b signed master &&
        echo foo >foo &&
        git add foo &&
 -      git commit -S -m signed_commit &&
 +      git commit -S -m signed_commit
 +'
 +
 +test_expect_success GPG 'log --graph --show-signature' '
        git log --graph --show-signature -n1 signed >actual &&
        grep "^| gpg: Signature made" actual &&
        grep "^| gpg: Good signature" actual
@@@ -893,76 -881,8 +907,76 @@@ test_expect_success GPG 'log --graph --
        grep "^| | gpg: Good signature" actual
  '
  
 +test_expect_success GPG '--no-show-signature overrides --show-signature' '
 +      git log -1 --show-signature --no-show-signature signed >actual &&
 +      ! grep "^gpg:" actual
 +'
 +
 +test_expect_success GPG 'log.showsignature=true behaves like --show-signature' '
 +      test_config log.showsignature true &&
 +      git log -1 signed >actual &&
 +      grep "gpg: Signature made" actual &&
 +      grep "gpg: Good signature" actual
 +'
 +
 +test_expect_success GPG '--no-show-signature overrides log.showsignature=true' '
 +      test_config log.showsignature true &&
 +      git log -1 --no-show-signature signed >actual &&
 +      ! grep "^gpg:" actual
 +'
 +
 +test_expect_success GPG '--show-signature overrides log.showsignature=false' '
 +      test_config log.showsignature false &&
 +      git log -1 --show-signature signed >actual &&
 +      grep "gpg: Signature made" actual &&
 +      grep "gpg: Good signature" actual
 +'
 +
  test_expect_success 'log --graph --no-walk is forbidden' '
        test_must_fail git log --graph --no-walk
  '
  
 +test_expect_success 'log diagnoses bogus HEAD' '
 +      git init empty &&
 +      test_must_fail git -C empty log 2>stderr &&
 +      test_i18ngrep does.not.have.any.commits stderr &&
 +      echo 1234abcd >empty/.git/refs/heads/master &&
 +      test_must_fail git -C empty log 2>stderr &&
 +      test_i18ngrep broken stderr &&
 +      echo "ref: refs/heads/invalid.lock" >empty/.git/HEAD &&
 +      test_must_fail git -C empty log 2>stderr &&
 +      test_i18ngrep broken stderr &&
 +      test_must_fail git -C empty log --default totally-bogus 2>stderr &&
 +      test_i18ngrep broken stderr
 +'
 +
 +test_expect_success 'set up --source tests' '
 +      git checkout --orphan source-a &&
 +      test_commit one &&
 +      test_commit two &&
 +      git checkout -b source-b HEAD^ &&
 +      test_commit three
 +'
 +
 +test_expect_success 'log --source paints branch names' '
 +      cat >expect <<-\EOF &&
 +      09e12a9 source-b three
 +      8e393e1 source-a two
 +      1ac6c77 source-b one
 +      EOF
 +      git log --oneline --source source-a source-b >actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'log --source paints tag names' '
 +      git tag -m tagged source-tag &&
 +      cat >expect <<-\EOF &&
 +      09e12a9 source-tag three
 +      8e393e1 source-a two
 +      1ac6c77 source-tag one
 +      EOF
 +      git log --oneline --source source-tag source-a >actual &&
 +      test_cmp expect actual
 +'
 +
  test_done