Merge branch 'jc/ignore-cr-at-eol'
authorJunio C Hamano <gitster@pobox.com>
Mon, 27 Nov 2017 02:06:31 +0000 (11:06 +0900)
committerJunio C Hamano <gitster@pobox.com>
Mon, 27 Nov 2017 02:06:31 +0000 (11:06 +0900)
The "diff" family of commands learned to ignore differences in
carriage return at the end of line.

* jc/ignore-cr-at-eol:
diff: --ignore-cr-at-eol
xdiff: reassign xpparm_t.flags bits

1  2 
Documentation/diff-options.txt
Documentation/merge-strategies.txt
contrib/completion/git-completion.bash
diff.c
merge-recursive.c
t/t4015-diff-whitespace.sh
xdiff/xdiff.h
xdiff/xutils.c
index dd0dba5b1d1951e762f0a43f1b2476bfa1029ad9,aa2c0ff74d6a9702ac80e5f5211875ef1a2ca867..3c93c216831e722ec9b8b8a231f455df328e7fe2
@@@ -63,12 -63,7 +63,12 @@@ ifndef::git-format-patch[
        Synonym for `-p --raw`.
  endif::git-format-patch[]
  
 -include::diff-heuristic-options.txt[]
 +--indent-heuristic::
 +      Enable the heuristic that shift diff hunk boundaries to make patches
 +      easier to read. This is the default.
 +
 +--no-indent-heuristic::
 +      Disable the indent heuristic.
  
  --minimal::
        Spend extra time to make sure the smallest possible
@@@ -236,40 -231,6 +236,40 @@@ ifdef::git-diff[
  endif::git-diff[]
        It is the same as `--color=never`.
  
 +--color-moved[=<mode>]::
 +      Moved lines of code are colored differently.
 +ifdef::git-diff[]
 +      It can be changed by the `diff.colorMoved` configuration setting.
 +endif::git-diff[]
 +      The <mode> defaults to 'no' if the option is not given
 +      and to 'zebra' if the option with no mode is given.
 +      The mode must be one of:
 ++
 +--
 +no::
 +      Moved lines are not highlighted.
 +default::
 +      Is a synonym for `zebra`. This may change to a more sensible mode
 +      in the future.
 +plain::
 +      Any line that is added in one location and was removed
 +      in another location will be colored with 'color.diff.newMoved'.
 +      Similarly 'color.diff.oldMoved' will be used for removed lines
 +      that are added somewhere else in the diff. This mode picks up any
 +      moved line, but it is not very useful in a review to determine
 +      if a block of code was moved without permutation.
 +zebra::
 +      Blocks of moved text of at least 20 alphanumeric characters
 +      are detected greedily. The detected blocks are
 +      painted using either the 'color.diff.{old,new}Moved' color or
 +      'color.diff.{old,new}MovedAlternative'. The change between
 +      the two colors indicates that a new block was detected.
 +dimmed_zebra::
 +      Similar to 'zebra', but additional dimming of uninteresting parts
 +      of moved code is performed. The bordering lines of two adjacent
 +      blocks are considered interesting, the rest is uninteresting.
 +--
 +
  --word-diff[=<mode>]::
        Show a word diff, using the <mode> to delimit changed words.
        By default, words are delimited by whitespace; see
@@@ -339,14 -300,15 +339,14 @@@ ifndef::git-format-patch[
        with --exit-code.
  
  --ws-error-highlight=<kind>::
 -      Highlight whitespace errors on lines specified by <kind>
 -      in the color specified by `color.diff.whitespace`.  <kind>
 -      is a comma separated list of `old`, `new`, `context`.  When
 -      this option is not given, only whitespace errors in `new`
 -      lines are highlighted.  E.g. `--ws-error-highlight=new,old`
 -      highlights whitespace errors on both deleted and added lines.
 -      `all` can be used as a short-hand for `old,new,context`.
 -      The `diff.wsErrorHighlight` configuration variable can be
 -      used to specify the default behaviour.
 +      Highlight whitespace errors in the `context`, `old` or `new`
 +      lines of the diff.  Multiple values are separated by comma,
 +      `none` resets previous values, `default` reset the list to
 +      `new` and `all` is a shorthand for `old,new,context`.  When
 +      this option is not given, and the configuration variable
 +      `diff.wsErrorHighlight` is not set, only whitespace errors in
 +      `new` lines are highlighted. The whitespace errors are colored
 +      whith `color.diff.whitespace`.
  
  endif::git-format-patch[]
  
@@@ -430,7 -392,7 +430,7 @@@ endif::git-log[
        the diff between the preimage and `/dev/null`. The resulting patch
        is not meant to be applied with `patch` or `git apply`; this is
        solely for people who want to just concentrate on reviewing the
 -      text after the change. In addition, the output obviously lack
 +      text after the change. In addition, the output obviously lacks
        enough information to apply such a patch in reverse, even manually,
        hence the name of the option.
  +
@@@ -557,6 -519,9 +557,9 @@@ endif::git-format-patch[
  --text::
        Treat all files as text.
  
+ --ignore-cr-at-eol::
+       Ignore carrige-return at the end of line when doing a comparison.
  --ignore-space-at-eol::
        Ignore changes in whitespace at EOL.
  
index a09d5974639fa772ba86c5547fe70716fd044f73,030744910ef39fd51dea611fec80d8787a11356e..fd5d748d1b508c9cef1063227b5c478952d3bc7e
@@@ -39,8 -39,7 +39,8 @@@ even look at what the other tree contai
  the other tree did, declaring 'our' history contains all that happened in it.
  
  theirs;;
 -      This is the opposite of 'ours'.
 +      This is the opposite of 'ours'; note that, unlike 'ours', there is
 +      no 'theirs' merge stragegy to confuse this merge option with.
  
  patience;;
        With this option, 'merge-recursive' spends a little extra time
@@@ -58,11 -57,12 +58,12 @@@ diff-algorithm=[patience|minimal|histog
  ignore-space-change;;
  ignore-all-space;;
  ignore-space-at-eol;;
+ ignore-cr-at-eol;;
        Treats lines with the indicated type of whitespace change as
        unchanged for the sake of a three-way merge.  Whitespace
        changes mixed with other changes to a line are not ignored.
-       See also linkgit:git-diff[1] `-b`, `-w`, and
-       `--ignore-space-at-eol`.
+       See also linkgit:git-diff[1] `-b`, `-w`,
+       `--ignore-space-at-eol`, and `--ignore-cr-at-eol`.
  +
  * If 'their' version only introduces whitespace changes to a line,
    'our' version is used;
index f07f16b28fe80de32ebc0431b5d1d8f5a64e19d8,8ad084252f1b8fac5ddd28028f23cd6766e9cca7..38d5e4a909f0386be18eeb0d3eec768d95dcc74f
@@@ -111,7 -111,8 +111,7 @@@ __git (
  #   GNU General Public License for more details.
  #
  #   You should have received a copy of the GNU General Public License
 -#   along with this program; if not, write to the Free Software Foundation,
 -#   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 +#   along with this program; if not, see <http://www.gnu.org/licenses/>.
  #
  #   The latest version of this software can be obtained here:
  #
@@@ -1249,8 -1250,7 +1249,8 @@@ _git_checkout (
        --*)
                __gitcomp "
                        --quiet --ours --theirs --track --no-track --merge
 -                      --conflict= --orphan --patch
 +                      --conflict= --orphan --patch --detach --ignore-skip-worktree-bits
 +                      --recurse-submodules --no-recurse-submodules
                        "
                ;;
        *)
@@@ -1319,7 -1319,6 +1319,7 @@@ _git_clone (
                        --template=
                        --depth
                        --single-branch
 +                      --no-tags
                        --branch
                        --recurse-submodules
                        --no-single-branch
@@@ -1385,7 -1384,7 +1385,7 @@@ _git_describe (
                __gitcomp "
                        --all --tags --contains --abbrev= --candidates=
                        --exact-match --debug --long --match --always --first-parent
 -                      --exclude
 +                      --exclude --dirty --broken
                        "
                return
        esac
@@@ -1400,7 -1399,7 +1400,7 @@@ __git_diff_common_options="--stat --num
                        --patch-with-stat --name-only --name-status --color
                        --no-color --color-words --no-renames --check
                        --full-index --binary --abbrev --diff-filter=
-                       --find-copies-harder
+                       --find-copies-harder --ignore-cr-at-eol
                        --text --ignore-space-at-eol --ignore-space-change
                        --ignore-all-space --ignore-blank-lines --exit-code
                        --quiet --ext-diff --no-ext-diff
@@@ -2336,24 -2335,14 +2336,24 @@@ _git_config (
        esac
        __gitcomp "
                add.ignoreErrors
 +              advice.amWorkDir
                advice.commitBeforeMerge
                advice.detachedHead
                advice.implicitIdentity
 -              advice.pushNonFastForward
 +              advice.pushAlreadyExists
 +              advice.pushFetchFirst
 +              advice.pushNeedsForce
 +              advice.pushNonFFCurrent
 +              advice.pushNonFFMatching
 +              advice.pushUpdateRejected
                advice.resolveConflict
 +              advice.rmHints
                advice.statusHints
 +              advice.statusUoption
 +              advice.ignoredHook
                alias.
                am.keepcr
 +              am.threeWay
                apply.ignorewhitespace
                apply.whitespace
                branch.autosetupmerge
                color.status.added
                color.status.changed
                color.status.header
 +              color.status.localBranch
                color.status.nobranch
 +              color.status.remoteBranch
                color.status.unmerged
                color.status.untracked
                color.status.updated
                core.autocrlf
                core.bare
                core.bigFileThreshold
 +              core.checkStat
 +              core.commentChar
                core.compression
                core.createObject
                core.deltaBaseCacheLimit
                core.fileMode
                core.fsyncobjectfiles
                core.gitProxy
 +              core.hideDotFiles
 +              core.hooksPath
                core.ignoreStat
                core.ignorecase
                core.logAllRefUpdates
                core.notesRef
                core.packedGitLimit
                core.packedGitWindowSize
 +              core.packedRefsTimeout
                core.pager
 +              core.precomposeUnicode
                core.preferSymlinkRefs
                core.preloadindex
 +              core.protectHFS
 +              core.protectNTFS
                core.quotepath
                core.repositoryFormatVersion
                core.safecrlf
                core.sharedRepository
                core.sparseCheckout
 +              core.splitIndex
 +              core.sshCommand
                core.symlinks
                core.trustctime
                core.untrackedCache
                core.warnAmbiguousRefs
                core.whitespace
                core.worktree
 +              credential.helper
 +              credential.useHttpPath
 +              credential.username
 +              credentialCache.ignoreSIGHUP
                diff.autorefreshindex
                diff.external
                diff.ignoreSubmodules
                format.thread
                format.to
                gc.
 +              gc.aggressiveDepth
                gc.aggressiveWindow
                gc.auto
 +              gc.autoDetach
                gc.autopacklimit
 +              gc.logExpiry
                gc.packrefs
                gc.pruneexpire
                gc.reflogexpire
                gc.reflogexpireunreachable
                gc.rerereresolved
                gc.rerereunresolved
 +              gc.worktreePruneExpire
                gitcvs.allbinary
                gitcvs.commitmsgannotation
                gitcvs.dbTableNamePrefix
                sendemail.thread
                sendemail.to
                sendemail.validate
 +              sendemail.smtpbatchsize
 +              sendemail.smtprelogindelay
                showbranch.default
                status.relativePaths
                status.showUntrackedFiles
diff --combined diff.c
index 0763e89263efac5fe75a57f0c068049fae610851,dd14e4190c48a8515e4b2089d36df1b444075ddb..2ebe2227b467e53bbd759c21e463646701071999
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -2,7 -2,6 +2,7 @@@
   * Copyright (C) 2005 Junio C Hamano
   */
  #include "cache.h"
 +#include "config.h"
  #include "tempfile.h"
  #include "quote.h"
  #include "diff.h"
  #include "userdiff.h"
  #include "submodule-config.h"
  #include "submodule.h"
 +#include "hashmap.h"
  #include "ll-merge.h"
  #include "string-list.h"
  #include "argv-array.h"
  #include "graph.h"
 +#include "packfile.h"
  
  #ifdef NO_FAST_WORKING_DIRECTORY
  #define FAST_WORKING_DIRECTORY 0
  #endif
  
  static int diff_detect_rename_default;
 -static int diff_indent_heuristic; /* experimental */
 +static int diff_indent_heuristic = 1;
  static int diff_rename_limit_default = 400;
  static int diff_suppress_blank_empty;
  static int diff_use_color_default = -1;
 +static int diff_color_moved_default;
  static int diff_context_default = 3;
  static int diff_interhunk_context_default;
  static const char *diff_word_regex_cfg;
@@@ -59,14 -55,6 +59,14 @@@ static char diff_colors[][COLOR_MAXLEN
        GIT_COLOR_YELLOW,       /* COMMIT */
        GIT_COLOR_BG_RED,       /* WHITESPACE */
        GIT_COLOR_NORMAL,       /* FUNCINFO */
 +      GIT_COLOR_BOLD_MAGENTA, /* OLD_MOVED */
 +      GIT_COLOR_BOLD_BLUE,    /* OLD_MOVED ALTERNATIVE */
 +      GIT_COLOR_FAINT,        /* OLD_MOVED_DIM */
 +      GIT_COLOR_FAINT_ITALIC, /* OLD_MOVED_ALTERNATIVE_DIM */
 +      GIT_COLOR_BOLD_CYAN,    /* NEW_MOVED */
 +      GIT_COLOR_BOLD_YELLOW,  /* NEW_MOVED ALTERNATIVE */
 +      GIT_COLOR_FAINT,        /* NEW_MOVED_DIM */
 +      GIT_COLOR_FAINT_ITALIC, /* NEW_MOVED_ALTERNATIVE_DIM */
  };
  
  static NORETURN void die_want_option(const char *option_name)
@@@ -92,22 -80,6 +92,22 @@@ static int parse_diff_color_slot(const 
                return DIFF_WHITESPACE;
        if (!strcasecmp(var, "func"))
                return DIFF_FUNCINFO;
 +      if (!strcasecmp(var, "oldmoved"))
 +              return DIFF_FILE_OLD_MOVED;
 +      if (!strcasecmp(var, "oldmovedalternative"))
 +              return DIFF_FILE_OLD_MOVED_ALT;
 +      if (!strcasecmp(var, "oldmoveddimmed"))
 +              return DIFF_FILE_OLD_MOVED_DIM;
 +      if (!strcasecmp(var, "oldmovedalternativedimmed"))
 +              return DIFF_FILE_OLD_MOVED_ALT_DIM;
 +      if (!strcasecmp(var, "newmoved"))
 +              return DIFF_FILE_NEW_MOVED;
 +      if (!strcasecmp(var, "newmovedalternative"))
 +              return DIFF_FILE_NEW_MOVED_ALT;
 +      if (!strcasecmp(var, "newmoveddimmed"))
 +              return DIFF_FILE_NEW_MOVED_DIM;
 +      if (!strcasecmp(var, "newmovedalternativedimmed"))
 +              return DIFF_FILE_NEW_MOVED_ALT_DIM;
        return -1;
  }
  
@@@ -124,18 -96,18 +124,18 @@@ static int parse_dirstat_params(struct 
        for (i = 0; i < params.nr; i++) {
                const char *p = params.items[i].string;
                if (!strcmp(p, "changes")) {
 -                      DIFF_OPT_CLR(options, DIRSTAT_BY_LINE);
 -                      DIFF_OPT_CLR(options, DIRSTAT_BY_FILE);
 +                      options->flags.dirstat_by_line = 0;
 +                      options->flags.dirstat_by_file = 0;
                } else if (!strcmp(p, "lines")) {
 -                      DIFF_OPT_SET(options, DIRSTAT_BY_LINE);
 -                      DIFF_OPT_CLR(options, DIRSTAT_BY_FILE);
 +                      options->flags.dirstat_by_line = 1;
 +                      options->flags.dirstat_by_file = 0;
                } else if (!strcmp(p, "files")) {
 -                      DIFF_OPT_CLR(options, DIRSTAT_BY_LINE);
 -                      DIFF_OPT_SET(options, DIRSTAT_BY_FILE);
 +                      options->flags.dirstat_by_line = 0;
 +                      options->flags.dirstat_by_file = 1;
                } else if (!strcmp(p, "noncumulative")) {
 -                      DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE);
 +                      options->flags.dirstat_cumulative = 0;
                } else if (!strcmp(p, "cumulative")) {
 -                      DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE);
 +                      options->flags.dirstat_cumulative = 1;
                } else if (isdigit(*p)) {
                        char *end;
                        int permille = strtoul(p, &end, 10) * 10;
@@@ -256,44 -228,12 +256,44 @@@ int git_diff_heuristic_config(const cha
        return 0;
  }
  
 +static int parse_color_moved(const char *arg)
 +{
 +      switch (git_parse_maybe_bool(arg)) {
 +      case 0:
 +              return COLOR_MOVED_NO;
 +      case 1:
 +              return COLOR_MOVED_DEFAULT;
 +      default:
 +              break;
 +      }
 +
 +      if (!strcmp(arg, "no"))
 +              return COLOR_MOVED_NO;
 +      else if (!strcmp(arg, "plain"))
 +              return COLOR_MOVED_PLAIN;
 +      else if (!strcmp(arg, "zebra"))
 +              return COLOR_MOVED_ZEBRA;
 +      else if (!strcmp(arg, "default"))
 +              return COLOR_MOVED_DEFAULT;
 +      else if (!strcmp(arg, "dimmed_zebra"))
 +              return COLOR_MOVED_ZEBRA_DIM;
 +      else
 +              return error(_("color moved setting must be one of 'no', 'default', 'zebra', 'dimmed_zebra', 'plain'"));
 +}
 +
  int git_diff_ui_config(const char *var, const char *value, void *cb)
  {
        if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
                diff_use_color_default = git_config_colorbool(var, value);
                return 0;
        }
 +      if (!strcmp(var, "diff.colormoved")) {
 +              int cm = parse_color_moved(value);
 +              if (cm < 0)
 +                      return -1;
 +              diff_color_moved_default = cm;
 +              return 0;
 +      }
        if (!strcmp(var, "diff.context")) {
                diff_context_default = git_config_int(var, value);
                if (diff_context_default < 0)
                return 0;
        }
  
 -      if (git_diff_heuristic_config(var, value, cb) < 0)
 -              return -1;
 -
        if (!strcmp(var, "diff.wserrorhighlight")) {
                int val = parse_ws_error_highlight(value);
                if (val < 0)
@@@ -405,8 -348,8 +405,8 @@@ int git_diff_basic_config(const char *v
                return 0;
        }
  
 -      if (starts_with(var, "submodule."))
 -              return parse_submodule_config_option(var, value);
 +      if (git_diff_heuristic_config(var, value, cb) < 0)
 +              return -1;
  
        return git_default_config(var, value, cb);
  }
@@@ -462,9 -405,11 +462,9 @@@ static struct diff_tempfile 
         * If this diff_tempfile instance refers to a temporary file,
         * this tempfile object is used to manage its lifetime.
         */
 -      struct tempfile tempfile;
 +      struct tempfile *tempfile;
  } 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_postimage;
        int lno_in_preimage;
        int lno_in_postimage;
 -      sane_truncate_fn truncate;
        const char **label_path;
        struct diff_words_data *diff_words;
        struct diff_options *opt;
@@@ -613,668 -559,68 +613,668 @@@ static void emit_line(struct diff_optio
        emit_line_0(o, set, reset, line[0], line+1, len-1);
  }
  
 -static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len)
 +enum diff_symbol {
 +      DIFF_SYMBOL_BINARY_DIFF_HEADER,
 +      DIFF_SYMBOL_BINARY_DIFF_HEADER_DELTA,
 +      DIFF_SYMBOL_BINARY_DIFF_HEADER_LITERAL,
 +      DIFF_SYMBOL_BINARY_DIFF_BODY,
 +      DIFF_SYMBOL_BINARY_DIFF_FOOTER,
 +      DIFF_SYMBOL_STATS_SUMMARY_NO_FILES,
 +      DIFF_SYMBOL_STATS_SUMMARY_ABBREV,
 +      DIFF_SYMBOL_STATS_SUMMARY_INSERTS_DELETES,
 +      DIFF_SYMBOL_STATS_LINE,
 +      DIFF_SYMBOL_WORD_DIFF,
 +      DIFF_SYMBOL_STAT_SEP,
 +      DIFF_SYMBOL_SUMMARY,
 +      DIFF_SYMBOL_SUBMODULE_ADD,
 +      DIFF_SYMBOL_SUBMODULE_DEL,
 +      DIFF_SYMBOL_SUBMODULE_UNTRACKED,
 +      DIFF_SYMBOL_SUBMODULE_MODIFIED,
 +      DIFF_SYMBOL_SUBMODULE_HEADER,
 +      DIFF_SYMBOL_SUBMODULE_ERROR,
 +      DIFF_SYMBOL_SUBMODULE_PIPETHROUGH,
 +      DIFF_SYMBOL_REWRITE_DIFF,
 +      DIFF_SYMBOL_BINARY_FILES,
 +      DIFF_SYMBOL_HEADER,
 +      DIFF_SYMBOL_FILEPAIR_PLUS,
 +      DIFF_SYMBOL_FILEPAIR_MINUS,
 +      DIFF_SYMBOL_WORDS_PORCELAIN,
 +      DIFF_SYMBOL_WORDS,
 +      DIFF_SYMBOL_CONTEXT,
 +      DIFF_SYMBOL_CONTEXT_INCOMPLETE,
 +      DIFF_SYMBOL_PLUS,
 +      DIFF_SYMBOL_MINUS,
 +      DIFF_SYMBOL_NO_LF_EOF,
 +      DIFF_SYMBOL_CONTEXT_FRAGINFO,
 +      DIFF_SYMBOL_CONTEXT_MARKER,
 +      DIFF_SYMBOL_SEPARATOR
 +};
 +/*
 + * Flags for content lines:
 + * 0..12 are whitespace rules
 + * 13-15 are WSEH_NEW | WSEH_OLD | WSEH_CONTEXT
 + * 16 is marking if the line is blank at EOF
 + */
 +#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF    (1<<16)
 +#define DIFF_SYMBOL_MOVED_LINE                        (1<<17)
 +#define DIFF_SYMBOL_MOVED_LINE_ALT            (1<<18)
 +#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING  (1<<19)
 +#define DIFF_SYMBOL_CONTENT_WS_MASK (WSEH_NEW | WSEH_OLD | WSEH_CONTEXT | WS_RULE_MASK)
 +
 +/*
 + * This struct is used when we need to buffer the output of the diff output.
 + *
 + * NEEDSWORK: Instead of storing a copy of the line, add an offset pointer
 + * into the pre/post image file. This pointer could be a union with the
 + * line pointer. By storing an offset into the file instead of the literal line,
 + * we can decrease the memory footprint for the buffered output. At first we
 + * may want to only have indirection for the content lines, but we could also
 + * enhance the state for emitting prefabricated lines, e.g. the similarity
 + * score line or hunk/file headers would only need to store a number or path
 + * and then the output can be constructed later on depending on state.
 + */
 +struct emitted_diff_symbol {
 +      const char *line;
 +      int len;
 +      int flags;
 +      enum diff_symbol s;
 +};
 +#define EMITTED_DIFF_SYMBOL_INIT {NULL}
 +
 +struct emitted_diff_symbols {
 +      struct emitted_diff_symbol *buf;
 +      int nr, alloc;
 +};
 +#define EMITTED_DIFF_SYMBOLS_INIT {NULL, 0, 0}
 +
 +static void append_emitted_diff_symbol(struct diff_options *o,
 +                                     struct emitted_diff_symbol *e)
  {
 -      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);
 +      struct emitted_diff_symbol *f;
 +
 +      ALLOC_GROW(o->emitted_symbols->buf,
 +                 o->emitted_symbols->nr + 1,
 +                 o->emitted_symbols->alloc);
 +      f = &o->emitted_symbols->buf[o->emitted_symbols->nr++];
 +
 +      memcpy(f, e, sizeof(struct emitted_diff_symbol));
 +      f->line = e->line ? xmemdupz(e->line, e->len) : NULL;
  }
  
 -static void emit_line_checked(const char *reset,
 -                            struct emit_callback *ecbdata,
 -                            const char *line, int len,
 -                            enum color_diff color,
 -                            unsigned ws_error_highlight,
 -                            char sign)
 +struct moved_entry {
 +      struct hashmap_entry ent;
 +      const struct emitted_diff_symbol *es;
 +      struct moved_entry *next_line;
 +};
 +
 +static int moved_entry_cmp(const struct diff_options *diffopt,
 +                         const struct moved_entry *a,
 +                         const struct moved_entry *b,
 +                         const void *keydata)
 +{
 +      return !xdiff_compare_lines(a->es->line, a->es->len,
 +                                  b->es->line, b->es->len,
 +                                  diffopt->xdl_opts);
 +}
 +
 +static struct moved_entry *prepare_entry(struct diff_options *o,
 +                                       int line_no)
 +{
 +      struct moved_entry *ret = xmalloc(sizeof(*ret));
 +      struct emitted_diff_symbol *l = &o->emitted_symbols->buf[line_no];
 +
 +      ret->ent.hash = xdiff_hash_string(l->line, l->len, o->xdl_opts);
 +      ret->es = l;
 +      ret->next_line = NULL;
 +
 +      return ret;
 +}
 +
 +static void add_lines_to_move_detection(struct diff_options *o,
 +                                      struct hashmap *add_lines,
 +                                      struct hashmap *del_lines)
 +{
 +      struct moved_entry *prev_line = NULL;
 +
 +      int n;
 +      for (n = 0; n < o->emitted_symbols->nr; n++) {
 +              struct hashmap *hm;
 +              struct moved_entry *key;
 +
 +              switch (o->emitted_symbols->buf[n].s) {
 +              case DIFF_SYMBOL_PLUS:
 +                      hm = add_lines;
 +                      break;
 +              case DIFF_SYMBOL_MINUS:
 +                      hm = del_lines;
 +                      break;
 +              default:
 +                      prev_line = NULL;
 +                      continue;
 +              }
 +
 +              key = prepare_entry(o, n);
 +              if (prev_line && prev_line->es->s == o->emitted_symbols->buf[n].s)
 +                      prev_line->next_line = key;
 +
 +              hashmap_add(hm, key);
 +              prev_line = key;
 +      }
 +}
 +
 +static int shrink_potential_moved_blocks(struct moved_entry **pmb,
 +                                       int pmb_nr)
 +{
 +      int lp, rp;
 +
 +      /* Shrink the set of potential block to the remaining running */
 +      for (lp = 0, rp = pmb_nr - 1; lp <= rp;) {
 +              while (lp < pmb_nr && pmb[lp])
 +                      lp++;
 +              /* lp points at the first NULL now */
 +
 +              while (rp > -1 && !pmb[rp])
 +                      rp--;
 +              /* rp points at the last non-NULL */
 +
 +              if (lp < pmb_nr && rp > -1 && lp < rp) {
 +                      pmb[lp] = pmb[rp];
 +                      pmb[rp] = NULL;
 +                      rp--;
 +                      lp++;
 +              }
 +      }
 +
 +      /* Remember the number of running sets */
 +      return rp + 1;
 +}
 +
 +/*
 + * If o->color_moved is COLOR_MOVED_PLAIN, this function does nothing.
 + *
 + * Otherwise, if the last block has fewer alphanumeric characters than
 + * COLOR_MOVED_MIN_ALNUM_COUNT, unset DIFF_SYMBOL_MOVED_LINE on all lines in
 + * that block.
 + *
 + * The last block consists of the (n - block_length)'th line up to but not
 + * including the nth line.
 + *
 + * NEEDSWORK: This uses the same heuristic as blame_entry_score() in blame.c.
 + * Think of a way to unify them.
 + */
 +static void adjust_last_block(struct diff_options *o, int n, int block_length)
 +{
 +      int i, alnum_count = 0;
 +      if (o->color_moved == COLOR_MOVED_PLAIN)
 +              return;
 +      for (i = 1; i < block_length + 1; i++) {
 +              const char *c = o->emitted_symbols->buf[n - i].line;
 +              for (; *c; c++) {
 +                      if (!isalnum(*c))
 +                              continue;
 +                      alnum_count++;
 +                      if (alnum_count >= COLOR_MOVED_MIN_ALNUM_COUNT)
 +                              return;
 +              }
 +      }
 +      for (i = 1; i < block_length + 1; i++)
 +              o->emitted_symbols->buf[n - i].flags &= ~DIFF_SYMBOL_MOVED_LINE;
 +}
 +
 +/* Find blocks of moved code, delegate actual coloring decision to helper */
 +static void mark_color_as_moved(struct diff_options *o,
 +                              struct hashmap *add_lines,
 +                              struct hashmap *del_lines)
 +{
 +      struct moved_entry **pmb = NULL; /* potentially moved blocks */
 +      int pmb_nr = 0, pmb_alloc = 0;
 +      int n, flipped_block = 1, block_length = 0;
 +
 +
 +      for (n = 0; n < o->emitted_symbols->nr; n++) {
 +              struct hashmap *hm = NULL;
 +              struct moved_entry *key;
 +              struct moved_entry *match = NULL;
 +              struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n];
 +              int i;
 +
 +              switch (l->s) {
 +              case DIFF_SYMBOL_PLUS:
 +                      hm = del_lines;
 +                      key = prepare_entry(o, n);
 +                      match = hashmap_get(hm, key, o);
 +                      free(key);
 +                      break;
 +              case DIFF_SYMBOL_MINUS:
 +                      hm = add_lines;
 +                      key = prepare_entry(o, n);
 +                      match = hashmap_get(hm, key, o);
 +                      free(key);
 +                      break;
 +              default:
 +                      flipped_block = 1;
 +              }
 +
 +              if (!match) {
 +                      adjust_last_block(o, n, block_length);
 +                      pmb_nr = 0;
 +                      block_length = 0;
 +                      continue;
 +              }
 +
 +              l->flags |= DIFF_SYMBOL_MOVED_LINE;
 +
 +              if (o->color_moved == COLOR_MOVED_PLAIN)
 +                      continue;
 +
 +              /* Check any potential block runs, advance each or nullify */
 +              for (i = 0; i < pmb_nr; i++) {
 +                      struct moved_entry *p = pmb[i];
 +                      struct moved_entry *pnext = (p && p->next_line) ?
 +                                      p->next_line : NULL;
 +                      if (pnext && !hm->cmpfn(o, pnext, match, NULL)) {
 +                              pmb[i] = p->next_line;
 +                      } else {
 +                              pmb[i] = NULL;
 +                      }
 +              }
 +
 +              pmb_nr = shrink_potential_moved_blocks(pmb, pmb_nr);
 +
 +              if (pmb_nr == 0) {
 +                      /*
 +                       * The current line is the start of a new block.
 +                       * Setup the set of potential blocks.
 +                       */
 +                      for (; match; match = hashmap_get_next(hm, match)) {
 +                              ALLOC_GROW(pmb, pmb_nr + 1, pmb_alloc);
 +                              pmb[pmb_nr++] = match;
 +                      }
 +
 +                      flipped_block = (flipped_block + 1) % 2;
 +
 +                      adjust_last_block(o, n, block_length);
 +                      block_length = 0;
 +              }
 +
 +              block_length++;
 +
 +              if (flipped_block)
 +                      l->flags |= DIFF_SYMBOL_MOVED_LINE_ALT;
 +      }
 +      adjust_last_block(o, n, block_length);
 +
 +      free(pmb);
 +}
 +
 +#define DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK \
 +  (DIFF_SYMBOL_MOVED_LINE | DIFF_SYMBOL_MOVED_LINE_ALT)
 +static void dim_moved_lines(struct diff_options *o)
 +{
 +      int n;
 +      for (n = 0; n < o->emitted_symbols->nr; n++) {
 +              struct emitted_diff_symbol *prev = (n != 0) ?
 +                              &o->emitted_symbols->buf[n - 1] : NULL;
 +              struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n];
 +              struct emitted_diff_symbol *next =
 +                              (n < o->emitted_symbols->nr - 1) ?
 +                              &o->emitted_symbols->buf[n + 1] : NULL;
 +
 +              /* Not a plus or minus line? */
 +              if (l->s != DIFF_SYMBOL_PLUS && l->s != DIFF_SYMBOL_MINUS)
 +                      continue;
 +
 +              /* Not a moved line? */
 +              if (!(l->flags & DIFF_SYMBOL_MOVED_LINE))
 +                      continue;
 +
 +              /*
 +               * If prev or next are not a plus or minus line,
 +               * pretend they don't exist
 +               */
 +              if (prev && prev->s != DIFF_SYMBOL_PLUS &&
 +                          prev->s != DIFF_SYMBOL_MINUS)
 +                      prev = NULL;
 +              if (next && next->s != DIFF_SYMBOL_PLUS &&
 +                          next->s != DIFF_SYMBOL_MINUS)
 +                      next = NULL;
 +
 +              /* Inside a block? */
 +              if ((prev &&
 +                  (prev->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK) ==
 +                  (l->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK)) &&
 +                  (next &&
 +                  (next->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK) ==
 +                  (l->flags & DIFF_SYMBOL_MOVED_LINE_ZEBRA_MASK))) {
 +                      l->flags |= DIFF_SYMBOL_MOVED_LINE_UNINTERESTING;
 +                      continue;
 +              }
 +
 +              /* Check if we are at an interesting bound: */
 +              if (prev && (prev->flags & DIFF_SYMBOL_MOVED_LINE) &&
 +                  (prev->flags & DIFF_SYMBOL_MOVED_LINE_ALT) !=
 +                     (l->flags & DIFF_SYMBOL_MOVED_LINE_ALT))
 +                      continue;
 +              if (next && (next->flags & DIFF_SYMBOL_MOVED_LINE) &&
 +                  (next->flags & DIFF_SYMBOL_MOVED_LINE_ALT) !=
 +                     (l->flags & DIFF_SYMBOL_MOVED_LINE_ALT))
 +                      continue;
 +
 +              /*
 +               * The boundary to prev and next are not interesting,
 +               * so this line is not interesting as a whole
 +               */
 +              l->flags |= DIFF_SYMBOL_MOVED_LINE_UNINTERESTING;
 +      }
 +}
 +
 +static void emit_line_ws_markup(struct diff_options *o,
 +                              const char *set, const char *reset,
 +                              const char *line, int len, char sign,
 +                              unsigned ws_rule, int blank_at_eof)
  {
 -      const char *set = diff_get_color(ecbdata->color_diff, color);
        const char *ws = NULL;
  
 -      if (ecbdata->opt->ws_error_highlight & ws_error_highlight) {
 -              ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
 +      if (o->ws_error_highlight & ws_rule) {
 +              ws = diff_get_color_opt(o, DIFF_WHITESPACE);
                if (!*ws)
                        ws = NULL;
        }
  
        if (!ws)
 -              emit_line_0(ecbdata->opt, set, reset, sign, line, len);
 -      else if (sign == '+' && new_blank_line_at_eof(ecbdata, line, len))
 +              emit_line_0(o, set, reset, sign, line, len);
 +      else if (blank_at_eof)
                /* Blank line at EOF - paint '+' as well */
 -              emit_line_0(ecbdata->opt, ws, reset, sign, line, len);
 +              emit_line_0(o, ws, reset, sign, line, len);
        else {
                /* Emit just the prefix, then the rest. */
 -              emit_line_0(ecbdata->opt, set, reset, sign, "", 0);
 -              ws_check_emit(line, len, ecbdata->ws_rule,
 -                            ecbdata->opt->file, set, reset, ws);
 +              emit_line_0(o, set, reset, sign, "", 0);
 +              ws_check_emit(line, len, ws_rule,
 +                            o->file, set, reset, ws);
 +      }
 +}
 +
 +static void emit_diff_symbol_from_struct(struct diff_options *o,
 +                                       struct emitted_diff_symbol *eds)
 +{
 +      static const char *nneof = " No newline at end of file\n";
 +      const char *context, *reset, *set, *meta, *fraginfo;
 +      struct strbuf sb = STRBUF_INIT;
 +
 +      enum diff_symbol s = eds->s;
 +      const char *line = eds->line;
 +      int len = eds->len;
 +      unsigned flags = eds->flags;
 +
 +      switch (s) {
 +      case DIFF_SYMBOL_NO_LF_EOF:
 +              context = diff_get_color_opt(o, DIFF_CONTEXT);
 +              reset = diff_get_color_opt(o, DIFF_RESET);
 +              putc('\n', o->file);
 +              emit_line_0(o, context, reset, '\\',
 +                          nneof, strlen(nneof));
 +              break;
 +      case DIFF_SYMBOL_SUBMODULE_HEADER:
 +      case DIFF_SYMBOL_SUBMODULE_ERROR:
 +      case DIFF_SYMBOL_SUBMODULE_PIPETHROUGH:
 +      case DIFF_SYMBOL_STATS_SUMMARY_INSERTS_DELETES:
 +      case DIFF_SYMBOL_SUMMARY:
 +      case DIFF_SYMBOL_STATS_LINE:
 +      case DIFF_SYMBOL_BINARY_DIFF_BODY:
 +      case DIFF_SYMBOL_CONTEXT_FRAGINFO:
 +              emit_line(o, "", "", line, len);
 +              break;
 +      case DIFF_SYMBOL_CONTEXT_INCOMPLETE:
 +      case DIFF_SYMBOL_CONTEXT_MARKER:
 +              context = diff_get_color_opt(o, DIFF_CONTEXT);
 +              reset = diff_get_color_opt(o, DIFF_RESET);
 +              emit_line(o, context, reset, line, len);
 +              break;
 +      case DIFF_SYMBOL_SEPARATOR:
 +              fprintf(o->file, "%s%c",
 +                      diff_line_prefix(o),
 +                      o->line_termination);
 +              break;
 +      case DIFF_SYMBOL_CONTEXT:
 +              set = diff_get_color_opt(o, DIFF_CONTEXT);
 +              reset = diff_get_color_opt(o, DIFF_RESET);
 +              emit_line_ws_markup(o, set, reset, line, len, ' ',
 +                                  flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);
 +              break;
 +      case DIFF_SYMBOL_PLUS:
 +              switch (flags & (DIFF_SYMBOL_MOVED_LINE |
 +                               DIFF_SYMBOL_MOVED_LINE_ALT |
 +                               DIFF_SYMBOL_MOVED_LINE_UNINTERESTING)) {
 +              case DIFF_SYMBOL_MOVED_LINE |
 +                   DIFF_SYMBOL_MOVED_LINE_ALT |
 +                   DIFF_SYMBOL_MOVED_LINE_UNINTERESTING:
 +                      set = diff_get_color_opt(o, DIFF_FILE_NEW_MOVED_ALT_DIM);
 +                      break;
 +              case DIFF_SYMBOL_MOVED_LINE |
 +                   DIFF_SYMBOL_MOVED_LINE_ALT:
 +                      set = diff_get_color_opt(o, DIFF_FILE_NEW_MOVED_ALT);
 +                      break;
 +              case DIFF_SYMBOL_MOVED_LINE |
 +                   DIFF_SYMBOL_MOVED_LINE_UNINTERESTING:
 +                      set = diff_get_color_opt(o, DIFF_FILE_NEW_MOVED_DIM);
 +                      break;
 +              case DIFF_SYMBOL_MOVED_LINE:
 +                      set = diff_get_color_opt(o, DIFF_FILE_NEW_MOVED);
 +                      break;
 +              default:
 +                      set = diff_get_color_opt(o, DIFF_FILE_NEW);
 +              }
 +              reset = diff_get_color_opt(o, DIFF_RESET);
 +              emit_line_ws_markup(o, set, reset, line, len, '+',
 +                                  flags & DIFF_SYMBOL_CONTENT_WS_MASK,
 +                                  flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);
 +              break;
 +      case DIFF_SYMBOL_MINUS:
 +              switch (flags & (DIFF_SYMBOL_MOVED_LINE |
 +                               DIFF_SYMBOL_MOVED_LINE_ALT |
 +                               DIFF_SYMBOL_MOVED_LINE_UNINTERESTING)) {
 +              case DIFF_SYMBOL_MOVED_LINE |
 +                   DIFF_SYMBOL_MOVED_LINE_ALT |
 +                   DIFF_SYMBOL_MOVED_LINE_UNINTERESTING:
 +                      set = diff_get_color_opt(o, DIFF_FILE_OLD_MOVED_ALT_DIM);
 +                      break;
 +              case DIFF_SYMBOL_MOVED_LINE |
 +                   DIFF_SYMBOL_MOVED_LINE_ALT:
 +                      set = diff_get_color_opt(o, DIFF_FILE_OLD_MOVED_ALT);
 +                      break;
 +              case DIFF_SYMBOL_MOVED_LINE |
 +                   DIFF_SYMBOL_MOVED_LINE_UNINTERESTING:
 +                      set = diff_get_color_opt(o, DIFF_FILE_OLD_MOVED_DIM);
 +                      break;
 +              case DIFF_SYMBOL_MOVED_LINE:
 +                      set = diff_get_color_opt(o, DIFF_FILE_OLD_MOVED);
 +                      break;
 +              default:
 +                      set = diff_get_color_opt(o, DIFF_FILE_OLD);
 +              }
 +              reset = diff_get_color_opt(o, DIFF_RESET);
 +              emit_line_ws_markup(o, set, reset, line, len, '-',
 +                                  flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);
 +              break;
 +      case DIFF_SYMBOL_WORDS_PORCELAIN:
 +              context = diff_get_color_opt(o, DIFF_CONTEXT);
 +              reset = diff_get_color_opt(o, DIFF_RESET);
 +              emit_line(o, context, reset, line, len);
 +              fputs("~\n", o->file);
 +              break;
 +      case DIFF_SYMBOL_WORDS:
 +              context = diff_get_color_opt(o, DIFF_CONTEXT);
 +              reset = diff_get_color_opt(o, DIFF_RESET);
 +              /*
 +               * Skip the prefix character, if any.  With
 +               * diff_suppress_blank_empty, there may be
 +               * none.
 +               */
 +              if (line[0] != '\n') {
 +                      line++;
 +                      len--;
 +              }
 +              emit_line(o, context, reset, line, len);
 +              break;
 +      case DIFF_SYMBOL_FILEPAIR_PLUS:
 +              meta = diff_get_color_opt(o, DIFF_METAINFO);
 +              reset = diff_get_color_opt(o, DIFF_RESET);
 +              fprintf(o->file, "%s%s+++ %s%s%s\n", diff_line_prefix(o), meta,
 +                      line, reset,
 +                      strchr(line, ' ') ? "\t" : "");
 +              break;
 +      case DIFF_SYMBOL_FILEPAIR_MINUS:
 +              meta = diff_get_color_opt(o, DIFF_METAINFO);
 +              reset = diff_get_color_opt(o, DIFF_RESET);
 +              fprintf(o->file, "%s%s--- %s%s%s\n", diff_line_prefix(o), meta,
 +                      line, reset,
 +                      strchr(line, ' ') ? "\t" : "");
 +              break;
 +      case DIFF_SYMBOL_BINARY_FILES:
 +      case DIFF_SYMBOL_HEADER:
 +              fprintf(o->file, "%s", line);
 +              break;
 +      case DIFF_SYMBOL_BINARY_DIFF_HEADER:
 +              fprintf(o->file, "%sGIT binary patch\n", diff_line_prefix(o));
 +              break;
 +      case DIFF_SYMBOL_BINARY_DIFF_HEADER_DELTA:
 +              fprintf(o->file, "%sdelta %s\n", diff_line_prefix(o), line);
 +              break;
 +      case DIFF_SYMBOL_BINARY_DIFF_HEADER_LITERAL:
 +              fprintf(o->file, "%sliteral %s\n", diff_line_prefix(o), line);
 +              break;
 +      case DIFF_SYMBOL_BINARY_DIFF_FOOTER:
 +              fputs(diff_line_prefix(o), o->file);
 +              fputc('\n', o->file);
 +              break;
 +      case DIFF_SYMBOL_REWRITE_DIFF:
 +              fraginfo = diff_get_color(o->use_color, DIFF_FRAGINFO);
 +              reset = diff_get_color_opt(o, DIFF_RESET);
 +              emit_line(o, fraginfo, reset, line, len);
 +              break;
 +      case DIFF_SYMBOL_SUBMODULE_ADD:
 +              set = diff_get_color_opt(o, DIFF_FILE_NEW);
 +              reset = diff_get_color_opt(o, DIFF_RESET);
 +              emit_line(o, set, reset, line, len);
 +              break;
 +      case DIFF_SYMBOL_SUBMODULE_DEL:
 +              set = diff_get_color_opt(o, DIFF_FILE_OLD);
 +              reset = diff_get_color_opt(o, DIFF_RESET);
 +              emit_line(o, set, reset, line, len);
 +              break;
 +      case DIFF_SYMBOL_SUBMODULE_UNTRACKED:
 +              fprintf(o->file, "%sSubmodule %s contains untracked content\n",
 +                      diff_line_prefix(o), line);
 +              break;
 +      case DIFF_SYMBOL_SUBMODULE_MODIFIED:
 +              fprintf(o->file, "%sSubmodule %s contains modified content\n",
 +                      diff_line_prefix(o), line);
 +              break;
 +      case DIFF_SYMBOL_STATS_SUMMARY_NO_FILES:
 +              emit_line(o, "", "", " 0 files changed\n",
 +                        strlen(" 0 files changed\n"));
 +              break;
 +      case DIFF_SYMBOL_STATS_SUMMARY_ABBREV:
 +              emit_line(o, "", "", " ...\n", strlen(" ...\n"));
 +              break;
 +      case DIFF_SYMBOL_WORD_DIFF:
 +              fprintf(o->file, "%.*s", len, line);
 +              break;
 +      case DIFF_SYMBOL_STAT_SEP:
 +              fputs(o->stat_sep, o->file);
 +              break;
 +      default:
 +              die("BUG: unknown diff symbol");
        }
 +      strbuf_release(&sb);
 +}
 +
 +static void emit_diff_symbol(struct diff_options *o, enum diff_symbol s,
 +                           const char *line, int len, unsigned flags)
 +{
 +      struct emitted_diff_symbol e = {line, len, flags, s};
 +
 +      if (o->emitted_symbols)
 +              append_emitted_diff_symbol(o, &e);
 +      else
 +              emit_diff_symbol_from_struct(o, &e);
 +}
 +
 +void diff_emit_submodule_del(struct diff_options *o, const char *line)
 +{
 +      emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_DEL, line, strlen(line), 0);
 +}
 +
 +void diff_emit_submodule_add(struct diff_options *o, const char *line)
 +{
 +      emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_ADD, line, strlen(line), 0);
 +}
 +
 +void diff_emit_submodule_untracked(struct diff_options *o, const char *path)
 +{
 +      emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_UNTRACKED,
 +                       path, strlen(path), 0);
 +}
 +
 +void diff_emit_submodule_modified(struct diff_options *o, const char *path)
 +{
 +      emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_MODIFIED,
 +                       path, strlen(path), 0);
 +}
 +
 +void diff_emit_submodule_header(struct diff_options *o, const char *header)
 +{
 +      emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_HEADER,
 +                       header, strlen(header), 0);
 +}
 +
 +void diff_emit_submodule_error(struct diff_options *o, const char *err)
 +{
 +      emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_ERROR, err, strlen(err), 0);
 +}
 +
 +void diff_emit_submodule_pipethrough(struct diff_options *o,
 +                                   const char *line, int len)
 +{
 +      emit_diff_symbol(o, DIFF_SYMBOL_SUBMODULE_PIPETHROUGH, line, len, 0);
 +}
 +
 +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)
  {
 -      emit_line_checked(reset, ecbdata, line, len,
 -                        DIFF_FILE_NEW, WSEH_NEW, '+');
 +      unsigned flags = WSEH_NEW | ecbdata->ws_rule;
 +      if (new_blank_line_at_eof(ecbdata, line, len))
 +              flags |= DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF;
 +
 +      emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_PLUS, line, len, flags);
  }
  
  static void emit_del_line(const char *reset,
                          struct emit_callback *ecbdata,
                          const char *line, int len)
  {
 -      emit_line_checked(reset, ecbdata, line, len,
 -                        DIFF_FILE_OLD, WSEH_OLD, '-');
 +      unsigned flags = WSEH_OLD | ecbdata->ws_rule;
 +      emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_MINUS, line, len, flags);
  }
  
  static void emit_context_line(const char *reset,
                              struct emit_callback *ecbdata,
                              const char *line, int len)
  {
 -      emit_line_checked(reset, ecbdata, line, len,
 -                        DIFF_CONTEXT, WSEH_CONTEXT, ' ');
 +      unsigned flags = WSEH_CONTEXT | ecbdata->ws_rule;
 +      emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_CONTEXT, line, len, flags);
  }
  
  static void emit_hunk_header(struct emit_callback *ecbdata,
        if (len < 10 ||
            memcmp(line, atat, 2) ||
            !(ep = memmem(line + 2, len - 2, atat, 2))) {
 -              emit_line(ecbdata->opt, context, reset, line, len);
 +              emit_diff_symbol(ecbdata->opt,
 +                               DIFF_SYMBOL_CONTEXT_MARKER, line, len, 0);
                return;
        }
        ep += 2; /* skip over @@ */
        }
  
        strbuf_add(&msgbuf, line + len, org_len - len);
 -      emit_line(ecbdata->opt, "", "", msgbuf.buf, msgbuf.len);
 +      strbuf_complete_line(&msgbuf);
 +      emit_diff_symbol(ecbdata->opt,
 +                       DIFF_SYMBOL_CONTEXT_FRAGINFO, msgbuf.buf, msgbuf.len, 0);
        strbuf_release(&msgbuf);
  }
  
@@@ -1350,23 -693,23 +1350,23 @@@ static void remove_tempfile(void
  {
        int i;
        for (i = 0; i < ARRAY_SIZE(diff_temp); i++) {
 -              if (is_tempfile_active(&diff_temp[i].tempfile))
 +              if (is_tempfile_active(diff_temp[i].tempfile))
                        delete_tempfile(&diff_temp[i].tempfile);
                diff_temp[i].name = NULL;
        }
  }
  
 -static void print_line_count(FILE *file, int count)
 +static void add_line_count(struct strbuf *out, int count)
  {
        switch (count) {
        case 0:
 -              fprintf(file, "0,0");
 +              strbuf_addstr(out, "0,0");
                break;
        case 1:
 -              fprintf(file, "1");
 +              strbuf_addstr(out, "1");
                break;
        default:
 -              fprintf(file, "1,%d", count);
 +              strbuf_addf(out, "1,%d", count);
                break;
        }
  }
@@@ -1375,6 -718,7 +1375,6 @@@ static void emit_rewrite_lines(struct e
                               int prefix, const char *data, int size)
  {
        const char *endp = NULL;
 -      static const char *nneof = " No newline at end of file\n";
        const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET);
  
        while (0 < size) {
                size -= len;
                data += len;
        }
 -      if (!endp) {
 -              const char *context = diff_get_color(ecb->color_diff,
 -                                                   DIFF_CONTEXT);
 -              putc('\n', ecb->opt->file);
 -              emit_line_0(ecb->opt, context, reset, '\\',
 -                          nneof, strlen(nneof));
 -      }
 +      if (!endp)
 +              emit_diff_symbol(ecb->opt, DIFF_SYMBOL_NO_LF_EOF, NULL, 0, 0);
  }
  
  static void emit_rewrite_diff(const char *name_a,
                              struct diff_options *o)
  {
        int lc_a, lc_b;
 -      const char *name_a_tab, *name_b_tab;
 -      const char *metainfo = diff_get_color(o->use_color, DIFF_METAINFO);
 -      const char *fraginfo = diff_get_color(o->use_color, DIFF_FRAGINFO);
 -      const char *reset = diff_get_color(o->use_color, DIFF_RESET);
        static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
        const char *a_prefix, *b_prefix;
        char *data_one, *data_two;
        size_t size_one, size_two;
        struct emit_callback ecbdata;
 -      const char *line_prefix = diff_line_prefix(o);
 +      struct strbuf out = STRBUF_INIT;
  
 -      if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) {
 +      if (diff_mnemonic_prefix && o->flags.reverse_diff) {
                a_prefix = o->b_prefix;
                b_prefix = o->a_prefix;
        } else {
  
        name_a += (*name_a == '/');
        name_b += (*name_b == '/');
 -      name_a_tab = strchr(name_a, ' ') ? "\t" : "";
 -      name_b_tab = strchr(name_b, ' ') ? "\t" : "";
  
        strbuf_reset(&a_name);
        strbuf_reset(&b_name);
  
        lc_a = count_lines(data_one, size_one);
        lc_b = count_lines(data_two, size_two);
 -      fprintf(o->file,
 -              "%s%s--- %s%s%s\n%s%s+++ %s%s%s\n%s%s@@ -",
 -              line_prefix, metainfo, a_name.buf, name_a_tab, reset,
 -              line_prefix, metainfo, b_name.buf, name_b_tab, reset,
 -              line_prefix, fraginfo);
 +
 +      emit_diff_symbol(o, DIFF_SYMBOL_FILEPAIR_MINUS,
 +                       a_name.buf, a_name.len, 0);
 +      emit_diff_symbol(o, DIFF_SYMBOL_FILEPAIR_PLUS,
 +                       b_name.buf, b_name.len, 0);
 +
 +      strbuf_addstr(&out, "@@ -");
        if (!o->irreversible_delete)
 -              print_line_count(o->file, lc_a);
 +              add_line_count(&out, lc_a);
        else
 -              fprintf(o->file, "?,?");
 -      fprintf(o->file, " +");
 -      print_line_count(o->file, lc_b);
 -      fprintf(o->file, " @@%s\n", reset);
 +              strbuf_addstr(&out, "?,?");
 +      strbuf_addstr(&out, " +");
 +      add_line_count(&out, lc_b);
 +      strbuf_addstr(&out, " @@\n");
 +      emit_diff_symbol(o, DIFF_SYMBOL_REWRITE_DIFF, out.buf, out.len, 0);
 +      strbuf_release(&out);
 +
        if (lc_a && !o->irreversible_delete)
                emit_rewrite_lines(&ecbdata, '-', data_one, size_one);
        if (lc_b)
  
  struct diff_words_buffer {
        mmfile_t text;
 -      long alloc;
 +      unsigned long alloc;
        struct diff_words_orig {
                const char *begin, *end;
        } *orig;
@@@ -1524,49 -874,37 +1524,49 @@@ struct diff_words_data 
        struct diff_words_style *style;
  };
  
 -static int fn_out_diff_words_write_helper(FILE *fp,
 +static int fn_out_diff_words_write_helper(struct diff_options *o,
                                          struct diff_words_style_elem *st_el,
                                          const char *newline,
 -                                        size_t count, const char *buf,
 -                                        const char *line_prefix)
 +                                        size_t count, const char *buf)
  {
        int print = 0;
 +      struct strbuf sb = STRBUF_INIT;
  
        while (count) {
                char *p = memchr(buf, '\n', count);
                if (print)
 -                      fputs(line_prefix, fp);
 +                      strbuf_addstr(&sb, diff_line_prefix(o));
 +
                if (p != buf) {
 -                      if (st_el->color && fputs(st_el->color, fp) < 0)
 -                              return -1;
 -                      if (fputs(st_el->prefix, fp) < 0 ||
 -                          fwrite(buf, p ? p - buf : count, 1, fp) != 1 ||
 -                          fputs(st_el->suffix, fp) < 0)
 -                              return -1;
 -                      if (st_el->color && *st_el->color
 -                          && fputs(GIT_COLOR_RESET, fp) < 0)
 -                              return -1;
 +                      const char *reset = st_el->color && *st_el->color ?
 +                                          GIT_COLOR_RESET : NULL;
 +                      if (st_el->color && *st_el->color)
 +                              strbuf_addstr(&sb, st_el->color);
 +                      strbuf_addstr(&sb, st_el->prefix);
 +                      strbuf_add(&sb, buf, p ? p - buf : count);
 +                      strbuf_addstr(&sb, st_el->suffix);
 +                      if (reset)
 +                              strbuf_addstr(&sb, reset);
                }
                if (!p)
 -                      return 0;
 -              if (fputs(newline, fp) < 0)
 -                      return -1;
 +                      goto out;
 +
 +              strbuf_addstr(&sb, newline);
                count -= p + 1 - buf;
                buf = p + 1;
                print = 1;
 +              if (count) {
 +                      emit_diff_symbol(o, DIFF_SYMBOL_WORD_DIFF,
 +                                       sb.buf, sb.len, 0);
 +                      strbuf_reset(&sb);
 +              }
        }
 +
 +out:
 +      if (sb.len)
 +              emit_diff_symbol(o, DIFF_SYMBOL_WORD_DIFF,
 +                               sb.buf, sb.len, 0);
 +      strbuf_release(&sb);
        return 0;
  }
  
@@@ -1648,20 -986,24 +1648,20 @@@ static void fn_out_diff_words_aux(void 
                fputs(line_prefix, diff_words->opt->file);
        }
        if (diff_words->current_plus != plus_begin) {
 -              fn_out_diff_words_write_helper(diff_words->opt->file,
 +              fn_out_diff_words_write_helper(diff_words->opt,
                                &style->ctx, style->newline,
                                plus_begin - diff_words->current_plus,
 -                              diff_words->current_plus, line_prefix);
 -              if (*(plus_begin - 1) == '\n')
 -                      fputs(line_prefix, diff_words->opt->file);
 +                              diff_words->current_plus);
        }
        if (minus_begin != minus_end) {
 -              fn_out_diff_words_write_helper(diff_words->opt->file,
 +              fn_out_diff_words_write_helper(diff_words->opt,
                                &style->old, style->newline,
 -                              minus_end - minus_begin, minus_begin,
 -                              line_prefix);
 +                              minus_end - minus_begin, minus_begin);
        }
        if (plus_begin != plus_end) {
 -              fn_out_diff_words_write_helper(diff_words->opt->file,
 +              fn_out_diff_words_write_helper(diff_words->opt,
                                &style->new, style->newline,
 -                              plus_end - plus_begin, plus_begin,
 -                              line_prefix);
 +                              plus_end - plus_begin, plus_begin);
        }
  
        diff_words->current_plus = plus_end;
@@@ -1755,12 -1097,11 +1755,12 @@@ static void diff_words_show(struct diff
  
        /* special case: only removal */
        if (!diff_words->plus.text.size) {
 -              fputs(line_prefix, diff_words->opt->file);
 -              fn_out_diff_words_write_helper(diff_words->opt->file,
 +              emit_diff_symbol(diff_words->opt, DIFF_SYMBOL_WORD_DIFF,
 +                               line_prefix, strlen(line_prefix), 0);
 +              fn_out_diff_words_write_helper(diff_words->opt,
                        &style->old, style->newline,
                        diff_words->minus.text.size,
 -                      diff_words->minus.text.ptr, line_prefix);
 +                      diff_words->minus.text.ptr);
                diff_words->minus.text.size = 0;
                return;
        }
        if (diff_words->current_plus != diff_words->plus.text.ptr +
                        diff_words->plus.text.size) {
                if (color_words_output_graph_prefix(diff_words))
 -                      fputs(line_prefix, diff_words->opt->file);
 -              fn_out_diff_words_write_helper(diff_words->opt->file,
 +                      emit_diff_symbol(diff_words->opt, DIFF_SYMBOL_WORD_DIFF,
 +                                       line_prefix, strlen(line_prefix), 0);
 +              fn_out_diff_words_write_helper(diff_words->opt,
                        &style->ctx, style->newline,
                        diff_words->plus.text.ptr + diff_words->plus.text.size
 -                      - diff_words->current_plus, diff_words->current_plus,
 -                      line_prefix);
 +                      - diff_words->current_plus, diff_words->current_plus);
        }
        diff_words->minus.text.size = diff_words->plus.text.size = 0;
  }
  /* In "color-words" mode, show word-diff of words accumulated in the buffer */
  static void diff_words_flush(struct emit_callback *ecbdata)
  {
 +      struct diff_options *wo = ecbdata->diff_words->opt;
 +
        if (ecbdata->diff_words->minus.text.size ||
            ecbdata->diff_words->plus.text.size)
                diff_words_show(ecbdata->diff_words);
 +
 +      if (wo->emitted_symbols) {
 +              struct diff_options *o = ecbdata->opt;
 +              struct emitted_diff_symbols *wol = wo->emitted_symbols;
 +              int i;
 +
 +              /*
 +               * NEEDSWORK:
 +               * Instead of appending each, concat all words to a line?
 +               */
 +              for (i = 0; i < wol->nr; i++)
 +                      append_emitted_diff_symbol(o, &wol->buf[i]);
 +
 +              for (i = 0; i < wol->nr; i++)
 +                      free((void *)wol->buf[i].line);
 +
 +              wol->nr = 0;
 +      }
  }
  
  static void diff_filespec_load_driver(struct diff_filespec *one)
@@@ -1854,11 -1175,6 +1854,11 @@@ static void init_diff_words_data(struc
                xcalloc(1, sizeof(struct diff_words_data));
        ecbdata->diff_words->type = o->word_diff;
        ecbdata->diff_words->opt = o;
 +
 +      if (orig_opts->emitted_symbols)
 +              o->emitted_symbols =
 +                      xcalloc(1, sizeof(struct emitted_diff_symbols));
 +
        if (!o->word_regex)
                o->word_regex = userdiff_word_regex(one);
        if (!o->word_regex)
@@@ -1893,7 -1209,6 +1893,7 @@@ static void free_diff_words_data(struc
  {
        if (ecbdata->diff_words) {
                diff_words_flush(ecbdata);
 +              free (ecbdata->diff_words->opt->emitted_symbols);
                free (ecbdata->diff_words->opt);
                free (ecbdata->diff_words->minus.text.ptr);
                free (ecbdata->diff_words->minus.orig);
                        regfree(ecbdata->diff_words->word_regex);
                        free(ecbdata->diff_words->word_regex);
                }
 -              free(ecbdata->diff_words);
 -              ecbdata->diff_words = NULL;
 +              FREE_AND_NULL(ecbdata->diff_words);
        }
  }
  
@@@ -1930,6 -1246,8 +1930,6 @@@ static unsigned long sane_truncate_line
        unsigned long allot;
        size_t l = len;
  
 -      if (ecb->truncate)
 -              return ecb->truncate(line, len);
        cp = line;
        allot = l;
        while (0 < l) {
@@@ -1958,25 -1276,30 +1958,25 @@@ static void find_lno(const char *line, 
  static void fn_out_consume(void *priv, char *line, unsigned long len)
  {
        struct emit_callback *ecbdata = priv;
 -      const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
 -      const char *context = diff_get_color(ecbdata->color_diff, DIFF_CONTEXT);
        const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
        struct diff_options *o = ecbdata->opt;
 -      const char *line_prefix = diff_line_prefix(o);
  
        o->found_changes = 1;
  
        if (ecbdata->header) {
 -              fprintf(o->file, "%s", ecbdata->header->buf);
 +              emit_diff_symbol(o, DIFF_SYMBOL_HEADER,
 +                               ecbdata->header->buf, ecbdata->header->len, 0);
                strbuf_reset(ecbdata->header);
                ecbdata->header = NULL;
        }
  
        if (ecbdata->label_path[0]) {
 -              const char *name_a_tab, *name_b_tab;
 -
 -              name_a_tab = strchr(ecbdata->label_path[0], ' ') ? "\t" : "";
 -              name_b_tab = strchr(ecbdata->label_path[1], ' ') ? "\t" : "";
 -
 -              fprintf(o->file, "%s%s--- %s%s%s\n",
 -                      line_prefix, meta, ecbdata->label_path[0], reset, name_a_tab);
 -              fprintf(o->file, "%s%s+++ %s%s%s\n",
 -                      line_prefix, meta, ecbdata->label_path[1], reset, name_b_tab);
 +              emit_diff_symbol(o, DIFF_SYMBOL_FILEPAIR_MINUS,
 +                               ecbdata->label_path[0],
 +                               strlen(ecbdata->label_path[0]), 0);
 +              emit_diff_symbol(o, DIFF_SYMBOL_FILEPAIR_PLUS,
 +                               ecbdata->label_path[1],
 +                               strlen(ecbdata->label_path[1]), 0);
                ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
        }
  
                len = sane_truncate_line(ecbdata, line, len);
                find_lno(line, ecbdata);
                emit_hunk_header(ecbdata, line, len);
 -              if (line[len-1] != '\n')
 -                      putc('\n', o->file);
                return;
        }
  
        if (ecbdata->diff_words) {
 +              enum diff_symbol s =
 +                      ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN ?
 +                      DIFF_SYMBOL_WORDS_PORCELAIN : DIFF_SYMBOL_WORDS;
                if (line[0] == '-') {
                        diff_words_append(line, len,
                                          &ecbdata->diff_words->minus);
                        return;
                }
                diff_words_flush(ecbdata);
 -              if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) {
 -                      emit_line(o, context, reset, line, len);
 -                      fputs("~\n", o->file);
 -              } else {
 -                      /*
 -                       * Skip the prefix character, if any.  With
 -                       * diff_suppress_blank_empty, there may be
 -                       * none.
 -                       */
 -                      if (line[0] != '\n') {
 -                            line++;
 -                            len--;
 -                      }
 -                      emit_line(o, context, reset, line, len);
 -              }
 +              emit_diff_symbol(o, s, line, len, 0);
                return;
        }
  
        default:
                /* incomplete line at the end */
                ecbdata->lno_in_preimage++;
 -              emit_line(o, diff_get_color(ecbdata->color_diff, DIFF_CONTEXT),
 -                        reset, line, len);
 +              emit_diff_symbol(o, DIFF_SYMBOL_CONTEXT_INCOMPLETE,
 +                               line, len, 0);
                break;
        }
  }
@@@ -2185,14 -1521,20 +2185,14 @@@ static int scale_linear(int it, int wid
        return 1 + (it * (width - 1) / max_change);
  }
  
 -static void show_name(FILE *file,
 -                    const char *prefix, const char *name, int len)
 -{
 -      fprintf(file, " %s%-*s |", prefix, len, name);
 -}
 -
 -static void show_graph(FILE *file, char ch, int cnt, const char *set, const char *reset)
 +static void show_graph(struct strbuf *out, char ch, int cnt,
 +                     const char *set, const char *reset)
  {
        if (cnt <= 0)
                return;
 -      fprintf(file, "%s", set);
 -      while (cnt--)
 -              putc(ch, file);
 -      fprintf(file, "%s", reset);
 +      strbuf_addstr(out, set);
 +      strbuf_addchars(out, ch, cnt);
 +      strbuf_addstr(out, reset);
  }
  
  static void fill_print_name(struct diffstat_file *file)
        file->print_name = pname;
  }
  
 -int print_stat_summary(FILE *fp, int files, int insertions, int deletions)
 +static void print_stat_summary_inserts_deletes(struct diff_options *options,
 +              int files, int insertions, int deletions)
  {
        struct strbuf sb = STRBUF_INIT;
 -      int ret;
  
        if (!files) {
                assert(insertions == 0 && deletions == 0);
 -              return fprintf(fp, "%s\n", " 0 files changed");
 +              emit_diff_symbol(options, DIFF_SYMBOL_STATS_SUMMARY_NO_FILES,
 +                               NULL, 0, 0);
 +              return;
        }
  
        strbuf_addf(&sb,
                            deletions);
        }
        strbuf_addch(&sb, '\n');
 -      ret = fputs(sb.buf, fp);
 +      emit_diff_symbol(options, DIFF_SYMBOL_STATS_SUMMARY_INSERTS_DELETES,
 +                       sb.buf, sb.len, 0);
        strbuf_release(&sb);
 -      return ret;
 +}
 +
 +void print_stat_summary(FILE *fp, int files,
 +                      int insertions, int deletions)
 +{
 +      struct diff_options o;
 +      memset(&o, 0, sizeof(o));
 +      o.file = fp;
 +
 +      print_stat_summary_inserts_deletes(&o, files, insertions, deletions);
  }
  
  static void show_stats(struct diffstat_t *data, struct diff_options *options)
        int total_files = data->nr, count;
        int width, name_width, graph_width, number_width = 0, bin_width = 0;
        const char *reset, *add_c, *del_c;
 -      const char *line_prefix = "";
        int extra_shown = 0;
 +      const char *line_prefix = diff_line_prefix(options);
 +      struct strbuf out = STRBUF_INIT;
  
        if (data->nr == 0)
                return;
  
 -      line_prefix = diff_line_prefix(options);
        count = options->stat_count ? options->stat_count : data->nr;
  
        reset = diff_get_color_opt(options, DIFF_RESET);
                }
  
                if (file->is_binary) {
 -                      fprintf(options->file, "%s", line_prefix);
 -                      show_name(options->file, prefix, name, len);
 -                      fprintf(options->file, " %*s", number_width, "Bin");
 +                      strbuf_addf(&out, " %s%-*s |", prefix, len, name);
 +                      strbuf_addf(&out, " %*s", number_width, "Bin");
                        if (!added && !deleted) {
 -                              putc('\n', options->file);
 +                              strbuf_addch(&out, '\n');
 +                              emit_diff_symbol(options, DIFF_SYMBOL_STATS_LINE,
 +                                               out.buf, out.len, 0);
 +                              strbuf_reset(&out);
                                continue;
                        }
 -                      fprintf(options->file, " %s%"PRIuMAX"%s",
 +                      strbuf_addf(&out, " %s%"PRIuMAX"%s",
                                del_c, deleted, reset);
 -                      fprintf(options->file, " -> ");
 -                      fprintf(options->file, "%s%"PRIuMAX"%s",
 +                      strbuf_addstr(&out, " -> ");
 +                      strbuf_addf(&out, "%s%"PRIuMAX"%s",
                                add_c, added, reset);
 -                      fprintf(options->file, " bytes");
 -                      fprintf(options->file, "\n");
 +                      strbuf_addstr(&out, " bytes\n");
 +                      emit_diff_symbol(options, DIFF_SYMBOL_STATS_LINE,
 +                                       out.buf, out.len, 0);
 +                      strbuf_reset(&out);
                        continue;
                }
                else if (file->is_unmerged) {
 -                      fprintf(options->file, "%s", line_prefix);
 -                      show_name(options->file, prefix, name, len);
 -                      fprintf(options->file, " Unmerged\n");
 +                      strbuf_addf(&out, " %s%-*s |", prefix, len, name);
 +                      strbuf_addstr(&out, " Unmerged\n");
 +                      emit_diff_symbol(options, DIFF_SYMBOL_STATS_LINE,
 +                                       out.buf, out.len, 0);
 +                      strbuf_reset(&out);
                        continue;
                }
  
                                add = total - del;
                        }
                }
 -              fprintf(options->file, "%s", line_prefix);
 -              show_name(options->file, prefix, name, len);
 -              fprintf(options->file, " %*"PRIuMAX"%s",
 +              strbuf_addf(&out, " %s%-*s |", prefix, len, name);
 +              strbuf_addf(&out, " %*"PRIuMAX"%s",
                        number_width, added + deleted,
                        added + deleted ? " " : "");
 -              show_graph(options->file, '+', add, add_c, reset);
 -              show_graph(options->file, '-', del, del_c, reset);
 -              fprintf(options->file, "\n");
 +              show_graph(&out, '+', add, add_c, reset);
 +              show_graph(&out, '-', del, del_c, reset);
 +              strbuf_addch(&out, '\n');
 +              emit_diff_symbol(options, DIFF_SYMBOL_STATS_LINE,
 +                               out.buf, out.len, 0);
 +              strbuf_reset(&out);
        }
  
        for (i = 0; i < data->nr; i++) {
                if (i < count)
                        continue;
                if (!extra_shown)
 -                      fprintf(options->file, "%s ...\n", line_prefix);
 +                      emit_diff_symbol(options,
 +                                       DIFF_SYMBOL_STATS_SUMMARY_ABBREV,
 +                                       NULL, 0, 0);
                extra_shown = 1;
        }
 -      fprintf(options->file, "%s", line_prefix);
 -      print_stat_summary(options->file, total_files, adds, dels);
 +
 +      print_stat_summary_inserts_deletes(options, total_files, adds, dels);
 +      strbuf_release(&out);
  }
  
  static void show_shortstats(struct diffstat_t *data, struct diff_options *options)
  
        for (i = 0; i < data->nr; i++) {
                int added = data->files[i]->added;
 -              int deleted= data->files[i]->deleted;
 +              int deleted = data->files[i]->deleted;
  
                if (data->files[i]->is_unmerged ||
                    (!data->files[i]->is_interesting && (added + deleted == 0))) {
                        dels += deleted;
                }
        }
 -      fprintf(options->file, "%s", diff_line_prefix(options));
 -      print_stat_summary(options->file, total_files, adds, dels);
 +      print_stat_summary_inserts_deletes(options, total_files, adds, dels);
  }
  
  static void show_numstat(struct diffstat_t *data, struct diff_options *options)
@@@ -2660,7 -1980,7 +2660,7 @@@ static void show_dirstat(struct diff_op
        dir.alloc = 0;
        dir.nr = 0;
        dir.permille = options->dirstat_permille;
 -      dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE);
 +      dir.cumulative = options->flags.dirstat_cumulative;
  
        changed = 0;
        for (i = 0; i < q->nr; i++) {
                        goto found_damage;
                }
  
 -              if (DIFF_OPT_TST(options, DIRSTAT_BY_FILE)) {
 +              if (options->flags.dirstat_by_file) {
                        /*
                         * In --dirstat-by-file mode, we don't really need to
                         * look at the actual file contents at all.
@@@ -2761,7 -2081,7 +2761,7 @@@ static void show_dirstat_by_line(struc
        dir.alloc = 0;
        dir.nr = 0;
        dir.permille = options->dirstat_permille;
 -      dir.cumulative = DIFF_OPT_TST(options, DIRSTAT_CUMULATIVE);
 +      dir.cumulative = options->flags.dirstat_cumulative;
  
        changed = 0;
        for (i = 0; i < data->nr; i++) {
                         * bytes per "line".
                         * This is stupid and ugly, but very cheap...
                         */
 -                      damage = (damage + 63) / 64;
 +                      damage = DIV_ROUND_UP(damage, 64);
                ALLOC_GROW(dir.files, dir.nr + 1, dir.alloc);
                dir.files[dir.nr].name = file->name;
                dir.files[dir.nr].changed = damage;
@@@ -2905,8 -2225,8 +2905,8 @@@ static unsigned char *deflate_it(char *
        return deflated;
  }
  
 -static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two,
 -                                const char *prefix)
 +static void emit_binary_diff_body(struct diff_options *o,
 +                                mmfile_t *one, mmfile_t *two)
  {
        void *cp;
        void *delta;
        }
  
        if (delta && delta_size < deflate_size) {
 -              fprintf(file, "%sdelta %lu\n", prefix, orig_size);
 +              char *s = xstrfmt("%lu", orig_size);
 +              emit_diff_symbol(o, DIFF_SYMBOL_BINARY_DIFF_HEADER_DELTA,
 +                               s, strlen(s), 0);
 +              free(s);
                free(deflated);
                data = delta;
                data_size = delta_size;
 -      }
 -      else {
 -              fprintf(file, "%sliteral %lu\n", prefix, two->size);
 +      } else {
 +              char *s = xstrfmt("%lu", two->size);
 +              emit_diff_symbol(o, DIFF_SYMBOL_BINARY_DIFF_HEADER_LITERAL,
 +                               s, strlen(s), 0);
 +              free(s);
                free(delta);
                data = deflated;
                data_size = deflate_size;
        /* emit data encoded in base85 */
        cp = data;
        while (data_size) {
 +              int len;
                int bytes = (52 < data_size) ? 52 : data_size;
 -              char line[70];
 +              char line[71];
                data_size -= bytes;
                if (bytes <= 26)
                        line[0] = bytes + 'A' - 1;
                        line[0] = bytes - 26 + 'a' - 1;
                encode_85(line + 1, cp, bytes);
                cp = (char *) cp + bytes;
 -              fprintf(file, "%s", prefix);
 -              fputs(line, file);
 -              fputc('\n', file);
 +
 +              len = strlen(line);
 +              line[len++] = '\n';
 +              line[len] = '\0';
 +
 +              emit_diff_symbol(o, DIFF_SYMBOL_BINARY_DIFF_BODY,
 +                               line, len, 0);
        }
 -      fprintf(file, "%s\n", prefix);
 +      emit_diff_symbol(o, DIFF_SYMBOL_BINARY_DIFF_FOOTER, NULL, 0, 0);
        free(data);
  }
  
 -static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two,
 -                           const char *prefix)
 +static void emit_binary_diff(struct diff_options *o,
 +                           mmfile_t *one, mmfile_t *two)
  {
 -      fprintf(file, "%sGIT binary patch\n", prefix);
 -      emit_binary_diff_body(file, one, two, prefix);
 -      emit_binary_diff_body(file, two, one, prefix);
 +      emit_diff_symbol(o, DIFF_SYMBOL_BINARY_DIFF_HEADER, NULL, 0, 0);
 +      emit_binary_diff_body(o, one, two);
 +      emit_binary_diff_body(o, two, one);
  }
  
  int diff_filespec_is_binary(struct diff_filespec *one)
@@@ -3048,7 -2358,7 +3048,7 @@@ static void builtin_diff(const char *na
        const char *line_prefix = diff_line_prefix(o);
  
        diff_set_mnemonic_prefix(o, "a/", "b/");
 -      if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
 +      if (o->flags.reverse_diff) {
                a_prefix = o->b_prefix;
                b_prefix = o->a_prefix;
        } else {
        if (o->submodule_format == DIFF_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->path ? one->path : two->path,
 -                              line_prefix,
 +              show_submodule_summary(o, one->path ? one->path : two->path,
                                &one->oid, &two->oid,
 -                              two->dirty_submodule,
 -                              meta, del, add, reset);
 +                              two->dirty_submodule);
                return;
        } else if (o->submodule_format == DIFF_SUBMODULE_INLINE_DIFF &&
                   (!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_inline_diff(o->file, one->path ? one->path : two->path,
 -                              line_prefix,
 +              show_submodule_inline_diff(o, one->path ? one->path : two->path,
                                &one->oid, &two->oid,
 -                              two->dirty_submodule,
 -                              meta, del, add, reset, o);
 +                              two->dirty_submodule);
                return;
        }
  
 -      if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) {
 +      if (o->flags.allow_textconv) {
                textconv_one = get_textconv(one);
                textconv_two = get_textconv(two);
        }
                if (complete_rewrite &&
                    (textconv_one || !diff_filespec_is_binary(one)) &&
                    (textconv_two || !diff_filespec_is_binary(two))) {
 -                      fprintf(o->file, "%s", header.buf);
 +                      emit_diff_symbol(o, DIFF_SYMBOL_HEADER,
 +                                       header.buf, header.len, 0);
                        strbuf_reset(&header);
                        emit_rewrite_diff(name_a, name_b, one, two,
                                                textconv_one, textconv_two, o);
        }
  
        if (o->irreversible_delete && lbl[1][0] == '/') {
 -              fprintf(o->file, "%s", header.buf);
 +              emit_diff_symbol(o, DIFF_SYMBOL_HEADER, header.buf,
 +                               header.len, 0);
                strbuf_reset(&header);
                goto free_ab_and_return;
 -      } else if (!DIFF_OPT_TST(o, TEXT) &&
 +      } else if (!o->flags.text &&
            ( (!textconv_one && diff_filespec_is_binary(one)) ||
              (!textconv_two && diff_filespec_is_binary(two)) )) {
 +              struct strbuf sb = STRBUF_INIT;
                if (!one->data && !two->data &&
                    S_ISREG(one->mode) && S_ISREG(two->mode) &&
 -                  !DIFF_OPT_TST(o, BINARY)) {
 +                  !o->flags.binary) {
                        if (!oidcmp(&one->oid, &two->oid)) {
                                if (must_show_header)
 -                                      fprintf(o->file, "%s", header.buf);
 +                                      emit_diff_symbol(o, DIFF_SYMBOL_HEADER,
 +                                                       header.buf, header.len,
 +                                                       0);
                                goto free_ab_and_return;
                        }
 -                      fprintf(o->file, "%s", header.buf);
 -                      fprintf(o->file, "%sBinary files %s and %s differ\n",
 -                              line_prefix, lbl[0], lbl[1]);
 +                      emit_diff_symbol(o, DIFF_SYMBOL_HEADER,
 +                                       header.buf, header.len, 0);
 +                      strbuf_addf(&sb, "%sBinary files %s and %s differ\n",
 +                                  diff_line_prefix(o), lbl[0], lbl[1]);
 +                      emit_diff_symbol(o, DIFF_SYMBOL_BINARY_FILES,
 +                                       sb.buf, sb.len, 0);
 +                      strbuf_release(&sb);
                        goto free_ab_and_return;
                }
                if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                if (mf1.size == mf2.size &&
                    !memcmp(mf1.ptr, mf2.ptr, mf1.size)) {
                        if (must_show_header)
 -                              fprintf(o->file, "%s", header.buf);
 +                              emit_diff_symbol(o, DIFF_SYMBOL_HEADER,
 +                                               header.buf, header.len, 0);
                        goto free_ab_and_return;
                }
 -              fprintf(o->file, "%s", header.buf);
 +              emit_diff_symbol(o, DIFF_SYMBOL_HEADER, header.buf, header.len, 0);
                strbuf_reset(&header);
 -              if (DIFF_OPT_TST(o, BINARY))
 -                      emit_binary_diff(o->file, &mf1, &mf2, line_prefix);
 -              else
 -                      fprintf(o->file, "%sBinary files %s and %s differ\n",
 -                              line_prefix, lbl[0], lbl[1]);
 +              if (o->flags.binary)
 +                      emit_binary_diff(o, &mf1, &mf2);
 +              else {
 +                      strbuf_addf(&sb, "%sBinary files %s and %s differ\n",
 +                                  diff_line_prefix(o), lbl[0], lbl[1]);
 +                      emit_diff_symbol(o, DIFF_SYMBOL_BINARY_FILES,
 +                                       sb.buf, sb.len, 0);
 +                      strbuf_release(&sb);
 +              }
                o->found_changes = 1;
        } else {
                /* Crazy xdl interfaces.. */
                const struct userdiff_funcname *pe;
  
                if (must_show_header) {
 -                      fprintf(o->file, "%s", header.buf);
 +                      emit_diff_symbol(o, DIFF_SYMBOL_HEADER,
 +                                       header.buf, header.len, 0);
                        strbuf_reset(&header);
                }
  
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
 -              if (DIFF_OPT_TST(o, FUNCCONTEXT))
 +              if (o->flags.funccontext)
                        xecfg.flags |= XDL_EMIT_FUNCCONTEXT;
                if (pe)
                        xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
@@@ -3378,7 -2681,7 +3378,7 @@@ static void builtin_checkdiff(const cha
        diff_free_filespec_data(one);
        diff_free_filespec_data(two);
        if (data.status)
 -              DIFF_OPT_SET(o, CHECK_FAILED);
 +              o->flags.check_failed = 1;
  }
  
  struct diff_filespec *alloc_filespec(const char *path)
@@@ -3399,13 -2702,13 +3399,13 @@@ void free_filespec(struct diff_filespe
        }
  }
  
 -void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
 -                 int sha1_valid, unsigned short mode)
 +void fill_filespec(struct diff_filespec *spec, const struct object_id *oid,
 +                 int oid_valid, unsigned short mode)
  {
        if (mode) {
                spec->mode = canon_mode(mode);
 -              hashcpy(spec->oid.hash, sha1);
 -              spec->oid_valid = sha1_valid;
 +              oidcpy(&spec->oid, oid);
 +              spec->oid_valid = oid_valid;
        }
  }
  
   * the work tree has that object contents, return true, so that
   * prepare_temp_file() does not have to inflate and extract.
   */
 -static int reuse_worktree_file(const char *name, const unsigned char *sha1, int want_file)
 +static int reuse_worktree_file(const char *name, const struct object_id *oid, int want_file)
  {
        const struct cache_entry *ce;
        struct stat st;
         * objects however would tend to be slower as they need
         * to be individually opened and inflated.
         */
 -      if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(sha1))
 +      if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(oid->hash))
                return 0;
  
        /*
         * Similarly, if we'd have to convert the file contents anyway, that
         * makes the optimization not worthwhile.
         */
 -      if (!want_file && would_convert_to_git(name))
 +      if (!want_file && would_convert_to_git(&the_index, name))
                return 0;
  
        len = strlen(name);
         * This is not the sha1 we are looking for, or
         * unreusable because it is not a regular file.
         */
 -      if (hashcmp(sha1, ce->oid.hash) || !S_ISREG(ce->ce_mode))
 +      if (oidcmp(oid, &ce->oid) || !S_ISREG(ce->ce_mode))
                return 0;
  
        /*
@@@ -3539,18 -2842,20 +3539,18 @@@ int diff_populate_filespec(struct diff_
                return diff_populate_gitlink(s, size_only);
  
        if (!s->oid_valid ||
 -          reuse_worktree_file(s->path, s->oid.hash, 0)) {
 +          reuse_worktree_file(s->path, &s->oid, 0)) {
                struct strbuf buf = STRBUF_INIT;
                struct stat st;
                int fd;
  
                if (lstat(s->path, &st) < 0) {
 -                      if (errno == ENOENT) {
 -                      err_empty:
 -                              err = -1;
 -                      empty:
 -                              s->data = (char *)"";
 -                              s->size = 0;
 -                              return err;
 -                      }
 +              err_empty:
 +                      err = -1;
 +              empty:
 +                      s->data = (char *)"";
 +                      s->size = 0;
 +                      return err;
                }
                s->size = xsize_t(st.st_size);
                if (!s->size)
                 * point if the path requires us to run the content
                 * conversion.
                 */
 -              if (size_only && !would_convert_to_git(s->path))
 +              if (size_only && !would_convert_to_git(&the_index, s->path))
                        return 0;
  
                /*
                /*
                 * Convert from working tree format to canonical git format
                 */
 -              if (convert_to_git(s->path, s->data, s->size, &buf, crlf_warn)) {
 +              if (convert_to_git(&the_index, s->path, s->data, s->size, &buf, crlf_warn)) {
                        size_t size = 0;
                        munmap(s->data, s->size);
                        s->should_munmap = 0;
@@@ -3646,7 -2951,8 +3646,7 @@@ void diff_free_filespec_blob(struct dif
  void diff_free_filespec_data(struct diff_filespec *s)
  {
        diff_free_filespec_blob(s);
 -      free(s->cnt_data);
 -      s->cnt_data = NULL;
 +      FREE_AND_NULL(s->cnt_data);
  }
  
  static void prep_temp_blob(const char *path, struct diff_tempfile *temp,
                           const struct object_id *oid,
                           int mode)
  {
 -      int fd;
        struct strbuf buf = STRBUF_INIT;
        struct strbuf template = STRBUF_INIT;
        char *path_dup = xstrdup(path);
        strbuf_addstr(&template, "XXXXXX_");
        strbuf_addstr(&template, base);
  
 -      fd = mks_tempfile_ts(&temp->tempfile, template.buf, strlen(base) + 1);
 -      if (fd < 0)
 +      temp->tempfile = mks_tempfile_ts(template.buf, strlen(base) + 1);
 +      if (!temp->tempfile)
                die_errno("unable to create temp-file");
        if (convert_to_working_tree(path,
                        (const char *)blob, (size_t)size, &buf)) {
                blob = buf.buf;
                size = buf.len;
        }
 -      if (write_in_full(fd, blob, size) != size)
 +      if (write_in_full(temp->tempfile->fd, blob, size) < 0 ||
 +          close_tempfile_gently(temp->tempfile))
                die_errno("unable to write temp-file");
 -      close_tempfile(&temp->tempfile);
 -      temp->name = get_tempfile_path(&temp->tempfile);
 +      temp->name = get_tempfile_path(temp->tempfile);
        oid_to_hex_r(temp->hex, oid);
        xsnprintf(temp->mode, sizeof(temp->mode), "%06o", mode);
        strbuf_release(&buf);
@@@ -3701,7 -3008,7 +3701,7 @@@ static struct diff_tempfile *prepare_te
  
        if (!S_ISGITLINK(one->mode) &&
            (!one->oid_valid ||
 -           reuse_worktree_file(name, one->oid.hash, 1))) {
 +           reuse_worktree_file(name, &one->oid, 1))) {
                struct stat st;
                if (lstat(name, &st) < 0) {
                        if (errno == ENOENT)
                        /* we can borrow from the file in the work tree */
                        temp->name = name;
                        if (!one->oid_valid)
 -                              sha1_to_hex_r(temp->hex, null_sha1);
 +                              oid_to_hex_r(temp->hex, &null_oid);
                        else
                                oid_to_hex_r(temp->hex, &one->oid);
                        /* Even though we may sometimes borrow the
                         * contents from the work tree, we always want
                         * one->mode.  mode is trustworthy even when
 -                       * !(one->sha1_valid), as long as
 +                       * !(one->oid_valid), as long as
                         * DIFF_FILE_VALID(one).
                         */
                        xsnprintf(temp->mode, sizeof(temp->mode), "%06o", one->mode);
@@@ -3870,9 -3177,9 +3870,9 @@@ static void fill_metainfo(struct strbu
                *must_show_header = 0;
        }
        if (one && two && oidcmp(&one->oid, &two->oid)) {
 -              int abbrev = DIFF_OPT_TST(o, FULL_INDEX) ? 40 : DEFAULT_ABBREV;
 +              int abbrev = o->flags.full_index ? 40 : DEFAULT_ABBREV;
  
 -              if (DIFF_OPT_TST(o, BINARY)) {
 +              if (o->flags.binary) {
                        mmfile_t mf;
                        if ((!fill_mmfile(&mf, one) && diff_filespec_is_binary(one)) ||
                            (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
@@@ -3902,7 -3209,7 +3902,7 @@@ static void run_diff_cmd(const char *pg
        int must_show_header = 0;
  
  
 -      if (DIFF_OPT_TST(o, ALLOW_EXTERNAL)) {
 +      if (o->flags.allow_external) {
                struct userdiff_driver *drv = userdiff_find_by_path(attr_path);
                if (drv && drv->external)
                        pgm = drv->external;
                fprintf(o->file, "* Unmerged path %s\n", name);
  }
  
 -static void diff_fill_sha1_info(struct diff_filespec *one)
 +static void diff_fill_oid_info(struct diff_filespec *one)
  {
        if (DIFF_FILE_VALID(one)) {
                if (!one->oid_valid) {
                        }
                        if (lstat(one->path, &st) < 0)
                                die_errno("stat '%s'", one->path);
 -                      if (index_path(one->oid.hash, one->path, &st, 0))
 +                      if (index_path(&one->oid, one->path, &st, 0))
                                die("cannot hash %s", one->path);
                }
        }
@@@ -3976,13 -3283,13 +3976,13 @@@ static void run_diff(struct diff_filepa
        const char *other;
        const char *attr_path;
  
 -      name  = p->one->path;
 -      other = (strcmp(name, p->two->path) ? p->two->path : NULL);
 +      name  = one->path;
 +      other = (strcmp(name, two->path) ? two->path : NULL);
        attr_path = name;
        if (o->prefix_length)
                strip_prefix(o->prefix_length, &name, &other);
  
 -      if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL))
 +      if (!o->flags.allow_external)
                pgm = NULL;
  
        if (DIFF_PAIR_UNMERGED(p)) {
                return;
        }
  
 -      diff_fill_sha1_info(one);
 -      diff_fill_sha1_info(two);
 +      diff_fill_oid_info(one);
 +      diff_fill_oid_info(two);
  
        if (!pgm &&
            DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) &&
@@@ -4037,8 -3344,8 +4037,8 @@@ static void run_diffstat(struct diff_fi
        if (o->prefix_length)
                strip_prefix(o->prefix_length, &name, &other);
  
 -      diff_fill_sha1_info(p->one);
 -      diff_fill_sha1_info(p->two);
 +      diff_fill_oid_info(p->one);
 +      diff_fill_oid_info(p->two);
  
        builtin_diffstat(name, other, p->one, p->two, diffstat, o, p);
  }
@@@ -4061,8 -3368,8 +4061,8 @@@ static void run_checkdiff(struct diff_f
        if (o->prefix_length)
                strip_prefix(o->prefix_length, &name, &other);
  
 -      diff_fill_sha1_info(p->one);
 -      diff_fill_sha1_info(p->two);
 +      diff_fill_oid_info(p->one);
 +      diff_fill_oid_info(p->two);
  
        builtin_checkdiff(name, other, attr_path, p->one, p->two, o);
  }
@@@ -4081,7 -3388,7 +4081,7 @@@ void diff_setup(struct diff_options *op
        options->context = diff_context_default;
        options->interhunkcontext = diff_interhunk_context_default;
        options->ws_error_highlight = ws_error_highlight_default;
 -      DIFF_OPT_SET(options, RENAME_EMPTY);
 +      options->flags.rename_empty = 1;
  
        /* pathchange left =NULL by default */
        options->change = diff_change;
                options->a_prefix = "a/";
                options->b_prefix = "b/";
        }
 +
 +      options->color_moved = diff_color_moved_default;
  }
  
  void diff_setup_done(struct diff_options *options)
         * inside contents.
         */
  
-       if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) ||
-           DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE) ||
-           DIFF_XDL_TST(options, IGNORE_WHITESPACE_AT_EOL))
+       if ((options->xdl_opts & XDF_WHITESPACE_FLAGS))
 -              DIFF_OPT_SET(options, DIFF_FROM_CONTENTS);
 +              options->flags.diff_from_contents = 1;
        else
 -              DIFF_OPT_CLR(options, DIFF_FROM_CONTENTS);
 +              options->flags.diff_from_contents = 0;
  
 -      if (DIFF_OPT_TST(options, FIND_COPIES_HARDER))
 +      if (options->flags.find_copies_harder)
                options->detect_rename = DIFF_DETECT_COPY;
  
 -      if (!DIFF_OPT_TST(options, RELATIVE_NAME))
 +      if (!options->flags.relative_name)
                options->prefix = NULL;
        if (options->prefix)
                options->prefix_length = strlen(options->prefix);
                                      DIFF_FORMAT_DIRSTAT |
                                      DIFF_FORMAT_SUMMARY |
                                      DIFF_FORMAT_CHECKDIFF))
 -              DIFF_OPT_SET(options, RECURSIVE);
 +              options->flags.recursive = 1;
        /*
         * Also pickaxe would not work very well if you do not say recursive
         */
        if (options->pickaxe)
 -              DIFF_OPT_SET(options, RECURSIVE);
 +              options->flags.recursive = 1;
        /*
         * When patches are generated, submodules diffed against the work tree
         * must be checked for dirtiness too so it can be shown in the output
         */
        if (options->output_format & DIFF_FORMAT_PATCH)
 -              DIFF_OPT_SET(options, DIRTY_SUBMODULES);
 +              options->flags.dirty_submodules = 1;
  
        if (options->detect_rename && options->rename_limit < 0)
                options->rename_limit = diff_rename_limit_default;
         * to have found.  It does not make sense not to return with
         * exit code in such a case either.
         */
 -      if (DIFF_OPT_TST(options, QUICK)) {
 +      if (options->flags.quick) {
                options->output_format = DIFF_FORMAT_NO_OUTPUT;
 -              DIFF_OPT_SET(options, EXIT_WITH_STATUS);
 +              options->flags.exit_with_status = 1;
        }
  
        options->diff_path_counter = 0;
  
 -      if (DIFF_OPT_TST(options, FOLLOW_RENAMES) && options->pathspec.nr != 1)
 +      if (options->flags.follow_renames && options->pathspec.nr != 1)
                die(_("--follow requires exactly one pathspec"));
 +
 +      if (!options->use_color || external_diff())
 +              options->color_moved = 0;
  }
  
  static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val)
@@@ -4559,7 -3859,7 +4557,7 @@@ int diff_opt_parse(struct diff_options 
        else if (starts_with(arg, "-C") || starts_with(arg, "--find-copies=") ||
                 !strcmp(arg, "--find-copies")) {
                if (options->detect_rename == DIFF_DETECT_COPY)
 -                      DIFF_OPT_SET(options, FIND_COPIES_HARDER);
 +                      options->flags.find_copies_harder = 1;
                if ((options->rename_score = diff_scoreopt_parse(arg)) == -1)
                        return error("invalid argument to -C: %s", arg+2);
                options->detect_rename = DIFF_DETECT_COPY;
        else if (!strcmp(arg, "--no-renames"))
                options->detect_rename = 0;
        else if (!strcmp(arg, "--rename-empty"))
 -              DIFF_OPT_SET(options, RENAME_EMPTY);
 +              options->flags.rename_empty = 1;
        else if (!strcmp(arg, "--no-rename-empty"))
 -              DIFF_OPT_CLR(options, RENAME_EMPTY);
 +              options->flags.rename_empty = 0;
        else if (!strcmp(arg, "--relative"))
 -              DIFF_OPT_SET(options, RELATIVE_NAME);
 +              options->flags.relative_name = 1;
        else if (skip_prefix(arg, "--relative=", &arg)) {
 -              DIFF_OPT_SET(options, RELATIVE_NAME);
 +              options->flags.relative_name = 1;
                options->prefix = arg;
        }
  
                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-cr-at-eol"))
+               DIFF_XDL_SET(options, IGNORE_CR_AT_EOL);
        else if (!strcmp(arg, "--ignore-blank-lines"))
                DIFF_XDL_SET(options, IGNORE_BLANK_LINES);
        else if (!strcmp(arg, "--indent-heuristic"))
        /* flags options */
        else if (!strcmp(arg, "--binary")) {
                enable_patch_output(&options->output_format);
 -              DIFF_OPT_SET(options, BINARY);
 +              options->flags.binary = 1;
        }
        else if (!strcmp(arg, "--full-index"))
 -              DIFF_OPT_SET(options, FULL_INDEX);
 +              options->flags.full_index = 1;
        else if (!strcmp(arg, "-a") || !strcmp(arg, "--text"))
 -              DIFF_OPT_SET(options, TEXT);
 +              options->flags.text = 1;
        else if (!strcmp(arg, "-R"))
 -              DIFF_OPT_SET(options, REVERSE_DIFF);
 +              options->flags.reverse_diff = 1;
        else if (!strcmp(arg, "--find-copies-harder"))
 -              DIFF_OPT_SET(options, FIND_COPIES_HARDER);
 +              options->flags.find_copies_harder = 1;
        else if (!strcmp(arg, "--follow"))
 -              DIFF_OPT_SET(options, FOLLOW_RENAMES);
 +              options->flags.follow_renames = 1;
        else if (!strcmp(arg, "--no-follow")) {
 -              DIFF_OPT_CLR(options, FOLLOW_RENAMES);
 -              DIFF_OPT_CLR(options, DEFAULT_FOLLOW_RENAMES);
 +              options->flags.follow_renames = 0;
 +              options->flags.default_follow_renames = 0;
        } else if (!strcmp(arg, "--color"))
                options->use_color = 1;
        else if (skip_prefix(arg, "--color=", &arg)) {
        }
        else if (!strcmp(arg, "--no-color"))
                options->use_color = 0;
 -      else if (!strcmp(arg, "--color-words")) {
 +      else if (!strcmp(arg, "--color-moved")) {
 +              if (diff_color_moved_default)
 +                      options->color_moved = diff_color_moved_default;
 +              if (options->color_moved == COLOR_MOVED_NO)
 +                      options->color_moved = COLOR_MOVED_DEFAULT;
 +      } else if (!strcmp(arg, "--no-color-moved"))
 +              options->color_moved = COLOR_MOVED_NO;
 +      else if (skip_prefix(arg, "--color-moved=", &arg)) {
 +              int cm = parse_color_moved(arg);
 +              if (cm < 0)
 +                      die("bad --color-moved argument: %s", arg);
 +              options->color_moved = cm;
 +      } else if (!strcmp(arg, "--color-words")) {
                options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
        }
                return argcount;
        }
        else if (!strcmp(arg, "--exit-code"))
 -              DIFF_OPT_SET(options, EXIT_WITH_STATUS);
 +              options->flags.exit_with_status = 1;
        else if (!strcmp(arg, "--quiet"))
 -              DIFF_OPT_SET(options, QUICK);
 +              options->flags.quick = 1;
        else if (!strcmp(arg, "--ext-diff"))
 -              DIFF_OPT_SET(options, ALLOW_EXTERNAL);
 +              options->flags.allow_external = 1;
        else if (!strcmp(arg, "--no-ext-diff"))
 -              DIFF_OPT_CLR(options, ALLOW_EXTERNAL);
 -      else if (!strcmp(arg, "--textconv"))
 -              DIFF_OPT_SET(options, ALLOW_TEXTCONV);
 -      else if (!strcmp(arg, "--no-textconv"))
 -              DIFF_OPT_CLR(options, ALLOW_TEXTCONV);
 +              options->flags.allow_external = 0;
 +      else if (!strcmp(arg, "--textconv")) {
 +              options->flags.allow_textconv = 1;
 +              options->flags.textconv_set_via_cmdline = 1;
 +      } else if (!strcmp(arg, "--no-textconv"))
 +              options->flags.allow_textconv = 0;
        else if (!strcmp(arg, "--ignore-submodules")) {
 -              DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
 +              options->flags.override_submodule_config = 1;
                handle_ignore_submodules_arg(options, "all");
        } else if (skip_prefix(arg, "--ignore-submodules=", &arg)) {
 -              DIFF_OPT_SET(options, OVERRIDE_SUBMODULE_CONFIG);
 +              options->flags.override_submodule_config = 1;
                handle_ignore_submodules_arg(options, arg);
        } else if (!strcmp(arg, "--submodule"))
                options->submodule_format = DIFF_SUBMODULE_LOG;
                         &options->interhunkcontext))
                ;
        else if (!strcmp(arg, "-W"))
 -              DIFF_OPT_SET(options, FUNCCONTEXT);
 +              options->flags.funccontext = 1;
        else if (!strcmp(arg, "--function-context"))
 -              DIFF_OPT_SET(options, FUNCCONTEXT);
 +              options->flags.funccontext = 1;
        else if (!strcmp(arg, "--no-function-context"))
 -              DIFF_OPT_CLR(options, FUNCCONTEXT);
 +              options->flags.funccontext = 0;
        else if ((argcount = parse_long_opt("output", av, &optarg))) {
                char *path = prefix_filename(prefix, optarg);
 -              options->file = fopen(path, "w");
 -              if (!options->file)
 -                      die_errno("Could not open '%s'", path);
 +              options->file = xfopen(path, "w");
                options->close_file = 1;
                if (options->use_color != GIT_COLOR_ALWAYS)
                        options->use_color = GIT_COLOR_NEVER;
@@@ -5181,79 -4472,67 +5181,79 @@@ static void flush_one_pair(struct diff_
        }
  }
  
 -static void show_file_mode_name(FILE *file, const char *newdelete, struct diff_filespec *fs)
 +static void show_file_mode_name(struct diff_options *opt, const char *newdelete, struct diff_filespec *fs)
  {
 +      struct strbuf sb = STRBUF_INIT;
        if (fs->mode)
 -              fprintf(file, " %s mode %06o ", newdelete, fs->mode);
 +              strbuf_addf(&sb, " %s mode %06o ", newdelete, fs->mode);
        else
 -              fprintf(file, " %s ", newdelete);
 -      write_name_quoted(fs->path, file, '\n');
 -}
 +              strbuf_addf(&sb, " %s ", newdelete);
  
 +      quote_c_style(fs->path, &sb, NULL, 0);
 +      strbuf_addch(&sb, '\n');
 +      emit_diff_symbol(opt, DIFF_SYMBOL_SUMMARY,
 +                       sb.buf, sb.len, 0);
 +      strbuf_release(&sb);
 +}
  
 -static void show_mode_change(FILE *file, struct diff_filepair *p, int show_name,
 -              const char *line_prefix)
 +static void show_mode_change(struct diff_options *opt, struct diff_filepair *p,
 +              int show_name)
  {
        if (p->one->mode && p->two->mode && p->one->mode != p->two->mode) {
 -              fprintf(file, "%s mode change %06o => %06o%c", line_prefix, p->one->mode,
 -                      p->two->mode, show_name ? ' ' : '\n');
 +              struct strbuf sb = STRBUF_INIT;
 +              strbuf_addf(&sb, " mode change %06o => %06o",
 +                          p->one->mode, p->two->mode);
                if (show_name) {
 -                      write_name_quoted(p->two->path, file, '\n');
 +                      strbuf_addch(&sb, ' ');
 +                      quote_c_style(p->two->path, &sb, NULL, 0);
                }
 +              strbuf_addch(&sb, '\n');
 +              emit_diff_symbol(opt, DIFF_SYMBOL_SUMMARY,
 +                               sb.buf, sb.len, 0);
 +              strbuf_release(&sb);
        }
  }
  
 -static void show_rename_copy(FILE *file, const char *renamecopy, struct diff_filepair *p,
 -                      const char *line_prefix)
 +static void show_rename_copy(struct diff_options *opt, const char *renamecopy,
 +              struct diff_filepair *p)
  {
 +      struct strbuf sb = STRBUF_INIT;
        char *names = pprint_rename(p->one->path, p->two->path);
 -
 -      fprintf(file, " %s %s (%d%%)\n", renamecopy, names, similarity_index(p));
 +      strbuf_addf(&sb, " %s %s (%d%%)\n",
 +                      renamecopy, names, similarity_index(p));
        free(names);
 -      show_mode_change(file, p, 0, line_prefix);
 +      emit_diff_symbol(opt, DIFF_SYMBOL_SUMMARY,
 +                               sb.buf, sb.len, 0);
 +      show_mode_change(opt, p, 0);
 +      strbuf_release(&sb);
  }
  
  static void diff_summary(struct diff_options *opt, struct diff_filepair *p)
  {
 -      FILE *file = opt->file;
 -      const char *line_prefix = diff_line_prefix(opt);
 -
        switch(p->status) {
        case DIFF_STATUS_DELETED:
 -              fputs(line_prefix, file);
 -              show_file_mode_name(file, "delete", p->one);
 +              show_file_mode_name(opt, "delete", p->one);
                break;
        case DIFF_STATUS_ADDED:
 -              fputs(line_prefix, file);
 -              show_file_mode_name(file, "create", p->two);
 +              show_file_mode_name(opt, "create", p->two);
                break;
        case DIFF_STATUS_COPIED:
 -              fputs(line_prefix, file);
 -              show_rename_copy(file, "copy", p, line_prefix);
 +              show_rename_copy(opt, "copy", p);
                break;
        case DIFF_STATUS_RENAMED:
 -              fputs(line_prefix, file);
 -              show_rename_copy(file, "rename", p, line_prefix);
 +              show_rename_copy(opt, "rename", p);
                break;
        default:
                if (p->score) {
 -                      fprintf(file, "%s rewrite ", line_prefix);
 -                      write_name_quoted(p->two->path, file, ' ');
 -                      fprintf(file, "(%d%%)\n", similarity_index(p));
 +                      struct strbuf sb = STRBUF_INIT;
 +                      strbuf_addstr(&sb, " rewrite ");
 +                      quote_c_style(p->two->path, &sb, NULL, 0);
 +                      strbuf_addf(&sb, " (%d%%)\n", similarity_index(p));
 +                      emit_diff_symbol(opt, DIFF_SYMBOL_SUMMARY,
 +                                       sb.buf, sb.len, 0);
 +                      strbuf_release(&sb);
                }
 -              show_mode_change(file, p, !p->score, line_prefix);
 +              show_mode_change(opt, p, !p->score);
                break;
        }
  }
@@@ -5305,7 -4584,7 +5305,7 @@@ static void patch_id_add_mode(git_SHA_C
  }
  
  /* returns 0 upon success, and writes result into sha1 */
 -static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1, int diff_header_only)
 +static int diff_get_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only)
  {
        struct diff_queue_struct *q = &diff_queued_diff;
        int i;
                if (DIFF_PAIR_UNMERGED(p))
                        continue;
  
 -              diff_fill_sha1_info(p->one);
 -              diff_fill_sha1_info(p->two);
 +              diff_fill_oid_info(p->one);
 +              diff_fill_oid_info(p->two);
  
                len1 = remove_space(p->one->path, strlen(p->one->path));
                len2 = remove_space(p->two->path, strlen(p->two->path));
                if (diff_filespec_is_binary(p->one) ||
                    diff_filespec_is_binary(p->two)) {
                        git_SHA1_Update(&ctx, oid_to_hex(&p->one->oid),
 -                                      40);
 +                                      GIT_SHA1_HEXSZ);
                        git_SHA1_Update(&ctx, oid_to_hex(&p->two->oid),
 -                                      40);
 +                                      GIT_SHA1_HEXSZ);
                        continue;
                }
  
                                     p->one->path);
        }
  
 -      git_SHA1_Final(sha1, &ctx);
 +      git_SHA1_Final(oid->hash, &ctx);
        return 0;
  }
  
 -int diff_flush_patch_id(struct diff_options *options, unsigned char *sha1, int diff_header_only)
 +int diff_flush_patch_id(struct diff_options *options, struct object_id *oid, int diff_header_only)
  {
        struct diff_queue_struct *q = &diff_queued_diff;
        int i;
 -      int result = diff_get_patch_id(options, sha1, diff_header_only);
 +      int result = diff_get_patch_id(options, oid, diff_header_only);
  
        for (i = 0; i < q->nr; i++)
                diff_free_filepair(q->queue[i]);
@@@ -5458,51 -4737,6 +5458,51 @@@ void diff_warn_rename_limit(const char 
                warning(_(rename_limit_advice), varname, needed);
  }
  
 +static void diff_flush_patch_all_file_pairs(struct diff_options *o)
 +{
 +      int i;
 +      static struct emitted_diff_symbols esm = EMITTED_DIFF_SYMBOLS_INIT;
 +      struct diff_queue_struct *q = &diff_queued_diff;
 +
 +      if (WSEH_NEW & WS_RULE_MASK)
 +              die("BUG: WS rules bit mask overlaps with diff symbol flags");
 +
 +      if (o->color_moved)
 +              o->emitted_symbols = &esm;
 +
 +      for (i = 0; i < q->nr; i++) {
 +              struct diff_filepair *p = q->queue[i];
 +              if (check_pair_status(p))
 +                      diff_flush_patch(p, o);
 +      }
 +
 +      if (o->emitted_symbols) {
 +              if (o->color_moved) {
 +                      struct hashmap add_lines, del_lines;
 +
 +                      hashmap_init(&del_lines,
 +                                   (hashmap_cmp_fn)moved_entry_cmp, o, 0);
 +                      hashmap_init(&add_lines,
 +                                   (hashmap_cmp_fn)moved_entry_cmp, o, 0);
 +
 +                      add_lines_to_move_detection(o, &add_lines, &del_lines);
 +                      mark_color_as_moved(o, &add_lines, &del_lines);
 +                      if (o->color_moved == COLOR_MOVED_ZEBRA_DIM)
 +                              dim_moved_lines(o);
 +
 +                      hashmap_free(&add_lines, 0);
 +                      hashmap_free(&del_lines, 0);
 +              }
 +
 +              for (i = 0; i < esm.nr; i++)
 +                      emit_diff_symbol_from_struct(o, &esm.buf[i]);
 +
 +              for (i = 0; i < esm.nr; i++)
 +                      free((void *)esm.buf[i].line);
 +      }
 +      esm.nr = 0;
 +}
 +
  void diff_flush(struct diff_options *options)
  {
        struct diff_queue_struct *q = &diff_queued_diff;
                separator++;
        }
  
 -      if (output_format & DIFF_FORMAT_DIRSTAT && DIFF_OPT_TST(options, DIRSTAT_BY_LINE))
 +      if (output_format & DIFF_FORMAT_DIRSTAT && options->flags.dirstat_by_line)
                dirstat_by_line = 1;
  
        if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT) ||
        }
  
        if (output_format & DIFF_FORMAT_NO_OUTPUT &&
 -          DIFF_OPT_TST(options, EXIT_WITH_STATUS) &&
 -          DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) {
 +          options->flags.exit_with_status &&
 +          options->flags.diff_from_contents) {
                /*
                 * run diff_flush_patch for the exit status. setting
                 * options->file to /dev/null should be safe, because we
                 */
                if (options->close_file)
                        fclose(options->file);
 -              options->file = fopen("/dev/null", "w");
 -              if (!options->file)
 -                      die_errno("Could not open /dev/null");
 +              options->file = xfopen("/dev/null", "w");
                options->close_file = 1;
 +              options->color_moved = 0;
                for (i = 0; i < q->nr; i++) {
                        struct diff_filepair *p = q->queue[i];
                        if (check_pair_status(p))
  
        if (output_format & DIFF_FORMAT_PATCH) {
                if (separator) {
 -                      fprintf(options->file, "%s%c",
 -                              diff_line_prefix(options),
 -                              options->line_termination);
 -                      if (options->stat_sep) {
 +                      emit_diff_symbol(options, DIFF_SYMBOL_SEPARATOR, NULL, 0, 0);
 +                      if (options->stat_sep)
                                /* attach patch instead of inline */
 -                              fputs(options->stat_sep, options->file);
 -                      }
 +                              emit_diff_symbol(options, DIFF_SYMBOL_STAT_SEP,
 +                                               NULL, 0, 0);
                }
  
 -              for (i = 0; i < q->nr; i++) {
 -                      struct diff_filepair *p = q->queue[i];
 -                      if (check_pair_status(p))
 -                              diff_flush_patch(p, options);
 -              }
 +              diff_flush_patch_all_file_pairs(options);
        }
  
        if (output_format & DIFF_FORMAT_CALLBACK)
@@@ -5613,11 -4854,11 +5613,11 @@@ free_queue
         * 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->flags.diff_from_contents) {
                if (options->found_changes)
 -                      DIFF_OPT_SET(options, HAS_CHANGES);
 +                      options->flags.has_changes = 1;
                else
 -                      DIFF_OPT_CLR(options, HAS_CHANGES);
 +                      options->flags.has_changes = 0;
        }
  }
  
@@@ -5737,7 -4978,7 +5737,7 @@@ static void diffcore_skip_stat_unmatch(
                         * to determine how many paths were dirty only
                         * due to stat info mismatch.
                         */
 -                      if (!DIFF_OPT_TST(diffopt, NO_INDEX))
 +                      if (!diffopt->flags.no_index)
                                diffopt->skip_stat_unmatch++;
                        diff_free_filepair(p);
                }
@@@ -5786,10 -5027,10 +5786,10 @@@ void diffcore_std(struct diff_options *
                diff_resolve_rename_copy();
        diffcore_apply_filter(options);
  
 -      if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
 -              DIFF_OPT_SET(options, HAS_CHANGES);
 +      if (diff_queued_diff.nr && !options->flags.diff_from_contents)
 +              options->flags.has_changes = 1;
        else
 -              DIFF_OPT_CLR(options, HAS_CHANGES);
 +              options->flags.has_changes = 0;
  
        options->found_follow = 0;
  }
@@@ -5801,23 -5042,23 +5801,23 @@@ int diff_result_code(struct diff_option
        diff_warn_rename_limit("diff.renameLimit",
                               opt->needed_rename_limit,
                               opt->degraded_cc_to_c);
 -      if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
 +      if (!opt->flags.exit_with_status &&
            !(opt->output_format & DIFF_FORMAT_CHECKDIFF))
                return status;
 -      if (DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
 -          DIFF_OPT_TST(opt, HAS_CHANGES))
 +      if (opt->flags.exit_with_status &&
 +          opt->flags.has_changes)
                result |= 01;
        if ((opt->output_format & DIFF_FORMAT_CHECKDIFF) &&
 -          DIFF_OPT_TST(opt, CHECK_FAILED))
 +          opt->flags.check_failed)
                result |= 02;
        return result;
  }
  
  int diff_can_quit_early(struct diff_options *opt)
  {
 -      return (DIFF_OPT_TST(opt, QUICK) &&
 +      return (opt->flags.quick &&
                !opt->filter &&
 -              DIFF_OPT_TST(opt, HAS_CHANGES));
 +              opt->flags.has_changes);
  }
  
  /*
  static int is_submodule_ignored(const char *path, struct diff_options *options)
  {
        int ignored = 0;
 -      unsigned orig_flags = options->flags;
 -      if (!DIFF_OPT_TST(options, OVERRIDE_SUBMODULE_CONFIG))
 +      struct diff_flags orig_flags = options->flags;
 +      if (!options->flags.override_submodule_config)
                set_diffopt_flags_from_submodule_config(options, path);
 -      if (DIFF_OPT_TST(options, IGNORE_SUBMODULES))
 +      if (options->flags.ignore_submodules)
                ignored = 1;
        options->flags = orig_flags;
        return ignored;
  
  void diff_addremove(struct diff_options *options,
                    int addremove, unsigned mode,
 -                  const unsigned char *sha1,
 -                  int sha1_valid,
 +                  const struct object_id *oid,
 +                  int oid_valid,
                    const char *concatpath, unsigned dirty_submodule)
  {
        struct diff_filespec *one, *two;
         * Before the final output happens, they are pruned after
         * merged into rename/copy pairs as appropriate.
         */
 -      if (DIFF_OPT_TST(options, REVERSE_DIFF))
 +      if (options->flags.reverse_diff)
                addremove = (addremove == '+' ? '-' :
                             addremove == '-' ? '+' : addremove);
  
        two = alloc_filespec(concatpath);
  
        if (addremove != '+')
 -              fill_filespec(one, sha1, sha1_valid, mode);
 +              fill_filespec(one, oid, oid_valid, mode);
        if (addremove != '-') {
 -              fill_filespec(two, sha1, sha1_valid, mode);
 +              fill_filespec(two, oid, oid_valid, mode);
                two->dirty_submodule = dirty_submodule;
        }
  
        diff_queue(&diff_queued_diff, one, two);
 -      if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
 -              DIFF_OPT_SET(options, HAS_CHANGES);
 +      if (!options->flags.diff_from_contents)
 +              options->flags.has_changes = 1;
  }
  
  void diff_change(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
 -               const unsigned char *old_sha1,
 -               const unsigned char *new_sha1,
 -               int old_sha1_valid, int new_sha1_valid,
 +               const struct object_id *old_oid,
 +               const struct object_id *new_oid,
 +               int old_oid_valid, int new_oid_valid,
                 const char *concatpath,
                 unsigned old_dirty_submodule, unsigned new_dirty_submodule)
  {
            is_submodule_ignored(concatpath, options))
                return;
  
 -      if (DIFF_OPT_TST(options, REVERSE_DIFF)) {
 +      if (options->flags.reverse_diff) {
                SWAP(old_mode, new_mode);
 -              SWAP(old_sha1, new_sha1);
 -              SWAP(old_sha1_valid, new_sha1_valid);
 +              SWAP(old_oid, new_oid);
 +              SWAP(old_oid_valid, new_oid_valid);
                SWAP(old_dirty_submodule, new_dirty_submodule);
        }
  
  
        one = alloc_filespec(concatpath);
        two = alloc_filespec(concatpath);
 -      fill_filespec(one, old_sha1, old_sha1_valid, old_mode);
 -      fill_filespec(two, new_sha1, new_sha1_valid, new_mode);
 +      fill_filespec(one, old_oid, old_oid_valid, old_mode);
 +      fill_filespec(two, new_oid, new_oid_valid, new_mode);
        one->dirty_submodule = old_dirty_submodule;
        two->dirty_submodule = new_dirty_submodule;
        p = diff_queue(&diff_queued_diff, one, two);
  
 -      if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
 +      if (options->flags.diff_from_contents)
                return;
  
 -      if (DIFF_OPT_TST(options, QUICK) && options->skip_stat_unmatch &&
 +      if (options->flags.quick && options->skip_stat_unmatch &&
            !diff_filespec_check_stat_unmatch(p))
                return;
  
 -      DIFF_OPT_SET(options, HAS_CHANGES);
 +      options->flags.has_changes = 1;
  }
  
  struct diff_filepair *diff_unmerge(struct diff_options *options, const char *path)
@@@ -6003,7 -5244,7 +6003,7 @@@ size_t fill_textconv(struct userdiff_dr
  
        if (driver->textconv_cache && df->oid_valid) {
                *outbuf = notes_cache_get(driver->textconv_cache,
 -                                        df->oid.hash,
 +                                        &df->oid,
                                          &size);
                if (*outbuf)
                        return size;
  
        if (driver->textconv_cache && df->oid_valid) {
                /* ignore errors, as we might be in a readonly repository */
 -              notes_cache_put(driver->textconv_cache, df->oid.hash, *outbuf,
 +              notes_cache_put(driver->textconv_cache, &df->oid, *outbuf,
                                size);
                /*
                 * we could save up changes and flush them all at the end,
        return size;
  }
  
 +int textconv_object(const char *path,
 +                  unsigned mode,
 +                  const struct object_id *oid,
 +                  int oid_valid,
 +                  char **buf,
 +                  unsigned long *buf_size)
 +{
 +      struct diff_filespec *df;
 +      struct userdiff_driver *textconv;
 +
 +      df = alloc_filespec(path);
 +      fill_filespec(df, oid, oid_valid, mode);
 +      textconv = get_textconv(df);
 +      if (!textconv) {
 +              free_filespec(df);
 +              return 0;
 +      }
 +
 +      *buf_size = fill_textconv(textconv, df, buf);
 +      free_filespec(df);
 +      return 1;
 +}
 +
  void setup_diff_pager(struct diff_options *opt)
  {
        /*
         * and because it is easy to find people oneline advising "git diff
         * --exit-code" in hooks and other scripts, we do not do so.
         */
 -      if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) &&
 +      if (!opt->flags.exit_with_status &&
            check_pager_config("diff") != 0)
                setup_pager();
  }
diff --combined merge-recursive.c
index b48b15a6fd88a89c03b259ebc3efed361c7f4d4a,006b94baf2991d5c81dcc02f86da66473e949626..85b4e58478fad71ca754673777dc255922773b7f
@@@ -4,7 -4,6 +4,7 @@@
   * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006
   */
  #include "cache.h"
 +#include "config.h"
  #include "advice.h"
  #include "lockfile.h"
  #include "cache-tree.h"
  #include "dir.h"
  #include "submodule.h"
  
 +struct path_hashmap_entry {
 +      struct hashmap_entry e;
 +      char path[FLEX_ARRAY];
 +};
 +
 +static int path_hashmap_cmp(const void *cmp_data,
 +                          const void *entry,
 +                          const void *entry_or_key,
 +                          const void *keydata)
 +{
 +      const struct path_hashmap_entry *a = entry;
 +      const struct path_hashmap_entry *b = entry_or_key;
 +      const char *key = keydata;
 +
 +      if (ignore_case)
 +              return strcasecmp(a->path, key ? key : b->path);
 +      else
 +              return strcmp(a->path, key ? key : b->path);
 +}
 +
 +static unsigned int path_hash(const char *path)
 +{
 +      return ignore_case ? strihash(path) : strhash(path);
 +}
 +
  static void flush_output(struct merge_options *o)
  {
        if (o->buffer_output < 2 && o->obuf.len) {
@@@ -93,7 -67,7 +93,7 @@@ static struct tree *shift_tree_object(s
        }
        if (!oidcmp(&two->object.oid, &shifted))
                return two;
 -      return lookup_tree(shifted.hash);
 +      return lookup_tree(&shifted);
  }
  
  static struct commit *make_virtual_commit(struct tree *tree, const char *comment)
@@@ -330,7 -304,7 +330,7 @@@ struct tree *write_tree_from_memory(str
                return NULL;
        }
  
 -      result = lookup_tree(active_cache_tree->sha1);
 +      result = lookup_tree(&active_cache_tree->oid);
  
        return result;
  }
@@@ -339,25 -313,29 +339,25 @@@ static int save_files_dirs(const unsign
                struct strbuf *base, const char *path,
                unsigned int mode, int stage, void *context)
  {
 +      struct path_hashmap_entry *entry;
        int baselen = base->len;
        struct merge_options *o = context;
  
        strbuf_addstr(base, path);
  
 -      if (S_ISDIR(mode))
 -              string_list_insert(&o->current_directory_set, base->buf);
 -      else
 -              string_list_insert(&o->current_file_set, base->buf);
 +      FLEX_ALLOC_MEM(entry, path, base->buf, base->len);
 +      hashmap_entry_init(entry, path_hash(entry->path));
 +      hashmap_add(&o->current_file_dir_set, entry);
  
        strbuf_setlen(base, baselen);
        return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
  }
  
 -static int get_files_dirs(struct merge_options *o, struct tree *tree)
 +static void get_files_dirs(struct merge_options *o, struct tree *tree)
  {
 -      int n;
        struct pathspec match_all;
        memset(&match_all, 0, sizeof(match_all));
 -      if (read_tree_recursive(tree, "", 0, 0, &match_all, save_files_dirs, o))
 -              return 0;
 -      n = o->current_file_set.nr + o->current_directory_set.nr;
 -      return n;
 +      read_tree_recursive(tree, "", 0, 0, &match_all, save_files_dirs, o);
  }
  
  /*
@@@ -540,8 -518,8 +540,8 @@@ static struct string_list *get_renames(
                return renames;
  
        diff_setup(&opts);
 -      DIFF_OPT_SET(&opts, RECURSIVE);
 -      DIFF_OPT_CLR(&opts, RENAME_EMPTY);
 +      opts.flags.recursive = 1;
 +      opts.flags.rename_empty = 0;
        opts.detect_rename = DIFF_DETECT_RENAME;
        opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit :
                            o->diff_rename_limit >= 0 ? o->diff_rename_limit :
        opts.show_rename_progress = o->show_rename_progress;
        opts.output_format = DIFF_FORMAT_NO_OUTPUT;
        diff_setup_done(&opts);
 -      diff_tree_sha1(o_tree->object.oid.hash, tree->object.oid.hash, "", &opts);
 +      diff_tree_oid(&o_tree->object.oid, &tree->object.oid, "", &opts);
        diffcore_std(&opts);
        if (opts.needed_rename_limit > o->needed_rename_limit)
                o->needed_rename_limit = opts.needed_rename_limit;
@@@ -667,7 -645,6 +667,7 @@@ static void add_flattened_path(struct s
  
  static char *unique_path(struct merge_options *o, const char *path, const char *branch)
  {
 +      struct path_hashmap_entry *entry;
        struct strbuf newpath = STRBUF_INIT;
        int suffix = 0;
        size_t base_len;
        add_flattened_path(&newpath, branch);
  
        base_len = newpath.len;
 -      while (string_list_has_string(&o->current_file_set, newpath.buf) ||
 -             string_list_has_string(&o->current_directory_set, newpath.buf) ||
 +      while (hashmap_get_from_hash(&o->current_file_dir_set,
 +                                   path_hash(newpath.buf), newpath.buf) ||
               (!o->call_depth && file_exists(newpath.buf))) {
                strbuf_setlen(&newpath, base_len);
                strbuf_addf(&newpath, "_%d", suffix++);
        }
  
 -      string_list_insert(&o->current_file_set, newpath.buf);
 +      FLEX_ALLOC_MEM(entry, path, newpath.buf, newpath.len);
 +      hashmap_entry_init(entry, path_hash(entry->path));
 +      hashmap_add(&o->current_file_dir_set, entry);
        return strbuf_detach(&newpath, NULL);
  }
  
@@@ -1019,11 -994,11 +1019,11 @@@ static int merge_file_1(struct merge_op
                                return ret;
                        result->clean = (merge_status == 0);
                } else if (S_ISGITLINK(a->mode)) {
 -                      result->clean = merge_submodule(result->oid.hash,
 +                      result->clean = merge_submodule(&result->oid,
                                                       one->path,
 -                                                     one->oid.hash,
 -                                                     a->oid.hash,
 -                                                     b->oid.hash,
 +                                                     &one->oid,
 +                                                     &a->oid,
 +                                                     &b->oid,
                                                       !o->call_depth);
                } else if (S_ISLNK(a->mode)) {
                        oidcpy(&result->oid, &a->oid);
@@@ -1664,8 -1639,8 +1664,8 @@@ static int blob_unchanged(struct merge_
         * performed.  Comparison can be skipped if both files are
         * unchanged since their sha1s have already been compared.
         */
 -      if (renormalize_buffer(path, o.buf, o.len, &o) |
 -          renormalize_buffer(path, a.buf, a.len, &a))
 +      if (renormalize_buffer(&the_index, path, o.buf, o.len, &o) |
 +          renormalize_buffer(&the_index, path, a.buf, a.len, &a))
                ret = (o.len == a.len && !memcmp(o.buf, a.buf, o.len));
  
  error_return:
@@@ -1951,7 -1926,7 +1951,7 @@@ int merge_trees(struct merge_options *o
        }
  
        if (oid_eq(&common->object.oid, &merge->object.oid)) {
 -              output(o, 0, _("Already up-to-date!"));
 +              output(o, 0, _("Already up to date!"));
                *result = head;
                return 1;
        }
        if (unmerged_cache()) {
                struct string_list *entries, *re_head, *re_merge;
                int i;
 -              string_list_clear(&o->current_file_set, 1);
 -              string_list_clear(&o->current_directory_set, 1);
 +              /*
 +               * Only need the hashmap while processing entries, so
 +               * initialize it here and free it when we are done running
 +               * through the entries. Keeping it in the merge_options as
 +               * opposed to decaring a local hashmap is for convenience
 +               * so that we don't have to pass it to around.
 +               */
 +              hashmap_init(&o->current_file_dir_set, path_hashmap_cmp, NULL, 512);
                get_files_dirs(o, head);
                get_files_dirs(o, merge);
  
                re_merge = get_renames(o, merge, common, head, merge, entries);
                clean = process_renames(o, re_head, re_merge);
                if (clean < 0)
 -                      return clean;
 +                      goto cleanup;
                for (i = entries->nr-1; 0 <= i; i--) {
                        const char *path = entries->items[i].string;
                        struct stage_data *e = entries->items[i].util;
                                int ret = process_entry(o, path, e);
                                if (!ret)
                                        clean = 0;
 -                              else if (ret < 0)
 -                                      return ret;
 +                              else if (ret < 0) {
 +                                      clean = ret;
 +                                      goto cleanup;
 +                              }
                        }
                }
                for (i = 0; i < entries->nr; i++) {
                                    entries->items[i].string);
                }
  
 +cleanup:
                string_list_clear(re_merge, 0);
                string_list_clear(re_head, 0);
                string_list_clear(entries, 1);
  
 +              hashmap_free(&o->current_file_dir_set, 1);
 +
                free(re_merge);
                free(re_head);
                free(entries);
 +
 +              if (clean < 0)
 +                      return clean;
        }
        else
                clean = 1;
@@@ -2081,7 -2042,7 +2081,7 @@@ int merge_recursive(struct merge_option
                /* if there is no common ancestor, use an empty tree */
                struct tree *tree;
  
 -              tree = lookup_tree(EMPTY_TREE_SHA1_BIN);
 +              tree = lookup_tree(&empty_tree_oid);
                merged_common_ancestors = make_virtual_commit(tree, "ancestor");
        }
  
@@@ -2142,7 -2103,7 +2142,7 @@@ static struct commit *get_ref(const str
  {
        struct object *object;
  
 -      object = deref_tag(parse_object(oid->hash), name, strlen(name));
 +      object = deref_tag(parse_object(oid), name, strlen(name));
        if (!object)
                return NULL;
        if (object->type == OBJ_TREE)
@@@ -2162,7 -2123,7 +2162,7 @@@ int merge_recursive_generic(struct merg
                            struct commit **result)
  {
        int clean;
 -      struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
 +      struct lock_file lock = LOCK_INIT;
        struct commit *head_commit = get_ref(head, o->branch1);
        struct commit *next_commit = get_ref(merge, o->branch2);
        struct commit_list *ca = NULL;
                }
        }
  
 -      hold_locked_index(lock, LOCK_DIE_ON_ERROR);
 +      hold_locked_index(&lock, LOCK_DIE_ON_ERROR);
        clean = merge_recursive(o, head_commit, next_commit, ca,
                        result);
        if (clean < 0)
                return clean;
  
        if (active_cache_changed &&
 -          write_locked_index(&the_index, lock, COMMIT_LOCK))
 +          write_locked_index(&the_index, &lock, COMMIT_LOCK))
                return err(o, _("Unable to write index."));
  
        return clean ? 0 : 1;
@@@ -2201,7 -2162,6 +2201,7 @@@ static void merge_recursive_config(stru
  
  void init_merge_options(struct merge_options *o)
  {
 +      const char *merge_verbosity;
        memset(o, 0, sizeof(struct merge_options));
        o->verbosity = 2;
        o->buffer_output = 1;
        o->renormalize = 0;
        o->detect_rename = 1;
        merge_recursive_config(o);
 -      if (getenv("GIT_MERGE_VERBOSITY"))
 -              o->verbosity =
 -                      strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10);
 +      merge_verbosity = getenv("GIT_MERGE_VERBOSITY");
 +      if (merge_verbosity)
 +              o->verbosity = strtol(merge_verbosity, NULL, 10);
        if (o->verbosity >= 5)
                o->buffer_output = 0;
        strbuf_init(&o->obuf, 0);
 -      string_list_init(&o->current_file_set, 1);
 -      string_list_init(&o->current_directory_set, 1);
        string_list_init(&o->df_conflict_file_set, 1);
  }
  
@@@ -2252,6 -2214,8 +2252,8 @@@ int parse_merge_opt(struct merge_option
                DIFF_XDL_SET(o, IGNORE_WHITESPACE);
        else if (!strcmp(s, "ignore-space-at-eol"))
                DIFF_XDL_SET(o, IGNORE_WHITESPACE_AT_EOL);
+       else if (!strcmp(s, "ignore-cr-at-eol"))
+               DIFF_XDL_SET(o, IGNORE_CR_AT_EOL);
        else if (!strcmp(s, "renormalize"))
                o->renormalize = 1;
        else if (!strcmp(s, "no-renormalize"))
index 6c9a93b734542cc17fa1591525dc3c49019b77fe,32dd54c21da6a3d76b753009f1a5c53eba3a90e6..559a7541a83eb8bd8b465aac154cf67f8783b13d
@@@ -106,6 -106,8 +106,8 @@@ test_expect_success 'another test, with
        git diff -w -b --ignore-space-at-eol >out &&
        test_cmp expect out &&
  
+       git diff -w --ignore-cr-at-eol >out &&
+       test_cmp expect out &&
  
        tr "Q_" "\015 " <<-\EOF >expect &&
        diff --git a/x b/x
        git diff -b --ignore-space-at-eol >out &&
        test_cmp expect out &&
  
+       git diff -b --ignore-cr-at-eol >out &&
+       test_cmp expect out &&
        tr "Q_" "\015 " <<-\EOF >expect &&
        diff --git a/x b/x
        index d99af23..22d9f73 100644
         CR at end
        EOF
        git diff --ignore-space-at-eol >out &&
+       test_cmp expect out &&
+       git diff --ignore-space-at-eol --ignore-cr-at-eol >out &&
+       test_cmp expect out &&
+       tr "Q_" "\015 " <<-\EOF >expect &&
+       diff --git a/x b/x
+       index_d99af23..22d9f73 100644
+       --- a/x
+       +++ b/x
+       @@ -1,6 +1,6 @@
+       -whitespace at beginning
+       -whitespace change
+       -whitespace in the middle
+       -whitespace at end
+       +_      whitespace at beginning
+       +whitespace_    _change
+       +white space in the middle
+       +whitespace at end__
+        unchanged line
+        CR at end
+       EOF
+       git diff --ignore-cr-at-eol >out &&
        test_cmp expect out
  '
  
@@@ -155,7 -183,7 +183,7 @@@ test_expect_success 'ignore-blank-lines
  " >x &&
        git diff --ignore-blank-lines >out &&
        >expect &&
 -      test_cmp out expect
 +      test_cmp expect out
  '
  
  test_expect_success 'ignore-blank-lines: only new lines with space' '
   " >x &&
        git diff -w --ignore-blank-lines >out &&
        >expect &&
 -      test_cmp out expect
 +      test_cmp expect out
  '
  
  test_expect_success 'ignore-blank-lines: after change' '
@@@ -802,6 -830,7 +830,6 @@@ test_expect_success 'combined diff wit
  # Start testing the colored format for whitespace checks
  
  test_expect_success 'setup diff colors' '
 -      git config color.diff always &&
        git config color.diff.plain normal &&
        git config color.diff.meta bold &&
        git config color.diff.frag cyan &&
@@@ -820,7 -849,7 +848,7 @@@ test_expect_success 'diff that introduc
        echo "test" >x &&
        git commit -m "initial" x &&
        echo "{NTN}" | tr "NT" "\n\t" >>x &&
 -      git -c color.diff=always diff | test_decode_color >current &&
 +      git diff --color | test_decode_color >current &&
  
        cat >expected <<-\EOF &&
        <BOLD>diff --git a/x b/x<RESET>
@@@ -850,7 -879,7 +878,7 @@@ test_expect_success 'diff that introduc
                echo "2. and a new line "
        } >x &&
  
 -      git -c color.diff=always diff |
 +      git diff --color |
        test_decode_color >current &&
  
        cat >expected <<-\EOF &&
@@@ -922,15 -951,15 +950,15 @@@ test_expect_success 'ws-error-highligh
  
  test_expect_success 'test --ws-error-highlight option' '
  
 -      git -c color.diff=always diff --ws-error-highlight=default,old |
 +      git diff --color --ws-error-highlight=default,old |
        test_decode_color >current &&
        test_cmp expect.default-old current &&
  
 -      git -c color.diff=always diff --ws-error-highlight=all |
 +      git diff --color --ws-error-highlight=all |
        test_decode_color >current &&
        test_cmp expect.all current &&
  
 -      git -c color.diff=always diff --ws-error-highlight=none |
 +      git diff --color --ws-error-highlight=none |
        test_decode_color >current &&
        test_cmp expect.none current
  
  
  test_expect_success 'test diff.wsErrorHighlight config' '
  
 -      git -c color.diff=always -c diff.wsErrorHighlight=default,old diff |
 +      git -c diff.wsErrorHighlight=default,old diff --color |
        test_decode_color >current &&
        test_cmp expect.default-old current &&
  
 -      git -c color.diff=always -c diff.wsErrorHighlight=all diff |
 +      git -c diff.wsErrorHighlight=all diff --color |
        test_decode_color >current &&
        test_cmp expect.all current &&
  
 -      git -c color.diff=always -c diff.wsErrorHighlight=none diff |
 +      git -c diff.wsErrorHighlight=none diff --color |
        test_decode_color >current &&
        test_cmp expect.none current
  
  
  test_expect_success 'option overrides diff.wsErrorHighlight' '
  
 -      git -c color.diff=always -c diff.wsErrorHighlight=none \
 -              diff --ws-error-highlight=default,old |
 +      git -c diff.wsErrorHighlight=none \
 +              diff --color --ws-error-highlight=default,old |
        test_decode_color >current &&
        test_cmp expect.default-old current &&
  
 -      git -c color.diff=always -c diff.wsErrorHighlight=default \
 -              diff --ws-error-highlight=all |
 +      git -c diff.wsErrorHighlight=default \
 +              diff --color --ws-error-highlight=all |
        test_decode_color >current &&
        test_cmp expect.all current &&
  
 -      git -c color.diff=always -c diff.wsErrorHighlight=all \
 -              diff --ws-error-highlight=none |
 +      git -c diff.wsErrorHighlight=all \
 +              diff --color --ws-error-highlight=none |
        test_decode_color >current &&
        test_cmp expect.none current
  
  '
  
 +test_expect_success 'detect moved code, complete file' '
 +      git reset --hard &&
 +      cat <<-\EOF >test.c &&
 +      #include<stdio.h>
 +      main()
 +      {
 +      printf("Hello World");
 +      }
 +      EOF
 +      git add test.c &&
 +      git commit -m "add main function" &&
 +      git mv test.c main.c &&
 +      test_config color.diff.oldMoved "normal red" &&
 +      test_config color.diff.newMoved "normal green" &&
 +      git diff HEAD --color-moved=zebra --color --no-renames | test_decode_color >actual &&
 +      cat >expected <<-\EOF &&
 +      <BOLD>diff --git a/main.c b/main.c<RESET>
 +      <BOLD>new file mode 100644<RESET>
 +      <BOLD>index 0000000..a986c57<RESET>
 +      <BOLD>--- /dev/null<RESET>
 +      <BOLD>+++ b/main.c<RESET>
 +      <CYAN>@@ -0,0 +1,5 @@<RESET>
 +      <BGREEN>+<RESET><BGREEN>#include<stdio.h><RESET>
 +      <BGREEN>+<RESET><BGREEN>main()<RESET>
 +      <BGREEN>+<RESET><BGREEN>{<RESET>
 +      <BGREEN>+<RESET><BGREEN>printf("Hello World");<RESET>
 +      <BGREEN>+<RESET><BGREEN>}<RESET>
 +      <BOLD>diff --git a/test.c b/test.c<RESET>
 +      <BOLD>deleted file mode 100644<RESET>
 +      <BOLD>index a986c57..0000000<RESET>
 +      <BOLD>--- a/test.c<RESET>
 +      <BOLD>+++ /dev/null<RESET>
 +      <CYAN>@@ -1,5 +0,0 @@<RESET>
 +      <BRED>-#include<stdio.h><RESET>
 +      <BRED>-main()<RESET>
 +      <BRED>-{<RESET>
 +      <BRED>-printf("Hello World");<RESET>
 +      <BRED>-}<RESET>
 +      EOF
 +
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'detect malicious moved code, inside file' '
 +      test_config color.diff.oldMoved "normal red" &&
 +      test_config color.diff.newMoved "normal green" &&
 +      test_config color.diff.oldMovedAlternative "blue" &&
 +      test_config color.diff.newMovedAlternative "yellow" &&
 +      git reset --hard &&
 +      cat <<-\EOF >main.c &&
 +              #include<stdio.h>
 +              int stuff()
 +              {
 +                      printf("Hello ");
 +                      printf("World\n");
 +              }
 +
 +              int secure_foo(struct user *u)
 +              {
 +                      if (!u->is_allowed_foo)
 +                              return;
 +                      foo(u);
 +              }
 +
 +              int main()
 +              {
 +                      foo();
 +              }
 +      EOF
 +      cat <<-\EOF >test.c &&
 +              #include<stdio.h>
 +              int bar()
 +              {
 +                      printf("Hello World, but different\n");
 +              }
 +
 +              int another_function()
 +              {
 +                      bar();
 +              }
 +      EOF
 +      git add main.c test.c &&
 +      git commit -m "add main and test file" &&
 +      cat <<-\EOF >main.c &&
 +              #include<stdio.h>
 +              int stuff()
 +              {
 +                      printf("Hello ");
 +                      printf("World\n");
 +              }
 +
 +              int main()
 +              {
 +                      foo();
 +              }
 +      EOF
 +      cat <<-\EOF >test.c &&
 +              #include<stdio.h>
 +              int bar()
 +              {
 +                      printf("Hello World, but different\n");
 +              }
 +
 +              int secure_foo(struct user *u)
 +              {
 +                      foo(u);
 +                      if (!u->is_allowed_foo)
 +                              return;
 +              }
 +
 +              int another_function()
 +              {
 +                      bar();
 +              }
 +      EOF
 +      git diff HEAD --no-renames --color-moved=zebra --color | test_decode_color >actual &&
 +      cat <<-\EOF >expected &&
 +      <BOLD>diff --git a/main.c b/main.c<RESET>
 +      <BOLD>index 27a619c..7cf9336 100644<RESET>
 +      <BOLD>--- a/main.c<RESET>
 +      <BOLD>+++ b/main.c<RESET>
 +      <CYAN>@@ -5,13 +5,6 @@<RESET> <RESET>printf("Hello ");<RESET>
 +       printf("World\n");<RESET>
 +       }<RESET>
 +       <RESET>
 +      <BRED>-int secure_foo(struct user *u)<RESET>
 +      <BRED>-{<RESET>
 +      <BLUE>-if (!u->is_allowed_foo)<RESET>
 +      <BLUE>-return;<RESET>
 +      <RED>-foo(u);<RESET>
 +      <RED>-}<RESET>
 +      <RED>-<RESET>
 +       int main()<RESET>
 +       {<RESET>
 +       foo();<RESET>
 +      <BOLD>diff --git a/test.c b/test.c<RESET>
 +      <BOLD>index 1dc1d85..2bedec9 100644<RESET>
 +      <BOLD>--- a/test.c<RESET>
 +      <BOLD>+++ b/test.c<RESET>
 +      <CYAN>@@ -4,6 +4,13 @@<RESET> <RESET>int bar()<RESET>
 +       printf("Hello World, but different\n");<RESET>
 +       }<RESET>
 +       <RESET>
 +      <BGREEN>+<RESET><BGREEN>int secure_foo(struct user *u)<RESET>
 +      <BGREEN>+<RESET><BGREEN>{<RESET>
 +      <GREEN>+<RESET><GREEN>foo(u);<RESET>
 +      <BGREEN>+<RESET><BGREEN>if (!u->is_allowed_foo)<RESET>
 +      <BGREEN>+<RESET><BGREEN>return;<RESET>
 +      <GREEN>+<RESET><GREEN>}<RESET>
 +      <GREEN>+<RESET>
 +       int another_function()<RESET>
 +       {<RESET>
 +       bar();<RESET>
 +      EOF
 +
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'plain moved code, inside file' '
 +      test_config color.diff.oldMoved "normal red" &&
 +      test_config color.diff.newMoved "normal green" &&
 +      test_config color.diff.oldMovedAlternative "blue" &&
 +      test_config color.diff.newMovedAlternative "yellow" &&
 +      # needs previous test as setup
 +      git diff HEAD --no-renames --color-moved=plain --color | test_decode_color >actual &&
 +      cat <<-\EOF >expected &&
 +      <BOLD>diff --git a/main.c b/main.c<RESET>
 +      <BOLD>index 27a619c..7cf9336 100644<RESET>
 +      <BOLD>--- a/main.c<RESET>
 +      <BOLD>+++ b/main.c<RESET>
 +      <CYAN>@@ -5,13 +5,6 @@<RESET> <RESET>printf("Hello ");<RESET>
 +       printf("World\n");<RESET>
 +       }<RESET>
 +       <RESET>
 +      <BRED>-int secure_foo(struct user *u)<RESET>
 +      <BRED>-{<RESET>
 +      <BRED>-if (!u->is_allowed_foo)<RESET>
 +      <BRED>-return;<RESET>
 +      <BRED>-foo(u);<RESET>
 +      <BRED>-}<RESET>
 +      <BRED>-<RESET>
 +       int main()<RESET>
 +       {<RESET>
 +       foo();<RESET>
 +      <BOLD>diff --git a/test.c b/test.c<RESET>
 +      <BOLD>index 1dc1d85..2bedec9 100644<RESET>
 +      <BOLD>--- a/test.c<RESET>
 +      <BOLD>+++ b/test.c<RESET>
 +      <CYAN>@@ -4,6 +4,13 @@<RESET> <RESET>int bar()<RESET>
 +       printf("Hello World, but different\n");<RESET>
 +       }<RESET>
 +       <RESET>
 +      <BGREEN>+<RESET><BGREEN>int secure_foo(struct user *u)<RESET>
 +      <BGREEN>+<RESET><BGREEN>{<RESET>
 +      <BGREEN>+<RESET><BGREEN>foo(u);<RESET>
 +      <BGREEN>+<RESET><BGREEN>if (!u->is_allowed_foo)<RESET>
 +      <BGREEN>+<RESET><BGREEN>return;<RESET>
 +      <BGREEN>+<RESET><BGREEN>}<RESET>
 +      <BGREEN>+<RESET>
 +       int another_function()<RESET>
 +       {<RESET>
 +       bar();<RESET>
 +      EOF
 +
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'detect permutations inside moved code -- dimmed_zebra' '
 +      git reset --hard &&
 +      cat <<-\EOF >lines.txt &&
 +              long line 1
 +              long line 2
 +              long line 3
 +              line 4
 +              line 5
 +              line 6
 +              line 7
 +              line 8
 +              line 9
 +              line 10
 +              line 11
 +              line 12
 +              line 13
 +              long line 14
 +              long line 15
 +              long line 16
 +      EOF
 +      git add lines.txt &&
 +      git commit -m "add poetry" &&
 +      cat <<-\EOF >lines.txt &&
 +              line 4
 +              line 5
 +              line 6
 +              line 7
 +              line 8
 +              line 9
 +              long line 1
 +              long line 2
 +              long line 3
 +              long line 14
 +              long line 15
 +              long line 16
 +              line 10
 +              line 11
 +              line 12
 +              line 13
 +      EOF
 +      test_config color.diff.oldMoved "magenta" &&
 +      test_config color.diff.newMoved "cyan" &&
 +      test_config color.diff.oldMovedAlternative "blue" &&
 +      test_config color.diff.newMovedAlternative "yellow" &&
 +      test_config color.diff.oldMovedDimmed "normal magenta" &&
 +      test_config color.diff.newMovedDimmed "normal cyan" &&
 +      test_config color.diff.oldMovedAlternativeDimmed "normal blue" &&
 +      test_config color.diff.newMovedAlternativeDimmed "normal yellow" &&
 +      git diff HEAD --no-renames --color-moved=dimmed_zebra --color |
 +              grep -v "index" |
 +              test_decode_color >actual &&
 +      cat <<-\EOF >expected &&
 +      <BOLD>diff --git a/lines.txt b/lines.txt<RESET>
 +      <BOLD>--- a/lines.txt<RESET>
 +      <BOLD>+++ b/lines.txt<RESET>
 +      <CYAN>@@ -1,16 +1,16 @@<RESET>
 +      <BMAGENTA>-long line 1<RESET>
 +      <BMAGENTA>-long line 2<RESET>
 +      <BMAGENTA>-long line 3<RESET>
 +       line 4<RESET>
 +       line 5<RESET>
 +       line 6<RESET>
 +       line 7<RESET>
 +       line 8<RESET>
 +       line 9<RESET>
 +      <BCYAN>+<RESET><BCYAN>long line 1<RESET>
 +      <BCYAN>+<RESET><BCYAN>long line 2<RESET>
 +      <CYAN>+<RESET><CYAN>long line 3<RESET>
 +      <YELLOW>+<RESET><YELLOW>long line 14<RESET>
 +      <BYELLOW>+<RESET><BYELLOW>long line 15<RESET>
 +      <BYELLOW>+<RESET><BYELLOW>long line 16<RESET>
 +       line 10<RESET>
 +       line 11<RESET>
 +       line 12<RESET>
 +       line 13<RESET>
 +      <BMAGENTA>-long line 14<RESET>
 +      <BMAGENTA>-long line 15<RESET>
 +      <BMAGENTA>-long line 16<RESET>
 +      EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'cmd option assumes configured colored-moved' '
 +      test_config color.diff.oldMoved "magenta" &&
 +      test_config color.diff.newMoved "cyan" &&
 +      test_config color.diff.oldMovedAlternative "blue" &&
 +      test_config color.diff.newMovedAlternative "yellow" &&
 +      test_config color.diff.oldMovedDimmed "normal magenta" &&
 +      test_config color.diff.newMovedDimmed "normal cyan" &&
 +      test_config color.diff.oldMovedAlternativeDimmed "normal blue" &&
 +      test_config color.diff.newMovedAlternativeDimmed "normal yellow" &&
 +      test_config diff.colorMoved zebra &&
 +      git diff HEAD --no-renames --color-moved --color |
 +              grep -v "index" |
 +              test_decode_color >actual &&
 +      cat <<-\EOF >expected &&
 +      <BOLD>diff --git a/lines.txt b/lines.txt<RESET>
 +      <BOLD>--- a/lines.txt<RESET>
 +      <BOLD>+++ b/lines.txt<RESET>
 +      <CYAN>@@ -1,16 +1,16 @@<RESET>
 +      <MAGENTA>-long line 1<RESET>
 +      <MAGENTA>-long line 2<RESET>
 +      <MAGENTA>-long line 3<RESET>
 +       line 4<RESET>
 +       line 5<RESET>
 +       line 6<RESET>
 +       line 7<RESET>
 +       line 8<RESET>
 +       line 9<RESET>
 +      <CYAN>+<RESET><CYAN>long line 1<RESET>
 +      <CYAN>+<RESET><CYAN>long line 2<RESET>
 +      <CYAN>+<RESET><CYAN>long line 3<RESET>
 +      <YELLOW>+<RESET><YELLOW>long line 14<RESET>
 +      <YELLOW>+<RESET><YELLOW>long line 15<RESET>
 +      <YELLOW>+<RESET><YELLOW>long line 16<RESET>
 +       line 10<RESET>
 +       line 11<RESET>
 +       line 12<RESET>
 +       line 13<RESET>
 +      <MAGENTA>-long line 14<RESET>
 +      <MAGENTA>-long line 15<RESET>
 +      <MAGENTA>-long line 16<RESET>
 +      EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'no effect from --color-moved with --word-diff' '
 +      cat <<-\EOF >text.txt &&
 +      Lorem Ipsum is simply dummy text of the printing and typesetting industry.
 +      EOF
 +      git add text.txt &&
 +      git commit -a -m "clean state" &&
 +      cat <<-\EOF >text.txt &&
 +      simply Lorem Ipsum dummy is text of the typesetting and printing industry.
 +      EOF
 +      git diff --color-moved --word-diff >actual &&
 +      git diff --word-diff >expect &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'set up whitespace tests' '
 +      git reset --hard &&
 +      # Note that these lines have no leading or trailing whitespace.
 +      cat <<-\EOF >lines.txt &&
 +      line 1
 +      line 2
 +      line 3
 +      line 4
 +      line 5
 +      long line 6
 +      long line 7
 +      long line 8
 +      long line 9
 +      EOF
 +      git add lines.txt &&
 +      git commit -m "add poetry" &&
 +      git config color.diff.oldMoved "magenta" &&
 +      git config color.diff.newMoved "cyan"
 +'
 +
 +test_expect_success 'move detection ignoring whitespace ' '
 +      q_to_tab <<-\EOF >lines.txt &&
 +      Qlong line 6
 +      Qlong line 7
 +      Qlong line 8
 +      Qchanged long line 9
 +      line 1
 +      line 2
 +      line 3
 +      line 4
 +      line 5
 +      EOF
 +      git diff HEAD --no-renames --color-moved --color |
 +              grep -v "index" |
 +              test_decode_color >actual &&
 +      cat <<-\EOF >expected &&
 +      <BOLD>diff --git a/lines.txt b/lines.txt<RESET>
 +      <BOLD>--- a/lines.txt<RESET>
 +      <BOLD>+++ b/lines.txt<RESET>
 +      <CYAN>@@ -1,9 +1,9 @@<RESET>
 +      <GREEN>+<RESET> <GREEN>long line 6<RESET>
 +      <GREEN>+<RESET> <GREEN>long line 7<RESET>
 +      <GREEN>+<RESET> <GREEN>long line 8<RESET>
 +      <GREEN>+<RESET> <GREEN>changed long line 9<RESET>
 +       line 1<RESET>
 +       line 2<RESET>
 +       line 3<RESET>
 +       line 4<RESET>
 +       line 5<RESET>
 +      <RED>-long line 6<RESET>
 +      <RED>-long line 7<RESET>
 +      <RED>-long line 8<RESET>
 +      <RED>-long line 9<RESET>
 +      EOF
 +      test_cmp expected actual &&
 +
 +      git diff HEAD --no-renames -w --color-moved --color |
 +              grep -v "index" |
 +              test_decode_color >actual &&
 +      cat <<-\EOF >expected &&
 +      <BOLD>diff --git a/lines.txt b/lines.txt<RESET>
 +      <BOLD>--- a/lines.txt<RESET>
 +      <BOLD>+++ b/lines.txt<RESET>
 +      <CYAN>@@ -1,9 +1,9 @@<RESET>
 +      <CYAN>+<RESET>  <CYAN>long line 6<RESET>
 +      <CYAN>+<RESET>  <CYAN>long line 7<RESET>
 +      <CYAN>+<RESET>  <CYAN>long line 8<RESET>
 +      <GREEN>+<RESET> <GREEN>changed long line 9<RESET>
 +       line 1<RESET>
 +       line 2<RESET>
 +       line 3<RESET>
 +       line 4<RESET>
 +       line 5<RESET>
 +      <MAGENTA>-long line 6<RESET>
 +      <MAGENTA>-long line 7<RESET>
 +      <MAGENTA>-long line 8<RESET>
 +      <RED>-long line 9<RESET>
 +      EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'move detection ignoring whitespace changes' '
 +      git reset --hard &&
 +      # Lines 6-8 have a space change, but 9 is new whitespace
 +      q_to_tab <<-\EOF >lines.txt &&
 +      longQline 6
 +      longQline 7
 +      longQline 8
 +      long liQne 9
 +      line 1
 +      line 2
 +      line 3
 +      line 4
 +      line 5
 +      EOF
 +
 +      git diff HEAD --no-renames --color-moved --color |
 +              grep -v "index" |
 +              test_decode_color >actual &&
 +      cat <<-\EOF >expected &&
 +      <BOLD>diff --git a/lines.txt b/lines.txt<RESET>
 +      <BOLD>--- a/lines.txt<RESET>
 +      <BOLD>+++ b/lines.txt<RESET>
 +      <CYAN>@@ -1,9 +1,9 @@<RESET>
 +      <GREEN>+<RESET><GREEN>long      line 6<RESET>
 +      <GREEN>+<RESET><GREEN>long      line 7<RESET>
 +      <GREEN>+<RESET><GREEN>long      line 8<RESET>
 +      <GREEN>+<RESET><GREEN>long li   ne 9<RESET>
 +       line 1<RESET>
 +       line 2<RESET>
 +       line 3<RESET>
 +       line 4<RESET>
 +       line 5<RESET>
 +      <RED>-long line 6<RESET>
 +      <RED>-long line 7<RESET>
 +      <RED>-long line 8<RESET>
 +      <RED>-long line 9<RESET>
 +      EOF
 +      test_cmp expected actual &&
 +
 +      git diff HEAD --no-renames -b --color-moved --color |
 +              grep -v "index" |
 +              test_decode_color >actual &&
 +      cat <<-\EOF >expected &&
 +      <BOLD>diff --git a/lines.txt b/lines.txt<RESET>
 +      <BOLD>--- a/lines.txt<RESET>
 +      <BOLD>+++ b/lines.txt<RESET>
 +      <CYAN>@@ -1,9 +1,9 @@<RESET>
 +      <CYAN>+<RESET><CYAN>long        line 6<RESET>
 +      <CYAN>+<RESET><CYAN>long        line 7<RESET>
 +      <CYAN>+<RESET><CYAN>long        line 8<RESET>
 +      <GREEN>+<RESET><GREEN>long li   ne 9<RESET>
 +       line 1<RESET>
 +       line 2<RESET>
 +       line 3<RESET>
 +       line 4<RESET>
 +       line 5<RESET>
 +      <MAGENTA>-long line 6<RESET>
 +      <MAGENTA>-long line 7<RESET>
 +      <MAGENTA>-long line 8<RESET>
 +      <RED>-long line 9<RESET>
 +      EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'move detection ignoring whitespace at eol' '
 +      git reset --hard &&
 +      # Lines 6-9 have new eol whitespace, but 9 also has it in the middle
 +      q_to_tab <<-\EOF >lines.txt &&
 +      long line 6Q
 +      long line 7Q
 +      long line 8Q
 +      longQline 9Q
 +      line 1
 +      line 2
 +      line 3
 +      line 4
 +      line 5
 +      EOF
 +
 +      # avoid cluttering the output with complaints about our eol whitespace
 +      test_config core.whitespace -blank-at-eol &&
 +
 +      git diff HEAD --no-renames --color-moved --color |
 +              grep -v "index" |
 +              test_decode_color >actual &&
 +      cat <<-\EOF >expected &&
 +      <BOLD>diff --git a/lines.txt b/lines.txt<RESET>
 +      <BOLD>--- a/lines.txt<RESET>
 +      <BOLD>+++ b/lines.txt<RESET>
 +      <CYAN>@@ -1,9 +1,9 @@<RESET>
 +      <GREEN>+<RESET><GREEN>long line 6       <RESET>
 +      <GREEN>+<RESET><GREEN>long line 7       <RESET>
 +      <GREEN>+<RESET><GREEN>long line 8       <RESET>
 +      <GREEN>+<RESET><GREEN>long      line 9  <RESET>
 +       line 1<RESET>
 +       line 2<RESET>
 +       line 3<RESET>
 +       line 4<RESET>
 +       line 5<RESET>
 +      <RED>-long line 6<RESET>
 +      <RED>-long line 7<RESET>
 +      <RED>-long line 8<RESET>
 +      <RED>-long line 9<RESET>
 +      EOF
 +      test_cmp expected actual &&
 +
 +      git diff HEAD --no-renames --ignore-space-at-eol --color-moved --color |
 +              grep -v "index" |
 +              test_decode_color >actual &&
 +      cat <<-\EOF >expected &&
 +      <BOLD>diff --git a/lines.txt b/lines.txt<RESET>
 +      <BOLD>--- a/lines.txt<RESET>
 +      <BOLD>+++ b/lines.txt<RESET>
 +      <CYAN>@@ -1,9 +1,9 @@<RESET>
 +      <CYAN>+<RESET><CYAN>long line 6 <RESET>
 +      <CYAN>+<RESET><CYAN>long line 7 <RESET>
 +      <CYAN>+<RESET><CYAN>long line 8 <RESET>
 +      <GREEN>+<RESET><GREEN>long      line 9  <RESET>
 +       line 1<RESET>
 +       line 2<RESET>
 +       line 3<RESET>
 +       line 4<RESET>
 +       line 5<RESET>
 +      <MAGENTA>-long line 6<RESET>
 +      <MAGENTA>-long line 7<RESET>
 +      <MAGENTA>-long line 8<RESET>
 +      <RED>-long line 9<RESET>
 +      EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'clean up whitespace-test colors' '
 +      git config --unset color.diff.oldMoved &&
 +      git config --unset color.diff.newMoved
 +'
 +
 +test_expect_success '--color-moved block at end of diff output respects MIN_ALNUM_COUNT' '
 +      git reset --hard &&
 +      >bar &&
 +      cat <<-\EOF >foo &&
 +      irrelevant_line
 +      line1
 +      EOF
 +      git add foo bar &&
 +      git commit -m x &&
 +
 +      cat <<-\EOF >bar &&
 +      line1
 +      EOF
 +      cat <<-\EOF >foo &&
 +      irrelevant_line
 +      EOF
 +
 +      git diff HEAD --color-moved=zebra --color --no-renames |
 +              grep -v "index" |
 +              test_decode_color >actual &&
 +      cat >expected <<-\EOF &&
 +      <BOLD>diff --git a/bar b/bar<RESET>
 +      <BOLD>--- a/bar<RESET>
 +      <BOLD>+++ b/bar<RESET>
 +      <CYAN>@@ -0,0 +1 @@<RESET>
 +      <GREEN>+<RESET><GREEN>line1<RESET>
 +      <BOLD>diff --git a/foo b/foo<RESET>
 +      <BOLD>--- a/foo<RESET>
 +      <BOLD>+++ b/foo<RESET>
 +      <CYAN>@@ -1,2 +1 @@<RESET>
 +       irrelevant_line<RESET>
 +      <RED>-line1<RESET>
 +      EOF
 +
 +      test_cmp expected actual
 +'
 +
 +test_expect_success '--color-moved respects MIN_ALNUM_COUNT' '
 +      git reset --hard &&
 +      cat <<-\EOF >foo &&
 +      nineteen chars 456789
 +      irrelevant_line
 +      twenty chars 234567890
 +      EOF
 +      >bar &&
 +      git add foo bar &&
 +      git commit -m x &&
 +
 +      cat <<-\EOF >foo &&
 +      irrelevant_line
 +      EOF
 +      cat <<-\EOF >bar &&
 +      twenty chars 234567890
 +      nineteen chars 456789
 +      EOF
 +
 +      git diff HEAD --color-moved=zebra --color --no-renames |
 +              grep -v "index" |
 +              test_decode_color >actual &&
 +      cat >expected <<-\EOF &&
 +      <BOLD>diff --git a/bar b/bar<RESET>
 +      <BOLD>--- a/bar<RESET>
 +      <BOLD>+++ b/bar<RESET>
 +      <CYAN>@@ -0,0 +1,2 @@<RESET>
 +      <BOLD;CYAN>+<RESET><BOLD;CYAN>twenty chars 234567890<RESET>
 +      <GREEN>+<RESET><GREEN>nineteen chars 456789<RESET>
 +      <BOLD>diff --git a/foo b/foo<RESET>
 +      <BOLD>--- a/foo<RESET>
 +      <BOLD>+++ b/foo<RESET>
 +      <CYAN>@@ -1,3 +1 @@<RESET>
 +      <RED>-nineteen chars 456789<RESET>
 +       irrelevant_line<RESET>
 +      <BOLD;MAGENTA>-twenty chars 234567890<RESET>
 +      EOF
 +
 +      test_cmp expected actual
 +'
 +
 +test_expect_success '--color-moved treats adjacent blocks as separate for MIN_ALNUM_COUNT' '
 +      git reset --hard &&
 +      cat <<-\EOF >foo &&
 +      7charsA
 +      irrelevant_line
 +      7charsB
 +      7charsC
 +      EOF
 +      >bar &&
 +      git add foo bar &&
 +      git commit -m x &&
 +
 +      cat <<-\EOF >foo &&
 +      irrelevant_line
 +      EOF
 +      cat <<-\EOF >bar &&
 +      7charsB
 +      7charsC
 +      7charsA
 +      EOF
 +
 +      git diff HEAD --color-moved=zebra --color --no-renames | grep -v "index" | test_decode_color >actual &&
 +      cat >expected <<-\EOF &&
 +      <BOLD>diff --git a/bar b/bar<RESET>
 +      <BOLD>--- a/bar<RESET>
 +      <BOLD>+++ b/bar<RESET>
 +      <CYAN>@@ -0,0 +1,3 @@<RESET>
 +      <GREEN>+<RESET><GREEN>7charsB<RESET>
 +      <GREEN>+<RESET><GREEN>7charsC<RESET>
 +      <GREEN>+<RESET><GREEN>7charsA<RESET>
 +      <BOLD>diff --git a/foo b/foo<RESET>
 +      <BOLD>--- a/foo<RESET>
 +      <BOLD>+++ b/foo<RESET>
 +      <CYAN>@@ -1,4 +1 @@<RESET>
 +      <RED>-7charsA<RESET>
 +       irrelevant_line<RESET>
 +      <RED>-7charsB<RESET>
 +      <RED>-7charsC<RESET>
 +      EOF
 +
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'move detection with submodules' '
 +      test_create_repo bananas &&
 +      echo ripe >bananas/recipe &&
 +      git -C bananas add recipe &&
 +      test_commit fruit &&
 +      test_commit -C bananas recipe &&
 +      git submodule add ./bananas &&
 +      git add bananas &&
 +      git commit -a -m "bananas are like a heavy library?" &&
 +      echo foul >bananas/recipe &&
 +      echo ripe >fruit.t &&
 +
 +      git diff --submodule=diff --color-moved --color >actual &&
 +
 +      # no move detection as the moved line is across repository boundaries.
 +      test_decode_color <actual >decoded_actual &&
 +      ! grep BGREEN decoded_actual &&
 +      ! grep BRED decoded_actual &&
 +
 +      # nor did we mess with it another way
 +      git diff --submodule=diff --color | test_decode_color >expect &&
 +      test_cmp expect decoded_actual
 +'
 +
  test_done
diff --combined xdiff/xdiff.h
index 785ecb0899454f55dc160a132b5cf932ec382f09,51f89262150d7ba6654a8c3d64d676290500cdd2..915591f7d4797ace61761195933ab170c6b70eff
@@@ -13,8 -13,8 +13,8 @@@
   *  Lesser General Public License for more details.
   *
   *  You should have received a copy of the GNU Lesser General Public
 - *  License along with this library; if not, write to the Free Software
 - *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 + *  License along with this library; if not, see
 + *  <http://www.gnu.org/licenses/>.
   *
   *  Davide Libenzi <davidel@xmailserver.org>
   *
  extern "C" {
  #endif /* #ifdef __cplusplus */
  
+ /* xpparm_t.flags */
+ #define XDF_NEED_MINIMAL (1 << 0)
+ #define XDF_IGNORE_WHITESPACE (1 << 1)
+ #define XDF_IGNORE_WHITESPACE_CHANGE (1 << 2)
+ #define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 3)
+ #define XDF_IGNORE_CR_AT_EOL (1 << 4)
+ #define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | \
+                             XDF_IGNORE_WHITESPACE_CHANGE | \
+                             XDF_IGNORE_WHITESPACE_AT_EOL | \
+                             XDF_IGNORE_CR_AT_EOL)
  
- #define XDF_NEED_MINIMAL (1 << 1)
- #define XDF_IGNORE_WHITESPACE (1 << 2)
- #define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3)
- #define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 4)
- #define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE | XDF_IGNORE_WHITESPACE_AT_EOL)
+ #define XDF_IGNORE_BLANK_LINES (1 << 7)
  
- #define XDF_PATIENCE_DIFF (1 << 5)
- #define XDF_HISTOGRAM_DIFF (1 << 6)
+ #define XDF_PATIENCE_DIFF (1 << 14)
+ #define XDF_HISTOGRAM_DIFF (1 << 15)
  #define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF)
  #define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK)
  
- #define XDF_IGNORE_BLANK_LINES (1 << 7)
- #define XDF_INDENT_HEURISTIC (1 << 8)
+ #define XDF_INDENT_HEURISTIC (1 << 23)
  
+ /* xdemitconf_t.flags */
  #define XDL_EMIT_FUNCNAMES (1 << 0)
  #define XDL_EMIT_FUNCCONTEXT (1 << 2)
  
diff --combined xdiff/xutils.c
index 088001db4f45af338696ea3112559b7bc729a5d3,b2cbcc818fd0c536026ad33bf69eb7902d16a467..88e5995535467475216de82b1eeb744e744f3b4d
@@@ -13,8 -13,8 +13,8 @@@
   *  Lesser General Public License for more details.
   *
   *  You should have received a copy of the GNU Lesser General Public
 - *  License along with this library; if not, write to the Free Software
 - *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 + *  License along with this library; if not, see
 + *  <http://www.gnu.org/licenses/>.
   *
   *  Davide Libenzi <davidel@xmailserver.org>
   *
@@@ -156,6 -156,24 +156,24 @@@ int xdl_blankline(const char *line, lon
        return (i == size);
  }
  
+ /*
+  * Have we eaten everything on the line, except for an optional
+  * CR at the very end?
+  */
+ static int ends_with_optional_cr(const char *l, long s, long i)
+ {
+       int complete = s && l[s-1] == '\n';
+       if (complete)
+               s--;
+       if (s == i)
+               return 1;
+       /* do not ignore CR at the end of an incomplete line */
+       if (complete && s == i + 1 && l[i] == '\r')
+               return 1;
+       return 0;
+ }
  int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
  {
        int i1, i2;
  
        /*
         * -w matches everything that matches with -b, and -b in turn
-        * matches everything that matches with --ignore-space-at-eol.
+        * matches everything that matches with --ignore-space-at-eol,
+        * which in turn matches everything that matches with --ignore-cr-at-eol.
         *
         * Each flavor of ignoring needs different logic to skip whitespaces
         * while we have both sides to compare.
                        i1++;
                        i2++;
                }
+       } else if (flags & XDF_IGNORE_CR_AT_EOL) {
+               /* Find the first difference and see how the line ends */
+               while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) {
+                       i1++;
+                       i2++;
+               }
+               return (ends_with_optional_cr(l1, s1, i1) &&
+                       ends_with_optional_cr(l2, s2, i2));
        }
  
        /*
@@@ -230,9 -257,16 +257,16 @@@ static unsigned long xdl_hash_record_wi
                char const *top, long flags) {
        unsigned long ha = 5381;
        char const *ptr = *data;
+       int cr_at_eol_only = (flags & XDF_WHITESPACE_FLAGS) == XDF_IGNORE_CR_AT_EOL;
  
        for (; ptr < top && *ptr != '\n'; ptr++) {
-               if (XDL_ISSPACE(*ptr)) {
+               if (cr_at_eol_only) {
+                       /* do not ignore CR at the end of an incomplete line */
+                       if (*ptr == '\r' &&
+                           (ptr + 1 < top && ptr[1] == '\n'))
+                               continue;
+               }
+               else if (XDL_ISSPACE(*ptr)) {
                        const char *ptr2 = ptr;
                        int at_eol;
                        while (ptr + 1 < top && XDL_ISSPACE(ptr[1])