Merge branch 'sb/diff-color-move-more'
authorJunio C Hamano <gitster@pobox.com>
Thu, 2 Aug 2018 22:30:40 +0000 (15:30 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 2 Aug 2018 22:30:40 +0000 (15:30 -0700)
"git diff --color-moved" feature has further been tweaked.

* sb/diff-color-move-more:
diff.c: offer config option to control ws handling in move detection
diff.c: add white space mode to move detection that allows indent changes
diff.c: factor advance_or_nullify out of mark_color_as_moved
diff.c: decouple white space treatment from move detection algorithm
diff.c: add a blocks mode for moved code detection
diff.c: adjust hash function signature to match hashmap expectation
diff.c: do not pass diff options as keydata to hashmap
t4015: avoid git as a pipe input
xdiff/xdiffi.c: remove unneeded function declarations
xdiff/xdiff.h: remove unused flags

1  2 
Documentation/config.txt
Documentation/diff-options.txt
diff.c
diff.h
diff --combined Documentation/config.txt
index 8c4831b82ff101391435aff499aaff1fb16e0ef3,6ca7118b018646ace5c0a5fae5df79b4f3d2c279..3fdc6f0327fe8051bbf42c154fe0e56b06bfe88c
@@@ -354,7 -354,7 +354,7 @@@ advice.*:
                Advice on what to do when you've accidentally added one
                git repo inside of another.
        ignoredHook::
 -              Advice shown if an hook is ignored because the hook is not
 +              Advice shown if a hook is ignored because the hook is not
                set as executable.
        waitingForEditor::
                Print a message to the terminal whenever Git is waiting for
@@@ -390,19 -390,16 +390,19 @@@ core.hideDotFiles:
        default mode is 'dotGitOnly'.
  
  core.ignoreCase::
 -      If true, this option enables various workarounds to enable
 +      Internal variable which enables various workarounds to enable
        Git to work better on filesystems that are not case sensitive,
 -      like FAT. For example, if a directory listing finds
 -      "makefile" when Git expects "Makefile", Git will assume
 +      like APFS, HFS+, FAT, NTFS, etc. For example, if a directory listing
 +      finds "makefile" when Git expects "Makefile", Git will assume
        it is really the same file, and continue to remember it as
        "Makefile".
  +
  The default is false, except linkgit:git-clone[1] or linkgit:git-init[1]
  will probe and set core.ignoreCase true if appropriate when the repository
  is created.
 ++
 +Git relies on the proper configuration of this variable for your operating
 +and file system. Modifying this value may result in unexpected behavior.
  
  core.precomposeUnicode::
        This option is only used by Mac OS implementation of Git.
@@@ -533,12 -530,6 +533,12 @@@ core.autocrlf:
        This variable can be set to 'input',
        in which case no output conversion is performed.
  
 +core.checkRoundtripEncoding::
 +      A comma and/or whitespace separated list of encodings that Git
 +      performs UTF-8 round trip checks on if they are used in an
 +      `working-tree-encoding` attribute (see linkgit:gitattributes[5]).
 +      The default value is `SHIFT-JIS`.
 +
  core.symlinks::
        If false, symbolic links are checked out as small plain files that
        contain the link text. linkgit:git-update-index[1] and
@@@ -907,13 -898,6 +907,13 @@@ core.notesRef:
  This setting defaults to "refs/notes/commits", and it can be overridden by
  the `GIT_NOTES_REF` environment variable.  See linkgit:git-notes[1].
  
 +gc.commitGraph::
 +      If true, then gc will rewrite the commit-graph file when
 +      linkgit:git-gc[1] is run. When using linkgit:git-gc[1]
 +      '--auto' the commit-graph will be updated if housekeeping is
 +      required. Default is false. See linkgit:git-commit-graph[1]
 +      for details.
 +
  core.sparseCheckout::
        Enable "sparse checkout" feature. See section "Sparse checkout" in
        linkgit:git-read-tree[1] for more information.
@@@ -1074,10 -1058,6 +1074,10 @@@ branch.<name>.rebase:
        "git pull" is run. See "pull.rebase" for doing this in a non
        branch-specific manner.
  +
 +When `merges`, pass the `--rebase-merges` option to 'git rebase'
 +so that the local merge commits are included in the rebase (see
 +linkgit:git-rebase[1] for details).
 ++
  When preserve, also pass `--preserve-merges` along to 'git rebase'
  so that locally committed merge commits will not be flattened
  by running 'git pull'.
@@@ -1108,16 -1088,6 +1108,16 @@@ clean.requireForce:
        A boolean to make git-clean do nothing unless given -f,
        -i or -n.   Defaults to true.
  
 +color.advice::
 +      A boolean to enable/disable color in hints (e.g. when a push
 +      failed, see `advice.*` for a list).  May be set to `always`,
 +      `false` (or `never`) or `auto` (or `true`), in which case colors
 +      are used only when the error output goes to a terminal. If
 +      unset, then the value of `color.ui` is used (`auto` by default).
 +
 +color.advice.hint::
 +      Use customized color for hints.
 +
  color.branch::
        A boolean to enable/disable color in the output of
        linkgit:git-branch[1]. May be set to `always`,
@@@ -1152,6 -1122,11 +1152,11 @@@ diff.colorMoved:
        true the default color mode will be used. When set to false,
        moved lines are not colored.
  
+ diff.colorMovedWS::
+       When moved lines are colored using e.g. the `diff.colorMoved` setting,
+       this option controls the `<mode>` how spaces are treated
+       for details of valid modes see '--color-moved-ws' in linkgit:git-diff[1].
  color.diff.<slot>::
        Use customized color for diff colorization.  `<slot>` specifies
        which part of the patch to use the specified color, and is one
  color.decorate.<slot>::
        Use customized color for 'git log --decorate' output.  `<slot>` is one
        of `branch`, `remoteBranch`, `tag`, `stash` or `HEAD` for local
 -      branches, remote-tracking branches, tags, stash and HEAD, respectively.
 +      branches, remote-tracking branches, tags, stash and HEAD, respectively
 +      and `grafted` for grafted commits.
  
  color.grep::
        When set to `always`, always highlight matches.  When `false` (or
@@@ -1188,10 -1162,8 +1193,10 @@@ color.grep.<slot>:
        filename prefix (when not using `-h`)
  `function`;;
        function name lines (when using `-p`)
 -`linenumber`;;
 +`lineNumber`;;
        line number prefix (when using `-n`)
 +`column`;;
 +      column number prefix (when using `--column`)
  `match`;;
        matching text (same as setting `matchContext` and `matchSelected`)
  `matchContext`;;
@@@ -1223,15 -1195,6 +1228,15 @@@ color.pager:
        A boolean to enable/disable colored output when the pager is in
        use (default is true).
  
 +color.push::
 +      A boolean to enable/disable color in push errors. May be set to
 +      `always`, `false` (or `never`) or `auto` (or `true`), in which
 +      case colors are used only when the error output goes to a terminal.
 +      If unset, then the value of `color.ui` is used (`auto` by default).
 +
 +color.push.error::
 +      Use customized color for push errors.
 +
  color.showBranch::
        A boolean to enable/disable color in the output of
        linkgit:git-show-branch[1]. May be set to `always`,
@@@ -1260,42 -1223,6 +1265,42 @@@ color.status.<slot>:
        status short-format), or
        `unmerged` (files which have unmerged changes).
  
 +color.blame.repeatedLines::
 +      Use the customized color for the part of git-blame output that
 +      is repeated meta information per line (such as commit id,
 +      author name, date and timezone). Defaults to cyan.
 +
 +color.blame.highlightRecent::
 +      This can be used to color the metadata of a blame line depending
 +      on age of the line.
 ++
 +This setting should be set to a comma-separated list of color and date settings,
 +starting and ending with a color, the dates should be set from oldest to newest.
 +The metadata will be colored given the colors if the the line was introduced
 +before the given timestamp, overwriting older timestamped colors.
 ++
 +Instead of an absolute timestamp relative timestamps work as well, e.g.
 +2.weeks.ago is valid to address anything older than 2 weeks.
 ++
 +It defaults to 'blue,12 month ago,white,1 month ago,red', which colors
 +everything older than one year blue, recent changes between one month and
 +one year old are kept white, and lines introduced within the last month are
 +colored red.
 +
 +blame.coloring::
 +      This determines the coloring scheme to be applied to blame
 +      output. It can be 'repeatedLines', 'highlightRecent',
 +      or 'none' which is the default.
 +
 +color.transport::
 +      A boolean to enable/disable color when pushes are rejected. May be
 +      set to `always`, `false` (or `never`) or `auto` (or `true`), in which
 +      case colors are used only when the error output goes to a terminal.
 +      If unset, then the value of `color.ui` is used (`auto` by default).
 +
 +color.transport.rejected::
 +      Use customized color when a push was rejected.
 +
  color.ui::
        This variable determines the default value for variables such
        as `color.diff` and `color.grep` that control the use of color
@@@ -1421,14 -1348,6 +1426,14 @@@ credential.<url>.*:
  credentialCache.ignoreSIGHUP::
        Tell git-credential-cache--daemon to ignore SIGHUP, instead of quitting.
  
 +completion.commands::
 +      This is only used by git-completion.bash to add or remove
 +      commands from the list of completed commands. Normally only
 +      porcelain commands and a few select others are completed. You
 +      can add more commands, separated by space, in this
 +      variable. Prefixing the command with '-' will remove it from
 +      the existing list.
 +
  include::diff-config.txt[]
  
  difftool.<tool>.path::
@@@ -1644,18 -1563,6 +1649,18 @@@ gc.autoDetach:
        Make `git gc --auto` return immediately and run in background
        if the system supports it. Default is true.
  
 +gc.bigPackThreshold::
 +      If non-zero, all packs larger than this limit are kept when
 +      `git gc` is run. This is very similar to `--keep-base-pack`
 +      except that all packs that meet the threshold are kept, not
 +      just the base pack. Defaults to zero. Common unit suffixes of
 +      'k', 'm', or 'g' are supported.
 ++
 +Note that if the number of kept packs is more than gc.autoPackLimit,
 +this configuration variable is ignored, all packs except the base pack
 +will be repacked. After this the number of packs should go below
 +gc.autoPackLimit and gc.bigPackThreshold should be respected again.
 +
  gc.logExpiry::
        If the file gc.log exists, then `git gc --auto` won't run
        unless that file is more than 'gc.logExpiry' old.  Default is
@@@ -1806,9 -1713,6 +1811,9 @@@ gitweb.snapshot:
  grep.lineNumber::
        If set to true, enable `-n` option by default.
  
 +grep.column::
 +      If set to true, enable the `--column` option by default.
 +
  grep.patternType::
        Set the default matching behavior. Using a value of 'basic', 'extended',
        'fixed', or 'perl' will enable the `--basic-regexp`, `--extended-regexp`,
@@@ -2523,7 -2427,6 +2528,7 @@@ pack.window:
  pack.depth::
        The maximum delta depth used by linkgit:git-pack-objects[1] when no
        maximum depth is given on the command line. Defaults to 50.
 +      Maximum value is 4095.
  
  pack.windowMemory::
        The maximum size of memory that is consumed by each thread
@@@ -2560,8 -2463,7 +2565,8 @@@ pack.deltaCacheLimit:
        The maximum size of a delta, that is cached in
        linkgit:git-pack-objects[1]. This cache is used to speed up the
        writing object phase by not having to recompute the final delta
 -      result once the best match for all objects is found. Defaults to 1000.
 +      result once the best match for all objects is found.
 +      Defaults to 1000. Maximum value is 65535.
  
  pack.threads::
        Specifies the number of threads to spawn when searching for best
@@@ -2720,10 -2622,6 +2725,10 @@@ pull.rebase:
        pull" is run. See "branch.<name>.rebase" for setting this on a
        per-branch basis.
  +
 +When `merges`, pass the `--rebase-merges` option to 'git rebase'
 +so that the local merge commits are included in the rebase (see
 +linkgit:git-rebase[1] for details).
 ++
  When preserve, also pass `--preserve-merges` along to 'git rebase'
  so that locally committed merge commits will not be flattened
  by running 'git pull'.
@@@ -3226,18 -3124,6 +3231,18 @@@ status.displayCommentPrefix:
        behavior of linkgit:git-status[1] in Git 1.8.4 and previous.
        Defaults to false.
  
 +status.renameLimit::
 +      The number of files to consider when performing rename detection
 +      in linkgit:git-status[1] and linkgit:git-commit[1]. Defaults to
 +      the value of diff.renameLimit.
 +
 +status.renames::
 +      Whether and how Git detects renames in linkgit:git-status[1] and
 +      linkgit:git-commit[1] .  If set to "false", rename detection is
 +      disabled. If set to "true", basic rename detection is enabled.
 +      If set to "copies" or "copy", Git will detect copies, as well.
 +      Defaults to the value of diff.renames.
 +
  status.showStash::
        If set to true, linkgit:git-status[1] will display the number of
        entries currently stashed away.
@@@ -3339,13 -3225,12 +3344,13 @@@ submodule.<name>.ignore:
  submodule.<name>.active::
        Boolean value indicating if the submodule is of interest to git
        commands.  This config option takes precedence over the
 -      submodule.active config option.
 +      submodule.active config option. See linkgit:gitsubmodules[7] for
 +      details.
  
  submodule.active::
        A repeated field which contains a pathspec used to match against a
        submodule's path to determine if the submodule is of interest to git
 -      commands.
 +      commands. See linkgit:gitsubmodules[7] for details.
  
  submodule.recurse::
        Specifies if commands recurse into submodules by default. This
@@@ -3492,13 -3377,6 +3497,13 @@@ Note that this configuration variable i
  repository-level config (this is a safety measure against fetching from
  untrusted repositories).
  
 +uploadpack.allowRefInWant::
 +      If this option is set, `upload-pack` will support the `ref-in-want`
 +      feature of the protocol version 2 `fetch` command.  This feature
 +      is intended for the benefit of load-balanced servers which may
 +      not have the same view of what OIDs their refs point to due to
 +      replication delay.
 +
  url.<base>.insteadOf::
        Any URL that starts with this value will be rewritten to
        start, instead, with <base>. In cases where some site serves a
index 41064909ee42bca9a5decd375bc62ad658ae3c81,8da7fed4e222fcd5d50d789e770c3d9e02e93727..f394608b42c268f0f187dafabe50b718fde2a8bf
@@@ -64,7 -64,7 +64,7 @@@ ifndef::git-format-patch[
  endif::git-format-patch[]
  
  --indent-heuristic::
 -      Enable the heuristic that shift diff hunk boundaries to make patches
 +      Enable the heuristic that shifts diff hunk boundaries to make patches
        easier to read. This is the default.
  
  --no-indent-heuristic::
@@@ -106,7 -106,7 +106,7 @@@ diff" algorithm internally
        low-occurrence common elements".
  --
  +
 -For instance, if you configured diff.algorithm variable to a
 +For instance, if you configured the `diff.algorithm` variable to a
  non-default value and want to use the default one, then you
  have to use `--diff-algorithm=default` option.
  
@@@ -133,7 -133,7 +133,7 @@@ These parameters can also be set indivi
        as file creations or deletions ("new" or "gone", optionally "+l"
        if it's a symlink) and mode changes ("+x" or "-x" for adding
        or removing executable bit respectively) in diffstat. The
 -      information is put betwen the filename part and the graph
 +      information is put between the filename part and the graph
        part. Implies `--stat`.
  
  --numstat::
@@@ -276,10 -276,14 +276,14 @@@ plain:
        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::
        Blocks of moved text of at least 20 alphanumeric characters
        are detected greedily. The detected blocks are
-       painted using either the 'color.diff.{old,new}Moved' color or
+       painted using either the 'color.diff.{old,new}Moved' color.
+       Adjacent blocks cannot be told apart.
+ zebra::
+       Blocks of moved text are detected as in 'blocks' mode. The blocks
+       are painted using either the 'color.diff.{old,new}Moved' color or
        'color.diff.{old,new}MovedAlternative'. The change between
        the two colors indicates that a new block was detected.
  dimmed_zebra::
        blocks are considered interesting, the rest is uninteresting.
  --
  
+ --color-moved-ws=<modes>::
+       This configures how white spaces are ignored when performing the
+       move detection for `--color-moved`.
+ ifdef::git-diff[]
+       It can be set by the `diff.colorMovedWS` configuration setting.
+ endif::git-diff[]
+       These modes can be given as a comma separated list:
+ +
+ --
+ ignore-space-at-eol::
+       Ignore changes in whitespace at EOL.
+ ignore-space-change::
+       Ignore changes in amount of whitespace.  This ignores whitespace
+       at line end, and considers all other sequences of one or
+       more whitespace characters to be equivalent.
+ ignore-all-space::
+       Ignore whitespace when comparing lines. This ignores differences
+       even if one line has whitespace where the other line has none.
+ allow-indentation-change::
+       Initially ignore any white spaces in the move detection, then
+       group the moved code blocks only into a block if the change in
+       whitespace is the same per line. This is incompatible with the
+       other modes.
+ --
  --word-diff[=<mode>]::
        Show a word diff, using the <mode> to delimit changed words.
        By default, words are delimited by whitespace; see
@@@ -350,7 -379,7 +379,7 @@@ ifndef::git-format-patch[
        Warn if changes introduce conflict markers or whitespace errors.
        What are considered whitespace errors is controlled by `core.whitespace`
        configuration.  By default, trailing whitespaces (including
 -      lines that solely consist of whitespaces) and a space character
 +      lines that consist solely of whitespaces) and a space character
        that is immediately followed by a tab character inside the
        initial indent of the line are considered whitespace errors.
        Exits with non-zero status if problems are found. Not compatible
        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`.
 +      with `color.diff.whitespace`.
  
  endif::git-format-patch[]
  
@@@ -568,7 -597,7 +597,7 @@@ the normal order
  --
  +
  Patterns have the same syntax and semantics as patterns used for
 -fnmantch(3) without the FNM_PATHNAME flag, except a pathname also
 +fnmatch(3) without the FNM_PATHNAME flag, except a pathname also
  matches a pattern if removing any number of the final pathname
  components matches the pattern.  For example, the pattern "`foo*bar`"
  matches "`fooasdfbar`" and "`foo/bar/baz/asdf`" but not "`foobarx`".
@@@ -592,7 -621,7 +621,7 @@@ endif::git-format-patch[
        Treat all files as text.
  
  --ignore-cr-at-eol::
 -      Ignore carrige-return at the end of line when doing a comparison.
 +      Ignore carriage-return at the end of line when doing a comparison.
  
  --ignore-space-at-eol::
        Ignore changes in whitespace at EOL.
diff --combined diff.c
index d20614605e6f50a0e693dcda9499452475000d31,5089c6eb3a4fa754f1e0678970228601cc9b3850..04d044bbb67b77a9992a499d4f7728bb85cfe94f
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -13,7 -13,6 +13,7 @@@
  #include "attr.h"
  #include "run-command.h"
  #include "utf8.h"
 +#include "object-store.h"
  #include "userdiff.h"
  #include "submodule-config.h"
  #include "submodule.h"
@@@ -23,7 -22,6 +23,7 @@@
  #include "argv-array.h"
  #include "graph.h"
  #include "packfile.h"
 +#include "help.h"
  
  #ifdef NO_FAST_WORKING_DIRECTORY
  #define FAST_WORKING_DIRECTORY 0
@@@ -37,6 -35,7 +37,7 @@@ static int diff_rename_limit_default = 
  static int diff_suppress_blank_empty;
  static int diff_use_color_default = -1;
  static int diff_color_moved_default;
+ static int diff_color_moved_ws_default;
  static int diff_context_default = 3;
  static int diff_interhunk_context_default;
  static const char *diff_word_regex_cfg;
@@@ -71,37 -70,46 +72,37 @@@ static char diff_colors[][COLOR_MAXLEN
        GIT_COLOR_FAINT_ITALIC, /* NEW_MOVED_ALTERNATIVE_DIM */
  };
  
 +static const char *color_diff_slots[] = {
 +      [DIFF_CONTEXT]                = "context",
 +      [DIFF_METAINFO]               = "meta",
 +      [DIFF_FRAGINFO]               = "frag",
 +      [DIFF_FILE_OLD]               = "old",
 +      [DIFF_FILE_NEW]               = "new",
 +      [DIFF_COMMIT]                 = "commit",
 +      [DIFF_WHITESPACE]             = "whitespace",
 +      [DIFF_FUNCINFO]               = "func",
 +      [DIFF_FILE_OLD_MOVED]         = "oldMoved",
 +      [DIFF_FILE_OLD_MOVED_ALT]     = "oldMovedAlternative",
 +      [DIFF_FILE_OLD_MOVED_DIM]     = "oldMovedDimmed",
 +      [DIFF_FILE_OLD_MOVED_ALT_DIM] = "oldMovedAlternativeDimmed",
 +      [DIFF_FILE_NEW_MOVED]         = "newMoved",
 +      [DIFF_FILE_NEW_MOVED_ALT]     = "newMovedAlternative",
 +      [DIFF_FILE_NEW_MOVED_DIM]     = "newMovedDimmed",
 +      [DIFF_FILE_NEW_MOVED_ALT_DIM] = "newMovedAlternativeDimmed",
 +};
 +
  static NORETURN void die_want_option(const char *option_name)
  {
        die(_("option '%s' requires a value"), option_name);
  }
  
 +define_list_config_array_extra(color_diff_slots, {"plain"});
 +
  static int parse_diff_color_slot(const char *var)
  {
 -      if (!strcasecmp(var, "context") || !strcasecmp(var, "plain"))
 +      if (!strcasecmp(var, "plain"))
                return DIFF_CONTEXT;
 -      if (!strcasecmp(var, "meta"))
 -              return DIFF_METAINFO;
 -      if (!strcasecmp(var, "frag"))
 -              return DIFF_FRAGINFO;
 -      if (!strcasecmp(var, "old"))
 -              return DIFF_FILE_OLD;
 -      if (!strcasecmp(var, "new"))
 -              return DIFF_FILE_NEW;
 -      if (!strcasecmp(var, "commit"))
 -              return DIFF_COMMIT;
 -      if (!strcasecmp(var, "whitespace"))
 -              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;
 +      return LOOKUP_CONFIG(color_diff_slots, var);
  }
  
  static int parse_dirstat_params(struct diff_options *options, const char *params_string,
@@@ -170,7 -178,7 +171,7 @@@ static int parse_submodule_params(struc
        return 0;
  }
  
 -static int git_config_rename(const char *var, const char *value)
 +int git_config_rename(const char *var, const char *value)
  {
        if (!value)
                return DIFF_DETECT_RENAME;
@@@ -264,6 -272,8 +265,8 @@@ static int parse_color_moved(const cha
                return COLOR_MOVED_NO;
        else if (!strcmp(arg, "plain"))
                return COLOR_MOVED_PLAIN;
+       else if (!strcmp(arg, "blocks"))
+               return COLOR_MOVED_BLOCKS;
        else if (!strcmp(arg, "zebra"))
                return COLOR_MOVED_ZEBRA;
        else if (!strcmp(arg, "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'"));
+               return error(_("color moved setting must be one of 'no', 'default', 'blocks', 'zebra', 'dimmed_zebra', 'plain'"));
+ }
+ static int parse_color_moved_ws(const char *arg)
+ {
+       int ret = 0;
+       struct string_list l = STRING_LIST_INIT_DUP;
+       struct string_list_item *i;
+       string_list_split(&l, arg, ',', -1);
+       for_each_string_list_item(i, &l) {
+               struct strbuf sb = STRBUF_INIT;
+               strbuf_addstr(&sb, i->string);
+               strbuf_trim(&sb);
+               if (!strcmp(sb.buf, "ignore-space-change"))
+                       ret |= XDF_IGNORE_WHITESPACE_CHANGE;
+               else if (!strcmp(sb.buf, "ignore-space-at-eol"))
+                       ret |= XDF_IGNORE_WHITESPACE_AT_EOL;
+               else if (!strcmp(sb.buf, "ignore-all-space"))
+                       ret |= XDF_IGNORE_WHITESPACE;
+               else if (!strcmp(sb.buf, "allow-indentation-change"))
+                       ret |= COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE;
+               else
+                       error(_("ignoring unknown color-moved-ws mode '%s'"), sb.buf);
+               strbuf_release(&sb);
+       }
+       if ((ret & COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) &&
+           (ret & XDF_WHITESPACE_FLAGS))
+               die(_("color-moved-ws: allow-indentation-change cannot be combined with other white space modes"));
+       string_list_clear(&l, 0);
+       return ret;
  }
  
  int git_diff_ui_config(const char *var, const char *value, void *cb)
                diff_color_moved_default = cm;
                return 0;
        }
+       if (!strcmp(var, "diff.colormovedws")) {
+               int cm = parse_color_moved_ws(value);
+               if (cm < 0)
+                       return -1;
+               diff_color_moved_ws_default = cm;
+               return 0;
+       }
        if (!strcmp(var, "diff.context")) {
                diff_context_default = git_config_int(var, value);
                if (diff_context_default < 0)
@@@ -698,16 -751,116 +744,116 @@@ struct moved_entry 
        struct hashmap_entry ent;
        const struct emitted_diff_symbol *es;
        struct moved_entry *next_line;
+       struct ws_delta *wsd;
  };
  
- static int moved_entry_cmp(const struct diff_options *diffopt,
-                          const struct moved_entry *a,
-                          const struct moved_entry *b,
+ /**
+  * The struct ws_delta holds white space differences between moved lines, i.e.
+  * between '+' and '-' lines that have been detected to be a move.
+  * The string contains the difference in leading white spaces, before the
+  * rest of the line is compared using the white space config for move
+  * coloring. The current_longer indicates if the first string in the
+  * comparision is longer than the second.
+  */
+ struct ws_delta {
+       char *string;
+       unsigned int current_longer : 1;
+ };
+ #define WS_DELTA_INIT { NULL, 0 }
+ static int compute_ws_delta(const struct emitted_diff_symbol *a,
+                            const struct emitted_diff_symbol *b,
+                            struct ws_delta *out)
+ {
+       const struct emitted_diff_symbol *longer =  a->len > b->len ? a : b;
+       const struct emitted_diff_symbol *shorter = a->len > b->len ? b : a;
+       int d = longer->len - shorter->len;
+       out->string = xmemdupz(longer->line, d);
+       out->current_longer = (a == longer);
+       return !strncmp(longer->line + d, shorter->line, shorter->len);
+ }
+ static int cmp_in_block_with_wsd(const struct diff_options *o,
+                                const struct moved_entry *cur,
+                                const struct moved_entry *match,
+                                struct moved_entry *pmb,
+                                int n)
+ {
+       struct emitted_diff_symbol *l = &o->emitted_symbols->buf[n];
+       int al = cur->es->len, cl = l->len;
+       const char *a = cur->es->line,
+                  *b = match->es->line,
+                  *c = l->line;
+       int wslen;
+       /*
+        * We need to check if 'cur' is equal to 'match'.
+        * As those are from the same (+/-) side, we do not need to adjust for
+        * indent changes. However these were found using fuzzy matching
+        * so we do have to check if they are equal.
+        */
+       if (strcmp(a, b))
+               return 1;
+       if (!pmb->wsd)
+               /*
+                * No white space delta was carried forward? This can happen
+                * when we exit early in this function and do not carry
+                * forward ws.
+                */
+               return 1;
+       /*
+        * The indent changes of the block are known and carried forward in
+        * pmb->wsd; however we need to check if the indent changes of the
+        * current line are still the same as before.
+        *
+        * To do so we need to compare 'l' to 'cur', adjusting the
+        * one of them for the white spaces, depending which was longer.
+        */
+       wslen = strlen(pmb->wsd->string);
+       if (pmb->wsd->current_longer) {
+               c += wslen;
+               cl -= wslen;
+       } else {
+               a += wslen;
+               al -= wslen;
+       }
+       if (strcmp(a, c))
+               return 1;
+       return 0;
+ }
+ static int moved_entry_cmp(const void *hashmap_cmp_fn_data,
+                          const void *entry,
+                          const void *entry_or_key,
                           const void *keydata)
  {
+       const struct diff_options *diffopt = hashmap_cmp_fn_data;
+       const struct moved_entry *a = entry;
+       const struct moved_entry *b = entry_or_key;
+       unsigned flags = diffopt->color_moved_ws_handling
+                        & XDF_WHITESPACE_FLAGS;
+       if (diffopt->color_moved_ws_handling &
+           COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
+               /*
+                * As there is not specific white space config given,
+                * we'd need to check for a new block, so ignore all
+                * white space. The setup of the white space
+                * configuration for the next block is done else where
+                */
+               flags |= XDF_IGNORE_WHITESPACE;
        return !xdiff_compare_lines(a->es->line, a->es->len,
                                    b->es->line, b->es->len,
-                                   diffopt->xdl_opts);
+                                   flags);
  }
  
  static struct moved_entry *prepare_entry(struct diff_options *o,
  {
        struct moved_entry *ret = xmalloc(sizeof(*ret));
        struct emitted_diff_symbol *l = &o->emitted_symbols->buf[line_no];
+       unsigned flags = o->color_moved_ws_handling & XDF_WHITESPACE_FLAGS;
  
-       ret->ent.hash = xdiff_hash_string(l->line, l->len, o->xdl_opts);
+       ret->ent.hash = xdiff_hash_string(l->line, l->len, flags);
        ret->es = l;
        ret->next_line = NULL;
+       ret->wsd = NULL;
  
        return ret;
  }
@@@ -755,6 -910,56 +903,56 @@@ static void add_lines_to_move_detection
        }
  }
  
+ static void pmb_advance_or_null(struct diff_options *o,
+                               struct moved_entry *match,
+                               struct hashmap *hm,
+                               struct moved_entry **pmb,
+                               int pmb_nr)
+ {
+       int i;
+       for (i = 0; i < pmb_nr; i++) {
+               struct moved_entry *prev = pmb[i];
+               struct moved_entry *cur = (prev && prev->next_line) ?
+                               prev->next_line : NULL;
+               if (cur && !hm->cmpfn(o, cur, match, NULL)) {
+                       pmb[i] = cur;
+               } else {
+                       pmb[i] = NULL;
+               }
+       }
+ }
+ static void pmb_advance_or_null_multi_match(struct diff_options *o,
+                                           struct moved_entry *match,
+                                           struct hashmap *hm,
+                                           struct moved_entry **pmb,
+                                           int pmb_nr, int n)
+ {
+       int i;
+       char *got_match = xcalloc(1, pmb_nr);
+       for (; match; match = hashmap_get_next(hm, match)) {
+               for (i = 0; i < pmb_nr; i++) {
+                       struct moved_entry *prev = pmb[i];
+                       struct moved_entry *cur = (prev && prev->next_line) ?
+                                       prev->next_line : NULL;
+                       if (!cur)
+                               continue;
+                       if (!cmp_in_block_with_wsd(o, cur, match, pmb[i], n))
+                               got_match[i] |= 1;
+               }
+       }
+       for (i = 0; i < pmb_nr; i++) {
+               if (got_match[i]) {
+                       /* Carry the white space delta forward */
+                       pmb[i]->next_line->wsd = pmb[i]->wsd;
+                       pmb[i] = pmb[i]->next_line;
+               } else
+                       pmb[i] = NULL;
+       }
+ }
  static int shrink_potential_moved_blocks(struct moved_entry **pmb,
                                         int pmb_nr)
  {
  
                if (lp < pmb_nr && rp > -1 && lp < rp) {
                        pmb[lp] = pmb[rp];
+                       if (pmb[rp]->wsd) {
+                               free(pmb[rp]->wsd->string);
+                               FREE_AND_NULL(pmb[rp]->wsd);
+                       }
                        pmb[rp] = NULL;
                        rp--;
                        lp++;
@@@ -829,19 -1038,18 +1031,18 @@@ static void mark_color_as_moved(struct 
                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);
+                       match = hashmap_get(hm, key, NULL);
                        free(key);
                        break;
                case DIFF_SYMBOL_MINUS:
                        hm = add_lines;
                        key = prepare_entry(o, n);
-                       match = hashmap_get(hm, key, o);
+                       match = hashmap_get(hm, key, NULL);
                        free(key);
                        break;
                default:
                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;
-                       }
-               }
+               if (o->color_moved_ws_handling &
+                   COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
+                       pmb_advance_or_null_multi_match(o, match, hm, pmb, pmb_nr, n);
+               else
+                       pmb_advance_or_null(o, match, hm, pmb, pmb_nr);
  
                pmb_nr = shrink_potential_moved_blocks(pmb, pmb_nr);
  
                         */
                        for (; match; match = hashmap_get_next(hm, match)) {
                                ALLOC_GROW(pmb, pmb_nr + 1, pmb_alloc);
-                               pmb[pmb_nr++] = match;
+                               if (o->color_moved_ws_handling &
+                                   COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) {
+                                       struct ws_delta *wsd = xmalloc(sizeof(*match->wsd));
+                                       if (compute_ws_delta(l, match->es, wsd)) {
+                                               match->wsd = wsd;
+                                               pmb[pmb_nr++] = match;
+                                       } else
+                                               free(wsd);
+                               } else {
+                                       pmb[pmb_nr++] = match;
+                               }
                        }
  
                        flipped_block = (flipped_block + 1) % 2;
  
                block_length++;
  
-               if (flipped_block)
+               if (flipped_block && o->color_moved != COLOR_MOVED_BLOCKS)
                        l->flags |= DIFF_SYMBOL_MOVED_LINE_ALT;
        }
        adjust_last_block(o, n, block_length);
@@@ -1177,7 -1389,7 +1382,7 @@@ static void emit_diff_symbol_from_struc
                fputs(o->stat_sep, o->file);
                break;
        default:
 -              die("BUG: unknown diff symbol");
 +              BUG("unknown diff symbol");
        }
        strbuf_release(&sb);
  }
@@@ -1336,7 -1548,7 +1541,7 @@@ static struct diff_tempfile *claim_diff
        for (i = 0; i < ARRAY_SIZE(diff_temp); i++)
                if (!diff_temp[i].name)
                        return diff_temp + i;
 -      die("BUG: diff is failing to clean up its tempfiles");
 +      BUG("diff is failing to clean up its tempfiles");
  }
  
  static void remove_tempfile(void)
@@@ -3465,7 -3677,7 +3670,7 @@@ static int reuse_worktree_file(const ch
         * objects however would tend to be slower as they need
         * to be individually opened and inflated.
         */
 -      if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(oid->hash))
 +      if (!FAST_WORKING_DIRECTORY && !want_file && has_object_pack(oid))
                return 0;
  
        /*
@@@ -3631,8 -3843,7 +3836,8 @@@ int diff_populate_filespec(struct diff_
        else {
                enum object_type type;
                if (size_only || (flags & CHECK_BINARY)) {
 -                      type = oid_object_info(&s->oid, &s->size);
 +                      type = oid_object_info(the_repository, &s->oid,
 +                                             &s->size);
                        if (type < 0)
                                die("unable to read %s",
                                    oid_to_hex(&s->oid));
@@@ -3833,8 -4044,8 +4038,8 @@@ static const char *diff_abbrev_oid(cons
                char *hex = oid_to_hex(oid);
                if (abbrev < 0)
                        abbrev = FALLBACK_DEFAULT_ABBREV;
 -              if (abbrev > GIT_SHA1_HEXSZ)
 -                      die("BUG: oid abbreviation out of range: %d", abbrev);
 +              if (abbrev > the_hash_algo->hexsz)
 +                      BUG("oid abbreviation out of range: %d", abbrev);
                if (abbrev)
                        hex[abbrev] = '\0';
                return hex;
@@@ -3891,14 -4102,13 +4096,14 @@@ static void fill_metainfo(struct strbu
                *must_show_header = 0;
        }
        if (one && two && oidcmp(&one->oid, &two->oid)) {
 -              int abbrev = o->flags.full_index ? 40 : DEFAULT_ABBREV;
 +              const unsigned hexsz = the_hash_algo->hexsz;
 +              int abbrev = o->flags.full_index ? hexsz : DEFAULT_ABBREV;
  
                if (o->flags.binary) {
                        mmfile_t mf;
                        if ((!fill_mmfile(&mf, one) && diff_filespec_is_binary(one)) ||
                            (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
 -                              abbrev = 40;
 +                              abbrev = hexsz;
                }
                strbuf_addf(msg, "%s%sindex %s..%s", line_prefix, set,
                            diff_abbrev_oid(&one->oid, abbrev),
@@@ -4125,6 -4335,7 +4330,7 @@@ void diff_setup(struct diff_options *op
        }
  
        options->color_moved = diff_color_moved_default;
+       options->color_moved_ws_handling = diff_color_moved_ws_default;
  }
  
  void diff_setup_done(struct diff_options *options)
                              DIFF_FORMAT_NAME_STATUS |
                              DIFF_FORMAT_CHECKDIFF |
                              DIFF_FORMAT_NO_OUTPUT;
 +      /*
 +       * This must be signed because we're comparing against a potentially
 +       * negative value.
 +       */
 +      const int hexsz = the_hash_algo->hexsz;
  
        if (options->set_default)
                options->set_default(options);
                         */
                        read_cache();
        }
 -      if (40 < options->abbrev)
 -              options->abbrev = 40; /* full */
 +      if (hexsz < options->abbrev)
 +              options->abbrev = hexsz; /* full */
  
        /*
         * It does not make sense to show the first hit we happened
@@@ -4334,7 -4540,7 +4540,7 @@@ static int stat_opt(struct diff_option
        int argcount = 1;
  
        if (!skip_prefix(arg, "--stat", &arg))
 -              die("BUG: stat option does not begin with --stat: %s", arg);
 +              BUG("stat option does not begin with --stat: %s", arg);
        end = (char *)arg;
  
        switch (*arg) {
@@@ -4704,6 -4910,8 +4910,8 @@@ int diff_opt_parse(struct diff_options 
                if (cm < 0)
                        die("bad --color-moved argument: %s", arg);
                options->color_moved = cm;
+       } else if (skip_prefix(arg, "--color-moved-ws=", &arg)) {
+               options->color_moved_ws_handling = parse_color_moved_ws(arg);
        } else if (skip_to_optional_arg_default(arg, "--color-words", &options->word_regex, NULL)) {
                options->use_color = 1;
                options->word_diff = DIFF_WORDS_COLOR;
                options->abbrev = strtoul(arg, NULL, 10);
                if (options->abbrev < MINIMUM_ABBREV)
                        options->abbrev = MINIMUM_ABBREV;
 -              else if (40 < options->abbrev)
 -                      options->abbrev = 40;
 +              else if (the_hash_algo->hexsz < options->abbrev)
 +                      options->abbrev = the_hash_algo->hexsz;
        }
        else if ((argcount = parse_long_opt("src-prefix", av, &optarg))) {
                options->a_prefix = optarg;
@@@ -4948,7 -5156,7 +5156,7 @@@ const char *diff_aligned_abbrev(const s
        const char *abbrev;
  
        /* Do we want all 40 hex characters? */
 -      if (len == GIT_SHA1_HEXSZ)
 +      if (len == the_hash_algo->hexsz)
                return oid_to_hex(oid);
  
        /* An abbreviated value is fine, possibly followed by an ellipsis. */
         * the automatic sizing is supposed to give abblen that ensures
         * uniqueness across all objects (statistically speaking).
         */
 -      if (abblen < GIT_SHA1_HEXSZ - 3) {
 +      if (abblen < the_hash_algo->hexsz - 3) {
                static char hex[GIT_MAX_HEXSZ + 1];
                if (len < abblen && abblen <= len + 2)
                        xsnprintf(hex, sizeof(hex), "%s%.*s", abbrev, len+3-abblen, "..");
@@@ -5519,7 -5727,7 +5727,7 @@@ static void diff_flush_patch_all_file_p
        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");
 +              BUG("WS rules bit mask overlaps with diff symbol flags");
  
        if (o->color_moved)
                o->emitted_symbols = &esm;
                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);
+                       if (o->color_moved_ws_handling &
+                           COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
+                               o->color_moved_ws_handling |= XDF_IGNORE_WHITESPACE;
+                       hashmap_init(&del_lines, moved_entry_cmp, o, 0);
+                       hashmap_init(&add_lines, moved_entry_cmp, o, 0);
  
                        add_lines_to_move_detection(o, &add_lines, &del_lines);
                        mark_color_as_moved(o, &add_lines, &del_lines);
@@@ -6053,7 -6263,7 +6263,7 @@@ size_t fill_textconv(struct userdiff_dr
        }
  
        if (!driver->textconv)
 -              die("BUG: fill_textconv called with non-textconv driver");
 +              BUG("fill_textconv called with non-textconv driver");
  
        if (driver->textconv_cache && df->oid_valid) {
                *outbuf = notes_cache_get(driver->textconv_cache,
diff --combined diff.h
index dedac472ca5959bb5ff17888a54042ac75678631,5e6bcf092607562a4f29b6e21f449921f744f286..a14895bb824f2c844f8d2382b42d6c6f738e3835
--- 1/diff.h
--- 2/diff.h
+++ b/diff.h
@@@ -208,11 -208,16 +208,16 @@@ struct diff_options 
        enum {
                COLOR_MOVED_NO = 0,
                COLOR_MOVED_PLAIN = 1,
-               COLOR_MOVED_ZEBRA = 2,
-               COLOR_MOVED_ZEBRA_DIM = 3,
+               COLOR_MOVED_BLOCKS = 2,
+               COLOR_MOVED_ZEBRA = 3,
+               COLOR_MOVED_ZEBRA_DIM = 4,
        } color_moved;
        #define COLOR_MOVED_DEFAULT COLOR_MOVED_ZEBRA
        #define COLOR_MOVED_MIN_ALNUM_COUNT 20
+       /* XDF_WHITESPACE_FLAGS regarding block detection are set at 2, 3, 4 */
+       #define COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE (1<<5)
+       int color_moved_ws_handling;
  };
  
  void diff_emit_submodule_del(struct diff_options *o, const char *line);
@@@ -324,7 -329,6 +329,7 @@@ extern int git_diff_ui_config(const cha
  extern void diff_setup(struct diff_options *);
  extern int diff_opt_parse(struct diff_options *, const char **, int, const char *);
  extern void diff_setup_done(struct diff_options *);
 +extern int git_config_rename(const char *var, const char *value);
  
  #define DIFF_DETECT_RENAME    1
  #define DIFF_DETECT_COPY      2