Merge branch 'sb/diff-color-move'
authorJunio C Hamano <gitster@pobox.com>
Sun, 27 Aug 2017 05:55:03 +0000 (22:55 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 27 Aug 2017 05:55:03 +0000 (22:55 -0700)
"git diff" has been taught to optionally paint new lines that are
the same as deleted lines elsewhere differently from genuinely new
lines.

* sb/diff-color-move: (25 commits)
  diff: document the new --color-moved setting
  diff.c: add dimming to moved line detection
  diff.c: color moved lines differently, plain mode
  diff.c: color moved lines differently
  diff.c: buffer all output if asked to
  diff.c: emit_diff_symbol learns about DIFF_SYMBOL_SUMMARY
  diff.c: emit_diff_symbol learns about DIFF_SYMBOL_STAT_SEP
  diff.c: convert word diffing to use emit_diff_symbol
  diff.c: convert show_stats to use emit_diff_symbol
  diff.c: convert emit_binary_diff_body to use emit_diff_symbol
  submodule.c: migrate diff output to use emit_diff_symbol
  diff.c: emit_diff_symbol learns DIFF_SYMBOL_REWRITE_DIFF
  diff.c: emit_diff_symbol learns about DIFF_SYMBOL_BINARY_FILES
  diff.c: emit_diff_symbol learns DIFF_SYMBOL_HEADER
  diff.c: emit_diff_symbol learns DIFF_SYMBOL_FILEPAIR_{PLUS, MINUS}
  diff.c: emit_diff_symbol learns DIFF_SYMBOL_CONTEXT_INCOMPLETE
  diff.c: emit_diff_symbol learns DIFF_SYMBOL_WORDS[_PORCELAIN]
  diff.c: migrate emit_line_checked to use emit_diff_symbol
  diff.c: emit_diff_symbol learns DIFF_SYMBOL_NO_LF_EOF
  diff.c: emit_diff_symbol learns DIFF_SYMBOL_CONTEXT_FRAGINFO
  ...

1  2 
Documentation/config.txt
Documentation/diff-options.txt
cache.h
diff.c
submodule.c
submodule.h

diff --combined Documentation/config.txt
@@@ -216,15 -216,15 +216,15 @@@ boolean:
         synonyms are accepted for 'true' and 'false'; these are all
         case-insensitive.
  
 -       true;; Boolean true can be spelled as `yes`, `on`, `true`,
 -              or `1`.  Also, a variable defined without `= <value>`
 +      true;; Boolean true literals are `yes`, `on`, `true`,
 +              and `1`.  Also, a variable defined without `= <value>`
                is taken as true.
  
 -       false;; Boolean false can be spelled as `no`, `off`,
 -              `false`, or `0`.
 +      false;; Boolean false literals are `no`, `off`, `false`,
 +              `0` and the empty string.
  +
  When converting value to the canonical form using `--bool` type
 -specifier; 'git config' will ensure that the output is "true" or
 +specifier, 'git config' will ensure that the output is "true" or
  "false" (spelled in lowercase).
  
  integer::
@@@ -1077,14 -1077,25 +1077,25 @@@ This does not affect linkgit:git-format
  'git-diff-{asterisk}' plumbing commands.  Can be overridden on the
  command line with the `--color[=<when>]` option.
  
+ diff.colorMoved::
+       If set to either a valid `<mode>` or a true value, moved lines
+       in a diff are colored differently, for details of valid modes
+       see '--color-moved' in linkgit:git-diff[1]. If simply set to
+       true the default color mode will be used. When set to false,
+       moved lines are not colored.
  color.diff.<slot>::
        Use customized color for diff colorization.  `<slot>` specifies
        which part of the patch to use the specified color, and is one
        of `context` (context text - `plain` is a historical synonym),
        `meta` (metainformation), `frag`
        (hunk header), 'func' (function in hunk header), `old` (removed lines),
-       `new` (added lines), `commit` (commit headers), or `whitespace`
-       (highlighting whitespace errors).
+       `new` (added lines), `commit` (commit headers), `whitespace`
+       (highlighting whitespace errors), `oldMoved` (deleted lines),
+       `newMoved` (added lines), `oldMovedDimmed`, `oldMovedAlternative`,
+       `oldMovedAlternativeDimmed`, `newMovedDimmed`, `newMovedAlternative`
+       and `newMovedAlternativeDimmed` (See the '<mode>'
+       setting of '--color-moved' in linkgit:git-diff[1] for details).
  
  color.decorate.<slot>::
        Use customized color for 'git log --decorate' output.  `<slot>` is one
@@@ -2912,8 -2923,8 +2923,8 @@@ sendemail.smtpsslcertpath:
  
  sendemail.<identity>.*::
        Identity-specific versions of the 'sendemail.*' parameters
 -      found below, taking precedence over those when the this
 -      identity is selected, through command-line or
 +      found below, taking precedence over those when this
 +      identity is selected, through either the command-line or
        `sendemail.identity`.
  
  sendemail.aliasesFile::
@@@ -2946,16 -2957,6 +2957,16 @@@ sendemail.xmailer:
  sendemail.signedoffcc (deprecated)::
        Deprecated alias for `sendemail.signedoffbycc`.
  
 +sendemail.smtpBatchSize::
 +      Number of messages to be sent per connection, after that a relogin
 +      will happen.  If the value is 0 or undefined, send all messages in
 +      one connection.
 +      See also the `--batch-size` option of linkgit:git-send-email[1].
 +
 +sendemail.smtpReloginDelay::
 +      Seconds wait before reconnecting to smtp server.
 +      See also the `--relogin-delay` option of linkgit:git-send-email[1].
 +
  showbranch.default::
        The default set of branches for linkgit:git-show-branch[1].
        See linkgit:git-show-branch[1].
@@@ -231,6 -231,42 +231,42 @@@ 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 code 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. If there
+       are fewer than 3 adjacent moved lines, they are not marked up
+       as moved, but the regular colors 'color.diff.{old,new}' will be
+       used.
+ 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
@@@ -300,14 -336,15 +336,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[]
  
@@@ -391,7 -428,7 +427,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.
  +
diff --combined cache.h
+++ b/cache.h
@@@ -11,8 -11,6 +11,8 @@@
  #include "string-list.h"
  #include "pack-revindex.h"
  #include "hash.h"
 +#include "path.h"
 +#include "sha1-array.h"
  
  #ifndef platform_SHA_CTX
  /*
@@@ -417,6 -415,7 +417,6 @@@ static inline enum object_type object_t
  #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
  #define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX"
  #define GIT_SUPER_PREFIX_ENVIRONMENT "GIT_INTERNAL_SUPER_PREFIX"
 -#define GIT_TOPLEVEL_PREFIX_ENVIRONMENT "GIT_INTERNAL_TOPLEVEL_PREFIX"
  #define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
  #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
  #define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
  #define GITATTRIBUTES_FILE ".gitattributes"
  #define INFOATTRIBUTES_FILE "info/attributes"
  #define ATTRIBUTE_MACRO_PREFIX "[attr]"
 +#define GITMODULES_FILE ".gitmodules"
  #define GIT_NOTES_REF_ENVIRONMENT "GIT_NOTES_REF"
  #define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
  #define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF"
   */
  extern const char * const local_repo_env[];
  
 +extern void setup_git_env(void);
 +
  /*
   * Returns true iff we have a configured git repository (either via
   * setup_git_directory, or in the environment via $GIT_DIR).
@@@ -773,6 -769,7 +773,6 @@@ extern int core_apply_sparse_checkout
  extern int precomposed_unicode;
  extern int protect_hfs;
  extern int protect_ntfs;
 -extern int git_db_env, git_index_env, git_graft_env, git_common_dir_env;
  
  /*
   * Include broken refs in all ref iterations, which will
@@@ -894,6 -891,64 +894,6 @@@ extern void check_repository_format(voi
  #define DATA_CHANGED    0x0020
  #define TYPE_CHANGED    0x0040
  
 -/*
 - * Return a statically allocated filename, either generically (mkpath), in
 - * the repository directory (git_path), or in a submodule's repository
 - * directory (git_path_submodule). In all cases, note that the result
 - * may be overwritten by another call to _any_ of the functions. Consider
 - * using the safer "dup" or "strbuf" formats below (in some cases, the
 - * unsafe versions have already been removed).
 - */
 -extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 -extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 -extern const char *git_common_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 -
 -extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
 -      __attribute__((format (printf, 3, 4)));
 -extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 -      __attribute__((format (printf, 2, 3)));
 -extern void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...)
 -      __attribute__((format (printf, 2, 3)));
 -extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...)
 -      __attribute__((format (printf, 2, 3)));
 -extern int strbuf_git_path_submodule(struct strbuf *sb, const char *path,
 -                                   const char *fmt, ...)
 -      __attribute__((format (printf, 3, 4)));
 -extern char *git_pathdup(const char *fmt, ...)
 -      __attribute__((format (printf, 1, 2)));
 -extern char *mkpathdup(const char *fmt, ...)
 -      __attribute__((format (printf, 1, 2)));
 -extern char *git_pathdup_submodule(const char *path, const char *fmt, ...)
 -      __attribute__((format (printf, 2, 3)));
 -
 -extern void report_linked_checkout_garbage(void);
 -
 -/*
 - * You can define a static memoized git path like:
 - *
 - *    static GIT_PATH_FUNC(git_path_foo, "FOO");
 - *
 - * or use one of the global ones below.
 - */
 -#define GIT_PATH_FUNC(func, filename) \
 -      const char *func(void) \
 -      { \
 -              static char *ret; \
 -              if (!ret) \
 -                      ret = git_pathdup(filename); \
 -              return ret; \
 -      }
 -
 -const char *git_path_cherry_pick_head(void);
 -const char *git_path_revert_head(void);
 -const char *git_path_squash_msg(void);
 -const char *git_path_merge_msg(void);
 -const char *git_path_merge_rr(void);
 -const char *git_path_merge_mode(void);
 -const char *git_path_merge_head(void);
 -const char *git_path_fetch_head(void);
 -const char *git_path_shallow(void);
 -
  /*
   * Return the name of the file in the local object database that would
   * be used to store a loose object with the specified sha1.  The
@@@ -939,7 -994,14 +939,7 @@@ extern const struct object_id null_oid
  
  static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)
  {
 -      int i;
 -
 -      for (i = 0; i < GIT_SHA1_RAWSZ; i++, sha1++, sha2++) {
 -              if (*sha1 != *sha2)
 -                      return *sha1 - *sha2;
 -      }
 -
 -      return 0;
 +      return memcmp(sha1, sha2, GIT_SHA1_RAWSZ);
  }
  
  static inline int oidcmp(const struct object_id *oid1, const struct object_id *oid2)
@@@ -1139,14 -1201,6 +1139,14 @@@ char *strip_path_suffix(const char *pat
  int daemon_avoid_alias(const char *path);
  extern int is_ntfs_dotgit(const char *name);
  
 +/*
 + * Returns true iff "str" could be confused as a command-line option when
 + * passed to a sub-program like "ssh". Note that this has nothing to do with
 + * shell-quoting, which should be handled separately; we're assuming here that
 + * the string makes it verbatim to the sub-program.
 + */
 +int looks_like_command_line_option(const char *str);
 +
  /**
   * Return a newly allocated string with the evaluation of
   * "$XDG_CONFIG_HOME/git/$filename" if $XDG_CONFIG_HOME is non-empty, otherwise
@@@ -1161,12 -1215,13 +1161,12 @@@ extern char *xdg_config_home(const cha
   */
  extern char *xdg_cache_home(const char *filename);
  
 -/* object replacement */
 -#define LOOKUP_REPLACE_OBJECT 1
 -#define LOOKUP_UNKNOWN_OBJECT 2
 -extern void *read_sha1_file_extended(const unsigned char *sha1, enum object_type *type, unsigned long *size, unsigned flag);
 +extern void *read_sha1_file_extended(const unsigned char *sha1,
 +                                   enum object_type *type,
 +                                   unsigned long *size, int lookup_replace);
  static inline void *read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
  {
 -      return read_sha1_file_extended(sha1, type, size, LOOKUP_REPLACE_OBJECT);
 +      return read_sha1_file_extended(sha1, type, size, 1);
  }
  
  /*
@@@ -1188,6 -1243,13 +1188,6 @@@ static inline const unsigned char *look
        return do_lookup_replace_object(sha1);
  }
  
 -static inline const unsigned char *lookup_replace_object_extended(const unsigned char *sha1, unsigned flag)
 -{
 -      if (!(flag & LOOKUP_REPLACE_OBJECT))
 -              return sha1;
 -      return lookup_replace_object(sha1);
 -}
 -
  /* Read and unpack a sha1 file into memory, write memory to a sha1 file */
  extern int sha1_object_info(const unsigned char *, unsigned long *);
  extern int hash_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1);
@@@ -1224,10 -1286,15 +1224,10 @@@ int read_loose_object(const char *path
                      void **contents);
  
  /*
 - * Return true iff we have an object named sha1, whether local or in
 - * an alternate object database, and whether packed or loose.  This
 - * function does not respect replace references.
 - *
 - * If the QUICK flag is set, do not re-check the pack directory
 - * when we cannot find the object (this means we may give a false
 - * negative answer if another process is simultaneously repacking).
 + * Convenience for sha1_object_info_extended() with a NULL struct
 + * object_info. OBJECT_INFO_SKIP_CACHED is automatically set; pass
 + * nonzero flags to also set other flags.
   */
 -#define HAS_SHA1_QUICK 0x1
  extern int has_sha1_file_with_flags(const unsigned char *sha1, int flags);
  static inline int has_sha1_file(const unsigned char *sha1)
  {
@@@ -1284,37 -1351,38 +1284,37 @@@ struct object_context 
         */
        struct strbuf symlink_path;
        /*
 -       * If GET_SHA1_RECORD_PATH is set, this will record path (if any)
 +       * If GET_OID_RECORD_PATH is set, this will record path (if any)
         * found when resolving the name. The caller is responsible for
         * releasing the memory.
         */
        char *path;
  };
  
 -#define GET_SHA1_QUIETLY           01
 -#define GET_SHA1_COMMIT            02
 -#define GET_SHA1_COMMITTISH        04
 -#define GET_SHA1_TREE             010
 -#define GET_SHA1_TREEISH          020
 -#define GET_SHA1_BLOB             040
 -#define GET_SHA1_FOLLOW_SYMLINKS 0100
 -#define GET_SHA1_RECORD_PATH     0200
 -#define GET_SHA1_ONLY_TO_DIE    04000
 -
 -#define GET_SHA1_DISAMBIGUATORS \
 -      (GET_SHA1_COMMIT | GET_SHA1_COMMITTISH | \
 -      GET_SHA1_TREE | GET_SHA1_TREEISH | \
 -      GET_SHA1_BLOB)
 -
 -extern int get_sha1(const char *str, unsigned char *sha1);
 -extern int get_sha1_commit(const char *str, unsigned char *sha1);
 -extern int get_sha1_committish(const char *str, unsigned char *sha1);
 -extern int get_sha1_tree(const char *str, unsigned char *sha1);
 -extern int get_sha1_treeish(const char *str, unsigned char *sha1);
 -extern int get_sha1_blob(const char *str, unsigned char *sha1);
 -extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix);
 -extern int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *oc);
 +#define GET_OID_QUIETLY           01
 +#define GET_OID_COMMIT            02
 +#define GET_OID_COMMITTISH        04
 +#define GET_OID_TREE             010
 +#define GET_OID_TREEISH          020
 +#define GET_OID_BLOB             040
 +#define GET_OID_FOLLOW_SYMLINKS 0100
 +#define GET_OID_RECORD_PATH     0200
 +#define GET_OID_ONLY_TO_DIE    04000
 +
 +#define GET_OID_DISAMBIGUATORS \
 +      (GET_OID_COMMIT | GET_OID_COMMITTISH | \
 +      GET_OID_TREE | GET_OID_TREEISH | \
 +      GET_OID_BLOB)
  
  extern int get_oid(const char *str, struct object_id *oid);
 +extern int get_oid_commit(const char *str, struct object_id *oid);
 +extern int get_oid_committish(const char *str, struct object_id *oid);
 +extern int get_oid_tree(const char *str, struct object_id *oid);
 +extern int get_oid_treeish(const char *str, struct object_id *oid);
 +extern int get_oid_blob(const char *str, struct object_id *oid);
 +extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix);
 +extern int get_oid_with_context(const char *str, unsigned flags, struct object_id *oid, struct object_context *oc);
 +
  
  typedef int each_abbrev_fn(const struct object_id *oid, void *);
  extern int for_each_abbrev(const char *prefix, each_abbrev_fn, void *);
@@@ -1492,7 -1560,6 +1492,7 @@@ struct checkout 
        struct index_state *istate;
        const char *base_dir;
        int base_dir_len;
 +      struct delayed_checkout *delayed_checkout;
        unsigned force:1,
                 quiet:1,
                 not_new:1,
  
  #define TEMPORARY_FILENAME_LENGTH 25
  extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
 +extern void enable_delayed_checkout(struct checkout *state);
 +extern int finish_delayed_checkout(struct checkout *state);
  
  struct cache_def {
        struct strbuf path;
@@@ -1531,19 -1596,10 +1531,19 @@@ extern struct alternate_object_databas
        struct strbuf scratch;
        size_t base_len;
  
 +      /*
 +       * Used to store the results of readdir(3) calls when searching
 +       * for unique abbreviated hashes.  This cache is never
 +       * invalidated, thus it's racy and not necessarily accurate.
 +       * That's fine for its purpose; don't use it for tasks requiring
 +       * greater accuracy!
 +       */
 +      char loose_objects_subdir_seen[256];
 +      struct oid_array loose_objects_cache;
 +
        char path[FLEX_ARRAY];
  } *alt_odb_list;
  extern void prepare_alt_odb(void);
 -extern void read_info_alternates(const char * relative_base, int depth);
  extern char *compute_alternate_path(const char *path, struct strbuf *err);
  typedef int alt_odb_fn(struct alternate_object_database *, void *);
  extern int foreach_alt_odb(alt_odb_fn, void*);
@@@ -1755,15 -1811,9 +1755,15 @@@ typedef int each_loose_object_fn(const 
  typedef int each_loose_cruft_fn(const char *basename,
                                const char *path,
                                void *data);
 -typedef int each_loose_subdir_fn(int nr,
 +typedef int each_loose_subdir_fn(unsigned int nr,
                                 const char *path,
                                 void *data);
 +int for_each_file_in_obj_subdir(unsigned int subdir_nr,
 +                              struct strbuf *path,
 +                              each_loose_object_fn obj_cb,
 +                              each_loose_cruft_fn cruft_cb,
 +                              each_loose_subdir_fn subdir_cb,
 +                              void *data);
  int for_each_loose_file_in_objdir(const char *path,
                                  each_loose_object_fn obj_cb,
                                  each_loose_cruft_fn cruft_cb,
@@@ -1795,7 -1845,6 +1795,7 @@@ struct object_info 
        off_t *disk_sizep;
        unsigned char *delta_base_sha1;
        struct strbuf *typename;
 +      void **contentp;
  
        /* Response */
        enum {
   */
  #define OBJECT_INFO_INIT {NULL}
  
 +/* Invoke lookup_replace_object() on the given hash */
 +#define OBJECT_INFO_LOOKUP_REPLACE 1
 +/* Allow reading from a loose object file of unknown/bogus type */
 +#define OBJECT_INFO_ALLOW_UNKNOWN_TYPE 2
 +/* Do not check cached storage */
 +#define OBJECT_INFO_SKIP_CACHED 4
 +/* Do not retry packed storage after checking packed and loose storage */
 +#define OBJECT_INFO_QUICK 8
  extern int sha1_object_info_extended(const unsigned char *, struct object_info *, unsigned flags);
  extern int packed_object_info(struct packed_git *pack, off_t offset, struct object_info *);
  
@@@ -1955,6 -1996,8 +1955,8 @@@ void shift_tree_by(const struct object_
  #define WS_TRAILING_SPACE      (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF)
  #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|8)
  #define WS_TAB_WIDTH_MASK        077
+ /* All WS_* -- when extended, adapt diff.c emit_symbol */
+ #define WS_RULE_MASK           07777
  extern unsigned whitespace_rule_cfg;
  extern unsigned whitespace_rule(const char *);
  extern unsigned parse_whitespace_rule(const char *);
diff --combined diff.c
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -16,6 -16,7 +16,7 @@@
  #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"
@@@ -32,6 -33,7 +33,7 @@@ 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;
@@@ -56,6 -58,14 +58,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)
@@@ -81,6 -91,22 +91,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;
  }
  
@@@ -229,12 -255,44 +255,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_color_config(var, value, cb) < 0)
 -              return -1;
 -
        return git_diff_basic_config(var, value, cb);
  }
  
@@@ -406,6 -467,8 +464,6 @@@ static struct diff_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;
@@@ -554,68 -618,704 +612,704 @@@ 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 next_byte(const char **cp, const char **endp,
+                    const struct diff_options *diffopt)
+ {
+       int retval;
+       if (*cp > *endp)
+               return -1;
+       if (DIFF_XDL_TST(diffopt, IGNORE_WHITESPACE_CHANGE)) {
+               while (*cp < *endp && isspace(**cp))
+                       (*cp)++;
+               /*
+                * After skipping a couple of whitespaces, we still have to
+                * account for one space.
+                */
+               return (int)' ';
+       }
+       if (DIFF_XDL_TST(diffopt, IGNORE_WHITESPACE)) {
+               while (*cp < *endp && isspace(**cp))
+                       (*cp)++;
+               /* return the first non-ws character via the usual below */
+       }
+       retval = (unsigned char)(**cp);
+       (*cp)++;
+       return retval;
+ }
+ static int moved_entry_cmp(const struct diff_options *diffopt,
+                          const struct moved_entry *a,
+                          const struct moved_entry *b,
+                          const void *keydata)
+ {
+       const char *ap = a->es->line, *ae = a->es->line + a->es->len;
+       const char *bp = b->es->line, *be = b->es->line + b->es->len;
+       if (!(diffopt->xdl_opts & XDF_WHITESPACE_FLAGS))
+               return a->es->len != b->es->len  || memcmp(ap, bp, a->es->len);
+       if (DIFF_XDL_TST(diffopt, IGNORE_WHITESPACE_AT_EOL)) {
+               while (ae > ap && isspace(*ae))
+                       ae--;
+               while (be > bp && isspace(*be))
+                       be--;
+       }
+       while (1) {
+               int ca, cb;
+               ca = next_byte(&ap, &ae, diffopt);
+               cb = next_byte(&bp, &be, diffopt);
+               if (ca != cb)
+                       return 1;
+               if (ca < 0)
+                       return 0;
+       }
+ }
+ static unsigned get_string_hash(struct emitted_diff_symbol *es, struct diff_options *o)
+ {
+       if (o->xdl_opts & XDF_WHITESPACE_FLAGS) {
+               static struct strbuf sb = STRBUF_INIT;
+               const char *ap = es->line, *ae = es->line + es->len;
+               int c;
+               strbuf_reset(&sb);
+               while (ae > ap && isspace(*ae))
+                       ae--;
+               while ((c = next_byte(&ap, &ae, o)) > 0)
+                       strbuf_addch(&sb, c);
+               return memhash(sb.buf, sb.len);
+       } else {
+               return memhash(es->line, es->len);
+       }
+ }
+ 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 = get_string_hash(l, o);
+       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;
+ }
+ /* 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) {
+                       if (block_length < COLOR_MOVED_MIN_BLOCK_LENGTH &&
+                           o->color_moved != COLOR_MOVED_PLAIN) {
+                               for (i = 0; i < block_length + 1; i++) {
+                                       l = &o->emitted_symbols->buf[n - i];
+                                       l->flags &= ~DIFF_SYMBOL_MOVED_LINE;
+                               }
+                       }
+                       pmb_nr = 0;
+                       block_length = 0;
+                       continue;
+               }
+               l->flags |= DIFF_SYMBOL_MOVED_LINE;
+               block_length++;
+               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;
+               }
+               if (flipped_block)
+                       l->flags |= DIFF_SYMBOL_MOVED_LINE_ALT;
+       }
+       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);
  }
  
@@@ -694,17 -1397,17 +1391,17 @@@ static void remove_tempfile(void
        }
  }
  
- 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;
        }
  }
@@@ -713,7 -1416,6 +1410,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)) {
                a_prefix = o->b_prefix;
  
        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)
@@@ -869,37 -1565,49 +1559,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;
  }
  
@@@ -981,24 -1689,20 +1683,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;
@@@ -1092,11 -1796,12 +1790,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)
@@@ -1170,6 -1895,11 +1889,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)
@@@ -1204,6 -1934,7 +1928,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);
@@@ -1240,6 -1971,8 +1965,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) {
@@@ -1268,30 -2001,25 +1993,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;
        }
  }
@@@ -1513,20 -2228,14 +2220,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);
  }
  
  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)
@@@ -2087,7 -2817,7 +2809,7 @@@ static void show_dirstat_by_line(struc
                         * 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;
@@@ -2217,8 -2947,8 +2939,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)
@@@ -2361,24 -3101,16 +3093,16 @@@ static void builtin_diff(const char *na
        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 (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) &&
            ( (!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)) {
                        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]);
+                       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);
                }
  
@@@ -3274,8 -4021,8 +4013,8 @@@ 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);
@@@ -3398,6 -4145,8 +4137,8 @@@ void diff_setup(struct diff_options *op
                options->a_prefix = "a/";
                options->b_prefix = "b/";
        }
+       options->color_moved = diff_color_moved_default;
  }
  
  void diff_setup_done(struct diff_options *options)
  
        if (DIFF_OPT_TST(options, 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)
@@@ -3931,7 -4683,19 +4675,19 @@@ int diff_opt_parse(struct diff_options 
        }
        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;
        }
@@@ -4461,67 -5225,76 +5217,76 @@@ 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);
                }
+               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);
  }
  
  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);
                }
-               show_mode_change(file, p, !p->score, line_prefix);
+               show_mode_change(opt, p, !p->score);
                break;
        }
  }
@@@ -4726,6 -5499,51 +5491,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;
                        fclose(options->file);
                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)
diff --combined submodule.c
@@@ -1,5 -1,4 +1,5 @@@
  #include "cache.h"
 +#include "repository.h"
  #include "config.h"
  #include "submodule-config.h"
  #include "submodule.h"
  #include "worktree.h"
  #include "parse-options.h"
  
 -static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND;
  static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;
 -static int parallel_jobs = 1;
  static struct string_list changed_submodule_paths = STRING_LIST_INIT_DUP;
  static int initialized_fetch_ref_tips;
  static struct oid_array ref_tips_before_fetch;
  static struct oid_array ref_tips_after_fetch;
  
  /*
 - * The following flag is set if the .gitmodules file is unmerged. We then
 - * disable recursion for all submodules where .git/config doesn't have a
 - * matching config entry because we can't guess what might be configured in
 - * .gitmodules unless the user resolves the conflict. When a command line
 - * option is given (which always overrides configuration) this flag will be
 - * ignored.
 + * Check if the .gitmodules file is unmerged. Parsing of the .gitmodules file
 + * will be disabled because we can't guess what might be configured in
 + * .gitmodules unless the user resolves the conflict.
   */
 -static int gitmodules_is_unmerged;
 +int is_gitmodules_unmerged(const struct index_state *istate)
 +{
 +      int pos = index_name_pos(istate, GITMODULES_FILE, strlen(GITMODULES_FILE));
 +      if (pos < 0) { /* .gitmodules not found or isn't merged */
 +              pos = -1 - pos;
 +              if (istate->cache_nr > pos) {  /* there is a .gitmodules */
 +                      const struct cache_entry *ce = istate->cache[pos];
 +                      if (ce_namelen(ce) == strlen(GITMODULES_FILE) &&
 +                          !strcmp(ce->name, GITMODULES_FILE))
 +                              return 1;
 +              }
 +      }
 +
 +      return 0;
 +}
  
  /*
 - * This flag is set if the .gitmodules file had unstaged modifications on
 - * startup. This must be checked before allowing modifications to the
 - * .gitmodules file with the intention to stage them later, because when
 - * continuing we would stage the modifications the user didn't stage herself
 - * too. That might change in a future version when we learn to stage the
 - * changes we do ourselves without staging any previous modifications.
 + * Check if the .gitmodules file has unstaged modifications.  This must be
 + * checked before allowing modifications to the .gitmodules file with the
 + * intention to stage them later, because when continuing we would stage the
 + * modifications the user didn't stage herself too. That might change in a
 + * future version when we learn to stage the changes we do ourselves without
 + * staging any previous modifications.
   */
 -static int gitmodules_is_modified;
 -
 -int is_staging_gitmodules_ok(void)
 +int is_staging_gitmodules_ok(const struct index_state *istate)
  {
 -      return !gitmodules_is_modified;
 +      int pos = index_name_pos(istate, GITMODULES_FILE, strlen(GITMODULES_FILE));
 +
 +      if ((pos >= 0) && (pos < istate->cache_nr)) {
 +              struct stat st;
 +              if (lstat(GITMODULES_FILE, &st) == 0 &&
 +                  ce_match_stat(istate->cache[pos], &st, 0) & DATA_CHANGED)
 +                      return 0;
 +      }
 +
 +      return 1;
  }
  
  /*
@@@ -79,13 -62,13 +79,13 @@@ int update_path_in_gitmodules(const cha
        struct strbuf entry = STRBUF_INIT;
        const struct submodule *submodule;
  
 -      if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */
 +      if (!file_exists(GITMODULES_FILE)) /* Do nothing without .gitmodules */
                return -1;
  
 -      if (gitmodules_is_unmerged)
 +      if (is_gitmodules_unmerged(&the_index))
                die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first"));
  
 -      submodule = submodule_from_path(null_sha1, oldpath);
 +      submodule = submodule_from_path(&null_oid, oldpath);
        if (!submodule || !submodule->name) {
                warning(_("Could not find section in .gitmodules where path=%s"), oldpath);
                return -1;
@@@ -93,7 -76,7 +93,7 @@@
        strbuf_addstr(&entry, "submodule.");
        strbuf_addstr(&entry, submodule->name);
        strbuf_addstr(&entry, ".path");
 -      if (git_config_set_in_file_gently(".gitmodules", entry.buf, newpath) < 0) {
 +      if (git_config_set_in_file_gently(GITMODULES_FILE, entry.buf, newpath) < 0) {
                /* Maybe the user already did that, don't error out here */
                warning(_("Could not update .gitmodules entry %s"), entry.buf);
                strbuf_release(&entry);
@@@ -113,20 -96,20 +113,20 @@@ int remove_path_from_gitmodules(const c
        struct strbuf sect = STRBUF_INIT;
        const struct submodule *submodule;
  
 -      if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */
 +      if (!file_exists(GITMODULES_FILE)) /* Do nothing without .gitmodules */
                return -1;
  
 -      if (gitmodules_is_unmerged)
 +      if (is_gitmodules_unmerged(&the_index))
                die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first"));
  
 -      submodule = submodule_from_path(null_sha1, path);
 +      submodule = submodule_from_path(&null_oid, path);
        if (!submodule || !submodule->name) {
                warning(_("Could not find section in .gitmodules where path=%s"), path);
                return -1;
        }
        strbuf_addstr(&sect, "submodule.");
        strbuf_addstr(&sect, submodule->name);
 -      if (git_config_rename_section_in_file(".gitmodules", sect.buf, NULL) < 0) {
 +      if (git_config_rename_section_in_file(GITMODULES_FILE, sect.buf, NULL) < 0) {
                /* Maybe the user already did that, don't error out here */
                warning(_("Could not remove .gitmodules entry for %s"), path);
                strbuf_release(&sect);
  
  void stage_updated_gitmodules(void)
  {
 -      if (add_file_to_cache(".gitmodules", 0))
 +      if (add_file_to_cache(GITMODULES_FILE, 0))
                die(_("staging updated .gitmodules failed"));
  }
  
@@@ -163,11 -146,11 +163,11 @@@ done
  void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
                                             const char *path)
  {
 -      const struct submodule *submodule = submodule_from_path(null_sha1, path);
 +      const struct submodule *submodule = submodule_from_path(&null_oid, path);
        if (submodule) {
                if (submodule->ignore)
                        handle_ignore_submodules_arg(diffopt, submodule->ignore);
 -              else if (gitmodules_is_unmerged)
 +              else if (is_gitmodules_unmerged(&the_index))
                        DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES);
        }
  }
  /* For loading from the .gitmodules file. */
  static int git_modules_config(const char *var, const char *value, void *cb)
  {
 -      if (!strcmp(var, "submodule.fetchjobs")) {
 -              parallel_jobs = git_config_int(var, value);
 -              if (parallel_jobs < 0)
 -                      die(_("negative values not allowed for submodule.fetchJobs"));
 -              return 0;
 -      } else if (starts_with(var, "submodule."))
 +      if (starts_with(var, "submodule."))
                return parse_submodule_config_option(var, value);
 -      else if (!strcmp(var, "fetch.recursesubmodules")) {
 -              config_fetch_recurse_submodules = parse_fetch_recurse_submodules_arg(var, value);
 -              return 0;
 -      }
        return 0;
  }
  
@@@ -230,58 -222,67 +230,58 @@@ void load_submodule_cache(void
        git_config(submodule_config, NULL);
  }
  
 -void gitmodules_config(void)
 +static int gitmodules_cb(const char *var, const char *value, void *data)
  {
 -      const char *work_tree = get_git_work_tree();
 -      if (work_tree) {
 -              struct strbuf gitmodules_path = STRBUF_INIT;
 -              int pos;
 -              strbuf_addstr(&gitmodules_path, work_tree);
 -              strbuf_addstr(&gitmodules_path, "/.gitmodules");
 -              if (read_cache() < 0)
 -                      die("index file corrupt");
 -              pos = cache_name_pos(".gitmodules", 11);
 -              if (pos < 0) { /* .gitmodules not found or isn't merged */
 -                      pos = -1 - pos;
 -                      if (active_nr > pos) {  /* there is a .gitmodules */
 -                              const struct cache_entry *ce = active_cache[pos];
 -                              if (ce_namelen(ce) == 11 &&
 -                                  !memcmp(ce->name, ".gitmodules", 11))
 -                                      gitmodules_is_unmerged = 1;
 -                      }
 -              } else if (pos < active_nr) {
 -                      struct stat st;
 -                      if (lstat(".gitmodules", &st) == 0 &&
 -                          ce_match_stat(active_cache[pos], &st, 0) & DATA_CHANGED)
 -                              gitmodules_is_modified = 1;
 -              }
 +      struct repository *repo = data;
 +      return submodule_config_option(repo, var, value);
 +}
  
 -              if (!gitmodules_is_unmerged)
 -                      git_config_from_file(git_modules_config,
 -                              gitmodules_path.buf, NULL);
 -              strbuf_release(&gitmodules_path);
 +void repo_read_gitmodules(struct repository *repo)
 +{
 +      if (repo->worktree) {
 +              char *gitmodules;
 +
 +              if (repo_read_index(repo) < 0)
 +                      return;
 +
 +              gitmodules = repo_worktree_path(repo, GITMODULES_FILE);
 +
 +              if (!is_gitmodules_unmerged(repo->index))
 +                      git_config_from_file(gitmodules_cb, gitmodules, repo);
 +
 +              free(gitmodules);
        }
  }
  
 -void gitmodules_config_sha1(const unsigned char *commit_sha1)
 +void gitmodules_config(void)
 +{
 +      repo_read_gitmodules(the_repository);
 +}
 +
 +void gitmodules_config_oid(const struct object_id *commit_oid)
  {
        struct strbuf rev = STRBUF_INIT;
 -      unsigned char sha1[20];
 +      struct object_id oid;
  
 -      if (gitmodule_sha1_from_commit(commit_sha1, sha1, &rev)) {
 -              git_config_from_blob_sha1(git_modules_config, rev.buf,
 -                                        sha1, NULL);
 +      if (gitmodule_oid_from_commit(commit_oid, &oid, &rev)) {
 +              git_config_from_blob_oid(submodule_config, rev.buf,
 +                                       &oid, NULL);
        }
        strbuf_release(&rev);
  }
  
  /*
 - * NEEDSWORK: With the addition of different configuration options to determine
 - * if a submodule is of interests, the validity of this function's name comes
 - * into question.  Once the dust has settled and more concrete terminology is
 - * decided upon, come up with a more proper name for this function.  One
 - * potential candidate could be 'is_submodule_active()'.
 - *
   * Determine if a submodule has been initialized at a given 'path'
   */
 -int is_submodule_initialized(const char *path)
 +int is_submodule_active(struct repository *repo, const char *path)
  {
        int ret = 0;
        char *key = NULL;
        char *value = NULL;
        const struct string_list *sl;
 -      const struct submodule *module = submodule_from_path(null_sha1, path);
 +      const struct submodule *module;
 +
 +      module = submodule_from_cache(repo, &null_oid, path);
  
        /* early return if there isn't a path->module mapping */
        if (!module)
  
        /* submodule.<name>.active is set */
        key = xstrfmt("submodule.%s.active", module->name);
 -      if (!git_config_get_bool(key, &ret)) {
 +      if (!repo_config_get_bool(repo, key, &ret)) {
                free(key);
                return ret;
        }
        free(key);
  
        /* submodule.active is set */
 -      sl = git_config_get_value_multi("submodule.active");
 +      sl = repo_config_get_value_multi(repo, "submodule.active");
        if (sl) {
                struct pathspec ps;
                struct argv_array args = ARGV_ARRAY_INIT;
  
        /* fallback to checking if the URL is set */
        key = xstrfmt("submodule.%s.url", module->name);
 -      ret = !git_config_get_string(key, &value);
 +      ret = !repo_config_get_string(repo, key, &value);
  
        free(value);
        free(key);
@@@ -478,9 -479,7 +478,7 @@@ static int prepare_submodule_summary(st
        return prepare_revision_walk(rev);
  }
  
- static void print_submodule_summary(struct rev_info *rev, FILE *f,
-               const char *line_prefix,
-               const char *del, const char *add, const char *reset)
+ static void print_submodule_summary(struct rev_info *rev, struct diff_options *o)
  {
        static const char format[] = "  %m %s";
        struct strbuf sb = STRBUF_INIT;
                ctx.date_mode = rev->date_mode;
                ctx.output_encoding = get_log_output_encoding();
                strbuf_setlen(&sb, 0);
-               strbuf_addstr(&sb, line_prefix);
-               if (commit->object.flags & SYMMETRIC_LEFT) {
-                       if (del)
-                               strbuf_addstr(&sb, del);
-               }
-               else if (add)
-                       strbuf_addstr(&sb, add);
                format_commit_message(commit, format, &sb, &ctx);
-               if (reset)
-                       strbuf_addstr(&sb, reset);
                strbuf_addch(&sb, '\n');
-               fprintf(f, "%s", sb.buf);
+               if (commit->object.flags & SYMMETRIC_LEFT)
+                       diff_emit_submodule_del(o, sb.buf);
+               else
+                       diff_emit_submodule_add(o, sb.buf);
        }
        strbuf_release(&sb);
  }
@@@ -529,11 -522,9 +521,9 @@@ void prepare_submodule_repo_env(struct 
   * attempt to lookup both the left and right commits and put them into the
   * left and right pointers.
   */
- static void show_submodule_header(FILE *f, const char *path,
-               const char *line_prefix,
+ static void show_submodule_header(struct diff_options *o, const char *path,
                struct object_id *one, struct object_id *two,
-               unsigned dirty_submodule, const char *meta,
-               const char *reset,
+               unsigned dirty_submodule,
                struct commit **left, struct commit **right,
                struct commit_list **merge_bases)
  {
        int fast_forward = 0, fast_backward = 0;
  
        if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
-               fprintf(f, "%sSubmodule %s contains untracked content\n",
-                       line_prefix, path);
+               diff_emit_submodule_untracked(o, path);
        if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
-               fprintf(f, "%sSubmodule %s contains modified content\n",
-                       line_prefix, path);
+               diff_emit_submodule_modified(o, path);
  
        if (is_null_oid(one))
                message = "(new submodule)";
        }
  
  output_header:
-       strbuf_addf(&sb, "%s%sSubmodule %s ", line_prefix, meta, path);
+       strbuf_addf(&sb, "Submodule %s ", path);
        strbuf_add_unique_abbrev(&sb, one->hash, DEFAULT_ABBREV);
        strbuf_addstr(&sb, (fast_backward || fast_forward) ? ".." : "...");
        strbuf_add_unique_abbrev(&sb, two->hash, DEFAULT_ABBREV);
        if (message)
-               strbuf_addf(&sb, " %s%s\n", message, reset);
+               strbuf_addf(&sb, " %s\n", message);
        else
-               strbuf_addf(&sb, "%s:%s\n", fast_backward ? " (rewind)" : "", reset);
-       fwrite(sb.buf, sb.len, 1, f);
+               strbuf_addf(&sb, "%s:\n", fast_backward ? " (rewind)" : "");
+       diff_emit_submodule_header(o, sb.buf);
  
        strbuf_release(&sb);
  }
  
- void show_submodule_summary(FILE *f, const char *path,
-               const char *line_prefix,
+ void show_submodule_summary(struct diff_options *o, const char *path,
                struct object_id *one, struct object_id *two,
-               unsigned dirty_submodule, const char *meta,
-               const char *del, const char *add, const char *reset)
+               unsigned dirty_submodule)
  {
        struct rev_info rev;
        struct commit *left = NULL, *right = NULL;
        struct commit_list *merge_bases = NULL;
  
-       show_submodule_header(f, path, line_prefix, one, two, dirty_submodule,
-                             meta, reset, &left, &right, &merge_bases);
+       show_submodule_header(o, path, one, two, dirty_submodule,
+                             &left, &right, &merge_bases);
  
        /*
         * If we don't have both a left and a right pointer, there is no
  
        /* Treat revision walker failure the same as missing commits */
        if (prepare_submodule_summary(&rev, path, left, right, merge_bases)) {
-               fprintf(f, "%s(revision walker failed)\n", line_prefix);
+               diff_emit_submodule_error(o, "(revision walker failed)\n");
                goto out;
        }
  
-       print_submodule_summary(&rev, f, line_prefix, del, add, reset);
+       print_submodule_summary(&rev, o);
  
  out:
        if (merge_bases)
        clear_commit_marks(right, ~0);
  }
  
- void show_submodule_inline_diff(FILE *f, const char *path,
-               const char *line_prefix,
+ void show_submodule_inline_diff(struct diff_options *o, const char *path,
                struct object_id *one, struct object_id *two,
-               unsigned dirty_submodule, const char *meta,
-               const char *del, const char *add, const char *reset,
-               const struct diff_options *o)
+               unsigned dirty_submodule)
  {
        const struct object_id *old = &empty_tree_oid, *new = &empty_tree_oid;
        struct commit *left = NULL, *right = NULL;
        struct commit_list *merge_bases = NULL;
-       struct strbuf submodule_dir = STRBUF_INIT;
        struct child_process cp = CHILD_PROCESS_INIT;
+       struct strbuf sb = STRBUF_INIT;
  
-       show_submodule_header(f, path, line_prefix, one, two, dirty_submodule,
-                             meta, reset, &left, &right, &merge_bases);
+       show_submodule_header(o, path, one, two, dirty_submodule,
+                             &left, &right, &merge_bases);
  
        /* We need a valid left and right commit to display a difference */
        if (!(left || is_null_oid(one)) ||
        if (right)
                new = two;
  
-       fflush(f);
        cp.git_cmd = 1;
        cp.dir = path;
-       cp.out = dup(fileno(f));
+       cp.out = -1;
        cp.no_stdin = 1;
  
        /* TODO: other options may need to be passed here. */
        argv_array_pushl(&cp.args, "diff", "--submodule=diff", NULL);
+       argv_array_pushf(&cp.args, "--color=%s", want_color(o->use_color) ?
+                        "always" : "never");
  
-       argv_array_pushf(&cp.args, "--line-prefix=%s", line_prefix);
        if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
                argv_array_pushf(&cp.args, "--src-prefix=%s%s/",
                                 o->b_prefix, path);
                argv_array_push(&cp.args, oid_to_hex(new));
  
        prepare_submodule_repo_env(&cp.env_array);
-       if (run_command(&cp))
-               fprintf(f, "(diff failed)\n");
+       if (start_command(&cp))
+               diff_emit_submodule_error(o, "(diff failed)\n");
+       while (strbuf_getwholeline_fd(&sb, cp.out, '\n') != EOF)
+               diff_emit_submodule_pipethrough(o, sb.buf, sb.len);
+       if (finish_command(&cp))
+               diff_emit_submodule_error(o, "(diff failed)\n");
  
  done:
-       strbuf_release(&submodule_dir);
+       strbuf_release(&sb);
        if (merge_bases)
                free_commit_list(merge_bases);
        if (left)
                clear_commit_marks(right, ~0);
  }
  
 -void set_config_fetch_recurse_submodules(int value)
 -{
 -      config_fetch_recurse_submodules = value;
 -}
 -
  int should_update_submodules(void)
  {
        return config_update_recurse_submodules == RECURSE_SUBMODULES_ON;
@@@ -721,7 -717,7 +711,7 @@@ const struct submodule *submodule_from_
        if (!should_update_submodules())
                return NULL;
  
 -      return submodule_from_path(null_sha1, ce->name);
 +      return submodule_from_path(&null_oid, ce->name);
  }
  
  static struct oid_array *submodule_commits(struct string_list *submodules,
@@@ -840,9 -836,9 +830,9 @@@ static int submodule_has_commits(const 
        int has_commit = 1;
  
        /*
 -       * Perform a cheap, but incorrect check for the existance of 'commits'.
 +       * Perform a cheap, but incorrect check for the existence of 'commits'.
         * This is done by adding the submodule's object store to the in-core
 -       * object store, and then querying for each commit's existance.  If we
 +       * object store, and then querying for each commit's existence.  If we
         * do not have the commit object anywhere, there is no chance we have
         * it in the object store of the correct submodule and have it
         * reachable from a ref, so we can fail early without spawning rev-list
@@@ -998,8 -994,7 +988,8 @@@ static int push_submodule(const char *p
   * Perform a check in the submodule to see if the remote and refspec work.
   * Die if the submodule can't be pushed.
   */
 -static void submodule_push_check(const char *path, const struct remote *remote,
 +static void submodule_push_check(const char *path, const char *head,
 +                               const struct remote *remote,
                                 const char **refspec, int refspec_nr)
  {
        struct child_process cp = CHILD_PROCESS_INIT;
  
        argv_array_push(&cp.args, "submodule--helper");
        argv_array_push(&cp.args, "push-check");
 +      argv_array_push(&cp.args, head);
        argv_array_push(&cp.args, remote->name);
  
        for (i = 0; i < refspec_nr; i++)
@@@ -1046,20 -1040,10 +1036,20 @@@ int push_unpushed_submodules(struct oid
         * won't be propagated due to the remote being unconfigured (e.g. a URL
         * instead of a remote name).
         */
 -      if (remote->origin != REMOTE_UNCONFIGURED)
 +      if (remote->origin != REMOTE_UNCONFIGURED) {
 +              char *head;
 +              struct object_id head_oid;
 +
 +              head = resolve_refdup("HEAD", 0, head_oid.hash, NULL);
 +              if (!head)
 +                      die(_("Failed to resolve HEAD as a valid ref."));
 +
                for (i = 0; i < needs_pushing.nr; i++)
                        submodule_push_check(needs_pushing.items[i].string,
 -                                           remote, refspec, refspec_nr);
 +                                           head, remote,
 +                                           refspec, refspec_nr);
 +              free(head);
 +      }
  
        /* Actually push the submodules */
        for (i = 0; i < needs_pushing.nr; i++) {
@@@ -1133,43 -1117,16 +1123,43 @@@ static void calculate_changed_submodule
        initialized_fetch_ref_tips = 0;
  }
  
 +int submodule_touches_in_range(struct object_id *excl_oid,
 +                             struct object_id *incl_oid)
 +{
 +      struct string_list subs = STRING_LIST_INIT_DUP;
 +      struct argv_array args = ARGV_ARRAY_INIT;
 +      int ret;
 +
 +      gitmodules_config();
 +      /* No need to check if there are no submodules configured */
 +      if (!submodule_from_path(NULL, NULL))
 +              return 0;
 +
 +      argv_array_push(&args, "--"); /* args[0] program name */
 +      argv_array_push(&args, oid_to_hex(incl_oid));
 +      argv_array_push(&args, "--not");
 +      argv_array_push(&args, oid_to_hex(excl_oid));
 +
 +      collect_changed_submodules(&subs, &args);
 +      ret = subs.nr;
 +
 +      argv_array_clear(&args);
 +
 +      free_submodules_oids(&subs);
 +      return ret;
 +}
 +
  struct submodule_parallel_fetch {
        int count;
        struct argv_array args;
        const char *work_tree;
        const char *prefix;
        int command_line_option;
 +      int default_option;
        int quiet;
        int result;
  };
 -#define SPF_INIT {0, ARGV_ARRAY_INIT, NULL, NULL, 0, 0, 0}
 +#define SPF_INIT {0, ARGV_ARRAY_INIT, NULL, NULL, 0, 0, 0, 0}
  
  static int get_next_submodule(struct child_process *cp,
                              struct strbuf *err, void *data, void **task_cb)
                if (!S_ISGITLINK(ce->ce_mode))
                        continue;
  
 -              submodule = submodule_from_path(null_sha1, ce->name);
 +              submodule = submodule_from_path(&null_oid, ce->name);
                if (!submodule)
 -                      submodule = submodule_from_name(null_sha1, ce->name);
 +                      submodule = submodule_from_name(&null_oid, ce->name);
  
                default_argv = "yes";
                if (spf->command_line_option == RECURSE_SUBMODULES_DEFAULT) {
                                        default_argv = "on-demand";
                                }
                        } else {
 -                              if ((config_fetch_recurse_submodules == RECURSE_SUBMODULES_OFF) ||
 -                                  gitmodules_is_unmerged)
 +                              if (spf->default_option == RECURSE_SUBMODULES_OFF)
                                        continue;
 -                              if (config_fetch_recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) {
 +                              if (spf->default_option == RECURSE_SUBMODULES_ON_DEMAND) {
                                        if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name))
                                                continue;
                                        default_argv = "on-demand";
@@@ -1276,7 -1234,6 +1266,7 @@@ static int fetch_finish(int retvalue, s
  
  int fetch_populated_submodules(const struct argv_array *options,
                               const char *prefix, int command_line_option,
 +                             int default_option,
                               int quiet, int max_parallel_jobs)
  {
        int i;
  
        spf.work_tree = get_git_work_tree();
        spf.command_line_option = command_line_option;
 +      spf.default_option = default_option;
        spf.quiet = quiet;
        spf.prefix = prefix;
  
        argv_array_push(&spf.args, "--recurse-submodules-default");
        /* default value, "--submodule-prefix" and its value are added later */
  
 -      if (max_parallel_jobs < 0)
 -              max_parallel_jobs = parallel_jobs;
 -
        calculate_changed_submodule_paths();
        run_processes_parallel(max_parallel_jobs,
                               get_next_submodule,
@@@ -1548,7 -1507,7 +1538,7 @@@ int submodule_move_head(const char *pat
        const struct submodule *sub;
        int *error_code_ptr, error_code;
  
 -      if (!is_submodule_initialized(path))
 +      if (!is_submodule_active(the_repository, path))
                return 0;
  
        if (flags & SUBMODULE_MOVE_HEAD_FORCE)
        if (old && !is_submodule_populated_gently(path, error_code_ptr))
                return 0;
  
 -      sub = submodule_from_path(null_sha1, path);
 +      sub = submodule_from_path(&null_oid, path);
  
        if (!sub)
                die("BUG: could not get submodule information for '%s'", path);
@@@ -1819,6 -1778,11 +1809,6 @@@ int merge_submodule(struct object_id *r
        return 0;
  }
  
 -int parallel_submodules(void)
 -{
 -      return parallel_jobs;
 -}
 -
  /*
   * Embeds a single submodules git directory into the superprojects git dir,
   * non recursively.
@@@ -1841,7 -1805,7 +1831,7 @@@ static void relocate_single_git_dir_int
  
        real_old_git_dir = real_pathdup(old_git_dir, 1);
  
 -      sub = submodule_from_path(null_sha1, path);
 +      sub = submodule_from_path(&null_oid, path);
        if (!sub)
                die(_("could not lookup name for submodule '%s'"), path);
  
@@@ -1897,7 -1861,7 +1887,7 @@@ void absorb_git_dir_into_superproject(c
                * superproject did not rewrite the git file links yet,
                * fix it now.
                */
 -              sub = submodule_from_path(null_sha1, path);
 +              sub = submodule_from_path(&null_oid, path);
                if (!sub)
                        die(_("could not lookup name for submodule '%s'"), path);
                connect_work_tree_and_git_dir(path,
@@@ -2040,7 -2004,7 +2030,7 @@@ int submodule_to_gitdir(struct strbuf *
        }
        if (!is_git_directory(buf->buf)) {
                gitmodules_config();
 -              sub = submodule_from_path(null_sha1, submodule);
 +              sub = submodule_from_path(&null_oid, submodule);
                if (!sub) {
                        ret = -1;
                        goto cleanup;
diff --combined submodule.h
@@@ -1,7 -1,6 +1,7 @@@
  #ifndef SUBMODULE_H
  #define SUBMODULE_H
  
 +struct repository;
  struct diff_options;
  struct argv_array;
  struct oid_array;
@@@ -33,8 -32,7 +33,8 @@@ struct submodule_update_strategy 
  };
  #define SUBMODULE_UPDATE_STRATEGY_INIT {SM_UPDATE_UNSPECIFIED, NULL}
  
 -extern int is_staging_gitmodules_ok(void);
 +extern int is_gitmodules_unmerged(const struct index_state *istate);
 +extern int is_staging_gitmodules_ok(const struct index_state *istate);
  extern int update_path_in_gitmodules(const char *oldpath, const char *newpath);
  extern int remove_path_from_gitmodules(const char *path);
  extern void stage_updated_gitmodules(void);
@@@ -48,9 -46,8 +48,9 @@@ int option_parse_recurse_submodules_wor
                                                     const char *arg, int unset);
  void load_submodule_cache(void);
  extern void gitmodules_config(void);
 -extern void gitmodules_config_sha1(const unsigned char *commit_sha1);
 -extern int is_submodule_initialized(const char *path);
 +extern void repo_read_gitmodules(struct repository *repo);
 +extern void gitmodules_config_oid(const struct object_id *commit_oid);
 +extern int is_submodule_active(struct repository *repo, const char *path);
  /*
   * Determine if a submodule has been populated at a given 'path' by checking if
   * the <path>/.git resolves to a valid git repository.
@@@ -66,17 -63,13 +66,12 @@@ extern int parse_submodule_update_strat
                struct submodule_update_strategy *dst);
  extern const char *submodule_strategy_to_string(const struct submodule_update_strategy *s);
  extern void handle_ignore_submodules_arg(struct diff_options *, const char *);
- extern void show_submodule_summary(FILE *f, const char *path,
-               const char *line_prefix,
+ extern void show_submodule_summary(struct diff_options *o, const char *path,
                struct object_id *one, struct object_id *two,
-               unsigned dirty_submodule, const char *meta,
-               const char *del, const char *add, const char *reset);
- extern void show_submodule_inline_diff(FILE *f, const char *path,
-               const char *line_prefix,
+               unsigned dirty_submodule);
+ extern void show_submodule_inline_diff(struct diff_options *o, const char *path,
                struct object_id *one, struct object_id *two,
-               unsigned dirty_submodule, const char *meta,
-               const char *del, const char *add, const char *reset,
-               const struct diff_options *opt);
+               unsigned dirty_submodule);
 -extern void set_config_fetch_recurse_submodules(int value);
  /* Check if we want to update any submodule.*/
  extern int should_update_submodules(void);
  /*
@@@ -87,7 -80,6 +82,7 @@@ extern const struct submodule *submodul
  extern void check_for_new_submodule_commits(struct object_id *oid);
  extern int fetch_populated_submodules(const struct argv_array *options,
                               const char *prefix, int command_line_option,
 +                             int default_option,
                               int quiet, int max_parallel_jobs);
  extern unsigned is_submodule_modified(const char *path, int ignore_untracked);
  extern int submodule_uses_gitfile(const char *path);
@@@ -100,10 -92,6 +95,10 @@@ extern int merge_submodule(struct objec
                           const struct object_id *base,
                           const struct object_id *a,
                           const struct object_id *b, int search);
 +
 +/* Checks if there are submodule changes in a..b. */
 +extern int submodule_touches_in_range(struct object_id *a,
 +                                    struct object_id *b);
  extern int find_unpushed_submodules(struct oid_array *commits,
                                    const char *remotes_name,
                                    struct string_list *needs_pushing);
@@@ -113,6 -101,7 +108,6 @@@ extern int push_unpushed_submodules(str
                                    const struct string_list *push_options,
                                    int dry_run);
  extern void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir);
 -extern int parallel_submodules(void);
  /*
   * Given a submodule path (as in the index), return the repository
   * path of that submodule in 'buf'. Return -1 on error or when the