]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'mg/more-textconv'
authorJunio C Hamano <gitster@pobox.com>
Wed, 23 Oct 2013 20:21:30 +0000 (13:21 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 23 Oct 2013 20:21:31 +0000 (13:21 -0700)
Make "git grep" and "git show" pay attention to --textconv when
dealing with blob objects.

* mg/more-textconv:
  grep: honor --textconv for the case rev:path
  grep: allow to use textconv filters
  t7008: demonstrate behavior of grep with textconv
  cat-file: do not die on --textconv without textconv filters
  show: honor --textconv for blobs
  diff_opt: track whether flags have been set explicitly
  t4030: demonstrate behavior of show with textconv

1  2 
Documentation/git-grep.txt
builtin/cat-file.c
builtin/grep.c
builtin/log.c
diff.c
diff.h
object.c
object.h
t/t4030-diff-textconv.sh
t/t8007-cat-file-textconv.sh

index 8497aa4494778134cad5ca2c2b11fb1c824e2a24,a5c5a27da45f86dd6cb2fb664729e7e4adfbc34d..f83733490f5a881f6f197b3b62000337c500ef9d
@@@ -9,7 -9,7 +9,7 @@@ git-grep - Print lines matching a patte
  SYNOPSIS
  --------
  [verse]
- 'git grep' [-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp]
+ 'git grep' [-a | --text] [-I] [--textconv] [-i | --ignore-case] [-w | --word-regexp]
           [-v | --invert-match] [-h|-H] [--full-name]
           [-E | --extended-regexp] [-G | --basic-regexp]
           [-P | --perl-regexp]
@@@ -25,7 -25,7 +25,7 @@@
           [-W | --function-context]
           [-f <file>] [-e] <pattern>
           [--and|--or|--not|(|)|-e <pattern>...]
 -         [ [--exclude-standard] [--cached | --no-index | --untracked] | <tree>...]
 +         [ [--[no-]exclude-standard] [--cached | --no-index | --untracked] | <tree>...]
           [--] [<pathspec>...]
  
  DESCRIPTION
@@@ -80,6 -80,13 +80,13 @@@ OPTION
  --text::
        Process binary files as if they were text.
  
+ --textconv::
+       Honor textconv filter settings.
+ --no-textconv::
+       Do not honor textconv filter settings.
+       This is the default.
  -i::
  --ignore-case::
        Ignore case differences between the patterns and the
diff --combined builtin/cat-file.c
index 41afaa534b02d8f8089973f3949ba507bd3cf94b,fbaa1213f16fef0911ff3c1b2bf1cc3350105022..b2ca775a80f54fcceba344d5545fe3cb34ba0184
  #include "userdiff.h"
  #include "streaming.h"
  
 -#define BATCH 1
 -#define BATCH_CHECK 2
 -
 -static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size)
 -{
 -      /* the parser in tag.c is useless here. */
 -      const char *endp = buf + size;
 -      const char *cp = buf;
 -
 -      while (cp < endp) {
 -              char c = *cp++;
 -              if (c != '\n')
 -                      continue;
 -              if (7 <= endp - cp && !memcmp("tagger ", cp, 7)) {
 -                      const char *tagger = cp;
 -
 -                      /* Found the tagger line.  Copy out the contents
 -                       * of the buffer so far.
 -                       */
 -                      write_or_die(1, buf, cp - buf);
 -
 -                      /*
 -                       * Do something intelligent, like pretty-printing
 -                       * the date.
 -                       */
 -                      while (cp < endp) {
 -                              if (*cp++ == '\n') {
 -                                      /* tagger to cp is a line
 -                                       * that has ident and time.
 -                                       */
 -                                      const char *sp = tagger;
 -                                      char *ep;
 -                                      unsigned long date;
 -                                      long tz;
 -                                      while (sp < cp && *sp != '>')
 -                                              sp++;
 -                                      if (sp == cp) {
 -                                              /* give up */
 -                                              write_or_die(1, tagger,
 -                                                           cp - tagger);
 -                                              break;
 -                                      }
 -                                      while (sp < cp &&
 -                                             !('0' <= *sp && *sp <= '9'))
 -                                              sp++;
 -                                      write_or_die(1, tagger, sp - tagger);
 -                                      date = strtoul(sp, &ep, 10);
 -                                      tz = strtol(ep, NULL, 10);
 -                                      sp = show_date(date, tz, 0);
 -                                      write_or_die(1, sp, strlen(sp));
 -                                      xwrite(1, "\n", 1);
 -                                      break;
 -                              }
 -                      }
 -                      break;
 -              }
 -              if (cp < endp && *cp == '\n')
 -                      /* end of header */
 -                      break;
 -      }
 -      /* At this point, we have copied out the header up to the end of
 -       * the tagger line and cp points at one past \n.  It could be the
 -       * next header line after the tagger line, or it could be another
 -       * \n that marks the end of the headers.  We need to copy out the
 -       * remainder as is.
 -       */
 -      if (cp < endp)
 -              write_or_die(1, cp, endp - cp);
 -}
 -
  static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
  {
        unsigned char sha1[20];
        case 'e':
                return !has_sha1_file(sha1);
  
+       case 'c':
+               if (!obj_context.path[0])
+                       die("git cat-file --textconv %s: <object> must be <sha1:path>",
+                           obj_name);
+               if (textconv_object(obj_context.path, obj_context.mode, sha1, 1, &buf, &size))
+                       break;
        case 'p':
                type = sha1_object_info(sha1, NULL);
                if (type < 0)
                buf = read_sha1_file(sha1, &type, &size);
                if (!buf)
                        die("Cannot read object %s", obj_name);
 -              if (type == OBJ_TAG) {
 -                      pprint_tag(sha1, buf, size);
 -                      return 0;
 -              }
  
                /* otherwise just spit out the data */
                break;
  
-       case 'c':
-               if (!obj_context.path[0])
-                       die("git cat-file --textconv %s: <object> must be <sha1:path>",
-                           obj_name);
-               if (!textconv_object(obj_context.path, obj_context.mode, sha1, 1, &buf, &size))
-                       die("git cat-file --textconv: unable to run textconv on %s",
-                           obj_name);
-               break;
        case 0:
                if (type_from_string(exp_type) == OBJ_BLOB) {
                        unsigned char blob_sha1[20];
        return 0;
  }
  
 -static int batch_one_object(const char *obj_name, int print_contents)
 -{
 +struct expand_data {
        unsigned char sha1[20];
 -      enum object_type type = 0;
 +      enum object_type type;
        unsigned long size;
 -      void *contents = NULL;
 +      unsigned long disk_size;
 +      const char *rest;
 +
 +      /*
 +       * If mark_query is true, we do not expand anything, but rather
 +       * just mark the object_info with items we wish to query.
 +       */
 +      int mark_query;
 +
 +      /*
 +       * Whether to split the input on whitespace before feeding it to
 +       * get_sha1; this is decided during the mark_query phase based on
 +       * whether we have a %(rest) token in our format.
 +       */
 +      int split_on_whitespace;
 +
 +      /*
 +       * After a mark_query run, this object_info is set up to be
 +       * passed to sha1_object_info_extended. It will point to the data
 +       * elements above, so you can retrieve the response from there.
 +       */
 +      struct object_info info;
 +};
 +
 +static int is_atom(const char *atom, const char *s, int slen)
 +{
 +      int alen = strlen(atom);
 +      return alen == slen && !memcmp(atom, s, alen);
 +}
 +
 +static void expand_atom(struct strbuf *sb, const char *atom, int len,
 +                      void *vdata)
 +{
 +      struct expand_data *data = vdata;
 +
 +      if (is_atom("objectname", atom, len)) {
 +              if (!data->mark_query)
 +                      strbuf_addstr(sb, sha1_to_hex(data->sha1));
 +      } else if (is_atom("objecttype", atom, len)) {
 +              if (data->mark_query)
 +                      data->info.typep = &data->type;
 +              else
 +                      strbuf_addstr(sb, typename(data->type));
 +      } else if (is_atom("objectsize", atom, len)) {
 +              if (data->mark_query)
 +                      data->info.sizep = &data->size;
 +              else
 +                      strbuf_addf(sb, "%lu", data->size);
 +      } else if (is_atom("objectsize:disk", atom, len)) {
 +              if (data->mark_query)
 +                      data->info.disk_sizep = &data->disk_size;
 +              else
 +                      strbuf_addf(sb, "%lu", data->disk_size);
 +      } else if (is_atom("rest", atom, len)) {
 +              if (data->mark_query)
 +                      data->split_on_whitespace = 1;
 +              else if (data->rest)
 +                      strbuf_addstr(sb, data->rest);
 +      } else
 +              die("unknown format element: %.*s", len, atom);
 +}
 +
 +static size_t expand_format(struct strbuf *sb, const char *start, void *data)
 +{
 +      const char *end;
 +
 +      if (*start != '(')
 +              return 0;
 +      end = strchr(start + 1, ')');
 +      if (!end)
 +              die("format element '%s' does not end in ')'", start);
 +
 +      expand_atom(sb, start + 1, end - start - 1, data);
 +
 +      return end - start + 1;
 +}
 +
 +static void print_object_or_die(int fd, const unsigned char *sha1,
 +                              enum object_type type, unsigned long size)
 +{
 +      if (type == OBJ_BLOB) {
 +              if (stream_blob_to_fd(fd, sha1, NULL, 0) < 0)
 +                      die("unable to stream %s to stdout", sha1_to_hex(sha1));
 +      }
 +      else {
 +              enum object_type rtype;
 +              unsigned long rsize;
 +              void *contents;
 +
 +              contents = read_sha1_file(sha1, &rtype, &rsize);
 +              if (!contents)
 +                      die("object %s disappeared", sha1_to_hex(sha1));
 +              if (rtype != type)
 +                      die("object %s changed type!?", sha1_to_hex(sha1));
 +              if (rsize != size)
 +                      die("object %s change size!?", sha1_to_hex(sha1));
 +
 +              write_or_die(fd, contents, size);
 +              free(contents);
 +      }
 +}
 +
 +struct batch_options {
 +      int enabled;
 +      int print_contents;
 +      const char *format;
 +};
 +
 +static int batch_one_object(const char *obj_name, struct batch_options *opt,
 +                          struct expand_data *data)
 +{
 +      struct strbuf buf = STRBUF_INIT;
  
        if (!obj_name)
           return 1;
  
 -      if (get_sha1(obj_name, sha1)) {
 +      if (get_sha1(obj_name, data->sha1)) {
                printf("%s missing\n", obj_name);
                fflush(stdout);
                return 0;
        }
  
 -      if (print_contents == BATCH)
 -              contents = read_sha1_file(sha1, &type, &size);
 -      else
 -              type = sha1_object_info(sha1, &size);
 -
 -      if (type <= 0) {
 +      if (sha1_object_info_extended(data->sha1, &data->info) < 0) {
                printf("%s missing\n", obj_name);
                fflush(stdout);
 -              if (print_contents == BATCH)
 -                      free(contents);
                return 0;
        }
  
 -      printf("%s %s %lu\n", sha1_to_hex(sha1), typename(type), size);
 -      fflush(stdout);
 +      strbuf_expand(&buf, opt->format, expand_format, data);
 +      strbuf_addch(&buf, '\n');
 +      write_or_die(1, buf.buf, buf.len);
 +      strbuf_release(&buf);
  
 -      if (print_contents == BATCH) {
 -              write_or_die(1, contents, size);
 -              printf("\n");
 -              fflush(stdout);
 -              free(contents);
 +      if (opt->print_contents) {
 +              print_object_or_die(1, data->sha1, data->type, data->size);
 +              write_or_die(1, "\n", 1);
        }
 -
        return 0;
  }
  
 -static int batch_objects(int print_contents)
 +static int batch_objects(struct batch_options *opt)
  {
        struct strbuf buf = STRBUF_INIT;
 +      struct expand_data data;
 +
 +      if (!opt->format)
 +              opt->format = "%(objectname) %(objecttype) %(objectsize)";
 +
 +      /*
 +       * Expand once with our special mark_query flag, which will prime the
 +       * object_info to be handed to sha1_object_info_extended for each
 +       * object.
 +       */
 +      memset(&data, 0, sizeof(data));
 +      data.mark_query = 1;
 +      strbuf_expand(&buf, opt->format, expand_format, &data);
 +      data.mark_query = 0;
 +
 +      /*
 +       * We are going to call get_sha1 on a potentially very large number of
 +       * objects. In most large cases, these will be actual object sha1s. The
 +       * cost to double-check that each one is not also a ref (just so we can
 +       * warn) ends up dwarfing the actual cost of the object lookups
 +       * themselves. We can work around it by just turning off the warning.
 +       */
 +      warn_on_object_refname_ambiguity = 0;
  
        while (strbuf_getline(&buf, stdin, '\n') != EOF) {
 -              int error = batch_one_object(buf.buf, print_contents);
 +              int error;
 +
 +              if (data.split_on_whitespace) {
 +                      /*
 +                       * Split at first whitespace, tying off the beginning
 +                       * of the string and saving the remainder (or NULL) in
 +                       * data.rest.
 +                       */
 +                      char *p = strpbrk(buf.buf, " \t");
 +                      if (p) {
 +                              while (*p && strchr(" \t", *p))
 +                                      *p++ = '\0';
 +                      }
 +                      data.rest = p;
 +              }
 +
 +              error = batch_one_object(buf.buf, opt, &data);
                if (error)
                        return error;
        }
@@@ -324,29 -255,10 +322,29 @@@ static int git_cat_file_config(const ch
        return git_default_config(var, value, cb);
  }
  
 +static int batch_option_callback(const struct option *opt,
 +                               const char *arg,
 +                               int unset)
 +{
 +      struct batch_options *bo = opt->value;
 +
 +      if (unset) {
 +              memset(bo, 0, sizeof(*bo));
 +              return 0;
 +      }
 +
 +      bo->enabled = 1;
 +      bo->print_contents = !strcmp(opt->long_name, "batch");
 +      bo->format = arg;
 +
 +      return 0;
 +}
 +
  int cmd_cat_file(int argc, const char **argv, const char *prefix)
  {
 -      int opt = 0, batch = 0;
 +      int opt = 0;
        const char *exp_type = NULL, *obj_name = NULL;
 +      struct batch_options batch = {0};
  
        const struct option options[] = {
                OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")),
                OPT_SET_INT('p', NULL, &opt, N_("pretty-print object's content"), 'p'),
                OPT_SET_INT(0, "textconv", &opt,
                            N_("for blob objects, run textconv on object's content"), 'c'),
 -              OPT_SET_INT(0, "batch", &batch,
 -                          N_("show info and content of objects fed from the standard input"),
 -                          BATCH),
 -              OPT_SET_INT(0, "batch-check", &batch,
 -                          N_("show info about objects fed from the standard input"),
 -                          BATCH_CHECK),
 +              { OPTION_CALLBACK, 0, "batch", &batch, "format",
 +                      N_("show info and content of objects fed from the standard input"),
 +                      PARSE_OPT_OPTARG, batch_option_callback },
 +              { OPTION_CALLBACK, 0, "batch-check", &batch, "format",
 +                      N_("show info about objects fed from the standard input"),
 +                      PARSE_OPT_OPTARG, batch_option_callback },
                OPT_END()
        };
  
                else
                        usage_with_options(cat_file_usage, options);
        }
 -      if (!opt && !batch) {
 +      if (!opt && !batch.enabled) {
                if (argc == 2) {
                        exp_type = argv[0];
                        obj_name = argv[1];
                } else
                        usage_with_options(cat_file_usage, options);
        }
 -      if (batch && (opt || argc)) {
 +      if (batch.enabled && (opt || argc)) {
                usage_with_options(cat_file_usage, options);
        }
  
 -      if (batch)
 -              return batch_objects(batch);
 +      if (batch.enabled)
 +              return batch_objects(&batch);
  
        return cat_one_file(opt, exp_type, obj_name);
  }
diff --combined builtin/grep.c
index 03bc442e3f96ba12ec389f0fc40349a57e073dc7,bb7f970ca063dff97f7f433d361cd1967e5c91da..63f86032d91f00fc607f7d3b26ec941bb7a4c76c
@@@ -17,7 -17,6 +17,7 @@@
  #include "grep.h"
  #include "quote.h"
  #include "dir.h"
 +#include "pathspec.h"
  
  static char const * const grep_usage[] = {
        N_("git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]"),
@@@ -287,7 -286,8 +287,7 @@@ static int grep_sha1(struct grep_opt *o
        struct strbuf pathbuf = STRBUF_INIT;
  
        if (opt->relative && opt->prefix_length) {
 -              quote_path_relative(filename + tree_name_len, -1, &pathbuf,
 -                                  opt->prefix);
 +              quote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf);
                strbuf_insert(&pathbuf, 0, filename, tree_name_len);
        } else {
                strbuf_addstr(&pathbuf, filename);
@@@ -318,7 -318,7 +318,7 @@@ static int grep_file(struct grep_opt *o
        struct strbuf buf = STRBUF_INIT;
  
        if (opt->relative && opt->prefix_length)
 -              quote_path_relative(filename, -1, &buf, opt->prefix);
 +              quote_path_relative(filename, opt->prefix, &buf);
        else
                strbuf_addstr(&buf, filename);
  
@@@ -376,7 -376,7 +376,7 @@@ static int grep_cache(struct grep_opt *
        read_cache();
  
        for (nr = 0; nr < active_nr; nr++) {
 -              struct cache_entry *ce = active_cache[nr];
 +              const struct cache_entry *ce = active_cache[nr];
                if (!S_ISREG(ce->ce_mode))
                        continue;
                if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL))
@@@ -458,10 -458,10 +458,10 @@@ static int grep_tree(struct grep_opt *o
  }
  
  static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
-                      struct object *obj, const char *name)
+                      struct object *obj, const char *name, struct object_context *oc)
  {
        if (obj->type == OBJ_BLOB)
-               return grep_sha1(opt, obj->sha1, name, 0, NULL);
+               return grep_sha1(opt, obj->sha1, name, 0, oc ? oc->path : NULL);
        if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) {
                struct tree_desc tree;
                void *data;
@@@ -503,7 -503,7 +503,7 @@@ static int grep_objects(struct grep_op
        for (i = 0; i < nr; i++) {
                struct object *real_obj;
                real_obj = deref_tag(list->objects[i].item, NULL, 0);
-               if (grep_object(opt, pathspec, real_obj, list->objects[i].name)) {
+               if (grep_object(opt, pathspec, real_obj, list->objects[i].name, list->objects[i].context)) {
                        hit = 1;
                        if (opt->status_only)
                                break;
@@@ -522,7 -522,7 +522,7 @@@ static int grep_directory(struct grep_o
        if (exc_std)
                setup_standard_excludes(&dir);
  
 -      fill_directory(&dir, pathspec->raw);
 +      fill_directory(&dir, pathspec);
        for (i = 0; i < dir.nr; i++) {
                const char *name = dir.entries[i]->name;
                int namelen = strlen(name);
@@@ -630,6 -630,7 +630,6 @@@ int cmd_grep(int argc, const char **arg
        const char *show_in_pager = NULL, *default_pager = "dummy";
        struct grep_opt opt;
        struct object_array list = OBJECT_ARRAY_INIT;
 -      const char **paths = NULL;
        struct pathspec pathspec;
        struct string_list path_list = STRING_LIST_INIT_NODUP;
        int i;
        int pattern_type_arg = GREP_PATTERN_TYPE_UNSPECIFIED;
  
        struct option options[] = {
 -              OPT_BOOLEAN(0, "cached", &cached,
 +              OPT_BOOL(0, "cached", &cached,
                        N_("search in index instead of in the work tree")),
                OPT_NEGBIT(0, "no-index", &use_index,
                         N_("find in contents not managed by git"), 1),
 -              OPT_BOOLEAN(0, "untracked", &untracked,
 +              OPT_BOOL(0, "untracked", &untracked,
                        N_("search in both tracked and untracked files")),
                OPT_SET_INT(0, "exclude-standard", &opt_exclude,
                            N_("search also in ignored files"), 1),
                OPT_GROUP(""),
 -              OPT_BOOLEAN('v', "invert-match", &opt.invert,
 +              OPT_BOOL('v', "invert-match", &opt.invert,
                        N_("show non-matching lines")),
 -              OPT_BOOLEAN('i', "ignore-case", &opt.ignore_case,
 +              OPT_BOOL('i', "ignore-case", &opt.ignore_case,
                        N_("case insensitive matching")),
 -              OPT_BOOLEAN('w', "word-regexp", &opt.word_regexp,
 +              OPT_BOOL('w', "word-regexp", &opt.word_regexp,
                        N_("match patterns only at word boundaries")),
                OPT_SET_INT('a', "text", &opt.binary,
                        N_("process binary files as text"), GREP_BINARY_TEXT),
                OPT_SET_INT('I', NULL, &opt.binary,
                        N_("don't match patterns in binary files"),
                        GREP_BINARY_NOMATCH),
+               OPT_BOOL(0, "textconv", &opt.allow_textconv,
+                        N_("process binary files with textconv filters")),
                { OPTION_INTEGER, 0, "max-depth", &opt.max_depth, N_("depth"),
                        N_("descend at most <depth> levels"), PARSE_OPT_NONEG,
                        NULL, 1 },
                            N_("use Perl-compatible regular expressions"),
                            GREP_PATTERN_TYPE_PCRE),
                OPT_GROUP(""),
 -              OPT_BOOLEAN('n', "line-number", &opt.linenum, N_("show line numbers")),
 +              OPT_BOOL('n', "line-number", &opt.linenum, N_("show line numbers")),
                OPT_NEGBIT('h', NULL, &opt.pathname, N_("don't show filenames"), 1),
                OPT_BIT('H', NULL, &opt.pathname, N_("show filenames"), 1),
                OPT_NEGBIT(0, "full-name", &opt.relative,
                        N_("show filenames relative to top directory"), 1),
 -              OPT_BOOLEAN('l', "files-with-matches", &opt.name_only,
 +              OPT_BOOL('l', "files-with-matches", &opt.name_only,
                        N_("show only filenames instead of matching lines")),
 -              OPT_BOOLEAN(0, "name-only", &opt.name_only,
 +              OPT_BOOL(0, "name-only", &opt.name_only,
                        N_("synonym for --files-with-matches")),
 -              OPT_BOOLEAN('L', "files-without-match",
 +              OPT_BOOL('L', "files-without-match",
                        &opt.unmatch_name_only,
                        N_("show only the names of files without match")),
 -              OPT_BOOLEAN('z', "null", &opt.null_following_name,
 +              OPT_BOOL('z', "null", &opt.null_following_name,
                        N_("print NUL after filenames")),
 -              OPT_BOOLEAN('c', "count", &opt.count,
 +              OPT_BOOL('c', "count", &opt.count,
                        N_("show the number of matches instead of matching lines")),
                OPT__COLOR(&opt.color, N_("highlight matches")),
 -              OPT_BOOLEAN(0, "break", &opt.file_break,
 +              OPT_BOOL(0, "break", &opt.file_break,
                        N_("print empty line between matches from different files")),
 -              OPT_BOOLEAN(0, "heading", &opt.heading,
 +              OPT_BOOL(0, "heading", &opt.heading,
                        N_("show filename only once above matches from same file")),
                OPT_GROUP(""),
                OPT_CALLBACK('C', "context", &opt, N_("n"),
                        N_("show <n> context lines after matches")),
                OPT_NUMBER_CALLBACK(&opt, N_("shortcut for -C NUM"),
                        context_callback),
 -              OPT_BOOLEAN('p', "show-function", &opt.funcname,
 +              OPT_BOOL('p', "show-function", &opt.funcname,
                        N_("show a line with the function name before matches")),
 -              OPT_BOOLEAN('W', "function-context", &opt.funcbody,
 +              OPT_BOOL('W', "function-context", &opt.funcbody,
                        N_("show the surrounding function")),
                OPT_GROUP(""),
                OPT_CALLBACK('f', NULL, &opt, N_("file"),
                { OPTION_CALLBACK, 0, "and", &opt, NULL,
                  N_("combine patterns specified with -e"),
                  PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback },
 -              OPT_BOOLEAN(0, "or", &dummy, ""),
 +              OPT_BOOL(0, "or", &dummy, ""),
                { OPTION_CALLBACK, 0, "not", &opt, NULL, "",
                  PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback },
                { OPTION_CALLBACK, '(', NULL, &opt, NULL, "",
                  close_callback },
                OPT__QUIET(&opt.status_only,
                           N_("indicate hit with exit status without output")),
 -              OPT_BOOLEAN(0, "all-match", &opt.all_match,
 +              OPT_BOOL(0, "all-match", &opt.all_match,
                        N_("show only matches from files that match all patterns")),
                { OPTION_SET_INT, 0, "debug", &opt.debug, NULL,
                  N_("show parse tree for grep expression"),
                { OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager,
                        N_("pager"), N_("show matching files in the pager"),
                        PARSE_OPT_OPTARG, NULL, (intptr_t)default_pager },
 -              OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed__ignored,
 -                          N_("allow calling of grep(1) (ignored by this build)")),
 +              OPT_BOOL(0, "ext-grep", &external_grep_allowed__ignored,
 +                       N_("allow calling of grep(1) (ignored by this build)")),
                { OPTION_CALLBACK, 0, "help-all", &options, NULL, N_("show usage"),
                  PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback },
                OPT_END()
        for (i = 0; i < argc; i++) {
                const char *arg = argv[i];
                unsigned char sha1[20];
+               struct object_context oc;
                /* Is it a rev? */
-               if (!get_sha1(arg, sha1)) {
+               if (!get_sha1_with_context(arg, 0, sha1, &oc)) {
                        struct object *object = parse_object_or_die(sha1, arg);
                        if (!seen_dashdash)
                                verify_non_filename(prefix, arg);
-                       add_object_array(object, arg, &list);
+                       add_object_array_with_context(object, arg, &list, xmemdupz(&oc, sizeof(struct object_context)));
                        continue;
                }
                if (!strcmp(arg, "--")) {
                        verify_filename(prefix, argv[j], j == i);
        }
  
 -      paths = get_pathspec(prefix, argv + i);
 -      init_pathspec(&pathspec, paths);
 +      parse_pathspec(&pathspec, 0,
 +                     PATHSPEC_PREFER_CWD |
 +                     (opt.max_depth != -1 ? PATHSPEC_MAXDEPTH_VALID : 0),
 +                     prefix, argv + i);
        pathspec.max_depth = opt.max_depth;
        pathspec.recursive = 1;
  
diff --combined builtin/log.c
index 77d0f5f3fdbcb48d75aee35a67ed57dd134c907e,815c5b84eda26a960924e9090b21afbdd771d249..b708517a3506517a25d4c05f3c9c92a660688f8d
@@@ -19,7 -19,6 +19,7 @@@
  #include "remote.h"
  #include "string-list.h"
  #include "parse-options.h"
 +#include "line-log.h"
  #include "branch.h"
  #include "streaming.h"
  #include "version.h"
@@@ -38,17 -37,11 +38,17 @@@ static const char *fmt_patch_subject_pr
  static const char *fmt_pretty;
  
  static const char * const builtin_log_usage[] = {
 -      N_("git log [<options>] [<since>..<until>] [[--] <path>...]\n")
 +      N_("git log [<options>] [<revision range>] [[--] <path>...]\n")
        N_("   or: git show [options] <object>..."),
        NULL
  };
  
 +struct line_opt_callback_data {
 +      struct rev_info *rev;
 +      const char *prefix;
 +      struct string_list args;
 +};
 +
  static int parse_decoration_style(const char *var, const char *value)
  {
        switch (git_config_maybe_bool(var, value)) {
@@@ -83,19 -76,6 +83,19 @@@ static int decorate_callback(const stru
        return 0;
  }
  
 +static int log_line_range_callback(const struct option *option, const char *arg, int unset)
 +{
 +      struct line_opt_callback_data *data = option->value;
 +
 +      if (!arg)
 +              return -1;
 +
 +      data->rev->line_level_traverse = 1;
 +      string_list_append(&data->args, arg);
 +
 +      return 0;
 +}
 +
  static void cmd_log_init_defaults(struct rev_info *rev)
  {
        if (fmt_pretty)
  
        if (default_date_mode)
                rev->date_mode = parse_date_format(default_date_mode);
+       rev->diffopt.touched_flags = 0;
  }
  
  static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
  {
        struct userformat_want w;
        int quiet = 0, source = 0, mailmap = 0;
 +      static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP};
  
        const struct option builtin_log_options[] = {
 -              OPT_BOOL(0, "quiet", &quiet, N_("suppress diff output")),
 +              OPT__QUIET(&quiet, N_("suppress diff output")),
                OPT_BOOL(0, "source", &source, N_("show source")),
                OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")),
                { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"),
                  PARSE_OPT_OPTARG, decorate_callback},
 +              OPT_CALLBACK('L', NULL, &line_cb, "n,m:file",
 +                           "Process line range n,m in file, counting from 1",
 +                           log_line_range_callback),
                OPT_END()
        };
  
 +      line_cb.rev = rev;
 +      line_cb.prefix = prefix;
 +
        mailmap = use_mailmap_config;
        argc = parse_options(argc, argv, prefix,
                             builtin_log_options, builtin_log_usage,
                rev->show_decorations = 1;
                load_ref_decorations(decoration_style);
        }
 +
 +      if (rev->line_level_traverse)
 +              line_log_init(rev, line_cb.prefix, &line_cb.args);
 +
        setup_pager();
  }
  
@@@ -237,7 -207,7 +238,7 @@@ static void log_show_early(struct rev_i
        int i = revs->early_output;
        int show_header = 1;
  
 -      sort_in_topological_order(&list, revs->lifo);
 +      sort_in_topological_order(&list, revs->sort_order);
        while (list && i) {
                struct commit *commit = list->item;
                switch (simplify_commit(revs, commit)) {
@@@ -436,10 -406,29 +437,29 @@@ static void show_tagger(char *buf, int 
        strbuf_release(&out);
  }
  
- static int show_blob_object(const unsigned char *sha1, struct rev_info *rev)
+ static int show_blob_object(const unsigned char *sha1, struct rev_info *rev, const char *obj_name)
  {
+       unsigned char sha1c[20];
+       struct object_context obj_context;
+       char *buf;
+       unsigned long size;
        fflush(stdout);
-       return stream_blob_to_fd(1, sha1, NULL, 0);
+       if (!DIFF_OPT_TOUCHED(&rev->diffopt, ALLOW_TEXTCONV) ||
+           !DIFF_OPT_TST(&rev->diffopt, ALLOW_TEXTCONV))
+               return stream_blob_to_fd(1, sha1, NULL, 0);
+       if (get_sha1_with_context(obj_name, 0, sha1c, &obj_context))
+               die("Not a valid object name %s", obj_name);
+       if (!obj_context.path[0] ||
+           !textconv_object(obj_context.path, obj_context.mode, sha1c, 1, &buf, &size))
+               return stream_blob_to_fd(1, sha1, NULL, 0);
+       if (!buf)
+               die("git show %s: bad file", obj_name);
+       write_or_die(1, buf, size);
+       return 0;
  }
  
  static int show_tag_object(const unsigned char *sha1, struct rev_info *rev)
@@@ -503,7 -492,7 +523,7 @@@ int cmd_show(int argc, const char **arg
        init_grep_defaults();
        git_config(git_log_config, NULL);
  
 -      init_pathspec(&match_all, NULL);
 +      memset(&match_all, 0, sizeof(match_all));
        init_revisions(&rev, prefix);
        rev.diff = 1;
        rev.always_show_header = 1;
                const char *name = objects[i].name;
                switch (o->type) {
                case OBJ_BLOB:
-                       ret = show_blob_object(o->sha1, NULL);
+                       ret = show_blob_object(o->sha1, &rev, name);
                        break;
                case OBJ_TAG: {
                        struct tag *t = (struct tag *)o;
@@@ -1112,21 -1101,6 +1132,21 @@@ static int cc_callback(const struct opt
        return 0;
  }
  
 +static int from_callback(const struct option *opt, const char *arg, int unset)
 +{
 +      char **from = opt->value;
 +
 +      free(*from);
 +
 +      if (unset)
 +              *from = NULL;
 +      else if (arg)
 +              *from = xstrdup(arg);
 +      else
 +              *from = xstrdup(git_committer_info(IDENT_NO_DATE));
 +      return 0;
 +}
 +
  int cmd_format_patch(int argc, const char **argv, const char *prefix)
  {
        struct commit *commit;
        int quiet = 0;
        int reroll_count = -1;
        char *branch_name = NULL;
 +      char *from = NULL;
        const struct option builtin_format_patch_options[] = {
                { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
                            N_("use [PATCH n/m] even with a single patch"),
                { OPTION_CALLBACK, 'k', "keep-subject", &rev, NULL,
                            N_("don't strip/add [PATCH]"),
                            PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback },
 -              OPT_BOOLEAN(0, "no-binary", &no_binary_diff,
 -                          N_("don't output binary diffs")),
 -              OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream,
 -                          N_("don't include a patch matching a commit upstream")),
 -              { OPTION_BOOLEAN, 'p', "no-stat", &use_patch_format, NULL,
 +              OPT_BOOL(0, "no-binary", &no_binary_diff,
 +                       N_("don't output binary diffs")),
 +              OPT_BOOL(0, "ignore-if-in-upstream", &ignore_if_in_upstream,
 +                       N_("don't include a patch matching a commit upstream")),
 +              { OPTION_SET_INT, 'p', "no-stat", &use_patch_format, NULL,
                  N_("show patch format instead of default (patch + stat)"),
 -                PARSE_OPT_NONEG | PARSE_OPT_NOARG },
 +                PARSE_OPT_NONEG | PARSE_OPT_NOARG, NULL, 1},
                OPT_GROUP(N_("Messaging")),
                { OPTION_CALLBACK, 0, "add-header", NULL, N_("header"),
                            N_("add email header"), 0, header_callback },
                            0, to_callback },
                { OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"),
                            0, cc_callback },
 +              { OPTION_CALLBACK, 0, "from", &from, N_("ident"),
 +                          N_("set From address to <ident> (or committer ident if absent)"),
 +                          PARSE_OPT_OPTARG, from_callback },
                OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"),
                            N_("make first mail a reply to <message-id>")),
                { OPTION_CALLBACK, 0, "attach", &rev, N_("boundary"),
                            PARSE_OPT_OPTARG, thread_callback },
                OPT_STRING(0, "signature", &signature, N_("signature"),
                            N_("add a signature")),
 -              OPT_BOOLEAN(0, "quiet", &quiet,
 -                          N_("don't print the patch filenames")),
 +              OPT__QUIET(&quiet, N_("don't print the patch filenames")),
                OPT_END()
        };
  
  
        rev.extra_headers = strbuf_detach(&buf, NULL);
  
 +      if (from) {
 +              if (split_ident_line(&rev.from_ident, from, strlen(from)))
 +                      die(_("invalid ident line: %s"), from);
 +      }
 +
        if (start_number < 0)
                start_number = 1;
  
diff --combined diff.c
index a04a34d0487f231481a7c9c475ac4a3e92b5ed2d,7c248726ad259ea08b6f31a7f5d3bf27214d9115..e34bf971207f7737c3b1a10968acd8ffb3ecf93d
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -669,7 -669,7 +669,7 @@@ static void emit_rewrite_diff(const cha
        memset(&ecbdata, 0, sizeof(ecbdata));
        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.ws_rule = whitespace_rule(name_b);
        ecbdata.opt = o;
        if (ecbdata.ws_rule & WS_BLANK_AT_EOF) {
                mmfile_t mf1, mf2;
@@@ -1683,7 -1683,9 +1683,7 @@@ static void show_stats(struct diffstat_
                del = deleted;
  
                if (graph_width <= max_change) {
 -                      int total = add + del;
 -
 -                      total = scale_linear(add + del, graph_width, max_change);
 +                      int total = scale_linear(add + del, graph_width, max_change);
                        if (total < 2 && add && del)
                                /* width >= 2 due to the sanity check */
                                total = 2;
@@@ -2252,7 -2254,7 +2252,7 @@@ static void builtin_diff(const char *na
                        (!two->mode || S_ISGITLINK(two->mode))) {
                const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
                const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
 -              show_submodule_summary(o->file, one ? one->path : two->path,
 +              show_submodule_summary(o->file, one->path ? one->path : two->path,
                                line_prefix,
                                one->sha1, two->sha1, two->dirty_submodule,
                                meta, del, add, reset);
                ecbdata.label_path = lbl;
                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.ws_rule = whitespace_rule(name_b);
                if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
                        check_blank_at_eof(&mf1, &mf2, &ecbdata);
                ecbdata.opt = o;
@@@ -2584,7 -2586,7 +2584,7 @@@ void fill_filespec(struct diff_filespe
   */
  static int reuse_worktree_file(const char *name, const unsigned char *sha1, int want_file)
  {
 -      struct cache_entry *ce;
 +      const struct cache_entry *ce;
        struct stat st;
        int pos, len;
  
@@@ -2675,14 -2677,6 +2675,14 @@@ static int diff_populate_gitlink(struc
  int diff_populate_filespec(struct diff_filespec *s, int size_only)
  {
        int err = 0;
 +      /*
 +       * demote FAIL to WARN to allow inspecting the situation
 +       * instead of refusing.
 +       */
 +      enum safe_crlf crlf_warn = (safe_crlf == SAFE_CRLF_FAIL
 +                                  ? SAFE_CRLF_WARN
 +                                  : safe_crlf);
 +
        if (!DIFF_FILE_VALID(s))
                die("internal error: asking to populate invalid file.");
        if (S_ISDIR(s->mode))
                /*
                 * Convert from working tree format to canonical git format
                 */
 -              if (convert_to_git(s->path, s->data, s->size, &buf, safe_crlf)) {
 +              if (convert_to_git(s->path, s->data, s->size, &buf, crlf_warn)) {
                        size_t size = 0;
                        munmap(s->data, s->size);
                        s->should_munmap = 0;
@@@ -3219,6 -3213,9 +3219,9 @@@ void diff_setup_done(struct diff_option
  {
        int count = 0;
  
+       if (options->set_default)
+               options->set_default(options);
        if (options->output_format & DIFF_FORMAT_NAME)
                count++;
        if (options->output_format & DIFF_FORMAT_NAME_STATUS)
@@@ -3503,93 -3500,6 +3506,93 @@@ static int parse_submodule_opt(struct d
        return 1;
  }
  
 +static const char diff_status_letters[] = {
 +      DIFF_STATUS_ADDED,
 +      DIFF_STATUS_COPIED,
 +      DIFF_STATUS_DELETED,
 +      DIFF_STATUS_MODIFIED,
 +      DIFF_STATUS_RENAMED,
 +      DIFF_STATUS_TYPE_CHANGED,
 +      DIFF_STATUS_UNKNOWN,
 +      DIFF_STATUS_UNMERGED,
 +      DIFF_STATUS_FILTER_AON,
 +      DIFF_STATUS_FILTER_BROKEN,
 +      '\0',
 +};
 +
 +static unsigned int filter_bit['Z' + 1];
 +
 +static void prepare_filter_bits(void)
 +{
 +      int i;
 +
 +      if (!filter_bit[DIFF_STATUS_ADDED]) {
 +              for (i = 0; diff_status_letters[i]; i++)
 +                      filter_bit[(int) diff_status_letters[i]] = (1 << i);
 +      }
 +}
 +
 +static unsigned filter_bit_tst(char status, const struct diff_options *opt)
 +{
 +      return opt->filter & filter_bit[(int) status];
 +}
 +
 +static int parse_diff_filter_opt(const char *optarg, struct diff_options *opt)
 +{
 +      int i, optch;
 +
 +      prepare_filter_bits();
 +
 +      /*
 +       * If there is a negation e.g. 'd' in the input, and we haven't
 +       * initialized the filter field with another --diff-filter, start
 +       * from full set of bits, except for AON.
 +       */
 +      if (!opt->filter) {
 +              for (i = 0; (optch = optarg[i]) != '\0'; i++) {
 +                      if (optch < 'a' || 'z' < optch)
 +                              continue;
 +                      opt->filter = (1 << (ARRAY_SIZE(diff_status_letters) - 1)) - 1;
 +                      opt->filter &= ~filter_bit[DIFF_STATUS_FILTER_AON];
 +                      break;
 +              }
 +      }
 +
 +      for (i = 0; (optch = optarg[i]) != '\0'; i++) {
 +              unsigned int bit;
 +              int negate;
 +
 +              if ('a' <= optch && optch <= 'z') {
 +                      negate = 1;
 +                      optch = toupper(optch);
 +              } else {
 +                      negate = 0;
 +              }
 +
 +              bit = (0 <= optch && optch <= 'Z') ? filter_bit[optch] : 0;
 +              if (!bit)
 +                      return optarg[i];
 +              if (negate)
 +                      opt->filter &= ~bit;
 +              else
 +                      opt->filter |= bit;
 +      }
 +      return 0;
 +}
 +
 +/* Used only by "diff-files" and "diff --no-index" */
 +void handle_deprecated_show_diff_q(struct diff_options *opt)
 +{
 +      warning("'diff -q' and 'diff-files -q' are deprecated.");
 +      warning("Use 'diff --diff-filter=d' instead to ignore deleted filepairs.");
 +      parse_diff_filter_opt("d", opt);
 +}
 +
 +static void enable_patch_output(int *fmt) {
 +      *fmt &= ~DIFF_FORMAT_NO_OUTPUT;
 +      *fmt |= DIFF_FORMAT_PATCH;
 +}
 +
  int diff_opt_parse(struct diff_options *options, const char **av, int ac)
  {
        const char *arg = av[0];
        int argcount;
  
        /* Output format options */
 -      if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch"))
 -              options->output_format |= DIFF_FORMAT_PATCH;
 -      else if (opt_arg(arg, 'U', "unified", &options->context))
 -              options->output_format |= DIFF_FORMAT_PATCH;
 +      if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch")
 +          || opt_arg(arg, 'U', "unified", &options->context))
 +              enable_patch_output(&options->output_format);
        else if (!strcmp(arg, "--raw"))
                options->output_format |= DIFF_FORMAT_RAW;
 -      else if (!strcmp(arg, "--patch-with-raw"))
 -              options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW;
 -      else if (!strcmp(arg, "--numstat"))
 +      else if (!strcmp(arg, "--patch-with-raw")) {
 +              enable_patch_output(&options->output_format);
 +              options->output_format |= DIFF_FORMAT_RAW;
 +      } else if (!strcmp(arg, "--numstat"))
                options->output_format |= DIFF_FORMAT_NUMSTAT;
        else if (!strcmp(arg, "--shortstat"))
                options->output_format |= DIFF_FORMAT_SHORTSTAT;
                options->output_format |= DIFF_FORMAT_CHECKDIFF;
        else if (!strcmp(arg, "--summary"))
                options->output_format |= DIFF_FORMAT_SUMMARY;
 -      else if (!strcmp(arg, "--patch-with-stat"))
 -              options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_DIFFSTAT;
 -      else if (!strcmp(arg, "--name-only"))
 +      else if (!strcmp(arg, "--patch-with-stat")) {
 +              enable_patch_output(&options->output_format);
 +              options->output_format |= DIFF_FORMAT_DIFFSTAT;
 +      } else if (!strcmp(arg, "--name-only"))
                options->output_format |= DIFF_FORMAT_NAME;
        else if (!strcmp(arg, "--name-status"))
                options->output_format |= DIFF_FORMAT_NAME_STATUS;
 -      else if (!strcmp(arg, "-s"))
 +      else if (!strcmp(arg, "-s") || !strcmp(arg, "--no-patch"))
                options->output_format |= DIFF_FORMAT_NO_OUTPUT;
        else if (!prefixcmp(arg, "--stat"))
                /* --stat, --stat-width, --stat-name-width, or --stat-count */
                DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE);
        else if (!strcmp(arg, "--ignore-space-at-eol"))
                DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
 +      else if (!strcmp(arg, "--ignore-blank-lines"))
 +              DIFF_XDL_SET(options, IGNORE_BLANK_LINES);
        else if (!strcmp(arg, "--patience"))
                options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
        else if (!strcmp(arg, "--histogram"))
  
        /* flags options */
        else if (!strcmp(arg, "--binary")) {
 -              options->output_format |= DIFF_FORMAT_PATCH;
 +              enable_patch_output(&options->output_format);
                DIFF_OPT_SET(options, BINARY);
        }
        else if (!strcmp(arg, "--full-index"))
                return argcount;
        }
        else if ((argcount = parse_long_opt("diff-filter", av, &optarg))) {
 -              options->filter = optarg;
 +              int offending = parse_diff_filter_opt(optarg, options);
 +              if (offending)
 +                      die("unknown change class '%c' in --diff-filter=%s",
 +                          offending, optarg);
                return argcount;
        }
        else if (!strcmp(arg, "--abbrev"))
@@@ -4549,7 -4453,7 +4552,7 @@@ void diff_flush(struct diff_options *op
            DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) {
                /*
                 * run diff_flush_patch for the exit status. setting
 -               * options->file to /dev/null should be safe, becaue we
 +               * options->file to /dev/null should be safe, because we
                 * aren't supposed to produce any output anyway.
                 */
                if (options->close_file)
@@@ -4609,32 -4513,27 +4612,32 @@@ free_queue
        }
  }
  
 -static void diffcore_apply_filter(const char *filter)
 +static int match_filter(const struct diff_options *options, const struct diff_filepair *p)
 +{
 +      return (((p->status == DIFF_STATUS_MODIFIED) &&
 +               ((p->score &&
 +                 filter_bit_tst(DIFF_STATUS_FILTER_BROKEN, options)) ||
 +                (!p->score &&
 +                 filter_bit_tst(DIFF_STATUS_MODIFIED, options)))) ||
 +              ((p->status != DIFF_STATUS_MODIFIED) &&
 +               filter_bit_tst(p->status, options)));
 +}
 +
 +static void diffcore_apply_filter(struct diff_options *options)
  {
        int i;
        struct diff_queue_struct *q = &diff_queued_diff;
        struct diff_queue_struct outq;
 +
        DIFF_QUEUE_CLEAR(&outq);
  
 -      if (!filter)
 +      if (!options->filter)
                return;
  
 -      if (strchr(filter, DIFF_STATUS_FILTER_AON)) {
 +      if (filter_bit_tst(DIFF_STATUS_FILTER_AON, options)) {
                int found;
                for (i = found = 0; !found && i < q->nr; i++) {
 -                      struct diff_filepair *p = q->queue[i];
 -                      if (((p->status == DIFF_STATUS_MODIFIED) &&
 -                           ((p->score &&
 -                             strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
 -                            (!p->score &&
 -                             strchr(filter, DIFF_STATUS_MODIFIED)))) ||
 -                          ((p->status != DIFF_STATUS_MODIFIED) &&
 -                           strchr(filter, p->status)))
 +                      if (match_filter(options, q->queue[i]))
                                found++;
                }
                if (found)
                /* Only the matching ones */
                for (i = 0; i < q->nr; i++) {
                        struct diff_filepair *p = q->queue[i];
 -
 -                      if (((p->status == DIFF_STATUS_MODIFIED) &&
 -                           ((p->score &&
 -                             strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
 -                            (!p->score &&
 -                             strchr(filter, DIFF_STATUS_MODIFIED)))) ||
 -                          ((p->status != DIFF_STATUS_MODIFIED) &&
 -                           strchr(filter, p->status)))
 +                      if (match_filter(options, p))
                                diff_q(&outq, p);
                        else
                                diff_free_filepair(p);
@@@ -4759,7 -4665,7 +4762,7 @@@ void diffcore_std(struct diff_options *
        if (!options->found_follow)
                /* See try_to_follow_renames() in tree-diff.c */
                diff_resolve_rename_copy();
 -      diffcore_apply_filter(options->filter);
 +      diffcore_apply_filter(options);
  
        if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
                DIFF_OPT_SET(options, HAS_CHANGES);
diff --combined diff.h
index 44092c2176468257644a19f787038a1ff3033eaa,e995ae12fee89605b0350b95ea782ed09b303a42..e34232501ee8e56a1d245da1c4f95ed8b928a837
--- 1/diff.h
--- 2/diff.h
+++ b/diff.h
@@@ -5,7 -5,6 +5,7 @@@
  #define DIFF_H
  
  #include "tree-walk.h"
 +#include "pathspec.h"
  
  struct rev_info;
  struct diff_options;
@@@ -88,8 -87,9 +88,9 @@@ typedef struct strbuf *(*diff_prefix_fn
  #define DIFF_OPT_PICKAXE_IGNORE_CASE (1 << 30)
  
  #define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)
- #define DIFF_OPT_SET(opts, flag)    ((opts)->flags |= DIFF_OPT_##flag)
- #define DIFF_OPT_CLR(opts, flag)    ((opts)->flags &= ~DIFF_OPT_##flag)
+ #define DIFF_OPT_TOUCHED(opts, flag)    ((opts)->touched_flags & DIFF_OPT_##flag)
+ #define DIFF_OPT_SET(opts, flag)    (((opts)->flags |= DIFF_OPT_##flag),((opts)->touched_flags |= DIFF_OPT_##flag))
+ #define DIFF_OPT_CLR(opts, flag)    (((opts)->flags &= ~DIFF_OPT_##flag),((opts)->touched_flags |= DIFF_OPT_##flag))
  #define DIFF_XDL_TST(opts, flag)    ((opts)->xdl_opts & XDF_##flag)
  #define DIFF_XDL_SET(opts, flag)    ((opts)->xdl_opts |= XDF_##flag)
  #define DIFF_XDL_CLR(opts, flag)    ((opts)->xdl_opts &= ~XDF_##flag)
@@@ -104,15 -104,13 +105,16 @@@ enum diff_words_type 
  };
  
  struct diff_options {
 -      const char *filter;
        const char *orderfile;
        const char *pickaxe;
        const char *single_follow;
        const char *a_prefix, *b_prefix;
        unsigned flags;
+       unsigned touched_flags;
 +
 +      /* diff-filter bits */
 +      unsigned int filter;
 +
        int use_color;
        int context;
        int interhunkcontext;
        /* to support internal diff recursion by --follow hack*/
        int found_follow;
  
+       void (*set_default)(struct diff_options *);
        FILE *file;
        int close_file;
  
@@@ -183,6 -183,8 +187,6 @@@ const char *diff_line_prefix(struct dif
  
  extern const char mime_boundary_leader[];
  
 -extern void diff_tree_setup_paths(const char **paths, struct diff_options *);
 -extern void diff_tree_release_paths(struct diff_options *);
  extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
                     const char *base, struct diff_options *opt);
  extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new,
@@@ -340,8 -342,6 +344,8 @@@ extern int parse_rename_score(const cha
  
  extern long parse_algorithm_value(const char *value);
  
 +extern void handle_deprecated_show_diff_q(struct diff_options *);
 +
  extern int print_stat_summary(FILE *fp, int files,
                              int insertions, int deletions);
  extern void setup_diff_pager(struct diff_options *);
diff --combined object.c
index 5f792cbfd38c0b30d763902ba5f9399992ddab89,c8ffc9e006e42d75f0c3632c378133b4dfbad6a0..584f7acb36b68d17678b3fd6251105241794ea48
+++ b/object.c
@@@ -43,17 -43,16 +43,17 @@@ int type_from_string(const char *str
        die("invalid object type \"%s\"", str);
  }
  
 -static unsigned int hash_obj(struct object *obj, unsigned int n)
 +static unsigned int hash_obj(const unsigned char *sha1, unsigned int n)
  {
        unsigned int hash;
 -      memcpy(&hash, obj->sha1, sizeof(unsigned int));
 -      return hash % n;
 +      memcpy(&hash, sha1, sizeof(unsigned int));
 +      /* Assumes power-of-2 hash sizes in grow_object_hash */
 +      return hash & (n - 1);
  }
  
  static void insert_obj_hash(struct object *obj, struct object **hash, unsigned int size)
  {
 -      unsigned int j = hash_obj(obj, size);
 +      unsigned int j = hash_obj(obj->sha1, size);
  
        while (hash[j]) {
                j++;
        hash[j] = obj;
  }
  
 -static unsigned int hashtable_index(const unsigned char *sha1)
 -{
 -      unsigned int i;
 -      memcpy(&i, sha1, sizeof(unsigned int));
 -      return i % obj_hash_size;
 -}
 -
  struct object *lookup_object(const unsigned char *sha1)
  {
 -      unsigned int i;
 +      unsigned int i, first;
        struct object *obj;
  
        if (!obj_hash)
                return NULL;
  
 -      i = hashtable_index(sha1);
 +      first = i = hash_obj(sha1, obj_hash_size);
        while ((obj = obj_hash[i]) != NULL) {
                if (!hashcmp(sha1, obj->sha1))
                        break;
                if (i == obj_hash_size)
                        i = 0;
        }
 +      if (obj && i != first) {
 +              /*
 +               * Move object to where we started to look for it so
 +               * that we do not need to walk the hash table the next
 +               * time we look for it.
 +               */
 +              struct object *tmp = obj_hash[i];
 +              obj_hash[i] = obj_hash[first];
 +              obj_hash[first] = tmp;
 +      }
        return obj;
  }
  
  static void grow_object_hash(void)
  {
        int i;
 +      /*
 +       * Note that this size must always be power-of-2 to match hash_obj
 +       * above.
 +       */
        int new_hash_size = obj_hash_size < 32 ? 32 : 2 * obj_hash_size;
        struct object **new_hash;
  
@@@ -143,7 -135,7 +143,7 @@@ struct object *lookup_unknown_object(co
  struct object *parse_object_buffer(const unsigned char *sha1, enum object_type type, unsigned long size, void *buffer, int *eaten_p)
  {
        struct object *obj;
 -      int eaten = 0;
 +      *eaten_p = 0;
  
        obj = NULL;
        if (type == OBJ_BLOB) {
                        if (!tree->object.parsed) {
                                if (parse_tree_buffer(tree, buffer, size))
                                        return NULL;
 -                              eaten = 1;
 +                              *eaten_p = 1;
                        }
                }
        } else if (type == OBJ_COMMIT) {
                                return NULL;
                        if (!commit->buffer) {
                                commit->buffer = buffer;
 -                              eaten = 1;
 +                              *eaten_p = 1;
                        }
                        obj = &commit->object;
                }
        }
        if (obj && obj->type == OBJ_NONE)
                obj->type = type;
 -      *eaten_p = eaten;
        return obj;
  }
  
@@@ -262,23 -255,11 +262,21 @@@ int object_list_contains(struct object_
        return 0;
  }
  
- void add_object_array(struct object *obj, const char *name, struct object_array *array)
- {
-       add_object_array_with_mode(obj, name, array, S_IFINVALID);
- }
 -static void add_object_array_with_mode_context(struct object *obj, const char *name, struct object_array *array, unsigned mode, struct object_context *context)
 +/*
 + * A zero-length string to which object_array_entry::name can be
 + * initialized without requiring a malloc/free.
 + */
 +static char object_array_slopbuf[1];
 +
- void add_object_array_with_mode(struct object *obj, const char *name, struct object_array *array, unsigned mode)
++static void add_object_array_with_mode_context(struct object *obj, const char *name,
++                                             struct object_array *array,
++                                             unsigned mode,
++                                             struct object_context *context)
  {
        unsigned nr = array->nr;
        unsigned alloc = array->alloc;
        struct object_array_entry *objects = array->objects;
 +      struct object_array_entry *entry;
  
        if (nr >= alloc) {
                alloc = (alloc + 32) * 2;
                array->alloc = alloc;
                array->objects = objects;
        }
 -      objects[nr].item = obj;
 -      objects[nr].name = name;
 -      objects[nr].mode = mode;
 -      objects[nr].context = context;
 +      entry = &objects[nr];
 +      entry->item = obj;
 +      if (!name)
 +              entry->name = NULL;
 +      else if (!*name)
 +              /* Use our own empty string instead of allocating one: */
 +              entry->name = object_array_slopbuf;
 +      else
 +              entry->name = xstrdup(name);
 +      entry->mode = mode;
++      entry->context = context;
        array->nr = ++nr;
  }
  
 -void object_array_remove_duplicates(struct object_array *array)
+ void add_object_array(struct object *obj, const char *name, struct object_array *array)
+ {
+       add_object_array_with_mode(obj, name, array, S_IFINVALID);
+ }
+ void add_object_array_with_mode(struct object *obj, const char *name, struct object_array *array, unsigned mode)
+ {
+       add_object_array_with_mode_context(obj, name, array, mode, NULL);
+ }
+ void add_object_array_with_context(struct object *obj, const char *name, struct object_array *array, struct object_context *context)
+ {
+       if (context)
+               add_object_array_with_mode_context(obj, name, array, context->mode, context);
+       else
+               add_object_array_with_mode_context(obj, name, array, S_IFINVALID, context);
+ }
 +void object_array_filter(struct object_array *array,
 +                       object_array_each_func_t want, void *cb_data)
  {
 -      unsigned int ref, src, dst;
 +      unsigned nr = array->nr, src, dst;
        struct object_array_entry *objects = array->objects;
  
 -      for (ref = 0; ref + 1 < array->nr; ref++) {
 -              for (src = ref + 1, dst = src;
 -                   src < array->nr;
 -                   src++) {
 -                      if (!strcmp(objects[ref].name, objects[src].name))
 -                              continue;
 +      for (src = dst = 0; src < nr; src++) {
 +              if (want(&objects[src], cb_data)) {
                        if (src != dst)
                                objects[dst] = objects[src];
                        dst++;
 +              } else {
 +                      if (objects[src].name != object_array_slopbuf)
 +                              free(objects[src].name);
 +              }
 +      }
 +      array->nr = dst;
 +}
 +
 +/*
 + * Return true iff array already contains an entry with name.
 + */
 +static int contains_name(struct object_array *array, const char *name)
 +{
 +      unsigned nr = array->nr, i;
 +      struct object_array_entry *object = array->objects;
 +
 +      for (i = 0; i < nr; i++, object++)
 +              if (!strcmp(object->name, name))
 +                      return 1;
 +      return 0;
 +}
 +
 +void object_array_remove_duplicates(struct object_array *array)
 +{
 +      unsigned nr = array->nr, src;
 +      struct object_array_entry *objects = array->objects;
 +
 +      array->nr = 0;
 +      for (src = 0; src < nr; src++) {
 +              if (!contains_name(array, objects[src].name)) {
 +                      if (src != array->nr)
 +                              objects[array->nr] = objects[src];
 +                      array->nr++;
 +              } else {
 +                      if (objects[src].name != object_array_slopbuf)
 +                              free(objects[src].name);
                }
 -              array->nr = dst;
        }
  }
  
diff --combined object.h
index 2ff68c52dd48842a188eb8f0c9b1e12ff27fae37,695847dfd4812c510c9766303ae34425bcfc6b29..dc5df8ce1d9579a28477f91300aa7a49f37ad4e4
+++ b/object.h
@@@ -11,14 -11,9 +11,15 @@@ struct object_array 
        unsigned int alloc;
        struct object_array_entry {
                struct object *item;
 -              const char *name;
 +              /*
 +               * name or NULL.  If non-NULL, the memory pointed to
 +               * is owned by this object *except* if it points at
 +               * object_array_slopbuf, which is a static copy of the
 +               * empty string.
 +               */
 +              char *name;
                unsigned mode;
+               struct object_context *context;
        } *objects;
  };
  
@@@ -91,22 -86,8 +92,23 @@@ int object_list_contains(struct object_
  /* Object array handling .. */
  void add_object_array(struct object *obj, const char *name, struct object_array *array);
  void add_object_array_with_mode(struct object *obj, const char *name, struct object_array *array, unsigned mode);
 -void object_array_remove_duplicates(struct object_array *);
+ void add_object_array_with_context(struct object *obj, const char *name, struct object_array *array, struct object_context *context);
 +
 +typedef int (*object_array_each_func_t)(struct object_array_entry *, void *);
 +
 +/*
 + * Apply want to each entry in array, retaining only the entries for
 + * which the function returns true.  Preserve the order of the entries
 + * that are retained.
 + */
 +void object_array_filter(struct object_array *array,
 +                       object_array_each_func_t want, void *cb_data);
 +
 +/*
 + * Remove from array all but the first entry with a given name.
 + * Warning: this function uses an O(N^2) algorithm.
 + */
 +void object_array_remove_duplicates(struct object_array *array);
  
  void clear_object_flags(unsigned flags);
  
diff --combined t/t4030-diff-textconv.sh
index f75f46f92d22451522d4676ff4ed709b49419481,0ebb028cccde070d0133c282068810673e98c2b4..aad6c7f78db34703b2f9b1ed72239cd3b4005016
@@@ -58,6 -58,12 +58,12 @@@ test_expect_success 'diff produces text
        test_cmp expect.text actual
  '
  
+ test_expect_success 'show commit produces text' '
+       git show HEAD >diff &&
+       find_diff <diff >actual &&
+       test_cmp expect.text actual
+ '
  test_expect_success 'diff-tree produces binary' '
        git diff-tree -p HEAD^ HEAD >diff &&
        find_diff <diff >actual &&
@@@ -84,6 -90,24 +90,24 @@@ test_expect_success 'status -v produce
        git reset --soft HEAD@{1}
  '
  
+ test_expect_success 'show blob produces binary' '
+       git show HEAD:file >actual &&
+       printf "\\0\\n\\01\\n" >expect &&
+       test_cmp expect actual
+ '
+ test_expect_success 'show --textconv blob produces text' '
+       git show --textconv HEAD:file >actual &&
+       printf "0\\n1\\n" >expect &&
+       test_cmp expect actual
+ '
+ test_expect_success 'show --no-textconv blob produces binary' '
+       git show --no-textconv HEAD:file >actual &&
+       printf "\\0\\n\\01\\n" >expect &&
+       test_cmp expect actual
+ '
  test_expect_success 'grep-diff (-G) operates on textconv data (add)' '
        echo one >expect &&
        git log --root --format=%s -G0 >actual &&
@@@ -139,10 -163,12 +163,10 @@@ index 0000000..67be42
  +frotz
  \ No newline at end of file
  EOF
 -# make a symlink the hard way that works on symlink-challenged file systems
 +
  test_expect_success 'textconv does not act on symlinks' '
 -      printf frotz > file &&
 -      git add file &&
 -      git ls-files -s | sed -e s/100644/120000/ |
 -              git update-index --index-info &&
 +      rm -f file &&
 +      test_ln_s_add frotz file &&
        git commit -m typechange &&
        git show >diff &&
        find_diff <diff >actual &&
index b95e102891db65c184a2ee3137f1b3e20cdab2db,83c663678f5700f994da1508dbfb4529597b751d..eacd49ade636f5be6166e05d4e200b9a324073be
@@@ -12,7 -12,9 +12,7 @@@ chmod +x helpe
  
  test_expect_success 'setup ' '
        echo "bin: test" >one.bin &&
 -      if test_have_prereq SYMLINKS; then
 -              ln -s one.bin symlink.bin
 -      fi &&
 +      test_ln_s_add one.bin symlink.bin &&
        git add . &&
        GIT_AUTHOR_NAME=Number1 git commit -a -m First --date="2010-01-01 18:00:00" &&
        echo "bin: test version 2" >one.bin &&
  '
  
  cat >expected <<EOF
- fatal: git cat-file --textconv: unable to run textconv on :one.bin
+ bin: test version 2
  EOF
  
  test_expect_success 'no filter specified' '
-       git cat-file --textconv :one.bin 2>result
+       git cat-file --textconv :one.bin >result &&
        test_cmp expected result
  '
  
@@@ -34,10 -36,6 +34,6 @@@ test_expect_success 'setup textconv fil
        git config diff.test.cachetextconv false
  '
  
- cat >expected <<EOF
- bin: test version 2
- EOF
  test_expect_success 'cat-file without --textconv' '
        git cat-file blob :one.bin >result &&
        test_cmp expected result
@@@ -70,26 -68,20 +66,20 @@@ test_expect_success 'cat-file --textcon
        test_cmp expected result
  '
  
 -test_expect_success SYMLINKS 'cat-file without --textconv (symlink)' '
 +test_expect_success 'cat-file without --textconv (symlink)' '
+       printf "%s" "one.bin" >expected &&
        git cat-file blob :symlink.bin >result &&
-       printf "%s" "one.bin" >expected
        test_cmp expected result
  '
  
  
 -test_expect_success SYMLINKS 'cat-file --textconv on index (symlink)' '
 +test_expect_success 'cat-file --textconv on index (symlink)' '
-       ! git cat-file --textconv :symlink.bin 2>result &&
-       cat >expected <<\EOF &&
- fatal: git cat-file --textconv: unable to run textconv on :symlink.bin
- EOF
+       git cat-file --textconv :symlink.bin >result &&
        test_cmp expected result
  '
  
 -test_expect_success SYMLINKS 'cat-file --textconv on HEAD (symlink)' '
 +test_expect_success 'cat-file --textconv on HEAD (symlink)' '
-       ! git cat-file --textconv HEAD:symlink.bin 2>result &&
-       cat >expected <<EOF &&
- fatal: git cat-file --textconv: unable to run textconv on HEAD:symlink.bin
- EOF
+       git cat-file --textconv HEAD:symlink.bin >result &&
        test_cmp expected result
  '