Merge branch 'rs/blame'
authorJunio C Hamano <gitster@pobox.com>
Sun, 9 Nov 2008 00:05:39 +0000 (16:05 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sun, 9 Nov 2008 00:05:39 +0000 (16:05 -0800)
* rs/blame:
blame: use xdi_diff_hunks(), get rid of struct patch
add xdi_diff_hunks() for callers that only need hunk lengths
Allow alternate "low-level" emit function from xdl_diff
Always initialize xpparam_t to 0
blame: inline get_patch()

1  2 
builtin-blame.c
diff.c
xdiff-interface.c
diff --combined builtin-blame.c
index 2457e71fc03fa9f6a37805617a8a9f4771ff1670,b6bc5cf6a91a587d2fab979010e958f66b2db346..a0d60145f26d32e45f4d04c22164cbe8060739ca
@@@ -442,131 -442,6 +442,6 @@@ static struct origin *find_rename(struc
        return porigin;
  }
  
- /*
-  * Parsing of patch chunks...
-  */
- struct chunk {
-       /* line number in postimage; up to but not including this
-        * line is the same as preimage
-        */
-       int same;
-       /* preimage line number after this chunk */
-       int p_next;
-       /* postimage line number after this chunk */
-       int t_next;
- };
- struct patch {
-       struct chunk *chunks;
-       int num;
- };
- struct blame_diff_state {
-       struct patch *ret;
-       unsigned hunk_post_context;
-       unsigned hunk_in_pre_context : 1;
- };
- static void process_u_diff(void *state_, char *line, unsigned long len)
- {
-       struct blame_diff_state *state = state_;
-       struct chunk *chunk;
-       int off1, off2, len1, len2, num;
-       num = state->ret->num;
-       if (len < 4 || line[0] != '@' || line[1] != '@') {
-               if (state->hunk_in_pre_context && line[0] == ' ')
-                       state->ret->chunks[num - 1].same++;
-               else {
-                       state->hunk_in_pre_context = 0;
-                       if (line[0] == ' ')
-                               state->hunk_post_context++;
-                       else
-                               state->hunk_post_context = 0;
-               }
-               return;
-       }
-       if (num && state->hunk_post_context) {
-               chunk = &state->ret->chunks[num - 1];
-               chunk->p_next -= state->hunk_post_context;
-               chunk->t_next -= state->hunk_post_context;
-       }
-       state->ret->num = ++num;
-       state->ret->chunks = xrealloc(state->ret->chunks,
-                                     sizeof(struct chunk) * num);
-       chunk = &state->ret->chunks[num - 1];
-       if (parse_hunk_header(line, len, &off1, &len1, &off2, &len2)) {
-               state->ret->num--;
-               return;
-       }
-       /* Line numbers in patch output are one based. */
-       off1--;
-       off2--;
-       chunk->same = len2 ? off2 : (off2 + 1);
-       chunk->p_next = off1 + (len1 ? len1 : 1);
-       chunk->t_next = chunk->same + len2;
-       state->hunk_in_pre_context = 1;
-       state->hunk_post_context = 0;
- }
- static struct patch *compare_buffer(mmfile_t *file_p, mmfile_t *file_o,
-                                   int context)
- {
-       struct blame_diff_state state;
-       xpparam_t xpp;
-       xdemitconf_t xecfg;
-       xdemitcb_t ecb;
-       xpp.flags = xdl_opts;
-       memset(&xecfg, 0, sizeof(xecfg));
-       xecfg.ctxlen = context;
-       memset(&state, 0, sizeof(state));
-       state.ret = xmalloc(sizeof(struct patch));
-       state.ret->chunks = NULL;
-       state.ret->num = 0;
-       xdi_diff_outf(file_p, file_o, process_u_diff, &state, &xpp, &xecfg, &ecb);
-       if (state.ret->num) {
-               struct chunk *chunk;
-               chunk = &state.ret->chunks[state.ret->num - 1];
-               chunk->p_next -= state.hunk_post_context;
-               chunk->t_next -= state.hunk_post_context;
-       }
-       return state.ret;
- }
- /*
-  * Run diff between two origins and grab the patch output, so that
-  * we can pass blame for lines origin is currently suspected for
-  * to its parent.
-  */
- static struct patch *get_patch(struct origin *parent, struct origin *origin)
- {
-       mmfile_t file_p, file_o;
-       struct patch *patch;
-       fill_origin_blob(parent, &file_p);
-       fill_origin_blob(origin, &file_o);
-       if (!file_p.ptr || !file_o.ptr)
-               return NULL;
-       patch = compare_buffer(&file_p, &file_o, 0);
-       num_get_patch++;
-       return patch;
- }
- static void free_patch(struct patch *p)
- {
-       free(p->chunks);
-       free(p);
- }
  /*
   * Link in a new blame entry to the scoreboard.  Entries that cover the
   * same line range have been removed from the scoreboard previously.
@@@ -813,6 -688,22 +688,22 @@@ static void blame_chunk(struct scoreboa
        }
  }
  
+ struct blame_chunk_cb_data {
+       struct scoreboard *sb;
+       struct origin *target;
+       struct origin *parent;
+       long plno;
+       long tlno;
+ };
+ static void blame_chunk_cb(void *data, long same, long p_next, long t_next)
+ {
+       struct blame_chunk_cb_data *d = data;
+       blame_chunk(d->sb, d->tlno, d->plno, same, d->target, d->parent);
+       d->plno = p_next;
+       d->tlno = t_next;
+ }
  /*
   * We are looking at the origin 'target' and aiming to pass blame
   * for the lines it is suspected to its parent.  Run diff to find
@@@ -822,26 -713,28 +713,28 @@@ static int pass_blame_to_parent(struct 
                                struct origin *target,
                                struct origin *parent)
  {
-       int i, last_in_target, plno, tlno;
-       struct patch *patch;
+       int last_in_target;
+       mmfile_t file_p, file_o;
+       struct blame_chunk_cb_data d = { sb, target, parent, 0, 0 };
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
  
        last_in_target = find_last_in_target(sb, target);
        if (last_in_target < 0)
                return 1; /* nothing remains for this target */
  
-       patch = get_patch(parent, target);
-       plno = tlno = 0;
-       for (i = 0; i < patch->num; i++) {
-               struct chunk *chunk = &patch->chunks[i];
+       fill_origin_blob(parent, &file_p);
+       fill_origin_blob(target, &file_o);
+       num_get_patch++;
  
-               blame_chunk(sb, tlno, plno, chunk->same, target, parent);
-               plno = chunk->p_next;
-               tlno = chunk->t_next;
-       }
+       memset(&xpp, 0, sizeof(xpp));
+       xpp.flags = xdl_opts;
+       memset(&xecfg, 0, sizeof(xecfg));
+       xecfg.ctxlen = 0;
+       xdi_diff_hunks(&file_p, &file_o, blame_chunk_cb, &d, &xpp, &xecfg);
        /* The rest (i.e. anything after tlno) are the same as the parent */
-       blame_chunk(sb, tlno, plno, last_in_target, target, parent);
+       blame_chunk(sb, d.tlno, d.plno, last_in_target, target, parent);
  
-       free_patch(patch);
        return 0;
  }
  
@@@ -933,6 -826,23 +826,23 @@@ static void handle_split(struct scorebo
        }
  }
  
+ struct handle_split_cb_data {
+       struct scoreboard *sb;
+       struct blame_entry *ent;
+       struct origin *parent;
+       struct blame_entry *split;
+       long plno;
+       long tlno;
+ };
+ static void handle_split_cb(void *data, long same, long p_next, long t_next)
+ {
+       struct handle_split_cb_data *d = data;
+       handle_split(d->sb, d->ent, d->tlno, d->plno, same, d->parent, d->split);
+       d->plno = p_next;
+       d->tlno = t_next;
+ }
  /*
   * Find the lines from parent that are the same as ent so that
   * we can pass blames to it.  file_p has the blob contents for
@@@ -947,8 -857,9 +857,9 @@@ static void find_copy_in_blob(struct sc
        const char *cp;
        int cnt;
        mmfile_t file_o;
-       struct patch *patch;
-       int i, plno, tlno;
+       struct handle_split_cb_data d = { sb, ent, parent, split, 0, 0 };
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
  
        /*
         * Prepare mmfile that contains only the lines in ent.
        }
        file_o.size = cp - file_o.ptr;
  
-       patch = compare_buffer(file_p, &file_o, 1);
        /*
         * file_o is a part of final image we are annotating.
         * file_p partially may match that image.
         */
+       memset(&xpp, 0, sizeof(xpp));
+       xpp.flags = xdl_opts;
+       memset(&xecfg, 0, sizeof(xecfg));
+       xecfg.ctxlen = 1;
        memset(split, 0, sizeof(struct blame_entry [3]));
-       plno = tlno = 0;
-       for (i = 0; i < patch->num; i++) {
-               struct chunk *chunk = &patch->chunks[i];
-               handle_split(sb, ent, tlno, plno, chunk->same, parent, split);
-               plno = chunk->p_next;
-               tlno = chunk->t_next;
-       }
+       xdi_diff_hunks(file_p, &file_o, handle_split_cb, &d, &xpp, &xecfg);
        /* remainder, if any, all match the preimage */
-       handle_split(sb, ent, tlno, plno, ent->num_lines, parent, split);
-       free_patch(patch);
+       handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
  }
  
  /*
@@@ -1431,7 -1336,7 +1336,7 @@@ static void get_commit_info(struct comm
                            int detailed)
  {
        int len;
 -      char *tmp, *endp;
 +      char *tmp, *endp, *reencoded, *message;
        static char author_buf[1024];
        static char committer_buf[1024];
        static char summary_buf[1024];
                        die("Cannot read commit %s",
                            sha1_to_hex(commit->object.sha1));
        }
 +      reencoded = reencode_commit_message(commit, NULL);
 +      message   = reencoded ? reencoded : commit->buffer;
        ret->author = author_buf;
 -      get_ac_line(commit->buffer, "\nauthor ",
 +      get_ac_line(message, "\nauthor ",
                    sizeof(author_buf), author_buf, &ret->author_mail,
                    &ret->author_time, &ret->author_tz);
  
 -      if (!detailed)
 +      if (!detailed) {
 +              free(reencoded);
                return;
 +      }
  
        ret->committer = committer_buf;
 -      get_ac_line(commit->buffer, "\ncommitter ",
 +      get_ac_line(message, "\ncommitter ",
                    sizeof(committer_buf), committer_buf, &ret->committer_mail,
                    &ret->committer_time, &ret->committer_tz);
  
        ret->summary = summary_buf;
 -      tmp = strstr(commit->buffer, "\n\n");
 +      tmp = strstr(message, "\n\n");
        if (!tmp) {
        error_out:
                sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1));
 +              free(reencoded);
                return;
        }
        tmp += 2;
                goto error_out;
        memcpy(summary_buf, tmp, len);
        summary_buf[len] = 0;
 +      free(reencoded);
  }
  
  /*
diff --combined diff.c
index e368fef14fdccf63fc3525349841ae93aea93046,f141e7c8ff661733c2be6652709cc60c3340100e..1918b73d5368d1ad89f501e63777e47763d0d0fe
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -11,7 -11,6 +11,7 @@@
  #include "attr.h"
  #include "run-command.h"
  #include "utf8.h"
 +#include "userdiff.h"
  
  #ifdef NO_FAST_WORKING_DIRECTORY
  #define FAST_WORKING_DIRECTORY 0
@@@ -38,9 -37,6 +38,9 @@@ static char diff_colors[][COLOR_MAXLEN
        "\033[41m",     /* WHITESPACE (red background) */
  };
  
 +static void diff_filespec_load_driver(struct diff_filespec *one);
 +static char *run_textconv(const char *, struct diff_filespec *, size_t *);
 +
  static int parse_diff_color_slot(const char *var, int ofs)
  {
        if (!strcasecmp(var+ofs, "plain"))
        die("bad config variable '%s'", var);
  }
  
 -static struct ll_diff_driver {
 -      const char *name;
 -      struct ll_diff_driver *next;
 -      const char *cmd;
 -} *user_diff, **user_diff_tail;
 -
 -/*
 - * Currently there is only "diff.<drivername>.command" variable;
 - * because there are "diff.color.<slot>" variables, we are parsing
 - * this in a bit convoluted way to allow low level diff driver
 - * called "color".
 - */
 -static int parse_lldiff_command(const char *var, const char *ep, const char *value)
 -{
 -      const char *name;
 -      int namelen;
 -      struct ll_diff_driver *drv;
 -
 -      name = var + 5;
 -      namelen = ep - name;
 -      for (drv = user_diff; drv; drv = drv->next)
 -              if (!strncmp(drv->name, name, namelen) && !drv->name[namelen])
 -                      break;
 -      if (!drv) {
 -              drv = xcalloc(1, sizeof(struct ll_diff_driver));
 -              drv->name = xmemdupz(name, namelen);
 -              if (!user_diff_tail)
 -                      user_diff_tail = &user_diff;
 -              *user_diff_tail = drv;
 -              user_diff_tail = &(drv->next);
 -      }
 -
 -      return git_config_string(&(drv->cmd), var, value);
 -}
 -
 -/*
 - * 'diff.<what>.funcname' attribute can be specified in the configuration
 - * to define a customized regexp to find the beginning of a function to
 - * be used for hunk header lines of "diff -p" style output.
 - */
 -struct funcname_pattern_entry {
 -      char *name;
 -      char *pattern;
 -      int cflags;
 -};
 -static struct funcname_pattern_list {
 -      struct funcname_pattern_list *next;
 -      struct funcname_pattern_entry e;
 -} *funcname_pattern_list;
 -
 -static int parse_funcname_pattern(const char *var, const char *ep, const char *value, int cflags)
 -{
 -      const char *name;
 -      int namelen;
 -      struct funcname_pattern_list *pp;
 -
 -      name = var + 5; /* "diff." */
 -      namelen = ep - name;
 -
 -      for (pp = funcname_pattern_list; pp; pp = pp->next)
 -              if (!strncmp(pp->e.name, name, namelen) && !pp->e.name[namelen])
 -                      break;
 -      if (!pp) {
 -              pp = xcalloc(1, sizeof(*pp));
 -              pp->e.name = xmemdupz(name, namelen);
 -              pp->next = funcname_pattern_list;
 -              funcname_pattern_list = pp;
 -      }
 -      free(pp->e.pattern);
 -      pp->e.pattern = xstrdup(value);
 -      pp->e.cflags = cflags;
 -      return 0;
 -}
 -
  /*
   * These are to give UI layer defaults.
   * The core-level commands such as git-diff-files should
@@@ -92,11 -162,11 +92,11 @@@ int git_diff_ui_config(const char *var
        }
        if (!strcmp(var, "diff.external"))
                return git_config_string(&external_diff_cmd_cfg, var, value);
 -      if (!prefixcmp(var, "diff.")) {
 -              const char *ep = strrchr(var, '.');
  
 -              if (ep != var + 4 && !strcmp(ep, ".command"))
 -                      return parse_lldiff_command(var, ep, value);
 +      switch (userdiff_config_porcelain(var, value)) {
 +              case 0: break;
 +              case -1: return -1;
 +              default: return 0;
        }
  
        return git_diff_basic_config(var, value, cb);
@@@ -123,10 -193,21 +123,10 @@@ int git_diff_basic_config(const char *v
                return 0;
        }
  
 -      if (!prefixcmp(var, "diff.")) {
 -              const char *ep = strrchr(var, '.');
 -              if (ep != var + 4) {
 -                      if (!strcmp(ep, ".funcname")) {
 -                              if (!value)
 -                                      return config_error_nonbool(var);
 -                              return parse_funcname_pattern(var, ep, value,
 -                                      0);
 -                      } else if (!strcmp(ep, ".xfuncname")) {
 -                              if (!value)
 -                                      return config_error_nonbool(var);
 -                              return parse_funcname_pattern(var, ep, value,
 -                                      REG_EXTENDED);
 -                      }
 -              }
 +      switch (userdiff_config_basic(var, value)) {
 +              case 0: break;
 +              case -1: return -1;
 +              default: return 0;
        }
  
        return git_color_default_config(var, value, cb);
@@@ -293,19 -374,8 +293,19 @@@ static int fill_mmfile(mmfile_t *mf, st
        }
        else if (diff_populate_filespec(one, 0))
                return -1;
 -      mf->ptr = one->data;
 -      mf->size = one->size;
 +
 +      diff_filespec_load_driver(one);
 +      if (one->driver->textconv) {
 +              size_t size;
 +              mf->ptr = run_textconv(one->driver->textconv, one, &size);
 +              if (!mf->ptr)
 +                      return -1;
 +              mf->size = size;
 +      }
 +      else {
 +              mf->ptr = one->data;
 +              mf->size = one->size;
 +      }
        return 0;
  }
  
@@@ -400,6 -470,7 +400,7 @@@ static void diff_words_show(struct diff
        mmfile_t minus, plus;
        int i;
  
+       memset(&xpp, 0, sizeof(xpp));
        memset(&xecfg, 0, sizeof(xecfg));
        minus.size = diff_words->minus.text.size;
        minus.ptr = xmalloc(minus.size);
@@@ -1282,37 -1353,136 +1283,37 @@@ static void emit_binary_diff(FILE *file
        emit_binary_diff_body(file, two, one);
  }
  
 -static void setup_diff_attr_check(struct git_attr_check *check)
 +void diff_filespec_load_driver(struct diff_filespec *one)
  {
 -      static struct git_attr *attr_diff;
 -
 -      if (!attr_diff) {
 -              attr_diff = git_attr("diff", 4);
 -      }
 -      check[0].attr = attr_diff;
 -}
 -
 -static void diff_filespec_check_attr(struct diff_filespec *one)
 -{
 -      struct git_attr_check attr_diff_check;
 -      int check_from_data = 0;
 -
 -      if (one->checked_attr)
 -              return;
 -
 -      setup_diff_attr_check(&attr_diff_check);
 -      one->is_binary = 0;
 -      one->funcname_pattern_ident = NULL;
 -
 -      if (!git_checkattr(one->path, 1, &attr_diff_check)) {
 -              const char *value;
 -
 -              /* binaryness */
 -              value = attr_diff_check.value;
 -              if (ATTR_TRUE(value))
 -                      ;
 -              else if (ATTR_FALSE(value))
 -                      one->is_binary = 1;
 -              else
 -                      check_from_data = 1;
 -
 -              /* funcname pattern ident */
 -              if (ATTR_TRUE(value) || ATTR_FALSE(value) || ATTR_UNSET(value))
 -                      ;
 -              else
 -                      one->funcname_pattern_ident = value;
 -      }
 -
 -      if (check_from_data) {
 -              if (!one->data && DIFF_FILE_VALID(one))
 -                      diff_populate_filespec(one, 0);
 -
 -              if (one->data)
 -                      one->is_binary = buffer_is_binary(one->data, one->size);
 -      }
 +      if (!one->driver)
 +              one->driver = userdiff_find_by_path(one->path);
 +      if (!one->driver)
 +              one->driver = userdiff_find_by_name("default");
  }
  
  int diff_filespec_is_binary(struct diff_filespec *one)
  {
 -      diff_filespec_check_attr(one);
 +      if (one->is_binary == -1) {
 +              diff_filespec_load_driver(one);
 +              if (one->driver->binary != -1)
 +                      one->is_binary = one->driver->binary;
 +              else {
 +                      if (!one->data && DIFF_FILE_VALID(one))
 +                              diff_populate_filespec(one, 0);
 +                      if (one->data)
 +                              one->is_binary = buffer_is_binary(one->data,
 +                                              one->size);
 +                      if (one->is_binary == -1)
 +                              one->is_binary = 0;
 +              }
 +      }
        return one->is_binary;
  }
  
 -static const struct funcname_pattern_entry *funcname_pattern(const char *ident)
 -{
 -      struct funcname_pattern_list *pp;
 -
 -      for (pp = funcname_pattern_list; pp; pp = pp->next)
 -              if (!strcmp(ident, pp->e.name))
 -                      return &pp->e;
 -      return NULL;
 -}
 -
 -static const struct funcname_pattern_entry builtin_funcname_pattern[] = {
 -      { "bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$",
 -        REG_EXTENDED },
 -      { "html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$", REG_EXTENDED },
 -      { "java",
 -        "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n"
 -        "^[ \t]*(([ \t]*[A-Za-z_][A-Za-z_0-9]*){2,}[ \t]*\\([^;]*)$",
 -        REG_EXTENDED },
 -      { "objc",
 -        /* Negate C statements that can look like functions */
 -        "!^[ \t]*(do|for|if|else|return|switch|while)\n"
 -        /* Objective-C methods */
 -        "^[ \t]*([-+][ \t]*\\([ \t]*[A-Za-z_][A-Za-z_0-9* \t]*\\)[ \t]*[A-Za-z_].*)$\n"
 -        /* C functions */
 -        "^[ \t]*(([ \t]*[A-Za-z_][A-Za-z_0-9]*){2,}[ \t]*\\([^;]*)$\n"
 -        /* Objective-C class/protocol definitions */
 -        "^(@(implementation|interface|protocol)[ \t].*)$",
 -        REG_EXTENDED },
 -      { "pascal",
 -        "^((procedure|function|constructor|destructor|interface|"
 -              "implementation|initialization|finalization)[ \t]*.*)$"
 -        "\n"
 -        "^(.*=[ \t]*(class|record).*)$",
 -        REG_EXTENDED },
 -      { "php", "^[\t ]*((function|class).*)", REG_EXTENDED },
 -      { "python", "^[ \t]*((class|def)[ \t].*)$", REG_EXTENDED },
 -      { "ruby", "^[ \t]*((class|module|def)[ \t].*)$",
 -        REG_EXTENDED },
 -      { "tex",
 -        "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$",
 -        REG_EXTENDED },
 -};
 -
 -static const struct funcname_pattern_entry *diff_funcname_pattern(struct diff_filespec *one)
 +static const struct userdiff_funcname *diff_funcname_pattern(struct diff_filespec *one)
  {
 -      const char *ident;
 -      const struct funcname_pattern_entry *pe;
 -      int i;
 -
 -      diff_filespec_check_attr(one);
 -      ident = one->funcname_pattern_ident;
 -
 -      if (!ident)
 -              /*
 -               * If the config file has "funcname.default" defined, that
 -               * regexp is used; otherwise NULL is returned and xemit uses
 -               * the built-in default.
 -               */
 -              return funcname_pattern("default");
 -
 -      /* Look up custom "funcname.$ident" regexp from config. */
 -      pe = funcname_pattern(ident);
 -      if (pe)
 -              return pe;
 -
 -      /*
 -       * And define built-in fallback patterns here.  Note that
 -       * these can be overridden by the user's config settings.
 -       */
 -      for (i = 0; i < ARRAY_SIZE(builtin_funcname_pattern); i++)
 -              if (!strcmp(ident, builtin_funcname_pattern[i].name))
 -                      return &builtin_funcname_pattern[i];
 -
 -      return NULL;
 +      diff_filespec_load_driver(one);
 +      return one->driver->funcname.pattern ? &one->driver->funcname : NULL;
  }
  
  void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b)
@@@ -1410,12 -1580,13 +1411,13 @@@ static void builtin_diff(const char *na
                xdemitconf_t xecfg;
                xdemitcb_t ecb;
                struct emit_callback ecbdata;
 -              const struct funcname_pattern_entry *pe;
 +              const struct userdiff_funcname *pe;
  
                pe = diff_funcname_pattern(one);
                if (!pe)
                        pe = diff_funcname_pattern(two);
  
+               memset(&xpp, 0, sizeof(xpp));
                memset(&xecfg, 0, sizeof(xecfg));
                memset(&ecbdata, 0, sizeof(ecbdata));
                ecbdata.label_path = lbl;
@@@ -1489,6 -1660,7 +1491,7 @@@ static void builtin_diffstat(const cha
                xdemitconf_t xecfg;
                xdemitcb_t ecb;
  
+               memset(&xpp, 0, sizeof(xpp));
                memset(&xecfg, 0, sizeof(xecfg));
                xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
                xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
@@@ -1535,6 -1707,7 +1538,7 @@@ static void builtin_checkdiff(const cha
                xdemitconf_t xecfg;
                xdemitcb_t ecb;
  
+               memset(&xpp, 0, sizeof(xpp));
                memset(&xecfg, 0, sizeof(xecfg));
                xecfg.ctxlen = 1; /* at least one context line */
                xpp.flags = XDF_NEED_MINIMAL;
@@@ -1564,7 -1737,6 +1568,7 @@@ struct diff_filespec *alloc_filespec(co
        spec->path = (char *)(spec + 1);
        memcpy(spec->path, path, namelen+1);
        spec->count = 1;
 +      spec->is_binary = -1;
        return spec;
  }
  
@@@ -1949,6 -2121,29 +1953,6 @@@ static void run_external_diff(const cha
        }
  }
  
 -static const char *external_diff_attr(const char *name)
 -{
 -      struct git_attr_check attr_diff_check;
 -
 -      if (!name)
 -              return NULL;
 -
 -      setup_diff_attr_check(&attr_diff_check);
 -      if (!git_checkattr(name, 1, &attr_diff_check)) {
 -              const char *value = attr_diff_check.value;
 -              if (!ATTR_TRUE(value) &&
 -                  !ATTR_FALSE(value) &&
 -                  !ATTR_UNSET(value)) {
 -                      struct ll_diff_driver *drv;
 -
 -                      for (drv = user_diff; drv; drv = drv->next)
 -                              if (!strcmp(drv->name, value))
 -                                      return drv->cmd;
 -              }
 -      }
 -      return NULL;
 -}
 -
  static void run_diff_cmd(const char *pgm,
                         const char *name,
                         const char *other,
        if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL))
                pgm = NULL;
        else {
 -              const char *cmd = external_diff_attr(attr_path);
 -              if (cmd)
 -                      pgm = cmd;
 +              struct userdiff_driver *drv = userdiff_find_by_path(attr_path);
 +              if (drv && drv->external)
 +                      pgm = drv->external;
        }
  
        if (pgm) {
@@@ -2958,6 -3153,7 +2962,7 @@@ static int diff_get_patch_id(struct dif
                struct diff_filepair *p = q->queue[i];
                int len1, len2;
  
+               memset(&xpp, 0, sizeof(xpp));
                memset(&xecfg, 0, sizeof(xecfg));
                if (p->status == 0)
                        return error("internal diff status error");
@@@ -3387,34 -3583,3 +3392,34 @@@ void diff_unmerge(struct diff_options *
        fill_filespec(one, sha1, mode);
        diff_queue(&diff_queued_diff, one, two)->is_unmerged = 1;
  }
 +
 +static char *run_textconv(const char *pgm, struct diff_filespec *spec,
 +              size_t *outsize)
 +{
 +      struct diff_tempfile temp;
 +      const char *argv[3];
 +      const char **arg = argv;
 +      struct child_process child;
 +      struct strbuf buf = STRBUF_INIT;
 +
 +      prepare_temp_file(spec->path, &temp, spec);
 +      *arg++ = pgm;
 +      *arg++ = temp.name;
 +      *arg = NULL;
 +
 +      memset(&child, 0, sizeof(child));
 +      child.argv = argv;
 +      child.out = -1;
 +      if (start_command(&child) != 0 ||
 +          strbuf_read(&buf, child.out, 0) < 0 ||
 +          finish_command(&child) != 0) {
 +              if (temp.name == temp.tmp_path)
 +                      unlink(temp.name);
 +              error("error running textconv command '%s'", pgm);
 +              return NULL;
 +      }
 +      if (temp.name == temp.tmp_path)
 +              unlink(temp.name);
 +
 +      return strbuf_detach(&buf, outsize);
 +}
diff --combined xdiff-interface.c
index 49e06af710ceee5538eb10f0c6cf9c30f31748b4,70794c71fcbf198c7425a49789a912369fba28ad..e8ef46d10dc53a1ee1781d07685984734bc3160f
@@@ -1,6 -1,9 +1,9 @@@
  #include "cache.h"
  #include "xdiff-interface.h"
- #include "strbuf.h"
+ #include "xdiff/xtypes.h"
+ #include "xdiff/xdiffi.h"
+ #include "xdiff/xemit.h"
+ #include "xdiff/xmacros.h"
  
  struct xdiff_emit_state {
        xdiff_emit_consume_fn consume;
@@@ -153,6 -156,50 +156,50 @@@ int xdi_diff_outf(mmfile_t *mf1, mmfile
        return ret;
  }
  
+ struct xdiff_emit_hunk_state {
+       xdiff_emit_hunk_consume_fn consume;
+       void *consume_callback_data;
+ };
+ static int process_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+                       xdemitconf_t const *xecfg)
+ {
+       long s1, s2, same, p_next, t_next;
+       xdchange_t *xch, *xche;
+       struct xdiff_emit_hunk_state *state = ecb->priv;
+       xdiff_emit_hunk_consume_fn fn = state->consume;
+       void *consume_callback_data = state->consume_callback_data;
+       for (xch = xscr; xch; xch = xche->next) {
+               xche = xdl_get_hunk(xch, xecfg);
+               s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0);
+               s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0);
+               same = s2 + XDL_MAX(xch->i1 - s1, 0);
+               p_next = xche->i1 + xche->chg1;
+               t_next = xche->i2 + xche->chg2;
+               fn(consume_callback_data, same, p_next, t_next);
+       }
+       return 0;
+ }
+ int xdi_diff_hunks(mmfile_t *mf1, mmfile_t *mf2,
+                  xdiff_emit_hunk_consume_fn fn, void *consume_callback_data,
+                  xpparam_t const *xpp, xdemitconf_t *xecfg)
+ {
+       struct xdiff_emit_hunk_state state;
+       xdemitcb_t ecb;
+       memset(&state, 0, sizeof(state));
+       memset(&ecb, 0, sizeof(ecb));
+       state.consume = fn;
+       state.consume_callback_data = consume_callback_data;
+       xecfg->emit_func = (void (*)())process_diff;
+       ecb.priv = &state;
+       return xdi_diff(mf1, mf2, xpp, xecfg, &ecb);
+ }
  int read_mmfile(mmfile_t *ptr, const char *filename)
  {
        struct stat st;
@@@ -199,16 -246,6 +246,16 @@@ static long ff_regexp(const char *line
  
        /* Exclude terminating newline (and cr) from matching */
        if (len > 0 && line[len-1] == '\n') {
 +              if (len > 1 && line[len-2] == '\r')
 +                      len -= 2;
 +              else
 +                      len--;
 +      }
 +
 +      line_buffer = xstrndup(line, len); /* make NUL terminated */
 +
 +      /* Exclude terminating newline (and cr) from matching */
 +      if (len > 0 && line[len-1] == '\n') {
                if (len > 1 && line[len-2] == '\r')
                        len -= 2;
                else