Merge branch 'jc/1.7.0-diff-whitespace-only-status'
authorJunio C Hamano <gitster@pobox.com>
Sat, 26 Dec 2009 22:03:18 +0000 (14:03 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sat, 26 Dec 2009 22:03:18 +0000 (14:03 -0800)
* jc/1.7.0-diff-whitespace-only-status:
  diff.c: fix typoes in comments
  Make test case number unique
  diff: Rename QUIET internal option to QUICK
  diff: change semantics of "ignore whitespace" options

Conflicts:
diff.h

1  2 
builtin-log.c
builtin-rev-list.c
diff-lib.c
diff.c
diff.h
revision.c

diff --combined builtin-log.c
@@@ -27,15 -27,10 +27,15 @@@ static int default_show_root = 1
  static const char *fmt_patch_subject_prefix = "PATCH";
  static const char *fmt_pretty;
  
 +static const char * const builtin_log_usage =
 +      "git log [<options>] [<since>..<until>] [[--] <path>...]\n"
 +      "   or: git show [options] <object>...";
 +
  static void cmd_log_init(int argc, const char **argv, const char *prefix,
                      struct rev_info *rev)
  {
        int i;
 +      int decoration_style = 0;
  
        rev->abbrev = DEFAULT_ABBREV;
        rev->commit_format = CMIT_FMT_DEFAULT;
        if (default_date_mode)
                rev->date_mode = parse_date_format(default_date_mode);
  
 +      /*
 +       * Check for -h before setup_revisions(), or "git log -h" will
 +       * fail when run without a git directory.
 +       */
 +      if (argc == 2 && !strcmp(argv[1], "-h"))
 +              usage(builtin_log_usage);
        argc = setup_revisions(argc, argv, rev, "HEAD");
  
        if (rev->diffopt.pickaxe || rev->diffopt.filter)
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
                if (!strcmp(arg, "--decorate")) {
 -                      load_ref_decorations();
 -                      rev->show_decorations = 1;
 +                      decoration_style = DECORATE_SHORT_REFS;
 +              } else if (!prefixcmp(arg, "--decorate=")) {
 +                      const char *v = skip_prefix(arg, "--decorate=");
 +                      if (!strcmp(v, "full"))
 +                              decoration_style = DECORATE_FULL_REFS;
 +                      else if (!strcmp(v, "short"))
 +                              decoration_style = DECORATE_SHORT_REFS;
 +                      else
 +                              die("invalid --decorate option: %s", arg);
                } else if (!strcmp(arg, "--source")) {
                        rev->show_source = 1;
 +              } else if (!strcmp(arg, "-h")) {
 +                      usage(builtin_log_usage);
                } else
                        die("unrecognized argument: %s", arg);
        }
 +      if (decoration_style) {
 +              rev->show_decorations = 1;
 +              load_ref_decorations(decoration_style);
 +      }
  }
  
  /*
@@@ -281,7 -257,7 +281,7 @@@ static void show_tagger(char *buf, int 
        pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode,
                git_log_output_encoding ?
                git_log_output_encoding: git_commit_encoding);
 -      printf("%s\n", out.buf);
 +      printf("%s", out.buf);
        strbuf_release(&out);
  }
  
@@@ -353,14 -329,11 +353,14 @@@ int cmd_show(int argc, const char **arg
                case OBJ_TAG: {
                        struct tag *t = (struct tag *)o;
  
 +                      if (rev.shown_one)
 +                              putchar('\n');
                        printf("%stag %s%s\n",
                                        diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
                                        t->tag,
                                        diff_get_color_opt(&rev.diffopt, DIFF_RESET));
                        ret = show_object(o->sha1, 1, &rev);
 +                      rev.shown_one = 1;
                        if (ret)
                                break;
                        o = parse_object(t->tagged->sha1);
                        break;
                }
                case OBJ_TREE:
 +                      if (rev.shown_one)
 +                              putchar('\n');
                        printf("%stree %s%s\n\n",
                                        diff_get_color_opt(&rev.diffopt, DIFF_COMMIT),
                                        name,
                                        diff_get_color_opt(&rev.diffopt, DIFF_RESET));
                        read_tree_recursive((struct tree *)o, "", 0, 0, NULL,
                                        show_tree_object, NULL);
 +                      rev.shown_one = 1;
                        break;
                case OBJ_COMMIT:
                        rev.pending.nr = rev.pending.alloc = 0;
@@@ -567,7 -537,7 +567,7 @@@ static int reopen_stdout(struct commit 
  
        get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename);
  
-       if (!DIFF_OPT_TST(&rev->diffopt, QUIET))
+       if (!DIFF_OPT_TST(&rev->diffopt, QUICK))
                fprintf(realstdout, "%s\n", filename.buf + outdir_offset);
  
        if (freopen(filename.buf, "w", stdout) == NULL)
@@@ -688,10 -658,6 +688,10 @@@ static void make_cover_letter(struct re
        log_write_email_headers(rev, head, &subject_start, &extra_headers,
                                &need_8bit_cte);
  
 +      for (i = 0; !need_8bit_cte && i < nr; i++)
 +              if (has_non_ascii(list[i]->buffer))
 +                      need_8bit_cte = 1;
 +
        msg = body;
        pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822,
                     encoding);
@@@ -897,7 -863,6 +897,7 @@@ int cmd_format_patch(int argc, const ch
        struct patch_ids ids;
        char *add_signoff = NULL;
        struct strbuf buf = STRBUF_INIT;
 +      int use_patch_format = 0;
        const struct option builtin_format_patch_options[] = {
                { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
                            "use [PATCH n/m] even with a single patch",
                            "don't output binary diffs"),
                OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream,
                            "don't include a patch matching a commit upstream"),
 +              { OPTION_BOOLEAN, 'p', "no-stat", &use_patch_format, NULL,
 +                "show patch format instead of default (patch + stat)",
 +                PARSE_OPT_NONEG | PARSE_OPT_NOARG },
                OPT_GROUP("Messaging"),
                { OPTION_CALLBACK, 0, "add-header", NULL, "header",
                            "add email header", PARSE_OPT_NONEG,
         */
        argc = parse_options(argc, argv, prefix, builtin_format_patch_options,
                             builtin_format_patch_usage,
 -                           PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN);
 +                           PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
 +                           PARSE_OPT_KEEP_DASHDASH);
  
        if (do_signoff) {
                const char *committer;
        if (argc > 1)
                die ("unrecognized argument: %s", argv[1]);
  
 -      if (!rev.diffopt.output_format
 -              || rev.diffopt.output_format == DIFF_FORMAT_PATCH)
 -              rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH;
 +      if (rev.diffopt.output_format & DIFF_FORMAT_NAME)
 +              die("--name-only does not make sense");
 +      if (rev.diffopt.output_format & DIFF_FORMAT_NAME_STATUS)
 +              die("--name-status does not make sense");
 +      if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF)
 +              die("--check does not make sense");
 +
 +      if (!use_patch_format &&
 +              (!rev.diffopt.output_format ||
 +               rev.diffopt.output_format == DIFF_FORMAT_PATCH))
 +              rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY;
 +
 +      /* Always generate a patch */
 +      rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
  
        if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff)
                DIFF_OPT_SET(&rev.diffopt, BINARY);
@@@ -1259,9 -1209,6 +1259,9 @@@ int cmd_cherry(int argc, const char **a
                argv++;
        }
  
 +      if (argc > 1 && !strcmp(argv[1], "-h"))
 +              usage(cherry_usage);
 +
        switch (argc) {
        case 4:
                limit = argv[3];
  
                if (verbose) {
                        struct strbuf buf = STRBUF_INIT;
 +                      struct pretty_print_context ctx = {0};
                        pretty_print_commit(CMIT_FMT_ONELINE, commit,
 -                                          &buf, 0, NULL, NULL, 0, 0);
 +                                          &buf, &ctx);
                        printf("%c %s %s\n", sign,
                               sha1_to_hex(commit->object.sha1), buf.buf);
                        strbuf_release(&buf);
diff --combined builtin-rev-list.c
@@@ -96,10 -96,9 +96,10 @@@ static void show_commit(struct commit *
  
        if (revs->verbose_header && commit->buffer) {
                struct strbuf buf = STRBUF_INIT;
 -              pretty_print_commit(revs->commit_format, commit,
 -                                  &buf, revs->abbrev, NULL, NULL,
 -                                  revs->date_mode, 0);
 +              struct pretty_print_context ctx = {0};
 +              ctx.abbrev = revs->abbrev;
 +              ctx.date_mode = revs->date_mode;
 +              pretty_print_commit(revs->commit_format, commit, &buf, &ctx);
                if (revs->graph) {
                        if (buf.len) {
                                if (revs->commit_format != CMIT_FMT_ONELINE)
@@@ -306,6 -305,7 +306,6 @@@ int cmd_rev_list(int argc, const char *
        struct rev_info revs;
        struct rev_list_info info;
        int i;
 -      int read_from_stdin = 0;
        int bisect_list = 0;
        int bisect_show_vars = 0;
        int bisect_find_all = 0;
  
        memset(&info, 0, sizeof(info));
        info.revs = &revs;
 +      if (revs.bisect)
 +              bisect_list = 1;
  
-       quiet = DIFF_OPT_TST(&revs.diffopt, QUIET);
+       quiet = DIFF_OPT_TST(&revs.diffopt, QUICK);
        for (i = 1 ; i < argc; i++) {
                const char *arg = argv[i];
  
                        bisect_show_vars = 1;
                        continue;
                }
 -              if (!strcmp(arg, "--stdin")) {
 -                      if (read_from_stdin++)
 -                              die("--stdin given twice?");
 -                      read_revisions_from_stdin(&revs);
 -                      continue;
 -              }
                usage(rev_list_usage);
  
        }
diff --combined diff-lib.c
@@@ -73,7 -73,7 +73,7 @@@ int run_diff_files(struct rev_info *rev
                struct cache_entry *ce = active_cache[i];
                int changed;
  
-               if (DIFF_OPT_TST(&revs->diffopt, QUIET) &&
+               if (DIFF_OPT_TST(&revs->diffopt, QUICK) &&
                        DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
                        break;
  
                if (ce_uptodate(ce))
                        continue;
  
 -              changed = check_removed(ce, &st);
 +              /* If CE_VALID is set, don't look at workdir for file removal */
 +              changed = (ce->ce_flags & CE_VALID) ? 0 : check_removed(ce, &st);
                if (changed) {
                        if (changed < 0) {
                                perror(ce->name);
@@@ -309,6 -308,22 +309,6 @@@ static int show_modified(struct rev_inf
        return 0;
  }
  
 -/*
 - * This turns all merge entries into "stage 3". That guarantees that
 - * when we read in the new tree (into "stage 1"), we won't lose sight
 - * of the fact that we had unmerged entries.
 - */
 -static void mark_merge_entries(void)
 -{
 -      int i;
 -      for (i = 0; i < active_nr; i++) {
 -              struct cache_entry *ce = active_cache[i];
 -              if (!ce_stage(ce))
 -                      continue;
 -              ce->ce_flags |= CE_STAGEMASK;
 -      }
 -}
 -
  /*
   * This gets a mix of an existing index and a tree, one pathname entry
   * at a time. The index entry may be a single stage-0 one, but it could
@@@ -322,8 -337,6 +322,8 @@@ static void do_oneway_diff(struct unpac
        struct rev_info *revs = o->unpack_data;
        int match_missing, cached;
  
 +      /* if the entry is not checked out, don't examine work tree */
 +      cached = o->index_only || (idx && (idx->ce_flags & CE_VALID));
        /*
         * Backward compatibility wart - "diff-index -m" does
         * not mean "do not ignore merges", but "match_missing".
         * But with the revision flag parsing, that's found in
         * "!revs->ignore_merges".
         */
 -      cached = o->index_only;
        match_missing = !revs->ignore_merges;
  
        if (cached && idx && ce_stage(idx)) {
 -              if (tree)
 -                      diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode, idx->sha1);
 +              diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode,
 +                           idx->sha1);
                return;
        }
  
@@@ -383,7 -397,7 +383,7 @@@ static inline void skip_same_name(struc
   * For diffing, the index is more important, and we only have a
   * single tree.
   *
 - * We're supposed to return how many index entries we want to skip.
 + * We're supposed to advance o->pos to skip what we have already processed.
   *
   * This wrapper makes it all more readable, and takes care of all
   * the fairly complex unpack_trees() semantic requirements, including
@@@ -421,6 -435,8 +421,6 @@@ int run_diff_index(struct rev_info *rev
        struct unpack_trees_options opts;
        struct tree_desc t;
  
 -      mark_merge_entries();
 -
        ent = revs->pending.objects[0].item;
        tree_name = revs->pending.objects[0].name;
        tree = parse_tree_indirect(ent->sha1);
@@@ -507,7 -523,7 +507,7 @@@ int index_differs_from(const char *def
  
        init_revisions(&rev, NULL);
        setup_revisions(0, NULL, &rev, def);
-       DIFF_OPT_SET(&rev.diffopt, QUIET);
+       DIFF_OPT_SET(&rev.diffopt, QUICK);
        DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
        rev.diffopt.flags |= diff_flags;
        run_diff_index(&rev, 1);
diff --combined diff.c
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -13,7 -13,6 +13,7 @@@
  #include "utf8.h"
  #include "userdiff.h"
  #include "sigchain.h"
 +#include "submodule.h"
  
  #ifdef NO_FAST_WORKING_DIRECTORY
  #define FAST_WORKING_DIRECTORY 0
@@@ -39,7 -38,6 +39,7 @@@ static char diff_colors[][COLOR_MAXLEN
        GIT_COLOR_GREEN,        /* NEW */
        GIT_COLOR_YELLOW,       /* COMMIT */
        GIT_COLOR_BG_RED,       /* WHITESPACE */
 +      GIT_COLOR_NORMAL,       /* FUNCINFO */
  };
  
  static void diff_filespec_load_driver(struct diff_filespec *one);
@@@ -61,9 -59,7 +61,9 @@@ static int parse_diff_color_slot(const 
                return DIFF_COMMIT;
        if (!strcasecmp(var+ofs, "whitespace"))
                return DIFF_WHITESPACE;
 -      die("bad config variable '%s'", var);
 +      if (!strcasecmp(var+ofs, "func"))
 +              return DIFF_FUNCINFO;
 +      return -1;
  }
  
  static int git_config_rename(const char *var, const char *value)
@@@ -122,8 -118,6 +122,8 @@@ int git_diff_basic_config(const char *v
  
        if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
                int slot = parse_diff_color_slot(var, 11);
 +              if (slot < 0)
 +                      return 0;
                if (!value)
                        return config_error_nonbool(var);
                color_parse(value, var, diff_colors[slot]);
@@@ -180,212 -174,6 +180,212 @@@ static struct diff_tempfile 
        char tmp_path[PATH_MAX];
  } diff_temp[2];
  
 +typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
 +
 +struct emit_callback {
 +      int color_diff;
 +      unsigned ws_rule;
 +      int blank_at_eof_in_preimage;
 +      int blank_at_eof_in_postimage;
 +      int lno_in_preimage;
 +      int lno_in_postimage;
 +      sane_truncate_fn truncate;
 +      const char **label_path;
 +      struct diff_words_data *diff_words;
 +      int *found_changesp;
 +      FILE *file;
 +};
 +
 +static int count_lines(const char *data, int size)
 +{
 +      int count, ch, completely_empty = 1, nl_just_seen = 0;
 +      count = 0;
 +      while (0 < size--) {
 +              ch = *data++;
 +              if (ch == '\n') {
 +                      count++;
 +                      nl_just_seen = 1;
 +                      completely_empty = 0;
 +              }
 +              else {
 +                      nl_just_seen = 0;
 +                      completely_empty = 0;
 +              }
 +      }
 +      if (completely_empty)
 +              return 0;
 +      if (!nl_just_seen)
 +              count++; /* no trailing newline */
 +      return count;
 +}
 +
 +static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
 +{
 +      if (!DIFF_FILE_VALID(one)) {
 +              mf->ptr = (char *)""; /* does not matter */
 +              mf->size = 0;
 +              return 0;
 +      }
 +      else if (diff_populate_filespec(one, 0))
 +              return -1;
 +
 +      mf->ptr = one->data;
 +      mf->size = one->size;
 +      return 0;
 +}
 +
 +static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule)
 +{
 +      char *ptr = mf->ptr;
 +      long size = mf->size;
 +      int cnt = 0;
 +
 +      if (!size)
 +              return cnt;
 +      ptr += size - 1; /* pointing at the very end */
 +      if (*ptr != '\n')
 +              ; /* incomplete line */
 +      else
 +              ptr--; /* skip the last LF */
 +      while (mf->ptr < ptr) {
 +              char *prev_eol;
 +              for (prev_eol = ptr; mf->ptr <= prev_eol; prev_eol--)
 +                      if (*prev_eol == '\n')
 +                              break;
 +              if (!ws_blank_line(prev_eol + 1, ptr - prev_eol, ws_rule))
 +                      break;
 +              cnt++;
 +              ptr = prev_eol - 1;
 +      }
 +      return cnt;
 +}
 +
 +static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,
 +                             struct emit_callback *ecbdata)
 +{
 +      int l1, l2, at;
 +      unsigned ws_rule = ecbdata->ws_rule;
 +      l1 = count_trailing_blank(mf1, ws_rule);
 +      l2 = count_trailing_blank(mf2, ws_rule);
 +      if (l2 <= l1) {
 +              ecbdata->blank_at_eof_in_preimage = 0;
 +              ecbdata->blank_at_eof_in_postimage = 0;
 +              return;
 +      }
 +      at = count_lines(mf1->ptr, mf1->size);
 +      ecbdata->blank_at_eof_in_preimage = (at - l1) + 1;
 +
 +      at = count_lines(mf2->ptr, mf2->size);
 +      ecbdata->blank_at_eof_in_postimage = (at - l2) + 1;
 +}
 +
 +static void emit_line_0(FILE *file, const char *set, const char *reset,
 +                      int first, const char *line, int len)
 +{
 +      int has_trailing_newline, has_trailing_carriage_return;
 +      int nofirst;
 +
 +      if (len == 0) {
 +              has_trailing_newline = (first == '\n');
 +              has_trailing_carriage_return = (!has_trailing_newline &&
 +                                              (first == '\r'));
 +              nofirst = has_trailing_newline || has_trailing_carriage_return;
 +      } else {
 +              has_trailing_newline = (len > 0 && line[len-1] == '\n');
 +              if (has_trailing_newline)
 +                      len--;
 +              has_trailing_carriage_return = (len > 0 && line[len-1] == '\r');
 +              if (has_trailing_carriage_return)
 +                      len--;
 +              nofirst = 0;
 +      }
 +
 +      if (len || !nofirst) {
 +              fputs(set, file);
 +              if (!nofirst)
 +                      fputc(first, file);
 +              fwrite(line, len, 1, file);
 +              fputs(reset, file);
 +      }
 +      if (has_trailing_carriage_return)
 +              fputc('\r', file);
 +      if (has_trailing_newline)
 +              fputc('\n', file);
 +}
 +
 +static void emit_line(FILE *file, const char *set, const char *reset,
 +                    const char *line, int len)
 +{
 +      emit_line_0(file, set, reset, line[0], line+1, len-1);
 +}
 +
 +static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len)
 +{
 +      if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) &&
 +            ecbdata->blank_at_eof_in_preimage &&
 +            ecbdata->blank_at_eof_in_postimage &&
 +            ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage &&
 +            ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage))
 +              return 0;
 +      return ws_blank_line(line, len, ecbdata->ws_rule);
 +}
 +
 +static void emit_add_line(const char *reset,
 +                        struct emit_callback *ecbdata,
 +                        const char *line, int len)
 +{
 +      const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
 +      const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
 +
 +      if (!*ws)
 +              emit_line_0(ecbdata->file, set, reset, '+', line, len);
 +      else if (new_blank_line_at_eof(ecbdata, line, len))
 +              /* Blank line at EOF - paint '+' as well */
 +              emit_line_0(ecbdata->file, ws, reset, '+', line, len);
 +      else {
 +              /* Emit just the prefix, then the rest. */
 +              emit_line_0(ecbdata->file, set, reset, '+', "", 0);
 +              ws_check_emit(line, len, ecbdata->ws_rule,
 +                            ecbdata->file, set, reset, ws);
 +      }
 +}
 +
 +static void emit_hunk_header(struct emit_callback *ecbdata,
 +                           const char *line, int len)
 +{
 +      const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
 +      const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
 +      const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);
 +      const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
 +      static const char atat[2] = { '@', '@' };
 +      const char *cp, *ep;
 +
 +      /*
 +       * As a hunk header must begin with "@@ -<old>, +<new> @@",
 +       * it always is at least 10 bytes long.
 +       */
 +      if (len < 10 ||
 +          memcmp(line, atat, 2) ||
 +          !(ep = memmem(line + 2, len - 2, atat, 2))) {
 +              emit_line(ecbdata->file, plain, reset, line, len);
 +              return;
 +      }
 +      ep += 2; /* skip over @@ */
 +
 +      /* The hunk header in fraginfo color */
 +      emit_line(ecbdata->file, frag, reset, line, ep - line);
 +
 +      /* blank before the func header */
 +      for (cp = ep; ep - line < len; ep++)
 +              if (*ep != ' ' && *ep != '\t')
 +                      break;
 +      if (ep != cp)
 +              emit_line(ecbdata->file, plain, reset, cp, ep - cp);
 +
 +      if (ep < line + len)
 +              emit_line(ecbdata->file, func, reset, ep, line + len - ep);
 +}
 +
  static struct diff_tempfile *claim_diff_tempfile(void) {
        int i;
        for (i = 0; i < ARRAY_SIZE(diff_temp); i++)
@@@ -413,6 -201,29 +413,6 @@@ static void remove_tempfile_on_signal(i
        raise(signo);
  }
  
 -static int count_lines(const char *data, int size)
 -{
 -      int count, ch, completely_empty = 1, nl_just_seen = 0;
 -      count = 0;
 -      while (0 < size--) {
 -              ch = *data++;
 -              if (ch == '\n') {
 -                      count++;
 -                      nl_just_seen = 1;
 -                      completely_empty = 0;
 -              }
 -              else {
 -                      nl_just_seen = 0;
 -                      completely_empty = 0;
 -              }
 -      }
 -      if (completely_empty)
 -              return 0;
 -      if (!nl_just_seen)
 -              count++; /* no trailing newline */
 -      return count;
 -}
 -
  static void print_line_count(FILE *file, int count)
  {
        switch (count) {
        }
  }
  
 -static void copy_file_with_prefix(FILE *file,
 -                                int prefix, const char *data, int size,
 -                                const char *set, const char *reset)
 +static void emit_rewrite_lines(struct emit_callback *ecb,
 +                             int prefix, const char *data, int size)
  {
 -      int ch, nl_just_seen = 1;
 -      while (0 < size--) {
 -              ch = *data++;
 -              if (nl_just_seen) {
 -                      fputs(set, file);
 -                      putc(prefix, file);
 +      const char *endp = NULL;
 +      static const char *nneof = " No newline at end of file\n";
 +      const char *old = diff_get_color(ecb->color_diff, DIFF_FILE_OLD);
 +      const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET);
 +
 +      while (0 < size) {
 +              int len;
 +
 +              endp = memchr(data, '\n', size);
 +              len = endp ? (endp - data + 1) : size;
 +              if (prefix != '+') {
 +                      ecb->lno_in_preimage++;
 +                      emit_line_0(ecb->file, old, reset, '-',
 +                                  data, len);
 +              } else {
 +                      ecb->lno_in_postimage++;
 +                      emit_add_line(reset, ecb, data, len);
                }
 -              if (ch == '\n') {
 -                      nl_just_seen = 1;
 -                      fputs(reset, file);
 -              } else
 -                      nl_just_seen = 0;
 -              putc(ch, file);
 +              size -= len;
 +              data += len;
 +      }
 +      if (!endp) {
 +              const char *plain = diff_get_color(ecb->color_diff,
 +                                                 DIFF_PLAIN);
 +              emit_line_0(ecb->file, plain, reset, '\\',
 +                          nneof, strlen(nneof));
        }
 -      if (!nl_just_seen)
 -              fprintf(file, "%s\n\\ No newline at end of file\n", reset);
  }
  
  static void emit_rewrite_diff(const char *name_a,
        const char *name_a_tab, *name_b_tab;
        const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO);
        const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO);
 -      const char *old = diff_get_color(color_diff, DIFF_FILE_OLD);
 -      const char *new = diff_get_color(color_diff, DIFF_FILE_NEW);
        const char *reset = diff_get_color(color_diff, DIFF_RESET);
        static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
        const char *a_prefix, *b_prefix;
        const char *data_one, *data_two;
        size_t size_one, size_two;
 +      struct emit_callback ecbdata;
  
        if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) {
                a_prefix = o->b_prefix;
                size_two = two->size;
        }
  
 +      memset(&ecbdata, 0, sizeof(ecbdata));
 +      ecbdata.color_diff = color_diff;
 +      ecbdata.found_changesp = &o->found_changes;
 +      ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
 +      ecbdata.file = o->file;
 +      if (ecbdata.ws_rule & WS_BLANK_AT_EOF) {
 +              mmfile_t mf1, mf2;
 +              mf1.ptr = (char *)data_one;
 +              mf2.ptr = (char *)data_two;
 +              mf1.size = size_one;
 +              mf2.size = size_two;
 +              check_blank_at_eof(&mf1, &mf2, &ecbdata);
 +      }
 +      ecbdata.lno_in_preimage = 1;
 +      ecbdata.lno_in_postimage = 1;
 +
        lc_a = count_lines(data_one, size_one);
        lc_b = count_lines(data_two, size_two);
        fprintf(o->file,
        print_line_count(o->file, lc_b);
        fprintf(o->file, " @@%s\n", reset);
        if (lc_a)
 -              copy_file_with_prefix(o->file, '-', data_one, size_one, old, reset);
 +              emit_rewrite_lines(&ecbdata, '-', data_one, size_one);
        if (lc_b)
 -              copy_file_with_prefix(o->file, '+', data_two, size_two, new, reset);
 -}
 -
 -static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
 -{
 -      if (!DIFF_FILE_VALID(one)) {
 -              mf->ptr = (char *)""; /* does not matter */
 -              mf->size = 0;
 -              return 0;
 -      }
 -      else if (diff_populate_filespec(one, 0))
 -              return -1;
 -
 -      mf->ptr = one->data;
 -      mf->size = one->size;
 -      return 0;
 +              emit_rewrite_lines(&ecbdata, '+', data_two, size_two);
  }
  
  struct diff_words_buffer {
@@@ -728,18 -529,26 +728,18 @@@ static void diff_words_show(struct diff
        diff_words->minus.text.size = diff_words->plus.text.size = 0;
  }
  
 -typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
 -
 -struct emit_callback {
 -      int nparents, color_diff;
 -      unsigned ws_rule;
 -      sane_truncate_fn truncate;
 -      const char **label_path;
 -      struct diff_words_data *diff_words;
 -      int *found_changesp;
 -      FILE *file;
 -};
 +/* In "color-words" mode, show word-diff of words accumulated in the buffer */
 +static void diff_words_flush(struct emit_callback *ecbdata)
 +{
 +      if (ecbdata->diff_words->minus.text.size ||
 +          ecbdata->diff_words->plus.text.size)
 +              diff_words_show(ecbdata->diff_words);
 +}
  
  static void free_diff_words_data(struct emit_callback *ecbdata)
  {
        if (ecbdata->diff_words) {
 -              /* flush buffers */
 -              if (ecbdata->diff_words->minus.text.size ||
 -                              ecbdata->diff_words->plus.text.size)
 -                      diff_words_show(ecbdata->diff_words);
 -
 +              diff_words_flush(ecbdata);
                free (ecbdata->diff_words->minus.text.ptr);
                free (ecbdata->diff_words->minus.orig);
                free (ecbdata->diff_words->plus.text.ptr);
@@@ -757,6 -566,42 +757,6 @@@ const char *diff_get_color(int diff_use
        return "";
  }
  
 -static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len)
 -{
 -      int has_trailing_newline, has_trailing_carriage_return;
 -
 -      has_trailing_newline = (len > 0 && line[len-1] == '\n');
 -      if (has_trailing_newline)
 -              len--;
 -      has_trailing_carriage_return = (len > 0 && line[len-1] == '\r');
 -      if (has_trailing_carriage_return)
 -              len--;
 -
 -      fputs(set, file);
 -      fwrite(line, len, 1, file);
 -      fputs(reset, file);
 -      if (has_trailing_carriage_return)
 -              fputc('\r', file);
 -      if (has_trailing_newline)
 -              fputc('\n', file);
 -}
 -
 -static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len)
 -{
 -      const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
 -      const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
 -
 -      if (!*ws)
 -              emit_line(ecbdata->file, set, reset, line, len);
 -      else {
 -              /* Emit just the prefix, then the rest. */
 -              emit_line(ecbdata->file, set, reset, line, ecbdata->nparents);
 -              ws_check_emit(line + ecbdata->nparents,
 -                            len - ecbdata->nparents, ecbdata->ws_rule,
 -                            ecbdata->file, set, reset, ws);
 -      }
 -}
 -
  static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len)
  {
        const char *cp;
        return allot - l;
  }
  
 +static void find_lno(const char *line, struct emit_callback *ecbdata)
 +{
 +      const char *p;
 +      ecbdata->lno_in_preimage = 0;
 +      ecbdata->lno_in_postimage = 0;
 +      p = strchr(line, '-');
 +      if (!p)
 +              return; /* cannot happen */
 +      ecbdata->lno_in_preimage = strtol(p + 1, NULL, 10);
 +      p = strchr(p, '+');
 +      if (!p)
 +              return; /* cannot happen */
 +      ecbdata->lno_in_postimage = strtol(p + 1, NULL, 10);
 +}
 +
  static void fn_out_consume(void *priv, char *line, unsigned long len)
  {
 -      int i;
 -      int color;
        struct emit_callback *ecbdata = priv;
        const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
        const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
                len = 1;
        }
  
 -      /* This is not really necessary for now because
 -       * this codepath only deals with two-way diffs.
 -       */
 -      for (i = 0; i < len && line[i] == '@'; i++)
 -              ;
 -      if (2 <= i && i < len && line[i] == ' ') {
 -              ecbdata->nparents = i - 1;
 +      if (line[0] == '@') {
 +              if (ecbdata->diff_words)
 +                      diff_words_flush(ecbdata);
                len = sane_truncate_line(ecbdata, line, len);
 -              emit_line(ecbdata->file,
 -                        diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO),
 -                        reset, line, len);
 +              find_lno(line, ecbdata);
 +              emit_hunk_header(ecbdata, line, len);
                if (line[len-1] != '\n')
                        putc('\n', ecbdata->file);
                return;
        }
  
 -      if (len < ecbdata->nparents) {
 +      if (len < 1) {
                emit_line(ecbdata->file, reset, reset, line, len);
                return;
        }
  
 -      color = DIFF_PLAIN;
 -      if (ecbdata->diff_words && ecbdata->nparents != 1)
 -              /* fall back to normal diff */
 -              free_diff_words_data(ecbdata);
        if (ecbdata->diff_words) {
                if (line[0] == '-') {
                        diff_words_append(line, len,
                                          &ecbdata->diff_words->plus);
                        return;
                }
 -              if (ecbdata->diff_words->minus.text.size ||
 -                  ecbdata->diff_words->plus.text.size)
 -                      diff_words_show(ecbdata->diff_words);
 +              diff_words_flush(ecbdata);
                line++;
                len--;
                emit_line(ecbdata->file, plain, reset, line, len);
                return;
        }
 -      for (i = 0; i < ecbdata->nparents && len; i++) {
 -              if (line[i] == '-')
 -                      color = DIFF_FILE_OLD;
 -              else if (line[i] == '+')
 -                      color = DIFF_FILE_NEW;
 -      }
  
 -      if (color != DIFF_FILE_NEW) {
 -              emit_line(ecbdata->file,
 -                        diff_get_color(ecbdata->color_diff, color),
 -                        reset, line, len);
 -              return;
 +      if (line[0] != '+') {
 +              const char *color =
 +                      diff_get_color(ecbdata->color_diff,
 +                                     line[0] == '-' ? DIFF_FILE_OLD : DIFF_PLAIN);
 +              ecbdata->lno_in_preimage++;
 +              if (line[0] == ' ')
 +                      ecbdata->lno_in_postimage++;
 +              emit_line(ecbdata->file, color, reset, line, len);
 +      } else {
 +              ecbdata->lno_in_postimage++;
 +              emit_add_line(reset, ecbdata, line + 1, len - 1);
        }
 -      emit_add_line(reset, ecbdata, line, len);
  }
  
  static char *pprint_rename(const char *a, const char *b)
@@@ -1155,7 -999,7 +1155,7 @@@ static void show_stats(struct diffstat_
               total_files, adds, dels);
  }
  
 -static void show_shortstats(struct diffstat_tdata, struct diff_options *options)
 +static void show_shortstats(struct diffstat_t *data, struct diff_options *options)
  {
        int i, adds = 0, dels = 0, total_files = data->nr;
  
@@@ -1367,6 -1211,7 +1367,6 @@@ struct checkdiff_t 
        struct diff_options *o;
        unsigned ws_rule;
        unsigned status;
 -      int trailing_blanks_start;
  };
  
  static int is_conflict_marker(const char *line, unsigned long len)
@@@ -1410,6 -1255,10 +1410,6 @@@ static void checkdiff_consume(void *pri
        if (line[0] == '+') {
                unsigned bad;
                data->lineno++;
 -              if (!ws_blank_line(line + 1, len - 1, data->ws_rule))
 -                      data->trailing_blanks_start = 0;
 -              else if (!data->trailing_blanks_start)
 -                      data->trailing_blanks_start = data->lineno;
                if (is_conflict_marker(line + 1, len - 1)) {
                        data->status |= 1;
                        fprintf(data->o->file,
                              data->o->file, set, reset, ws);
        } else if (line[0] == ' ') {
                data->lineno++;
 -              data->trailing_blanks_start = 0;
        } else if (line[0] == '@') {
                char *plus = strchr(line, '+');
                if (plus)
                        data->lineno = strtol(plus, NULL, 10) - 1;
                else
                        die("invalid diff");
 -              data->trailing_blanks_start = 0;
        }
  }
  
@@@ -1602,17 -1453,6 +1602,17 @@@ static void builtin_diff(const char *na
        const char *a_prefix, *b_prefix;
        const char *textconv_one = NULL, *textconv_two = NULL;
  
 +      if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
 +                      (!one->mode || S_ISGITLINK(one->mode)) &&
 +                      (!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,
 +                              one->sha1, two->sha1,
 +                              del, add, reset);
 +              return;
 +      }
 +
        if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) {
                textconv_one = get_textconv(one);
                textconv_two = get_textconv(two);
                ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
                ecbdata.found_changesp = &o->found_changes;
                ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
 +              if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
 +                      check_blank_at_eof(&mf1, &mf2, &ecbdata);
                ecbdata.file = o->file;
                xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
                xecfg.ctxlen = o->context;
@@@ -1866,22 -1704,11 +1866,22 @@@ static void builtin_checkdiff(const cha
                xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data,
                              &xpp, &xecfg, &ecb);
  
 -              if ((data.ws_rule & WS_TRAILING_SPACE) &&
 -                  data.trailing_blanks_start) {
 -                      fprintf(o->file, "%s:%d: ends with blank lines.\n",
 -                              data.filename, data.trailing_blanks_start);
 -                      data.status = 1; /* report errors */
 +              if (data.ws_rule & WS_BLANK_AT_EOF) {
 +                      struct emit_callback ecbdata;
 +                      int blank_at_eof;
 +
 +                      ecbdata.ws_rule = data.ws_rule;
 +                      check_blank_at_eof(&mf1, &mf2, &ecbdata);
 +                      blank_at_eof = ecbdata.blank_at_eof_in_preimage;
 +
 +                      if (blank_at_eof) {
 +                              static char *err;
 +                              if (!err)
 +                                      err = whitespace_error_string(WS_BLANK_AT_EOF);
 +                              fprintf(o->file, "%s:%d: %s.\n",
 +                                      data.filename, blank_at_eof, err);
 +                              data.status = 1; /* report errors */
 +                      }
                }
        }
   free_and_return:
@@@ -2551,6 -2378,20 +2551,20 @@@ int diff_setup_done(struct diff_option
        if (count > 1)
                die("--name-only, --name-status, --check and -s are mutually exclusive");
  
+       /*
+        * Most of the time we can say "there are changes"
+        * only by checking if there are changed paths, but
+        * --ignore-whitespace* options force us to look
+        * inside contents.
+        */
+       if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) ||
+           DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE) ||
+           DIFF_XDL_TST(options, IGNORE_WHITESPACE_AT_EOL))
+               DIFF_OPT_SET(options, DIFF_FROM_CONTENTS);
+       else
+               DIFF_OPT_CLR(options, DIFF_FROM_CONTENTS);
        if (DIFF_OPT_TST(options, FIND_COPIES_HARDER))
                options->detect_rename = DIFF_DETECT_COPY;
  
         * to have found.  It does not make sense not to return with
         * exit code in such a case either.
         */
-       if (DIFF_OPT_TST(options, QUIET)) {
+       if (DIFF_OPT_TST(options, QUICK)) {
                options->output_format = DIFF_FORMAT_NO_OUTPUT;
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        }
@@@ -2802,7 -2643,7 +2816,7 @@@ int diff_opt_parse(struct diff_options 
        else if (!strcmp(arg, "--exit-code"))
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        else if (!strcmp(arg, "--quiet"))
-               DIFF_OPT_SET(options, QUIET);
+               DIFF_OPT_SET(options, QUICK);
        else if (!strcmp(arg, "--ext-diff"))
                DIFF_OPT_SET(options, ALLOW_EXTERNAL);
        else if (!strcmp(arg, "--no-ext-diff"))
                DIFF_OPT_CLR(options, ALLOW_TEXTCONV);
        else if (!strcmp(arg, "--ignore-submodules"))
                DIFF_OPT_SET(options, IGNORE_SUBMODULES);
 +      else if (!strcmp(arg, "--submodule"))
 +              DIFF_OPT_SET(options, SUBMODULE_LOG);
 +      else if (!prefixcmp(arg, "--submodule=")) {
 +              if (!strcmp(arg + 12, "log"))
 +                      DIFF_OPT_SET(options, SUBMODULE_LOG);
 +      }
  
        /* misc options */
        else if (!strcmp(arg, "-z"))
@@@ -2870,7 -2705,7 +2884,7 @@@ static int parse_num(const char **cp_p
        num = 0;
        scale = 1;
        dot = 0;
 -      for(;;) {
 +      for (;;) {
                ch = *cp;
                if ( !dot && ch == '.' ) {
                        scale = 1;
@@@ -3509,6 -3344,18 +3523,18 @@@ free_queue
        q->nr = q->alloc = 0;
        if (options->close_file)
                fclose(options->file);
+       /*
+        * Report the content-level differences with HAS_CHANGES;
+        * diff_addremove/diff_change does not set the bit when
+        * DIFF_FROM_CONTENTS is in effect (e.g. with -w).
+        */
+       if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) {
+               if (options->found_changes)
+                       DIFF_OPT_SET(options, HAS_CHANGES);
+               else
+                       DIFF_OPT_CLR(options, HAS_CHANGES);
+       }
  }
  
  static void diffcore_apply_filter(const char *filter)
@@@ -3645,7 -3492,7 +3671,7 @@@ void diffcore_std(struct diff_options *
        diff_resolve_rename_copy();
        diffcore_apply_filter(options->filter);
  
-       if (diff_queued_diff.nr)
+       if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
                DIFF_OPT_SET(options, HAS_CHANGES);
        else
                DIFF_OPT_CLR(options, HAS_CHANGES);
@@@ -3705,7 -3552,8 +3731,8 @@@ void diff_addremove(struct diff_option
                fill_filespec(two, sha1, mode);
  
        diff_queue(&diff_queued_diff, one, two);
-       DIFF_OPT_SET(options, HAS_CHANGES);
+       if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
+               DIFF_OPT_SET(options, HAS_CHANGES);
  }
  
  void diff_change(struct diff_options *options,
        fill_filespec(two, new_sha1, new_mode);
  
        diff_queue(&diff_queued_diff, one, two);
-       DIFF_OPT_SET(options, HAS_CHANGES);
+       if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
+               DIFF_OPT_SET(options, HAS_CHANGES);
  }
  
  void diff_unmerge(struct diff_options *options,
diff --combined diff.h
--- 1/diff.h
--- 2/diff.h
+++ b/diff.h
@@@ -55,7 -55,7 +55,7 @@@ typedef void (*diff_format_fn_t)(struc
  #define DIFF_OPT_COLOR_DIFF          (1 <<  8)
  #define DIFF_OPT_COLOR_DIFF_WORDS    (1 <<  9)
  #define DIFF_OPT_HAS_CHANGES         (1 << 10)
- #define DIFF_OPT_QUIET               (1 << 11)
+ #define DIFF_OPT_QUICK               (1 << 11)
  #define DIFF_OPT_NO_INDEX            (1 << 12)
  #define DIFF_OPT_ALLOW_EXTERNAL      (1 << 13)
  #define DIFF_OPT_EXIT_WITH_STATUS    (1 << 14)
@@@ -66,9 -66,7 +66,9 @@@
  #define DIFF_OPT_DIRSTAT_CUMULATIVE  (1 << 19)
  #define DIFF_OPT_DIRSTAT_BY_FILE     (1 << 20)
  #define DIFF_OPT_ALLOW_TEXTCONV      (1 << 21)
+ #define DIFF_OPT_DIFF_FROM_CONTENTS  (1 << 22)
 +#define DIFF_OPT_SUBMODULE_LOG       (1 << 23)
 +
  #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)
@@@ -130,7 -128,6 +130,7 @@@ enum color_diff 
        DIFF_FILE_NEW = 5,
        DIFF_COMMIT = 6,
        DIFF_WHITESPACE = 7,
 +      DIFF_FUNCINFO = 8,
  };
  const char *diff_get_color(int diff_use_color, enum color_diff ix);
  #define diff_get_color_opt(o, ix) \
diff --combined revision.c
@@@ -791,7 -791,7 +791,7 @@@ void init_revisions(struct rev_info *re
        revs->ignore_merges = 1;
        revs->simplify_history = 1;
        DIFF_OPT_SET(&revs->pruning, RECURSIVE);
-       DIFF_OPT_SET(&revs->pruning, QUIET);
+       DIFF_OPT_SET(&revs->pruning, QUICK);
        revs->pruning.add_remove = file_add_remove;
        revs->pruning.change = file_change;
        revs->lifo = 1;
@@@ -953,59 -953,21 +953,59 @@@ int handle_revision_arg(const char *arg
        return 0;
  }
  
 -void read_revisions_from_stdin(struct rev_info *revs)
 +static void read_pathspec_from_stdin(struct rev_info *revs, struct strbuf *sb, const char ***prune_data)
  {
 -      char line[1000];
 +      const char **prune = *prune_data;
 +      int prune_nr;
 +      int prune_alloc;
  
 -      while (fgets(line, sizeof(line), stdin) != NULL) {
 -              int len = strlen(line);
 -              if (len && line[len - 1] == '\n')
 -                      line[--len] = '\0';
 +      /* count existing ones */
 +      if (!prune)
 +              prune_nr = 0;
 +      else
 +              for (prune_nr = 0; prune[prune_nr]; prune_nr++)
 +                      ;
 +      prune_alloc = prune_nr; /* not really, but we do not know */
 +
 +      while (strbuf_getwholeline(sb, stdin, '\n') != EOF) {
 +              int len = sb->len;
 +              if (len && sb->buf[len - 1] == '\n')
 +                      sb->buf[--len] = '\0';
 +              ALLOC_GROW(prune, prune_nr+1, prune_alloc);
 +              prune[prune_nr++] = xstrdup(sb->buf);
 +      }
 +      if (prune) {
 +              ALLOC_GROW(prune, prune_nr+1, prune_alloc);
 +              prune[prune_nr] = NULL;
 +      }
 +      *prune_data = prune;
 +}
 +
 +static void read_revisions_from_stdin(struct rev_info *revs, const char ***prune)
 +{
 +      struct strbuf sb;
 +      int seen_dashdash = 0;
 +
 +      strbuf_init(&sb, 1000);
 +      while (strbuf_getwholeline(&sb, stdin, '\n') != EOF) {
 +              int len = sb.len;
 +              if (len && sb.buf[len - 1] == '\n')
 +                      sb.buf[--len] = '\0';
                if (!len)
                        break;
 -              if (line[0] == '-')
 +              if (sb.buf[0] == '-') {
 +                      if (len == 2 && sb.buf[1] == '-') {
 +                              seen_dashdash = 1;
 +                              break;
 +                      }
                        die("options not supported in --stdin mode");
 -              if (handle_revision_arg(line, revs, 0, 1))
 -                      die("bad revision '%s'", line);
 +              }
 +              if (handle_revision_arg(sb.buf, revs, 0, 1))
 +                      die("bad revision '%s'", sb.buf);
        }
 +      if (seen_dashdash)
 +              read_pathspec_from_stdin(revs, &sb, prune);
 +      strbuf_release(&sb);
  }
  
  static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what)
@@@ -1032,8 -994,7 +1032,8 @@@ static int handle_revision_opt(struct r
        if (!strcmp(arg, "--all") || !strcmp(arg, "--branches") ||
            !strcmp(arg, "--tags") || !strcmp(arg, "--remotes") ||
            !strcmp(arg, "--reflog") || !strcmp(arg, "--not") ||
 -          !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk"))
 +          !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk") ||
 +          !strcmp(arg, "--bisect"))
        {
                unkv[(*unkc)++] = arg;
                return 1;
                revs->simplify_by_decoration = 1;
                revs->limited = 1;
                revs->prune = 1;
 -              load_ref_decorations();
 +              load_ref_decorations(DECORATE_SHORT_REFS);
        } else if (!strcmp(arg, "--date-order")) {
                revs->lifo = 0;
                revs->topo_order = 1;
                revs->simplify_history = 0;
        } else if (!strcmp(arg, "--relative-date")) {
                revs->date_mode = DATE_RELATIVE;
 +              revs->date_mode_explicit = 1;
        } else if (!strncmp(arg, "--date=", 7)) {
                revs->date_mode = parse_date_format(arg + 7);
 +              revs->date_mode_explicit = 1;
        } else if (!strcmp(arg, "--log-size")) {
                revs->show_log_size = 1;
        }
@@@ -1257,44 -1216,6 +1257,44 @@@ void parse_revision_opt(struct rev_inf
        ctx->argc -= n;
  }
  
 +static int for_each_bad_bisect_ref(each_ref_fn fn, void *cb_data)
 +{
 +      return for_each_ref_in("refs/bisect/bad", fn, cb_data);
 +}
 +
 +static int for_each_good_bisect_ref(each_ref_fn fn, void *cb_data)
 +{
 +      return for_each_ref_in("refs/bisect/good", fn, cb_data);
 +}
 +
 +static void append_prune_data(const char ***prune_data, const char **av)
 +{
 +      const char **prune = *prune_data;
 +      int prune_nr;
 +      int prune_alloc;
 +
 +      if (!prune) {
 +              *prune_data = av;
 +              return;
 +      }
 +
 +      /* count existing ones */
 +      for (prune_nr = 0; prune[prune_nr]; prune_nr++)
 +              ;
 +      prune_alloc = prune_nr; /* not really, but we do not know */
 +
 +      while (*av) {
 +              ALLOC_GROW(prune, prune_nr+1, prune_alloc);
 +              prune[prune_nr++] = *av;
 +              av++;
 +      }
 +      if (prune) {
 +              ALLOC_GROW(prune, prune_nr+1, prune_alloc);
 +              prune[prune_nr] = NULL;
 +      }
 +      *prune_data = prune;
 +}
 +
  /*
   * Parse revision information, filling in the "rev_info" structure,
   * and removing the used arguments from the argument list.
   */
  int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def)
  {
 -      int i, flags, left, seen_dashdash;
 +      int i, flags, left, seen_dashdash, read_from_stdin;
 +      const char **prune_data = NULL;
  
        /* First, search for "--" */
        seen_dashdash = 0;
                argv[i] = NULL;
                argc = i;
                if (argv[i + 1])
 -                      revs->prune_data = get_pathspec(revs->prefix, argv + i + 1);
 +                      prune_data = argv + i + 1;
                seen_dashdash = 1;
                break;
        }
  
        /* Second, deal with arguments and options */
        flags = 0;
 +      read_from_stdin = 0;
        for (left = i = 1; i < argc; i++) {
                const char *arg = argv[i];
                if (*arg == '-') {
                                handle_refs(revs, flags, for_each_branch_ref);
                                continue;
                        }
 +                      if (!strcmp(arg, "--bisect")) {
 +                              handle_refs(revs, flags, for_each_bad_bisect_ref);
 +                              handle_refs(revs, flags ^ UNINTERESTING, for_each_good_bisect_ref);
 +                              revs->bisect = 1;
 +                              continue;
 +                      }
                        if (!strcmp(arg, "--tags")) {
                                handle_refs(revs, flags, for_each_tag_ref);
                                continue;
                                revs->no_walk = 0;
                                continue;
                        }
 +                      if (!strcmp(arg, "--stdin")) {
 +                              if (revs->disable_stdin) {
 +                                      argv[left++] = arg;
 +                                      continue;
 +                              }
 +                              if (read_from_stdin++)
 +                                      die("--stdin given twice?");
 +                              read_revisions_from_stdin(revs, &prune_data);
 +                              continue;
 +                      }
  
                        opts = handle_revision_opt(revs, argc - i, argv + i, &left, argv);
                        if (opts > 0) {
                        for (j = i; j < argc; j++)
                                verify_filename(revs->prefix, argv[j]);
  
 -                      revs->prune_data = get_pathspec(revs->prefix,
 -                                                      argv + i);
 +                      append_prune_data(&prune_data, argv + i);
                        break;
                }
        }
  
 +      if (prune_data)
 +              revs->prune_data = get_pathspec(revs->prefix, prune_data);
 +
        if (revs->def == NULL)
                revs->def = def;
        if (revs->show_merge)
@@@ -1763,7 -1664,7 +1763,7 @@@ static inline int want_ancestry(struct 
        return (revs->rewrite_parents || revs->children.name);
  }
  
 -enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
 +enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit)
  {
        if (commit->object.flags & SHOWN)
                return commit_ignore;
                        if (!commit->parents || !commit->parents->next)
                                return commit_ignore;
                }
 -              if (want_ancestry(revs) && rewrite_parents(revs, commit) < 0)
 -                      return commit_error;
        }
        return commit_show;
  }
  
 +enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
 +{
 +      enum commit_action action = get_commit_action(revs, commit);
 +
 +      if (action == commit_show &&
 +          !revs->show_all &&
 +          revs->prune && revs->dense && want_ancestry(revs)) {
 +              if (rewrite_parents(revs, commit) < 0)
 +                      return commit_error;
 +      }
 +      return action;
 +}
 +
  static struct commit *get_revision_1(struct rev_info *revs)
  {
        if (!revs->commits)