Merge branch 'mp/diff-algo-config'
authorJunio C Hamano <gitster@pobox.com>
Sun, 17 Feb 2013 23:25:51 +0000 (15:25 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sun, 17 Feb 2013 23:25:52 +0000 (15:25 -0800)
Add diff.algorithm configuration so that the user does not type
"diff --histogram".

* mp/diff-algo-config:
diff: Introduce --diff-algorithm command line option
config: Introduce diff.algorithm variable
git-completion.bash: Autocomplete --minimal and --histogram for git-diff

1  2 
Documentation/diff-config.txt
Documentation/diff-options.txt
contrib/completion/git-completion.bash
diff.c
diff.h
index 7c70c2a2feca88a2c1247794e2ee0fbed767bb4a,b8a8724f6f87fdc820f0d686078e0466adbad204..ac770502553d9b4d3d1176f63ad01791736602a8
@@@ -99,7 -99,7 +99,7 @@@ diff.renameLimit:
        detection; equivalent to the 'git diff' option '-l'.
  
  diff.renames::
 -      Tells git to detect renames.  If set to any boolean value, it
 +      Tells Git to detect renames.  If set to any boolean value, it
        will enable basic rename detection.  If set to "copies" or
        "copy", it will detect copies, as well.
  
@@@ -149,10 -149,26 +149,27 @@@ diff.<driver>.cachetextconv:
        conversion outputs.  See linkgit:gitattributes[5] for details.
  
  diff.tool::
 -      The diff tool to be used by linkgit:git-difftool[1].  This
 -      option overrides `merge.tool`, and has the same valid built-in
 -      values as `merge.tool` minus "tortoisemerge" and plus
 -      "kompare".  Any other value is treated as a custom diff tool,
 -      and there must be a corresponding `difftool.<tool>.cmd`
 -      option.
 +      Controls which diff tool is used by linkgit:git-difftool[1].
 +      This variable overrides the value configured in `merge.tool`.
 +      The list below shows the valid built-in values.
 +      Any other value is treated as a custom diff tool and requires
 +      that a corresponding difftool.<tool>.cmd variable is defined.
 +
 +include::mergetools-diff.txt[]
+ diff.algorithm::
+       Choose a diff algorithm.  The variants are as follows:
+ +
+ --
+ `default`, `myers`;;
+       The basic greedy diff algorithm. Currently, this is the default.
+ `minimal`;;
+       Spend extra time to make sure the smallest possible diff is
+       produced.
+ `patience`;;
+       Use "patience diff" algorithm when generating patches.
+ `histogram`;;
+       This algorithm extends the patience algorithm to "support
+       low-occurrence common elements".
+ --
+ +
index 7a8747352622b511e1edcb00919b2fb298dcba08,81e5be13da27175c3392320b37dd189c7f8d7003..869d965a3b93e25c71fd4c81a7f7742ff2222cfe
@@@ -55,6 -55,26 +55,26 @@@ endif::git-format-patch[
  --histogram::
        Generate a diff using the "histogram diff" algorithm.
  
+ --diff-algorithm={patience|minimal|histogram|myers}::
+       Choose a diff algorithm. The variants are as follows:
+ +
+ --
+ `default`, `myers`;;
+       The basic greedy diff algorithm. Currently, this is the default.
+ `minimal`;;
+       Spend extra time to make sure the smallest possible diff is
+       produced.
+ `patience`;;
+       Use "patience diff" algorithm when generating patches.
+ `histogram`;;
+       This algorithm extends the patience algorithm to "support
+       low-occurrence common elements".
+ --
+ +
+ For instance, if you configured diff.algorithm variable to a
+ non-default value and want to use the default one, then you
+ have to use `--diff-algorithm=default` option.
  --stat[=<width>[,<name-width>[,<count>]]]::
        Generate a diffstat. By default, as much space as necessary
        will be used for the filename part, and the rest for the graph
@@@ -283,7 -303,7 +303,7 @@@ few lines that happen to match textuall
  single deletion of everything old followed by a single insertion of
  everything new, and the number `m` controls this aspect of the -B
  option (defaults to 60%). `-B/70%` specifies that less than 30% of the
 -original should remain in the result for git to consider it a total
 +original should remain in the result for Git to consider it a total
  rewrite (i.e. otherwise the resulting patch will be a series of
  deletion and insertion mixed together with context lines).
  +
@@@ -307,7 -327,7 +327,7 @@@ ifdef::git-log[
  endif::git-log[]
        If `n` is specified, it is a threshold on the similarity
        index (i.e. amount of addition/deletions compared to the
 -      file's size). For example, `-M90%` means git should consider a
 +      file's size). For example, `-M90%` means Git should consider a
        delete/add pair to be a rename if more than 90% of the file
        hasn't changed.  Without a `%` sign, the number is to be read as
        a fraction, with a decimal point before it.  I.e., `-M5` becomes
index 059ba9d3e482c08434fb6860667179a6b73808cd,d592cf9be95c35dd7df64bd21214d1f24afc242e..b62bec027963edc8607067e990509b694cdbc9db
@@@ -13,7 -13,6 +13,7 @@@
  #    *) .git/remotes file names
  #    *) git 'subcommands'
  #    *) tree paths within 'ref:path/to/file' expressions
 +#    *) file paths within current working directory and index
  #    *) common --long-options
  #
  # To use these routines:
@@@ -234,118 -233,6 +234,118 @@@ __gitcomp_nl (
        COMPREPLY=($(compgen -P "${2-}" -S "${4- }" -W "$1" -- "${3-$cur}"))
  }
  
 +# Generates completion reply with compgen from newline-separated possible
 +# completion filenames.
 +# It accepts 1 to 3 arguments:
 +# 1: List of possible completion filenames, separated by a single newline.
 +# 2: A directory prefix to be added to each possible completion filename
 +#    (optional).
 +# 3: Generate possible completion matches for this word (optional).
 +__gitcomp_file ()
 +{
 +      local IFS=$'\n'
 +
 +      # XXX does not work when the directory prefix contains a tilde,
 +      # since tilde expansion is not applied.
 +      # This means that COMPREPLY will be empty and Bash default
 +      # completion will be used.
 +      COMPREPLY=($(compgen -P "${2-}" -W "$1" -- "${3-$cur}"))
 +
 +      # Tell Bash that compspec generates filenames.
 +      compopt -o filenames 2>/dev/null
 +}
 +
 +__git_index_file_list_filter_compat ()
 +{
 +      local path
 +
 +      while read -r path; do
 +              case "$path" in
 +              ?*/*) echo "${path%%/*}/" ;;
 +              *) echo "$path" ;;
 +              esac
 +      done
 +}
 +
 +__git_index_file_list_filter_bash ()
 +{
 +      local path
 +
 +      while read -r path; do
 +              case "$path" in
 +              ?*/*)
 +                      # XXX if we append a slash to directory names when using
 +                      # `compopt -o filenames`, Bash will append another slash.
 +                      # This is pretty stupid, and this the reason why we have to
 +                      # define a compatible version for this function.
 +                      echo "${path%%/*}" ;;
 +              *)
 +                      echo "$path" ;;
 +              esac
 +      done
 +}
 +
 +# Process path list returned by "ls-files" and "diff-index --name-only"
 +# commands, in order to list only file names relative to a specified
 +# directory, and append a slash to directory names.
 +__git_index_file_list_filter ()
 +{
 +      # Default to Bash >= 4.x
 +      __git_index_file_list_filter_bash
 +}
 +
 +# Execute git ls-files, returning paths relative to the directory
 +# specified in the first argument, and using the options specified in
 +# the second argument.
 +__git_ls_files_helper ()
 +{
 +      # NOTE: $2 is not quoted in order to support multiple options
 +      cd "$1" && git ls-files --exclude-standard $2
 +} 2>/dev/null
 +
 +
 +# Execute git diff-index, returning paths relative to the directory
 +# specified in the first argument, and using the tree object id
 +# specified in the second argument.
 +__git_diff_index_helper ()
 +{
 +      cd "$1" && git diff-index --name-only --relative "$2"
 +} 2>/dev/null
 +
 +# __git_index_files accepts 1 or 2 arguments:
 +# 1: Options to pass to ls-files (required).
 +#    Supported options are --cached, --modified, --deleted, --others,
 +#    and --directory.
 +# 2: A directory path (optional).
 +#    If provided, only files within the specified directory are listed.
 +#    Sub directories are never recursed.  Path must have a trailing
 +#    slash.
 +__git_index_files ()
 +{
 +      local dir="$(__gitdir)" root="${2-.}"
 +
 +      if [ -d "$dir" ]; then
 +              __git_ls_files_helper "$root" "$1" | __git_index_file_list_filter |
 +                      sort | uniq
 +      fi
 +}
 +
 +# __git_diff_index_files accepts 1 or 2 arguments:
 +# 1) The id of a tree object.
 +# 2) A directory path (optional).
 +#    If provided, only files within the specified directory are listed.
 +#    Sub directories are never recursed.  Path must have a trailing
 +#    slash.
 +__git_diff_index_files ()
 +{
 +      local dir="$(__gitdir)" root="${2-.}"
 +
 +      if [ -d "$dir" ]; then
 +              __git_diff_index_helper "$root" "$1" | __git_index_file_list_filter |
 +                      sort | uniq
 +      fi
 +}
 +
  __git_heads ()
  {
        local dir="$(__gitdir)"
@@@ -510,7 -397,7 +510,7 @@@ __git_complete_revlist_file (
                *)   pfx="$ref:$pfx" ;;
                esac
  
 -              __gitcomp_nl "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \
 +              __gitcomp_nl "$(git --git-dir="$(__gitdir)" ls-tree "$ls" 2>/dev/null \
                                | sed '/^100... blob /{
                                           s,^.*        ,,
                                           s,$, ,
  }
  
  
 +# __git_complete_index_file requires 1 argument: the options to pass to
 +# ls-file
 +__git_complete_index_file ()
 +{
 +      local pfx cur_="$cur"
 +
 +      case "$cur_" in
 +      ?*/*)
 +              pfx="${cur_%/*}"
 +              cur_="${cur_##*/}"
 +              pfx="${pfx}/"
 +
 +              __gitcomp_file "$(__git_index_files "$1" "$pfx")" "$pfx" "$cur_"
 +              ;;
 +      *)
 +              __gitcomp_file "$(__git_index_files "$1")" "" "$cur_"
 +              ;;
 +      esac
 +}
 +
 +# __git_complete_diff_index_file requires 1 argument: the id of a tree
 +# object
 +__git_complete_diff_index_file ()
 +{
 +      local pfx cur_="$cur"
 +
 +      case "$cur_" in
 +      ?*/*)
 +              pfx="${cur_%/*}"
 +              cur_="${cur_##*/}"
 +              pfx="${pfx}/"
 +
 +              __gitcomp_file "$(__git_diff_index_files "$1" "$pfx")" "$pfx" "$cur_"
 +              ;;
 +      *)
 +              __gitcomp_file "$(__git_diff_index_files "$1")" "" "$cur_"
 +              ;;
 +      esac
 +}
 +
  __git_complete_file ()
  {
        __git_complete_revlist_file
@@@ -684,19 -531,10 +684,19 @@@ __git_complete_strategy (
        return 1
  }
  
 +__git_commands () {
 +      if test -n "${GIT_TESTING_COMMAND_COMPLETION:-}"
 +      then
 +              printf "%s" "${GIT_TESTING_COMMAND_COMPLETION}"
 +      else
 +              git help -a|egrep '^  [a-zA-Z0-9]'
 +      fi
 +}
 +
  __git_list_all_commands ()
  {
        local i IFS=" "$'\n'
 -      for i in $(git help -a|egrep '^  [a-zA-Z0-9]')
 +      for i in $(__git_commands)
        do
                case $i in
                *--*)             : helper pattern;;
@@@ -725,7 -563,6 +725,7 @@@ __git_list_porcelain_commands (
                archimport)       : import;;
                cat-file)         : plumbing;;
                check-attr)       : plumbing;;
 +              check-ignore)     : plumbing;;
                check-ref-format) : plumbing;;
                checkout-index)   : plumbing;;
                commit-tree)      : plumbing;;
@@@ -885,43 -722,6 +885,43 @@@ __git_has_doubledash (
        return 1
  }
  
 +# Try to count non option arguments passed on the command line for the
 +# specified git command.
 +# When options are used, it is necessary to use the special -- option to
 +# tell the implementation were non option arguments begin.
 +# XXX this can not be improved, since options can appear everywhere, as
 +# an example:
 +#     git mv x -n y
 +#
 +# __git_count_arguments requires 1 argument: the git command executed.
 +__git_count_arguments ()
 +{
 +      local word i c=0
 +
 +      # Skip "git" (first argument)
 +      for ((i=1; i < ${#words[@]}; i++)); do
 +              word="${words[i]}"
 +
 +              case "$word" in
 +                      --)
 +                              # Good; we can assume that the following are only non
 +                              # option arguments.
 +                              ((c = 0))
 +                              ;;
 +                      "$1")
 +                              # Skip the specified git command and discard git
 +                              # main options
 +                              ((c = 0))
 +                              ;;
 +                      ?*)
 +                              ((c++))
 +                              ;;
 +              esac
 +      done
 +
 +      printf "%d" $c
 +}
 +
  __git_whitespacelist="nowarn warn error error-all fix"
  
  _git_am ()
@@@ -970,6 -770,8 +970,6 @@@ _git_apply (
  
  _git_add ()
  {
 -      __git_has_doubledash && return
 -
        case "$cur" in
        --*)
                __gitcomp "
                        "
                return
        esac
 -      COMPREPLY=()
 +
 +      # XXX should we check for --update and --all options ?
 +      __git_complete_index_file "--others --modified"
  }
  
  _git_archive ()
@@@ -1130,15 -930,15 +1130,15 @@@ _git_cherry_pick (
  
  _git_clean ()
  {
 -      __git_has_doubledash && return
 -
        case "$cur" in
        --*)
                __gitcomp "--dry-run --quiet"
                return
                ;;
        esac
 -      COMPREPLY=()
 +
 +      # XXX should we check for -x option ?
 +      __git_complete_index_file "--others"
  }
  
  _git_clone ()
  
  _git_commit ()
  {
 -      __git_has_doubledash && return
 +      case "$prev" in
 +      -c|-C)
 +              __gitcomp_nl "$(__git_refs)" "" "${cur}"
 +              return
 +              ;;
 +      esac
  
        case "$prev" in
        -c|-C)
                        "
                return
        esac
 -      COMPREPLY=()
 +
 +      if git rev-parse --verify --quiet HEAD >/dev/null; then
 +              __git_complete_diff_index_file "HEAD"
 +      else
 +              # This is the first commit
 +              __git_complete_index_file "--cached"
 +      fi
  }
  
  _git_describe ()
        __gitcomp_nl "$(__git_refs)"
  }
  
+ __git_diff_algorithms="myers minimal patience histogram"
  __git_diff_common_options="--stat --numstat --shortstat --summary
                        --patch-with-stat --name-only --name-status --color
                        --no-color --color-words --no-renames --check
                        --no-ext-diff
                        --no-prefix --src-prefix= --dst-prefix=
                        --inter-hunk-context=
-                       --patience
+                       --patience --histogram --minimal
                        --raw
                        --dirstat --dirstat= --dirstat-by-file
                        --dirstat-by-file= --cumulative
+                       --diff-algorithm=
  "
  
  _git_diff ()
        __git_has_doubledash && return
  
        case "$cur" in
+       --diff-algorithm=*)
+               __gitcomp "$__git_diff_algorithms" "" "${cur##--diff-algorithm=}"
+               return
+               ;;
        --*)
                __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex
                        --base --ours --theirs --no-index
@@@ -1434,6 -1230,8 +1441,6 @@@ _git_init (
  
  _git_ls_files ()
  {
 -      __git_has_doubledash && return
 -
        case "$cur" in
        --*)
                __gitcomp "--cached --deleted --modified --others --ignored
                return
                ;;
        esac
 -      COMPREPLY=()
 +
 +      # XXX ignore options like --modified and always suggest all cached
 +      # files.
 +      __git_complete_index_file "--cached"
  }
  
  _git_ls_remote ()
@@@ -1581,14 -1376,7 +1588,14 @@@ _git_mv (
                return
                ;;
        esac
 -      COMPREPLY=()
 +
 +      if [ $(__git_count_arguments "mv") -gt 0 ]; then
 +              # We need to show both cached and untracked files (including
 +              # empty directories) since this may not be the last argument.
 +              __git_complete_index_file "--cached --others --directory"
 +      else
 +              __git_complete_index_file "--cached"
 +      fi
  }
  
  _git_name_rev ()
@@@ -1779,7 -1567,7 +1786,7 @@@ __git_config_get_set_variables (
        while [ $c -gt 1 ]; do
                word="${words[c]}"
                case "$word" in
 -              --global|--system|--file=*)
 +              --system|--global|--local|--file=*)
                        config_file="$word"
                        break
                        ;;
@@@ -1885,7 -1673,7 +1892,7 @@@ _git_config (
        case "$cur" in
        --*)
                __gitcomp "
 -                      --global --system --file=
 +                      --system --global --local --file=
                        --list --replace-all
                        --get --get-all --get-regexp
                        --add --unset --unset-all
                diff.suppressBlankEmpty
                diff.tool
                diff.wordRegex
+               diff.algorithm
                difftool.
                difftool.prompt
                fetch.recurseSubmodules
@@@ -2294,14 -2083,15 +2302,14 @@@ _git_revert (
  
  _git_rm ()
  {
 -      __git_has_doubledash && return
 -
        case "$cur" in
        --*)
                __gitcomp "--cached --dry-run --ignore-unmatch --quiet"
                return
                ;;
        esac
 -      COMPREPLY=()
 +
 +      __git_complete_index_file "--cached"
  }
  
  _git_shortlog ()
@@@ -2331,6 -2121,10 +2339,10 @@@ _git_show (
                        " "" "${cur#*=}"
                return
                ;;
+       --diff-algorithm=*)
+               __gitcomp "$__git_diff_algorithms" "" "${cur##--diff-algorithm=}"
+               return
+               ;;
        --*)
                __gitcomp "--pretty= --format= --abbrev-commit --oneline
                        $__git_diff_common_options
@@@ -2649,7 -2443,7 +2661,7 @@@ if [[ -n ${ZSH_VERSION-} ]]; the
                                --*=*|*.) ;;
                                *) c="$c " ;;
                                esac
 -                              array+=("$c")
 +                              array[$#array+1]="$c"
                        done
                        compset -P '*[=:]'
                        compadd -Q -S '' -p "${2-}" -a -- array && _ret=0
                compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0
        }
  
 +      __gitcomp_file ()
 +      {
 +              emulate -L zsh
 +
 +              local IFS=$'\n'
 +              compset -P '*[=:]'
 +              compadd -Q -p "${2-}" -f -- ${=1} && _ret=0
 +      }
 +
        __git_zsh_helper ()
        {
                emulate -L ksh
  
        compdef _git git gitk
        return
 +elif [[ -n ${BASH_VERSION-} ]]; then
 +      if ((${BASH_VERSINFO[0]} < 4)); then
 +              # compopt is not supported
 +              __git_index_file_list_filter ()
 +              {
 +                      __git_index_file_list_filter_compat
 +              }
 +      fi
  fi
  
  __git_func_wrap ()
diff --combined diff.c
index f441f6c7f8790a9601163a7e3280239e63036755,0724fa6ff6c4312733e520d59f88ea12740d408d..156fec447061f83855edf27d578f055bb606cacf
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -36,6 -36,7 +36,7 @@@ static int diff_no_prefix
  static int diff_stat_graph_width;
  static int diff_dirstat_permille_default = 30;
  static struct diff_options default_diff_options;
+ static long diff_algorithm;
  
  static char diff_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_RESET,
@@@ -143,6 -144,21 +144,21 @@@ static int git_config_rename(const cha
        return git_config_bool(var,value) ? DIFF_DETECT_RENAME : 0;
  }
  
+ long parse_algorithm_value(const char *value)
+ {
+       if (!value)
+               return -1;
+       else if (!strcasecmp(value, "myers") || !strcasecmp(value, "default"))
+               return 0;
+       else if (!strcasecmp(value, "minimal"))
+               return XDF_NEED_MINIMAL;
+       else if (!strcasecmp(value, "patience"))
+               return XDF_PATIENCE_DIFF;
+       else if (!strcasecmp(value, "histogram"))
+               return XDF_HISTOGRAM_DIFF;
+       return -1;
+ }
  /*
   * These are to give UI layer defaults.
   * The core-level commands such as git-diff-files should
@@@ -196,6 -212,13 +212,13 @@@ int git_diff_ui_config(const char *var
                return 0;
        }
  
+       if (!strcmp(var, "diff.algorithm")) {
+               diff_algorithm = parse_algorithm_value(value);
+               if (diff_algorithm < 0)
+                       return -1;
+               return 0;
+       }
        if (git_color_config(var, value, cb) < 0)
                return -1;
  
@@@ -402,7 -425,12 +425,7 @@@ static void emit_line_0(struct diff_opt
        int nofirst;
        FILE *file = o->file;
  
 -      if (o->output_prefix) {
 -              struct strbuf *msg = NULL;
 -              msg = o->output_prefix(o, o->output_prefix_data);
 -              assert(msg);
 -              fwrite(msg->buf, msg->len, 1, file);
 -      }
 +      fputs(diff_line_prefix(o), file);
  
        if (len == 0) {
                has_trailing_newline = (first == '\n');
@@@ -620,7 -648,13 +643,7 @@@ static void emit_rewrite_diff(const cha
        char *data_one, *data_two;
        size_t size_one, size_two;
        struct emit_callback ecbdata;
 -      char *line_prefix = "";
 -      struct strbuf *msgbuf;
 -
 -      if (o && o->output_prefix) {
 -              msgbuf = o->output_prefix(o, o->output_prefix_data);
 -              line_prefix = msgbuf->buf;
 -      }
 +      const char *line_prefix = diff_line_prefix(o);
  
        if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) {
                a_prefix = o->b_prefix;
@@@ -816,14 -850,18 +839,14 @@@ static void fn_out_diff_words_aux(void 
        int minus_first, minus_len, plus_first, plus_len;
        const char *minus_begin, *minus_end, *plus_begin, *plus_end;
        struct diff_options *opt = diff_words->opt;
 -      struct strbuf *msgbuf;
 -      char *line_prefix = "";
 +      const char *line_prefix;
  
        if (line[0] != '@' || parse_hunk_header(line, len,
                        &minus_first, &minus_len, &plus_first, &plus_len))
                return;
  
        assert(opt);
 -      if (opt->output_prefix) {
 -              msgbuf = opt->output_prefix(opt, opt->output_prefix_data);
 -              line_prefix = msgbuf->buf;
 -      }
 +      line_prefix = diff_line_prefix(opt);
  
        /* POSIX requires that first be decremented by one if len == 0... */
        if (minus_len) {
@@@ -947,10 -985,14 +970,10 @@@ static void diff_words_show(struct diff
        struct diff_words_style *style = diff_words->style;
  
        struct diff_options *opt = diff_words->opt;
 -      struct strbuf *msgbuf;
 -      char *line_prefix = "";
 +      const char *line_prefix;
  
        assert(opt);
 -      if (opt->output_prefix) {
 -              msgbuf = opt->output_prefix(opt, opt->output_prefix_data);
 -              line_prefix = msgbuf->buf;
 -      }
 +      line_prefix = diff_line_prefix(opt);
  
        /* special case: only removal */
        if (!diff_words->plus.text.size) {
@@@ -1086,16 -1128,6 +1109,16 @@@ const char *diff_get_color(int diff_use
        return "";
  }
  
 +const char *diff_line_prefix(struct diff_options *opt)
 +{
 +      struct strbuf *msgbuf;
 +      if (!opt->output_prefix)
 +              return "";
 +
 +      msgbuf = opt->output_prefix(opt, opt->output_prefix_data);
 +      return msgbuf->buf;
 +}
 +
  static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len)
  {
        const char *cp;
@@@ -1136,7 -1168,13 +1159,7 @@@ static void fn_out_consume(void *priv, 
        const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
        const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
        struct diff_options *o = ecbdata->opt;
 -      char *line_prefix = "";
 -      struct strbuf *msgbuf;
 -
 -      if (o && o->output_prefix) {
 -              msgbuf = o->output_prefix(o, o->output_prefix_data);
 -              line_prefix = msgbuf->buf;
 -      }
 +      const char *line_prefix = diff_line_prefix(o);
  
        if (ecbdata->header) {
                fprintf(ecbdata->opt->file, "%s", ecbdata->header->buf);
@@@ -1460,11 -1498,16 +1483,11 @@@ static void show_stats(struct diffstat_
        const char *reset, *add_c, *del_c;
        const char *line_prefix = "";
        int extra_shown = 0;
 -      struct strbuf *msg = NULL;
  
        if (data->nr == 0)
                return;
  
 -      if (options->output_prefix) {
 -              msg = options->output_prefix(options, options->output_prefix_data);
 -              line_prefix = msg->buf;
 -      }
 -
 +      line_prefix = diff_line_prefix(options);
        count = options->stat_count ? options->stat_count : data->nr;
  
        reset = diff_get_color_opt(options, DIFF_RESET);
@@@ -1716,7 -1759,12 +1739,7 @@@ static void show_shortstats(struct diff
                        dels += deleted;
                }
        }
 -      if (options->output_prefix) {
 -              struct strbuf *msg = NULL;
 -              msg = options->output_prefix(options,
 -                              options->output_prefix_data);
 -              fprintf(options->file, "%s", msg->buf);
 -      }
 +      fprintf(options->file, "%s", diff_line_prefix(options));
        print_stat_summary(options->file, total_files, adds, dels);
  }
  
@@@ -1730,7 -1778,12 +1753,7 @@@ static void show_numstat(struct diffsta
        for (i = 0; i < data->nr; i++) {
                struct diffstat_file *file = data->files[i];
  
 -              if (options->output_prefix) {
 -                      struct strbuf *msg = NULL;
 -                      msg = options->output_prefix(options,
 -                                      options->output_prefix_data);
 -                      fprintf(options->file, "%s", msg->buf);
 -              }
 +              fprintf(options->file, "%s", diff_line_prefix(options));
  
                if (file->is_binary)
                        fprintf(options->file, "-\t-\t");
@@@ -1772,7 -1825,13 +1795,7 @@@ static long gather_dirstat(struct diff_
  {
        unsigned long this_dir = 0;
        unsigned int sources = 0;
 -      const char *line_prefix = "";
 -      struct strbuf *msg = NULL;
 -
 -      if (opt->output_prefix) {
 -              msg = opt->output_prefix(opt, opt->output_prefix_data);
 -              line_prefix = msg->buf;
 -      }
 +      const char *line_prefix = diff_line_prefix(opt);
  
        while (dir->nr) {
                struct dirstat_file *f = dir->files;
@@@ -2022,10 -2081,15 +2045,10 @@@ static void checkdiff_consume(void *pri
        const char *reset = diff_get_color(data->o->use_color, DIFF_RESET);
        const char *set = diff_get_color(data->o->use_color, DIFF_FILE_NEW);
        char *err;
 -      char *line_prefix = "";
 -      struct strbuf *msgbuf;
 +      const char *line_prefix;
  
        assert(data->o);
 -      if (data->o->output_prefix) {
 -              msgbuf = data->o->output_prefix(data->o,
 -                      data->o->output_prefix_data);
 -              line_prefix = msgbuf->buf;
 -      }
 +      line_prefix = diff_line_prefix(data->o);
  
        if (line[0] == '+') {
                unsigned bad;
@@@ -2082,8 -2146,7 +2105,8 @@@ static unsigned char *deflate_it(char *
        return deflated;
  }
  
 -static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix)
 +static void emit_binary_diff_body(FILE *file, mmfile_t *one, mmfile_t *two,
 +                                const char *prefix)
  {
        void *cp;
        void *delta;
        free(data);
  }
  
 -static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two, char *prefix)
 +static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two,
 +                           const char *prefix)
  {
        fprintf(file, "%sGIT binary patch\n", prefix);
        emit_binary_diff_body(file, one, two, prefix);
@@@ -2212,7 -2274,13 +2235,7 @@@ static void builtin_diff(const char *na
        struct userdiff_driver *textconv_one = NULL;
        struct userdiff_driver *textconv_two = NULL;
        struct strbuf header = STRBUF_INIT;
 -      struct strbuf *msgbuf;
 -      char *line_prefix = "";
 -
 -      if (o->output_prefix) {
 -              msgbuf = o->output_prefix(o, o->output_prefix_data);
 -              line_prefix = msgbuf->buf;
 -      }
 +      const char *line_prefix = diff_line_prefix(o);
  
        if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
                        (!one->mode || S_ISGITLINK(one->mode)) &&
@@@ -2911,9 -2979,14 +2934,9 @@@ static void fill_metainfo(struct strbu
  {
        const char *set = diff_get_color(use_color, DIFF_METAINFO);
        const char *reset = diff_get_color(use_color, DIFF_RESET);
 -      struct strbuf *msgbuf;
 -      char *line_prefix = "";
 +      const char *line_prefix = diff_line_prefix(o);
  
        *must_show_header = 1;
 -      if (o->output_prefix) {
 -              msgbuf = o->output_prefix(o, o->output_prefix_data);
 -              line_prefix = msgbuf->buf;
 -      }
        strbuf_init(msg, PATH_MAX * 2 + 300);
        switch (p->status) {
        case DIFF_STATUS_COPIED:
@@@ -3163,6 -3236,7 +3186,7 @@@ void diff_setup(struct diff_options *op
        options->add_remove = diff_addremove;
        options->use_color = diff_use_color_default;
        options->detect_rename = diff_detect_rename_default;
+       options->xdl_opts |= diff_algorithm;
  
        if (diff_no_prefix) {
                options->a_prefix = options->b_prefix = "";
@@@ -3560,6 -3634,16 +3584,16 @@@ int diff_opt_parse(struct diff_options 
                options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF);
        else if (!strcmp(arg, "--histogram"))
                options->xdl_opts = DIFF_WITH_ALG(options, HISTOGRAM_DIFF);
+       else if (!prefixcmp(arg, "--diff-algorithm=")) {
+               long value = parse_algorithm_value(arg+17);
+               if (value < 0)
+                       return error("option diff-algorithm accepts \"myers\", "
+                                    "\"minimal\", \"patience\" and \"histogram\"");
+               /* clear out previous settings */
+               DIFF_XDL_CLR(options, NEED_MINIMAL);
+               options->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK;
+               options->xdl_opts |= value;
+       }
  
        /* flags options */
        else if (!strcmp(arg, "--binary")) {
                DIFF_OPT_SET(options, FIND_COPIES_HARDER);
        else if (!strcmp(arg, "--follow"))
                DIFF_OPT_SET(options, FOLLOW_RENAMES);
 +      else if (!strcmp(arg, "--no-follow"))
 +              DIFF_OPT_CLR(options, FOLLOW_RENAMES);
        else if (!strcmp(arg, "--color"))
                options->use_color = 1;
        else if (!prefixcmp(arg, "--color=")) {
@@@ -3850,8 -3932,12 +3884,8 @@@ static void diff_flush_raw(struct diff_
  {
        int line_termination = opt->line_termination;
        int inter_name_termination = line_termination ? '\t' : '\0';
 -      if (opt->output_prefix) {
 -              struct strbuf *msg = NULL;
 -              msg = opt->output_prefix(opt, opt->output_prefix_data);
 -              fprintf(opt->file, "%s", msg->buf);
 -      }
  
 +      fprintf(opt->file, "%s", diff_line_prefix(opt));
        if (!(opt->output_format & DIFF_FORMAT_NAME_STATUS)) {
                fprintf(opt->file, ":%06o %06o %s ", p->one->mode, p->two->mode,
                        diff_unique_abbrev(p->one->sha1, opt->abbrev));
@@@ -4121,7 -4207,12 +4155,7 @@@ static void show_rename_copy(FILE *file
  static void diff_summary(struct diff_options *opt, struct diff_filepair *p)
  {
        FILE *file = opt->file;
 -      char *line_prefix = "";
 -
 -      if (opt->output_prefix) {
 -              struct strbuf *buf = opt->output_prefix(opt, opt->output_prefix_data);
 -              line_prefix = buf->buf;
 -      }
 +      const char *line_prefix = diff_line_prefix(opt);
  
        switch(p->status) {
        case DIFF_STATUS_DELETED:
@@@ -4422,9 -4513,13 +4456,9 @@@ void diff_flush(struct diff_options *op
  
        if (output_format & DIFF_FORMAT_PATCH) {
                if (separator) {
 -                      if (options->output_prefix) {
 -                              struct strbuf *msg = NULL;
 -                              msg = options->output_prefix(options,
 -                                      options->output_prefix_data);
 -                              fwrite(msg->buf, msg->len, 1, stdout);
 -                      }
 -                      putc(options->line_termination, options->file);
 +                      fprintf(options->file, "%s%c",
 +                              diff_line_prefix(options),
 +                              options->line_termination);
                        if (options->stat_sep) {
                                /* attach patch instead of inline */
                                fputs(options->stat_sep, options->file);
diff --combined diff.h
index 76830e28b42ab623ef9dc1d23c4b82b952fadd3a,54c2590b4f81ed2cc370f1b8b16db8cd07ea9f64..78b4091dd5b59fe4442262cd79cf71f8a4a2b98f
--- 1/diff.h
--- 2/diff.h
+++ b/diff.h
@@@ -174,9 -174,6 +174,9 @@@ const char *diff_get_color(int diff_use
        diff_get_color((o)->use_color, ix)
  
  
 +const char *diff_line_prefix(struct diff_options *);
 +
 +
  extern const char mime_boundary_leader[];
  
  extern void diff_tree_setup_paths(const char **paths, struct diff_options *);
@@@ -336,6 -333,8 +336,8 @@@ extern struct userdiff_driver *get_text
  
  extern int parse_rename_score(const char **cp_p);
  
+ extern long parse_algorithm_value(const char *value);
  extern int print_stat_summary(FILE *fp, int files,
                              int insertions, int deletions);
  extern void setup_diff_pager(struct diff_options *);