Code

Merge branch 'maint-1.6.1' into maint
authorJunio C Hamano <gitster@pobox.com>
Wed, 30 Dec 2009 09:24:12 +0000 (01:24 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 30 Dec 2009 09:24:12 +0000 (01:24 -0800)
* maint-1.6.1:
  textconv: stop leaking file descriptors
  commit: --cleanup is a message option
  git count-objects: handle packs bigger than 4G
  t7102: make the test fail if one of its check fails

Conflicts:
builtin-commit.c
diff.c

1  2 
builtin-commit.c
builtin-count-objects.c
diff.c
t/t7102-reset.sh

diff --combined builtin-commit.c
index f54772f74a14e480ae978b38ce3bcd49ee994411,bf01ae776be92fcdf4878a008464777185a08e4b..33aa593c21b70cd29a54e7b8151d78e092cfad3a
@@@ -51,7 -51,7 +51,7 @@@ static const char *template_file
  static char *edit_message, *use_message;
  static char *author_name, *author_email, *author_date;
  static int all, edit_flag, also, interactive, only, amend, signoff;
 -static int quiet, verbose, no_verify, allow_empty;
 +static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
  static char *untracked_files_arg;
  /*
   * The default commit message cleanup mode will remove the lines
@@@ -86,17 -86,18 +86,19 @@@ static int opt_parse_m(const struct opt
  static struct option builtin_commit_options[] = {
        OPT__QUIET(&quiet),
        OPT__VERBOSE(&verbose),
-       OPT_GROUP("Commit message options"),
  
 -      OPT_STRING('F', "file", &logfile, "FILE", "read log from file"),
+       OPT_GROUP("Commit message options"),
 +      OPT_FILENAME('F', "file", &logfile, "read log from file"),
        OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"),
        OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m),
 -      OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit "),
 +      OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"),
        OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"),
 +      OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
        OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
 -      OPT_STRING('t', "template", &template_file, "FILE", "use specified template file"),
 +      OPT_FILENAME('t', "template", &template_file, "use specified template file"),
        OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
+       OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
+       /* end commit message options */
  
        OPT_GROUP("Commit contents options"),
        OPT_BOOLEAN('a', "all", &all, "commit all changed files"),
        OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"),
        OPT_BOOLEAN('o', "only", &only, "commit only specified files"),
        OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
 +      OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"),
        OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
        { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
        OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
-       OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
+       /* end commit contents options */
  
        OPT_END()
  };
@@@ -168,7 -168,7 +170,7 @@@ static int list_paths(struct string_lis
                struct cache_entry *ce = active_cache[i];
                if (ce->ce_flags & CE_UPDATE)
                        continue;
 -              if (!pathspec_match(pattern, m, ce->name, 0))
 +              if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m))
                        continue;
                string_list_insert(ce->name, list);
        }
@@@ -219,15 -219,12 +221,15 @@@ static void create_base_index(void
                exit(128); /* We've already reported the error, finish dying */
  }
  
 -static char *prepare_index(int argc, const char **argv, const char *prefix)
 +static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status)
  {
        int fd;
        struct string_list partial;
        const char **pathspec = NULL;
 +      int refresh_flags = REFRESH_QUIET;
  
 +      if (is_status)
 +              refresh_flags |= REFRESH_UNMERGED;
        if (interactive) {
                if (interactive_add(argc, argv, prefix) != 0)
                        die("interactive add failed");
        if (all || (also && pathspec && *pathspec)) {
                int fd = hold_locked_index(&index_lock, 1);
                add_files_to_cache(also ? prefix : NULL, pathspec, 0);
 -              refresh_cache(REFRESH_QUIET);
 +              refresh_cache(refresh_flags);
                if (write_cache(fd, active_cache, active_nr) ||
                    close_lock_file(&index_lock))
                        die("unable to write new_index file");
         */
        if (!pathspec || !*pathspec) {
                fd = hold_locked_index(&index_lock, 1);
 -              refresh_cache(REFRESH_QUIET);
 +              refresh_cache(refresh_flags);
                if (write_cache(fd, active_cache, active_nr) ||
                    commit_locked_index(&index_lock))
                        die("unable to write new_index file");
        return false_lock.filename;
  }
  
 -static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn)
 +static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
 +                    struct wt_status *s)
  {
 -      struct wt_status s;
 -
 -      wt_status_prepare(&s);
 -      if (wt_status_relative_paths)
 -              s.prefix = prefix;
 +      if (s->relative_paths)
 +              s->prefix = prefix;
  
        if (amend) {
 -              s.amend = 1;
 -              s.reference = "HEAD^1";
 +              s->amend = 1;
 +              s->reference = "HEAD^1";
        }
 -      s.verbose = verbose;
 -      s.untracked = (show_untracked_files == SHOW_ALL_UNTRACKED_FILES);
 -      s.index_file = index_file;
 -      s.fp = fp;
 -      s.nowarn = nowarn;
 +      s->verbose = verbose;
 +      s->index_file = index_file;
 +      s->fp = fp;
 +      s->nowarn = nowarn;
  
 -      wt_status_print(&s);
 +      wt_status_print(s);
  
 -      return s.commitable;
 -}
 -
 -static int run_hook(const char *index_file, const char *name, ...)
 -{
 -      struct child_process hook;
 -      const char *argv[10], *env[2];
 -      char index[PATH_MAX];
 -      va_list args;
 -      int i;
 -
 -      va_start(args, name);
 -      argv[0] = git_path("hooks/%s", name);
 -      i = 0;
 -      do {
 -              if (++i >= ARRAY_SIZE(argv))
 -                      die ("run_hook(): too many arguments");
 -              argv[i] = va_arg(args, const char *);
 -      } while (argv[i]);
 -      va_end(args);
 -
 -      snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
 -      env[0] = index;
 -      env[1] = NULL;
 -
 -      if (access(argv[0], X_OK) < 0)
 -              return 0;
 -
 -      memset(&hook, 0, sizeof(hook));
 -      hook.argv = argv;
 -      hook.no_stdin = 1;
 -      hook.stdout_to_stderr = 1;
 -      hook.env = env;
 -
 -      return run_command(&hook);
 +      return s->commitable;
  }
  
  static int is_a_merge(const unsigned char *sha1)
@@@ -382,7 -416,7 +384,7 @@@ static void determine_author_info(void
        email = getenv("GIT_AUTHOR_EMAIL");
        date = getenv("GIT_AUTHOR_DATE");
  
 -      if (use_message) {
 +      if (use_message && !renew_authorship) {
                const char *a, *lb, *rb, *eol;
  
                a = strstr(use_message_buffer, "\nauthor ");
        author_date = date;
  }
  
 -static int prepare_to_commit(const char *index_file, const char *prefix)
 +static int ends_rfc2822_footer(struct strbuf *sb)
 +{
 +      int ch;
 +      int hit = 0;
 +      int i, j, k;
 +      int len = sb->len;
 +      int first = 1;
 +      const char *buf = sb->buf;
 +
 +      for (i = len - 1; i > 0; i--) {
 +              if (hit && buf[i] == '\n')
 +                      break;
 +              hit = (buf[i] == '\n');
 +      }
 +
 +      while (i < len - 1 && buf[i] == '\n')
 +              i++;
 +
 +      for (; i < len; i = k) {
 +              for (k = i; k < len && buf[k] != '\n'; k++)
 +                      ; /* do nothing */
 +              k++;
 +
 +              if ((buf[k] == ' ' || buf[k] == '\t') && !first)
 +                      continue;
 +
 +              first = 0;
 +
 +              for (j = 0; i + j < len; j++) {
 +                      ch = buf[i + j];
 +                      if (ch == ':')
 +                              break;
 +                      if (isalnum(ch) ||
 +                          (ch == '-'))
 +                              continue;
 +                      return 0;
 +              }
 +      }
 +      return 1;
 +}
 +
 +static int prepare_to_commit(const char *index_file, const char *prefix,
 +                           struct wt_status *s)
  {
        struct stat statbuf;
        int commitable, saved_color_setting;
                if (isatty(0))
                        fprintf(stderr, "(reading log message from standard input)\n");
                if (strbuf_read(&sb, 0, 0) < 0)
 -                      die("could not read log from standard input");
 +                      die_errno("could not read log from standard input");
                hook_arg1 = "message";
        } else if (logfile) {
                if (strbuf_read_file(&sb, logfile, 0) < 0)
 -                      die("could not read log file '%s': %s",
 -                          logfile, strerror(errno));
 +                      die_errno("could not read log file '%s'",
 +                                logfile);
                hook_arg1 = "message";
        } else if (use_message) {
                buffer = strstr(use_message_buffer, "\n\n");
                hook_arg2 = use_message;
        } else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
                if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0)
 -                      die("could not read MERGE_MSG: %s", strerror(errno));
 +                      die_errno("could not read MERGE_MSG");
                hook_arg1 = "merge";
        } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) {
                if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0)
 -                      die("could not read SQUASH_MSG: %s", strerror(errno));
 +                      die_errno("could not read SQUASH_MSG");
                hook_arg1 = "squash";
        } else if (template_file && !stat(template_file, &statbuf)) {
                if (strbuf_read_file(&sb, template_file, 0) < 0)
 -                      die("could not read %s: %s",
 -                          template_file, strerror(errno));
 +                      die_errno("could not read '%s'", template_file);
                hook_arg1 = "template";
        }
  
  
        fp = fopen(git_path(commit_editmsg), "w");
        if (fp == NULL)
 -              die("could not open %s: %s",
 -                  git_path(commit_editmsg), strerror(errno));
 +              die_errno("could not open '%s'", git_path(commit_editmsg));
  
        if (cleanup_mode != CLEANUP_NONE)
                stripspace(&sb, 0);
                for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--)
                        ; /* do nothing */
                if (prefixcmp(sb.buf + i, sob.buf)) {
 -                      if (prefixcmp(sb.buf + i, sign_off_header))
 +                      if (!i || !ends_rfc2822_footer(&sb))
                                strbuf_addch(&sb, '\n');
                        strbuf_addbuf(&sb, &sob);
                }
        }
  
        if (fwrite(sb.buf, 1, sb.len, fp) < sb.len)
 -              die("could not write commit template: %s", strerror(errno));
 +              die_errno("could not write commit template");
  
        strbuf_release(&sb);
  
                if (ident_shown)
                        fprintf(fp, "#\n");
  
 -              saved_color_setting = wt_status_use_color;
 -              wt_status_use_color = 0;
 -              commitable = run_status(fp, index_file, prefix, 1);
 -              wt_status_use_color = saved_color_setting;
 +              saved_color_setting = s->use_color;
 +              s->use_color = 0;
 +              commitable = run_status(fp, index_file, prefix, 1, s);
 +              s->use_color = saved_color_setting;
        } else {
 -              struct rev_info rev;
                unsigned char sha1[20];
                const char *parent = "HEAD";
  
  
                if (get_sha1(parent, sha1))
                        commitable = !!active_nr;
 -              else {
 -                      init_revisions(&rev, "");
 -                      rev.abbrev = 0;
 -                      setup_revisions(0, NULL, &rev, parent);
 -                      DIFF_OPT_SET(&rev.diffopt, QUIET);
 -                      DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
 -                      run_diff_index(&rev, 1 /* cached */);
 -
 -                      commitable = !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES);
 -              }
 +              else
 +                      commitable = index_differs_from(parent, 0);
        }
  
        fclose(fp);
  
        if (!commitable && !in_merge && !allow_empty &&
            !(amend && is_a_merge(head_sha1))) {
 -              run_status(stdout, index_file, prefix, 0);
 +              run_status(stdout, index_file, prefix, 0, s);
                return 0;
        }
  
@@@ -726,10 -729,8 +728,10 @@@ static const char *find_author_by_nickn
        prepare_revision_walk(&revs);
        commit = get_revision(&revs);
        if (commit) {
 +              struct pretty_print_context ctx = {0};
 +              ctx.date_mode = DATE_NORMAL;
                strbuf_release(&buf);
 -              format_commit_message(commit, "%an <%ae>", &buf, DATE_NORMAL);
 +              format_commit_message(commit, "%an <%ae>", &buf, &ctx);
                return strbuf_detach(&buf, NULL);
        }
        die("No existing author found with '%s'", name);
  
  static int parse_and_validate_options(int argc, const char *argv[],
                                      const char * const usage[],
 -                                    const char *prefix)
 +                                    const char *prefix,
 +                                    struct wt_status *s)
  {
        int f = 0;
  
 -      argc = parse_options(argc, argv, builtin_commit_options, usage, 0);
 -      logfile = parse_options_fix_filename(prefix, logfile);
 -      template_file = parse_options_fix_filename(prefix, template_file);
 +      argc = parse_options(argc, argv, prefix, builtin_commit_options, usage,
 +                           0);
  
        if (force_author && !strchr(force_author, '>'))
                force_author = find_author_by_nickname(force_author);
  
 +      if (force_author && renew_authorship)
 +              die("Using both --reset-author and --author does not make sense");
 +
        if (logfile || message.len || use_message)
                use_editor = 0;
        if (edit_flag)
                use_message = edit_message;
        if (amend && !use_message)
                use_message = "HEAD";
 +      if (!use_message && renew_authorship)
 +              die("--reset-author can be used only with -C, -c or --amend.");
        if (use_message) {
                unsigned char sha1[20];
                static char utf8[] = "UTF-8";
        if (!untracked_files_arg)
                ; /* default already initialized */
        else if (!strcmp(untracked_files_arg, "no"))
 -              show_untracked_files = SHOW_NO_UNTRACKED_FILES;
 +              s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
        else if (!strcmp(untracked_files_arg, "normal"))
 -              show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
 +              s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
        else if (!strcmp(untracked_files_arg, "all"))
 -              show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
 +              s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
        else
                die("Invalid untracked files mode '%s'", untracked_files_arg);
  
        return argc;
  }
  
 -int cmd_status(int argc, const char **argv, const char *prefix)
 +static int dry_run_commit(int argc, const char **argv, const char *prefix,
 +                        struct wt_status *s)
  {
 -      const char *index_file;
        int commitable;
 +      const char *index_file;
  
 -      git_config(git_status_config, NULL);
 +      index_file = prepare_index(argc, argv, prefix, 1);
 +      commitable = run_status(stdout, index_file, prefix, 0, s);
 +      rollback_index_files();
  
 -      if (wt_status_use_color == -1)
 -              wt_status_use_color = git_use_color_default;
 +      return commitable ? 0 : 1;
 +}
  
 -      if (diff_use_color_default == -1)
 -              diff_use_color_default = git_use_color_default;
 +static int parse_status_slot(const char *var, int offset)
 +{
 +      if (!strcasecmp(var+offset, "header"))
 +              return WT_STATUS_HEADER;
 +      if (!strcasecmp(var+offset, "updated")
 +              || !strcasecmp(var+offset, "added"))
 +              return WT_STATUS_UPDATED;
 +      if (!strcasecmp(var+offset, "changed"))
 +              return WT_STATUS_CHANGED;
 +      if (!strcasecmp(var+offset, "untracked"))
 +              return WT_STATUS_UNTRACKED;
 +      if (!strcasecmp(var+offset, "nobranch"))
 +              return WT_STATUS_NOBRANCH;
 +      if (!strcasecmp(var+offset, "unmerged"))
 +              return WT_STATUS_UNMERGED;
 +      return -1;
 +}
  
 -      argc = parse_and_validate_options(argc, argv, builtin_status_usage, prefix);
 +static int git_status_config(const char *k, const char *v, void *cb)
 +{
 +      struct wt_status *s = cb;
  
 -      index_file = prepare_index(argc, argv, prefix);
 +      if (!strcmp(k, "status.submodulesummary")) {
 +              int is_bool;
 +              s->submodule_summary = git_config_bool_or_int(k, v, &is_bool);
 +              if (is_bool && s->submodule_summary)
 +                      s->submodule_summary = -1;
 +              return 0;
 +      }
 +      if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
 +              s->use_color = git_config_colorbool(k, v, -1);
 +              return 0;
 +      }
 +      if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
 +              int slot = parse_status_slot(k, 13);
 +              if (slot < 0)
 +                      return 0;
 +              if (!v)
 +                      return config_error_nonbool(k);
 +              color_parse(v, k, s->color_palette[slot]);
 +              return 0;
 +      }
 +      if (!strcmp(k, "status.relativepaths")) {
 +              s->relative_paths = git_config_bool(k, v);
 +              return 0;
 +      }
 +      if (!strcmp(k, "status.showuntrackedfiles")) {
 +              if (!v)
 +                      return config_error_nonbool(k);
 +              else if (!strcmp(v, "no"))
 +                      s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
 +              else if (!strcmp(v, "normal"))
 +                      s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
 +              else if (!strcmp(v, "all"))
 +                      s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
 +              else
 +                      return error("Invalid untracked files mode '%s'", v);
 +              return 0;
 +      }
 +      return git_diff_ui_config(k, v, NULL);
 +}
  
 -      commitable = run_status(stdout, index_file, prefix, 0);
 +int cmd_status(int argc, const char **argv, const char *prefix)
 +{
 +      struct wt_status s;
  
 -      rollback_index_files();
 +      wt_status_prepare(&s);
 +      git_config(git_status_config, &s);
 +      if (s.use_color == -1)
 +              s.use_color = git_use_color_default;
 +      if (diff_use_color_default == -1)
 +              diff_use_color_default = git_use_color_default;
  
 -      return commitable ? 0 : 1;
 +      argc = parse_and_validate_options(argc, argv, builtin_status_usage,
 +                                        prefix, &s);
 +      return dry_run_commit(argc, argv, prefix, &s);
  }
  
  static void print_summary(const char *prefix, const unsigned char *sha1)
  {
        struct rev_info rev;
        struct commit *commit;
 -      static const char *format = "format:%h: \"%s\"";
 +      static const char *format = "format:%h] %s";
        unsigned char junk_sha1[20];
        const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL);
  
        rev.diffopt.break_opt = 0;
        diff_setup_done(&rev.diffopt);
  
 -      printf("[%s%s]: created ",
 +      printf("[%s%s ",
                !prefixcmp(head, "refs/heads/") ?
                        head + 11 :
                        !strcmp(head, "HEAD") ?
                initial_commit ? " (root-commit)" : "");
  
        if (!log_tree_commit(&rev, commit)) {
 +              struct pretty_print_context ctx = {0};
                struct strbuf buf = STRBUF_INIT;
 -              format_commit_message(commit, format + 7, &buf, DATE_NORMAL);
 +              ctx.date_mode = DATE_NORMAL;
 +              format_commit_message(commit, format + 7, &buf, &ctx);
                printf("%s\n", buf.buf);
                strbuf_release(&buf);
        }
  
  static int git_commit_config(const char *k, const char *v, void *cb)
  {
 +      struct wt_status *s = cb;
 +
        if (!strcmp(k, "commit.template"))
 -              return git_config_string(&template_file, k, v);
 +              return git_config_pathname(&template_file, k, v);
  
 -      return git_status_config(k, v, cb);
 +      return git_status_config(k, v, s);
  }
  
  int cmd_commit(int argc, const char **argv, const char *prefix)
        struct commit_list *parents = NULL, **pptr = &parents;
        struct stat statbuf;
        int allow_fast_forward = 1;
 +      struct wt_status s;
  
 -      git_config(git_commit_config, NULL);
 -
 -      if (wt_status_use_color == -1)
 -              wt_status_use_color = git_use_color_default;
 +      wt_status_prepare(&s);
 +      git_config(git_commit_config, &s);
  
 -      argc = parse_and_validate_options(argc, argv, builtin_commit_usage, prefix);
 +      if (s.use_color == -1)
 +              s.use_color = git_use_color_default;
  
 -      index_file = prepare_index(argc, argv, prefix);
 +      argc = parse_and_validate_options(argc, argv, builtin_commit_usage,
 +                                        prefix, &s);
 +      if (dry_run) {
 +              if (diff_use_color_default == -1)
 +                      diff_use_color_default = git_use_color_default;
 +              return dry_run_commit(argc, argv, prefix, &s);
 +      }
 +      index_file = prepare_index(argc, argv, prefix, 0);
  
        /* Set up everything for writing the commit object.  This includes
           running hooks, writing the trees, and interacting with the user.  */
 -      if (!prepare_to_commit(index_file, prefix)) {
 +      if (!prepare_to_commit(index_file, prefix, &s)) {
                rollback_index_files();
                return 1;
        }
                pptr = &commit_list_insert(lookup_commit(head_sha1), pptr)->next;
                fp = fopen(git_path("MERGE_HEAD"), "r");
                if (fp == NULL)
 -                      die("could not open %s for reading: %s",
 -                          git_path("MERGE_HEAD"), strerror(errno));
 +                      die_errno("could not open '%s' for reading",
 +                                git_path("MERGE_HEAD"));
                while (strbuf_getline(&m, fp, '\n') != EOF) {
                        unsigned char sha1[20];
                        if (get_sha1_hex(m.buf, sha1) < 0)
                strbuf_release(&m);
                if (!stat(git_path("MERGE_MODE"), &statbuf)) {
                        if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0)
 -                              die("could not read MERGE_MODE: %s",
 -                                              strerror(errno));
 +                              die_errno("could not read MERGE_MODE");
                        if (!strcmp(sb.buf, "no-ff"))
                                allow_fast_forward = 0;
                }
        /* Finally, get the commit message */
        strbuf_reset(&sb);
        if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) {
 +              int saved_errno = errno;
                rollback_index_files();
 -              die("could not read commit message");
 +              die("could not read commit message: %s", strerror(saved_errno));
        }
  
        /* Truncate the message just before the diff, if any. */
diff --combined builtin-count-objects.c
index 1b0b6c84ea4279df56b82c5a406adbea59e58513,80b2614fd4dab25e4ecd5e43d9bdccc34fa1d48a..2bdd8ebde1002e852055385396ef053f26c91ae8
@@@ -5,13 -5,12 +5,13 @@@
   */
  
  #include "cache.h"
 +#include "dir.h"
  #include "builtin.h"
  #include "parse-options.h"
  
  static void count_objects(DIR *d, char *path, int len, int verbose,
                          unsigned long *loose,
-                         unsigned long *loose_size,
+                         off_t *loose_size,
                          unsigned long *packed_loose,
                          unsigned long *garbage)
  {
@@@ -22,7 -21,9 +22,7 @@@
                const char *cp;
                int bad = 0;
  
 -              if ((ent->d_name[0] == '.') &&
 -                  (ent->d_name[1] == 0 ||
 -                   ((ent->d_name[1] == '.') && (ent->d_name[2] == 0))))
 +              if (is_dot_or_dotdot(ent->d_name))
                        continue;
                for (cp = ent->d_name; *cp; cp++) {
                        int ch = *cp;
@@@ -77,13 -78,13 +77,13 @@@ int cmd_count_objects(int argc, const c
        int len = strlen(objdir);
        char *path = xmalloc(len + 50);
        unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0;
-       unsigned long loose_size = 0;
+       off_t loose_size = 0;
        struct option opts[] = {
                OPT__VERBOSE(&verbose),
                OPT_END(),
        };
  
 -      argc = parse_options(argc, argv, opts, count_objects_usage, 0);
 +      argc = parse_options(argc, argv, prefix, opts, count_objects_usage, 0);
        /* we do not take arguments other than flags for now */
        if (argc)
                usage_with_options(count_objects_usage, opts);
        if (verbose) {
                struct packed_git *p;
                unsigned long num_pack = 0;
-               unsigned long size_pack = 0;
+               off_t size_pack = 0;
                if (!packed_git)
                        prepare_packed_git();
                for (p = packed_git; p; p = p->next) {
                        num_pack++;
                }
                printf("count: %lu\n", loose);
-               printf("size: %lu\n", loose_size / 1024);
+               printf("size: %lu\n", (unsigned long) (loose_size / 1024));
                printf("in-pack: %lu\n", packed);
                printf("packs: %lu\n", num_pack);
-               printf("size-pack: %lu\n", size_pack / 1024);
+               printf("size-pack: %lu\n", (unsigned long) (size_pack / 1024));
                printf("prune-packable: %lu\n", packed_loose);
                printf("garbage: %lu\n", garbage);
        }
        else
                printf("%lu objects, %lu kilobytes\n",
-                      loose, loose_size / 1024);
+                      loose, (unsigned long) (loose_size / 1024));
        return 0;
  }
diff --combined diff.c
index 08bbd3e9070996b38f4d34cedf7640d93aa5808d,69147b802f73203ab27352ac1b3513138fa50417..6da52e0c49bc5646594ab47cba3971fd156e021d
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -12,8 -12,6 +12,8 @@@
  #include "run-command.h"
  #include "utf8.h"
  #include "userdiff.h"
 +#include "sigchain.h"
 +#include "submodule.h"
  
  #ifdef NO_FAST_WORKING_DIRECTORY
  #define FAST_WORKING_DIRECTORY 0
@@@ -25,21 -23,19 +25,21 @@@ static int diff_detect_rename_default
  static int diff_rename_limit_default = 200;
  static int diff_suppress_blank_empty;
  int diff_use_color_default = -1;
 +static const char *diff_word_regex_cfg;
  static const char *external_diff_cmd_cfg;
  int diff_auto_refresh_index = 1;
  static int diff_mnemonic_prefix;
  
  static char diff_colors[][COLOR_MAXLEN] = {
 -      "\033[m",       /* reset */
 -      "",             /* PLAIN (normal) */
 -      "\033[1m",      /* METAINFO (bold) */
 -      "\033[36m",     /* FRAGINFO (cyan) */
 -      "\033[31m",     /* OLD (red) */
 -      "\033[32m",     /* NEW (green) */
 -      "\033[33m",     /* COMMIT (yellow) */
 -      "\033[41m",     /* WHITESPACE (red background) */
 +      GIT_COLOR_RESET,
 +      GIT_COLOR_NORMAL,       /* PLAIN */
 +      GIT_COLOR_BOLD,         /* METAINFO */
 +      GIT_COLOR_CYAN,         /* FRAGINFO */
 +      GIT_COLOR_RED,          /* OLD */
 +      GIT_COLOR_GREEN,        /* NEW */
 +      GIT_COLOR_YELLOW,       /* COMMIT */
 +      GIT_COLOR_BG_RED,       /* WHITESPACE */
 +      GIT_COLOR_NORMAL,       /* FUNCINFO */
  };
  
  static void diff_filespec_load_driver(struct diff_filespec *one);
@@@ -61,18 -57,7 +61,18 @@@ static int parse_diff_color_slot(const 
                return DIFF_COMMIT;
        if (!strcasecmp(var+ofs, "whitespace"))
                return DIFF_WHITESPACE;
 -      die("bad config variable '%s'", var);
 +      if (!strcasecmp(var+ofs, "func"))
 +              return DIFF_FUNCINFO;
 +      return -1;
 +}
 +
 +static int git_config_rename(const char *var, const char *value)
 +{
 +      if (!value)
 +              return DIFF_DETECT_RENAME;
 +      if (!strcasecmp(value, "copies") || !strcasecmp(value, "copy"))
 +              return  DIFF_DETECT_COPY;
 +      return git_config_bool(var,value) ? DIFF_DETECT_RENAME : 0;
  }
  
  /*
@@@ -88,7 -73,13 +88,7 @@@ int git_diff_ui_config(const char *var
                return 0;
        }
        if (!strcmp(var, "diff.renames")) {
 -              if (!value)
 -                      diff_detect_rename_default = DIFF_DETECT_RENAME;
 -              else if (!strcasecmp(value, "copies") ||
 -                       !strcasecmp(value, "copy"))
 -                      diff_detect_rename_default = DIFF_DETECT_COPY;
 -              else if (git_config_bool(var,value))
 -                      diff_detect_rename_default = DIFF_DETECT_RENAME;
 +              diff_detect_rename_default = git_config_rename(var, value);
                return 0;
        }
        if (!strcmp(var, "diff.autorefreshindex")) {
        }
        if (!strcmp(var, "diff.external"))
                return git_config_string(&external_diff_cmd_cfg, var, value);
 +      if (!strcmp(var, "diff.wordregex"))
 +              return git_config_string(&diff_word_regex_cfg, var, value);
  
        return git_diff_basic_config(var, value, cb);
  }
@@@ -122,8 -111,6 +122,8 @@@ int git_diff_basic_config(const char *v
  
        if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
                int slot = parse_diff_color_slot(var, 11);
 +              if (slot < 0)
 +                      return 0;
                if (!value)
                        return config_error_nonbool(var);
                color_parse(value, var, diff_colors[slot]);
@@@ -180,22 -167,6 +180,22 @@@ static struct diff_tempfile 
        char tmp_path[PATH_MAX];
  } diff_temp[2];
  
 +typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
 +
 +struct emit_callback {
 +      int color_diff;
 +      unsigned ws_rule;
 +      int blank_at_eof_in_preimage;
 +      int blank_at_eof_in_postimage;
 +      int lno_in_preimage;
 +      int lno_in_postimage;
 +      sane_truncate_fn truncate;
 +      const char **label_path;
 +      struct diff_words_data *diff_words;
 +      int *found_changesp;
 +      FILE *file;
 +};
 +
  static int count_lines(const char *data, int size)
  {
        int count, ch, completely_empty = 1, nl_just_seen = 0;
        return count;
  }
  
 +static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
 +{
 +      if (!DIFF_FILE_VALID(one)) {
 +              mf->ptr = (char *)""; /* does not matter */
 +              mf->size = 0;
 +              return 0;
 +      }
 +      else if (diff_populate_filespec(one, 0))
 +              return -1;
 +
 +      mf->ptr = one->data;
 +      mf->size = one->size;
 +      return 0;
 +}
 +
 +static int count_trailing_blank(mmfile_t *mf, unsigned ws_rule)
 +{
 +      char *ptr = mf->ptr;
 +      long size = mf->size;
 +      int cnt = 0;
 +
 +      if (!size)
 +              return cnt;
 +      ptr += size - 1; /* pointing at the very end */
 +      if (*ptr != '\n')
 +              ; /* incomplete line */
 +      else
 +              ptr--; /* skip the last LF */
 +      while (mf->ptr < ptr) {
 +              char *prev_eol;
 +              for (prev_eol = ptr; mf->ptr <= prev_eol; prev_eol--)
 +                      if (*prev_eol == '\n')
 +                              break;
 +              if (!ws_blank_line(prev_eol + 1, ptr - prev_eol, ws_rule))
 +                      break;
 +              cnt++;
 +              ptr = prev_eol - 1;
 +      }
 +      return cnt;
 +}
 +
 +static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,
 +                             struct emit_callback *ecbdata)
 +{
 +      int l1, l2, at;
 +      unsigned ws_rule = ecbdata->ws_rule;
 +      l1 = count_trailing_blank(mf1, ws_rule);
 +      l2 = count_trailing_blank(mf2, ws_rule);
 +      if (l2 <= l1) {
 +              ecbdata->blank_at_eof_in_preimage = 0;
 +              ecbdata->blank_at_eof_in_postimage = 0;
 +              return;
 +      }
 +      at = count_lines(mf1->ptr, mf1->size);
 +      ecbdata->blank_at_eof_in_preimage = (at - l1) + 1;
 +
 +      at = count_lines(mf2->ptr, mf2->size);
 +      ecbdata->blank_at_eof_in_postimage = (at - l2) + 1;
 +}
 +
 +static void emit_line_0(FILE *file, const char *set, const char *reset,
 +                      int first, const char *line, int len)
 +{
 +      int has_trailing_newline, has_trailing_carriage_return;
 +      int nofirst;
 +
 +      if (len == 0) {
 +              has_trailing_newline = (first == '\n');
 +              has_trailing_carriage_return = (!has_trailing_newline &&
 +                                              (first == '\r'));
 +              nofirst = has_trailing_newline || has_trailing_carriage_return;
 +      } else {
 +              has_trailing_newline = (len > 0 && line[len-1] == '\n');
 +              if (has_trailing_newline)
 +                      len--;
 +              has_trailing_carriage_return = (len > 0 && line[len-1] == '\r');
 +              if (has_trailing_carriage_return)
 +                      len--;
 +              nofirst = 0;
 +      }
 +
 +      if (len || !nofirst) {
 +              fputs(set, file);
 +              if (!nofirst)
 +                      fputc(first, file);
 +              fwrite(line, len, 1, file);
 +              fputs(reset, file);
 +      }
 +      if (has_trailing_carriage_return)
 +              fputc('\r', file);
 +      if (has_trailing_newline)
 +              fputc('\n', file);
 +}
 +
 +static void emit_line(FILE *file, const char *set, const char *reset,
 +                    const char *line, int len)
 +{
 +      emit_line_0(file, set, reset, line[0], line+1, len-1);
 +}
 +
 +static int new_blank_line_at_eof(struct emit_callback *ecbdata, const char *line, int len)
 +{
 +      if (!((ecbdata->ws_rule & WS_BLANK_AT_EOF) &&
 +            ecbdata->blank_at_eof_in_preimage &&
 +            ecbdata->blank_at_eof_in_postimage &&
 +            ecbdata->blank_at_eof_in_preimage <= ecbdata->lno_in_preimage &&
 +            ecbdata->blank_at_eof_in_postimage <= ecbdata->lno_in_postimage))
 +              return 0;
 +      return ws_blank_line(line, len, ecbdata->ws_rule);
 +}
 +
 +static void emit_add_line(const char *reset,
 +                        struct emit_callback *ecbdata,
 +                        const char *line, int len)
 +{
 +      const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
 +      const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
 +
 +      if (!*ws)
 +              emit_line_0(ecbdata->file, set, reset, '+', line, len);
 +      else if (new_blank_line_at_eof(ecbdata, line, len))
 +              /* Blank line at EOF - paint '+' as well */
 +              emit_line_0(ecbdata->file, ws, reset, '+', line, len);
 +      else {
 +              /* Emit just the prefix, then the rest. */
 +              emit_line_0(ecbdata->file, set, reset, '+', "", 0);
 +              ws_check_emit(line, len, ecbdata->ws_rule,
 +                            ecbdata->file, set, reset, ws);
 +      }
 +}
 +
 +static void emit_hunk_header(struct emit_callback *ecbdata,
 +                           const char *line, int len)
 +{
 +      const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
 +      const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
 +      const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);
 +      const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
 +      static const char atat[2] = { '@', '@' };
 +      const char *cp, *ep;
 +
 +      /*
 +       * As a hunk header must begin with "@@ -<old>, +<new> @@",
 +       * it always is at least 10 bytes long.
 +       */
 +      if (len < 10 ||
 +          memcmp(line, atat, 2) ||
 +          !(ep = memmem(line + 2, len - 2, atat, 2))) {
 +              emit_line(ecbdata->file, plain, reset, line, len);
 +              return;
 +      }
 +      ep += 2; /* skip over @@ */
 +
 +      /* The hunk header in fraginfo color */
 +      emit_line(ecbdata->file, frag, reset, line, ep - line);
 +
 +      /* blank before the func header */
 +      for (cp = ep; ep - line < len; ep++)
 +              if (*ep != ' ' && *ep != '\t')
 +                      break;
 +      if (ep != cp)
 +              emit_line(ecbdata->file, plain, reset, cp, ep - cp);
 +
 +      if (ep < line + len)
 +              emit_line(ecbdata->file, func, reset, ep, line + len - ep);
 +}
 +
 +static struct diff_tempfile *claim_diff_tempfile(void) {
 +      int i;
 +      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");
 +}
 +
 +static int remove_tempfile_installed;
 +
 +static void remove_tempfile(void)
 +{
 +      int i;
 +      for (i = 0; i < ARRAY_SIZE(diff_temp); i++) {
 +              if (diff_temp[i].name == diff_temp[i].tmp_path)
 +                      unlink_or_warn(diff_temp[i].name);
 +              diff_temp[i].name = NULL;
 +      }
 +}
 +
 +static void remove_tempfile_on_signal(int signo)
 +{
 +      remove_tempfile();
 +      sigchain_pop(signo);
 +      raise(signo);
 +}
 +
  static void print_line_count(FILE *file, int count)
  {
        switch (count) {
        }
  }
  
 -static void copy_file_with_prefix(FILE *file,
 -                                int prefix, const char *data, int size,
 -                                const char *set, const char *reset)
 +static void emit_rewrite_lines(struct emit_callback *ecb,
 +                             int prefix, const char *data, int size)
  {
 -      int ch, nl_just_seen = 1;
 -      while (0 < size--) {
 -              ch = *data++;
 -              if (nl_just_seen) {
 -                      fputs(set, file);
 -                      putc(prefix, file);
 +      const char *endp = NULL;
 +      static const char *nneof = " No newline at end of file\n";
 +      const char *old = diff_get_color(ecb->color_diff, DIFF_FILE_OLD);
 +      const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET);
 +
 +      while (0 < size) {
 +              int len;
 +
 +              endp = memchr(data, '\n', size);
 +              len = endp ? (endp - data + 1) : size;
 +              if (prefix != '+') {
 +                      ecb->lno_in_preimage++;
 +                      emit_line_0(ecb->file, old, reset, '-',
 +                                  data, len);
 +              } else {
 +                      ecb->lno_in_postimage++;
 +                      emit_add_line(reset, ecb, data, len);
                }
 -              if (ch == '\n') {
 -                      nl_just_seen = 1;
 -                      fputs(reset, file);
 -              } else
 -                      nl_just_seen = 0;
 -              putc(ch, file);
 +              size -= len;
 +              data += len;
 +      }
 +      if (!endp) {
 +              const char *plain = diff_get_color(ecb->color_diff,
 +                                                 DIFF_PLAIN);
 +              emit_line_0(ecb->file, plain, reset, '\\',
 +                          nneof, strlen(nneof));
        }
 -      if (!nl_just_seen)
 -              fprintf(file, "%s\n\\ No newline at end of file\n", reset);
  }
  
  static void emit_rewrite_diff(const char *name_a,
        const char *name_a_tab, *name_b_tab;
        const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO);
        const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO);
 -      const char *old = diff_get_color(color_diff, DIFF_FILE_OLD);
 -      const char *new = diff_get_color(color_diff, DIFF_FILE_NEW);
        const char *reset = diff_get_color(color_diff, DIFF_RESET);
        static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT;
        const char *a_prefix, *b_prefix;
        const char *data_one, *data_two;
        size_t size_one, size_two;
 +      struct emit_callback ecbdata;
  
        if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) {
                a_prefix = o->b_prefix;
                size_two = two->size;
        }
  
 +      memset(&ecbdata, 0, sizeof(ecbdata));
 +      ecbdata.color_diff = color_diff;
 +      ecbdata.found_changesp = &o->found_changes;
 +      ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
 +      ecbdata.file = o->file;
 +      if (ecbdata.ws_rule & WS_BLANK_AT_EOF) {
 +              mmfile_t mf1, mf2;
 +              mf1.ptr = (char *)data_one;
 +              mf2.ptr = (char *)data_two;
 +              mf1.size = size_one;
 +              mf2.size = size_two;
 +              check_blank_at_eof(&mf1, &mf2, &ecbdata);
 +      }
 +      ecbdata.lno_in_preimage = 1;
 +      ecbdata.lno_in_postimage = 1;
 +
        lc_a = count_lines(data_one, size_one);
        lc_b = count_lines(data_two, size_two);
        fprintf(o->file,
        print_line_count(o->file, lc_b);
        fprintf(o->file, " @@%s\n", reset);
        if (lc_a)
 -              copy_file_with_prefix(o->file, '-', data_one, size_one, old, reset);
 +              emit_rewrite_lines(&ecbdata, '-', data_one, size_one);
        if (lc_b)
 -              copy_file_with_prefix(o->file, '+', data_two, size_two, new, reset);
 -}
 -
 -static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
 -{
 -      if (!DIFF_FILE_VALID(one)) {
 -              mf->ptr = (char *)""; /* does not matter */
 -              mf->size = 0;
 -              return 0;
 -      }
 -      else if (diff_populate_filespec(one, 0))
 -              return -1;
 -
 -      mf->ptr = one->data;
 -      mf->size = one->size;
 -      return 0;
 +              emit_rewrite_lines(&ecbdata, '+', data_two, size_two);
  }
  
  struct diff_words_buffer {
        mmfile_t text;
        long alloc;
 -      long current; /* output pointer */
 -      int suppressed_newline;
 +      struct diff_words_orig {
 +              const char *begin, *end;
 +      } *orig;
 +      int orig_nr, orig_alloc;
  };
  
  static void diff_words_append(char *line, unsigned long len,
                struct diff_words_buffer *buffer)
  {
 -      if (buffer->text.size + len > buffer->alloc) {
 -              buffer->alloc = (buffer->text.size + len) * 3 / 2;
 -              buffer->text.ptr = xrealloc(buffer->text.ptr, buffer->alloc);
 -      }
 +      ALLOC_GROW(buffer->text.ptr, buffer->text.size + len, buffer->alloc);
        line++;
        len--;
        memcpy(buffer->text.ptr + buffer->text.size, line, len);
        buffer->text.size += len;
 +      buffer->text.ptr[buffer->text.size] = '\0';
  }
  
  struct diff_words_data {
        struct diff_words_buffer minus, plus;
 +      const char *current_plus;
        FILE *file;
 +      regex_t *word_regex;
  };
  
 -static void print_word(FILE *file, struct diff_words_buffer *buffer, int len, int color,
 -              int suppress_newline)
 +static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)
  {
 -      const char *ptr;
 -      int eol = 0;
 +      struct diff_words_data *diff_words = priv;
 +      int minus_first, minus_len, plus_first, plus_len;
 +      const char *minus_begin, *minus_end, *plus_begin, *plus_end;
  
 -      if (len == 0)
 +      if (line[0] != '@' || parse_hunk_header(line, len,
 +                      &minus_first, &minus_len, &plus_first, &plus_len))
                return;
  
 -      ptr  = buffer->text.ptr + buffer->current;
 -      buffer->current += len;
 +      /* POSIX requires that first be decremented by one if len == 0... */
 +      if (minus_len) {
 +              minus_begin = diff_words->minus.orig[minus_first].begin;
 +              minus_end =
 +                      diff_words->minus.orig[minus_first + minus_len - 1].end;
 +      } else
 +              minus_begin = minus_end =
 +                      diff_words->minus.orig[minus_first].end;
  
 -      if (ptr[len - 1] == '\n') {
 -              eol = 1;
 -              len--;
 +      if (plus_len) {
 +              plus_begin = diff_words->plus.orig[plus_first].begin;
 +              plus_end = diff_words->plus.orig[plus_first + plus_len - 1].end;
 +      } else
 +              plus_begin = plus_end = diff_words->plus.orig[plus_first].end;
 +
 +      if (diff_words->current_plus != plus_begin)
 +              fwrite(diff_words->current_plus,
 +                              plus_begin - diff_words->current_plus, 1,
 +                              diff_words->file);
 +      if (minus_begin != minus_end)
 +              color_fwrite_lines(diff_words->file,
 +                              diff_get_color(1, DIFF_FILE_OLD),
 +                              minus_end - minus_begin, minus_begin);
 +      if (plus_begin != plus_end)
 +              color_fwrite_lines(diff_words->file,
 +                              diff_get_color(1, DIFF_FILE_NEW),
 +                              plus_end - plus_begin, plus_begin);
 +
 +      diff_words->current_plus = plus_end;
 +}
 +
 +/* This function starts looking at *begin, and returns 0 iff a word was found. */
 +static int find_word_boundaries(mmfile_t *buffer, regex_t *word_regex,
 +              int *begin, int *end)
 +{
 +      if (word_regex && *begin < buffer->size) {
 +              regmatch_t match[1];
 +              if (!regexec(word_regex, buffer->ptr + *begin, 1, match, 0)) {
 +                      char *p = memchr(buffer->ptr + *begin + match[0].rm_so,
 +                                      '\n', match[0].rm_eo - match[0].rm_so);
 +                      *end = p ? p - buffer->ptr : match[0].rm_eo + *begin;
 +                      *begin += match[0].rm_so;
 +                      return *begin >= *end;
 +              }
 +              return -1;
        }
  
 -      fputs(diff_get_color(1, color), file);
 -      fwrite(ptr, len, 1, file);
 -      fputs(diff_get_color(1, DIFF_RESET), file);
 +      /* find the next word */
 +      while (*begin < buffer->size && isspace(buffer->ptr[*begin]))
 +              (*begin)++;
 +      if (*begin >= buffer->size)
 +              return -1;
  
 -      if (eol) {
 -              if (suppress_newline)
 -                      buffer->suppressed_newline = 1;
 -              else
 -                      putc('\n', file);
 -      }
 +      /* find the end of the word */
 +      *end = *begin + 1;
 +      while (*end < buffer->size && !isspace(buffer->ptr[*end]))
 +              (*end)++;
 +
 +      return 0;
  }
  
 -static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)
 +/*
 + * This function splits the words in buffer->text, stores the list with
 + * newline separator into out, and saves the offsets of the original words
 + * in buffer->orig.
 + */
 +static void diff_words_fill(struct diff_words_buffer *buffer, mmfile_t *out,
 +              regex_t *word_regex)
  {
 -      struct diff_words_data *diff_words = priv;
 +      int i, j;
 +      long alloc = 0;
  
 -      if (diff_words->minus.suppressed_newline) {
 -              if (line[0] != '+')
 -                      putc('\n', diff_words->file);
 -              diff_words->minus.suppressed_newline = 0;
 -      }
 +      out->size = 0;
 +      out->ptr = NULL;
  
 -      len--;
 -      switch (line[0]) {
 -              case '-':
 -                      print_word(diff_words->file,
 -                                 &diff_words->minus, len, DIFF_FILE_OLD, 1);
 -                      break;
 -              case '+':
 -                      print_word(diff_words->file,
 -                                 &diff_words->plus, len, DIFF_FILE_NEW, 0);
 -                      break;
 -              case ' ':
 -                      print_word(diff_words->file,
 -                                 &diff_words->plus, len, DIFF_PLAIN, 0);
 -                      diff_words->minus.current += len;
 -                      break;
 +      /* fake an empty "0th" word */
 +      ALLOC_GROW(buffer->orig, 1, buffer->orig_alloc);
 +      buffer->orig[0].begin = buffer->orig[0].end = buffer->text.ptr;
 +      buffer->orig_nr = 1;
 +
 +      for (i = 0; i < buffer->text.size; i++) {
 +              if (find_word_boundaries(&buffer->text, word_regex, &i, &j))
 +                      return;
 +
 +              /* store original boundaries */
 +              ALLOC_GROW(buffer->orig, buffer->orig_nr + 1,
 +                              buffer->orig_alloc);
 +              buffer->orig[buffer->orig_nr].begin = buffer->text.ptr + i;
 +              buffer->orig[buffer->orig_nr].end = buffer->text.ptr + j;
 +              buffer->orig_nr++;
 +
 +              /* store one word */
 +              ALLOC_GROW(out->ptr, out->size + j - i + 1, alloc);
 +              memcpy(out->ptr + out->size, buffer->text.ptr + i, j - i);
 +              out->ptr[out->size + j - i] = '\n';
 +              out->size += j - i + 1;
 +
 +              i = j - 1;
        }
  }
  
@@@ -696,55 -407,62 +696,55 @@@ static void diff_words_show(struct diff
        xdemitconf_t xecfg;
        xdemitcb_t ecb;
        mmfile_t minus, plus;
 -      int i;
 +
 +      /* special case: only removal */
 +      if (!diff_words->plus.text.size) {
 +              color_fwrite_lines(diff_words->file,
 +                      diff_get_color(1, DIFF_FILE_OLD),
 +                      diff_words->minus.text.size, diff_words->minus.text.ptr);
 +              diff_words->minus.text.size = 0;
 +              return;
 +      }
 +
 +      diff_words->current_plus = diff_words->plus.text.ptr;
  
        memset(&xpp, 0, sizeof(xpp));
        memset(&xecfg, 0, sizeof(xecfg));
 -      minus.size = diff_words->minus.text.size;
 -      minus.ptr = xmalloc(minus.size);
 -      memcpy(minus.ptr, diff_words->minus.text.ptr, minus.size);
 -      for (i = 0; i < minus.size; i++)
 -              if (isspace(minus.ptr[i]))
 -                      minus.ptr[i] = '\n';
 -      diff_words->minus.current = 0;
 -
 -      plus.size = diff_words->plus.text.size;
 -      plus.ptr = xmalloc(plus.size);
 -      memcpy(plus.ptr, diff_words->plus.text.ptr, plus.size);
 -      for (i = 0; i < plus.size; i++)
 -              if (isspace(plus.ptr[i]))
 -                      plus.ptr[i] = '\n';
 -      diff_words->plus.current = 0;
 -
 +      diff_words_fill(&diff_words->minus, &minus, diff_words->word_regex);
 +      diff_words_fill(&diff_words->plus, &plus, diff_words->word_regex);
        xpp.flags = XDF_NEED_MINIMAL;
 -      xecfg.ctxlen = diff_words->minus.alloc + diff_words->plus.alloc;
 +      /* as only the hunk header will be parsed, we need a 0-context */
 +      xecfg.ctxlen = 0;
        xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words,
                      &xpp, &xecfg, &ecb);
        free(minus.ptr);
        free(plus.ptr);
 +      if (diff_words->current_plus != diff_words->plus.text.ptr +
 +                      diff_words->plus.text.size)
 +              fwrite(diff_words->current_plus,
 +                      diff_words->plus.text.ptr + diff_words->plus.text.size
 +                      - diff_words->current_plus, 1,
 +                      diff_words->file);
        diff_words->minus.text.size = diff_words->plus.text.size = 0;
 -
 -      if (diff_words->minus.suppressed_newline) {
 -              putc('\n', diff_words->file);
 -              diff_words->minus.suppressed_newline = 0;
 -      }
  }
  
 -typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
 -
 -struct emit_callback {
 -      int nparents, color_diff;
 -      unsigned ws_rule;
 -      sane_truncate_fn truncate;
 -      const char **label_path;
 -      struct diff_words_data *diff_words;
 -      int *found_changesp;
 -      FILE *file;
 -};
 +/* In "color-words" mode, show word-diff of words accumulated in the buffer */
 +static void diff_words_flush(struct emit_callback *ecbdata)
 +{
 +      if (ecbdata->diff_words->minus.text.size ||
 +          ecbdata->diff_words->plus.text.size)
 +              diff_words_show(ecbdata->diff_words);
 +}
  
  static void free_diff_words_data(struct emit_callback *ecbdata)
  {
        if (ecbdata->diff_words) {
 -              /* flush buffers */
 -              if (ecbdata->diff_words->minus.text.size ||
 -                              ecbdata->diff_words->plus.text.size)
 -                      diff_words_show(ecbdata->diff_words);
 -
 +              diff_words_flush(ecbdata);
                free (ecbdata->diff_words->minus.text.ptr);
 +              free (ecbdata->diff_words->minus.orig);
                free (ecbdata->diff_words->plus.text.ptr);
 +              free (ecbdata->diff_words->plus.orig);
 +              free(ecbdata->diff_words->word_regex);
                free(ecbdata->diff_words);
                ecbdata->diff_words = NULL;
        }
@@@ -757,6 -475,42 +757,6 @@@ const char *diff_get_color(int diff_use
        return "";
  }
  
 -static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len)
 -{
 -      int has_trailing_newline, has_trailing_carriage_return;
 -
 -      has_trailing_newline = (len > 0 && line[len-1] == '\n');
 -      if (has_trailing_newline)
 -              len--;
 -      has_trailing_carriage_return = (len > 0 && line[len-1] == '\r');
 -      if (has_trailing_carriage_return)
 -              len--;
 -
 -      fputs(set, file);
 -      fwrite(line, len, 1, file);
 -      fputs(reset, file);
 -      if (has_trailing_carriage_return)
 -              fputc('\r', file);
 -      if (has_trailing_newline)
 -              fputc('\n', file);
 -}
 -
 -static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len)
 -{
 -      const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
 -      const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
 -
 -      if (!*ws)
 -              emit_line(ecbdata->file, set, reset, line, len);
 -      else {
 -              /* Emit just the prefix, then the rest. */
 -              emit_line(ecbdata->file, set, reset, line, ecbdata->nparents);
 -              ws_check_emit(line + ecbdata->nparents,
 -                            len - ecbdata->nparents, ecbdata->ws_rule,
 -                            ecbdata->file, set, reset, ws);
 -      }
 -}
 -
  static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len)
  {
        const char *cp;
        return allot - l;
  }
  
 +static void find_lno(const char *line, struct emit_callback *ecbdata)
 +{
 +      const char *p;
 +      ecbdata->lno_in_preimage = 0;
 +      ecbdata->lno_in_postimage = 0;
 +      p = strchr(line, '-');
 +      if (!p)
 +              return; /* cannot happen */
 +      ecbdata->lno_in_preimage = strtol(p + 1, NULL, 10);
 +      p = strchr(p, '+');
 +      if (!p)
 +              return; /* cannot happen */
 +      ecbdata->lno_in_postimage = strtol(p + 1, NULL, 10);
 +}
 +
  static void fn_out_consume(void *priv, char *line, unsigned long len)
  {
 -      int i;
 -      int color;
        struct emit_callback *ecbdata = priv;
        const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
        const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
                len = 1;
        }
  
 -      /* This is not really necessary for now because
 -       * this codepath only deals with two-way diffs.
 -       */
 -      for (i = 0; i < len && line[i] == '@'; i++)
 -              ;
 -      if (2 <= i && i < len && line[i] == ' ') {
 -              ecbdata->nparents = i - 1;
 +      if (line[0] == '@') {
 +              if (ecbdata->diff_words)
 +                      diff_words_flush(ecbdata);
                len = sane_truncate_line(ecbdata, line, len);
 -              emit_line(ecbdata->file,
 -                        diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO),
 -                        reset, line, len);
 +              find_lno(line, ecbdata);
 +              emit_hunk_header(ecbdata, line, len);
                if (line[len-1] != '\n')
                        putc('\n', ecbdata->file);
                return;
        }
  
 -      if (len < ecbdata->nparents) {
 +      if (len < 1) {
                emit_line(ecbdata->file, reset, reset, line, len);
                return;
        }
  
 -      color = DIFF_PLAIN;
 -      if (ecbdata->diff_words && ecbdata->nparents != 1)
 -              /* fall back to normal diff */
 -              free_diff_words_data(ecbdata);
        if (ecbdata->diff_words) {
                if (line[0] == '-') {
                        diff_words_append(line, len,
                                          &ecbdata->diff_words->plus);
                        return;
                }
 -              if (ecbdata->diff_words->minus.text.size ||
 -                  ecbdata->diff_words->plus.text.size)
 -                      diff_words_show(ecbdata->diff_words);
 +              diff_words_flush(ecbdata);
                line++;
                len--;
                emit_line(ecbdata->file, plain, reset, line, len);
                return;
        }
 -      for (i = 0; i < ecbdata->nparents && len; i++) {
 -              if (line[i] == '-')
 -                      color = DIFF_FILE_OLD;
 -              else if (line[i] == '+')
 -                      color = DIFF_FILE_NEW;
 -      }
  
 -      if (color != DIFF_FILE_NEW) {
 -              emit_line(ecbdata->file,
 -                        diff_get_color(ecbdata->color_diff, color),
 -                        reset, line, len);
 -              return;
 +      if (line[0] != '+') {
 +              const char *color =
 +                      diff_get_color(ecbdata->color_diff,
 +                                     line[0] == '-' ? DIFF_FILE_OLD : DIFF_PLAIN);
 +              ecbdata->lno_in_preimage++;
 +              if (line[0] == ' ')
 +                      ecbdata->lno_in_postimage++;
 +              emit_line(ecbdata->file, color, reset, line, len);
 +      } else {
 +              ecbdata->lno_in_postimage++;
 +              emit_add_line(reset, ecbdata, line + 1, len - 1);
        }
 -      emit_add_line(reset, ecbdata, line, len);
  }
  
  static char *pprint_rename(const char *a, const char *b)
@@@ -995,9 -748,10 +995,9 @@@ static int scale_linear(int it, int wid
  }
  
  static void show_name(FILE *file,
 -                    const char *prefix, const char *name, int len,
 -                    const char *reset, const char *set)
 +                    const char *prefix, const char *name, int len)
  {
 -      fprintf(file, " %s%s%-*s%s |", set, prefix, len, name, reset);
 +      fprintf(file, " %s%-*s |", prefix, len, name);
  }
  
  static void show_graph(FILE *file, char ch, int cnt, const char *set, const char *reset)
@@@ -1031,9 -785,9 +1031,9 @@@ static void fill_print_name(struct diff
        file->print_name = pname;
  }
  
 -static void show_stats(struct diffstat_tdata, struct diff_options *options)
 +static void show_stats(struct diffstat_t *data, struct diff_options *options)
  {
 -      int i, len, add, del, total, adds = 0, dels = 0;
 +      int i, len, add, del, adds = 0, dels = 0;
        int max_change = 0, max_len = 0;
        int total_files = data->nr;
        int width, name_width;
                }
  
                if (data->files[i]->is_binary) {
 -                      show_name(options->file, prefix, name, len, reset, set);
 +                      show_name(options->file, prefix, name, len);
                        fprintf(options->file, "  Bin ");
                        fprintf(options->file, "%s%d%s", del_c, deleted, reset);
                        fprintf(options->file, " -> ");
                        continue;
                }
                else if (data->files[i]->is_unmerged) {
 -                      show_name(options->file, prefix, name, len, reset, set);
 +                      show_name(options->file, prefix, name, len);
                        fprintf(options->file, "  Unmerged\n");
                        continue;
                }
                 */
                add = added;
                del = deleted;
 -              total = add + del;
                adds += add;
                dels += del;
  
                if (width <= max_change) {
                        add = scale_linear(add, width, max_change);
                        del = scale_linear(del, width, max_change);
 -                      total = add + del;
                }
 -              show_name(options->file, prefix, name, len, reset, set);
 +              show_name(options->file, prefix, name, len);
                fprintf(options->file, "%5d%s", added + deleted,
                                added + deleted ? " " : "");
                show_graph(options->file, '+', add, add_c, reset);
                fprintf(options->file, "\n");
        }
        fprintf(options->file,
 -             "%s %d files changed, %d insertions(+), %d deletions(-)%s\n",
 -             set, total_files, adds, dels, reset);
 +             " %d files changed, %d insertions(+), %d deletions(-)\n",
 +             total_files, adds, dels);
  }
  
 -static void show_shortstats(struct diffstat_tdata, struct diff_options *options)
 +static void show_shortstats(struct diffstat_t *data, struct diff_options *options)
  {
        int i, adds = 0, dels = 0, total_files = data->nr;
  
               total_files, adds, dels);
  }
  
 -static void show_numstat(struct diffstat_tdata, struct diff_options *options)
 +static void show_numstat(struct diffstat_t *data, struct diff_options *options)
  {
        int i;
  
@@@ -1367,6 -1123,7 +1367,6 @@@ struct checkdiff_t 
        struct diff_options *o;
        unsigned ws_rule;
        unsigned status;
 -      int trailing_blanks_start;
  };
  
  static int is_conflict_marker(const char *line, unsigned long len)
@@@ -1410,6 -1167,10 +1410,6 @@@ static void checkdiff_consume(void *pri
        if (line[0] == '+') {
                unsigned bad;
                data->lineno++;
 -              if (!ws_blank_line(line + 1, len - 1, data->ws_rule))
 -                      data->trailing_blanks_start = 0;
 -              else if (!data->trailing_blanks_start)
 -                      data->trailing_blanks_start = data->lineno;
                if (is_conflict_marker(line + 1, len - 1)) {
                        data->status |= 1;
                        fprintf(data->o->file,
                              data->o->file, set, reset, ws);
        } else if (line[0] == ' ') {
                data->lineno++;
 -              data->trailing_blanks_start = 0;
        } else if (line[0] == '@') {
                char *plus = strchr(line, '+');
                if (plus)
                        data->lineno = strtol(plus, NULL, 10) - 1;
                else
                        die("invalid diff");
 -              data->trailing_blanks_start = 0;
        }
  }
  
@@@ -1562,12 -1325,6 +1562,12 @@@ static const struct userdiff_funcname *
        return one->driver->funcname.pattern ? &one->driver->funcname : NULL;
  }
  
 +static const char *userdiff_word_regex(struct diff_filespec *one)
 +{
 +      diff_filespec_load_driver(one);
 +      return one->driver->word_regex;
 +}
 +
  void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b)
  {
        if (!options->a_prefix)
@@@ -1602,17 -1359,6 +1602,17 @@@ static void builtin_diff(const char *na
        const char *a_prefix, *b_prefix;
        const char *textconv_one = NULL, *textconv_two = NULL;
  
 +      if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
 +                      (!one->mode || S_ISGITLINK(one->mode)) &&
 +                      (!two->mode || S_ISGITLINK(two->mode))) {
 +              const char *del = diff_get_color_opt(o, DIFF_FILE_OLD);
 +              const char *add = diff_get_color_opt(o, DIFF_FILE_NEW);
 +              show_submodule_summary(o->file, one ? one->path : two->path,
 +                              one->sha1, two->sha1,
 +                              del, add, reset);
 +              return;
 +      }
 +
        if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) {
                textconv_one = get_textconv(one);
                textconv_two = get_textconv(two);
                ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
                ecbdata.found_changesp = &o->found_changes;
                ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
 +              if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
 +                      check_blank_at_eof(&mf1, &mf2, &ecbdata);
                ecbdata.file = o->file;
                xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
                xecfg.ctxlen = o->context;
 +              xecfg.interhunkctxlen = o->interhunkcontext;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
                if (pe)
                        xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
                        ecbdata.diff_words =
                                xcalloc(1, sizeof(struct diff_words_data));
                        ecbdata.diff_words->file = o->file;
 +                      if (!o->word_regex)
 +                              o->word_regex = userdiff_word_regex(one);
 +                      if (!o->word_regex)
 +                              o->word_regex = userdiff_word_regex(two);
 +                      if (!o->word_regex)
 +                              o->word_regex = diff_word_regex_cfg;
 +                      if (o->word_regex) {
 +                              ecbdata.diff_words->word_regex = (regex_t *)
 +                                      xmalloc(sizeof(regex_t));
 +                              if (regcomp(ecbdata.diff_words->word_regex,
 +                                              o->word_regex,
 +                                              REG_EXTENDED | REG_NEWLINE))
 +                                      die ("Invalid regular expression: %s",
 +                                                      o->word_regex);
 +                      }
                }
                xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
                              &xpp, &xecfg, &ecb);
                        free(mf1.ptr);
                if (textconv_two)
                        free(mf2.ptr);
 +              xdiff_clear_find_func(&xecfg);
        }
  
   free_ab_and_return:
@@@ -1866,22 -1593,11 +1866,22 @@@ static void builtin_checkdiff(const cha
                xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data,
                              &xpp, &xecfg, &ecb);
  
 -              if ((data.ws_rule & WS_TRAILING_SPACE) &&
 -                  data.trailing_blanks_start) {
 -                      fprintf(o->file, "%s:%d: ends with blank lines.\n",
 -                              data.filename, data.trailing_blanks_start);
 -                      data.status = 1; /* report errors */
 +              if (data.ws_rule & WS_BLANK_AT_EOF) {
 +                      struct emit_callback ecbdata;
 +                      int blank_at_eof;
 +
 +                      ecbdata.ws_rule = data.ws_rule;
 +                      check_blank_at_eof(&mf1, &mf2, &ecbdata);
 +                      blank_at_eof = ecbdata.blank_at_eof_in_preimage;
 +
 +                      if (blank_at_eof) {
 +                              static char *err;
 +                              if (!err)
 +                                      err = whitespace_error_string(WS_BLANK_AT_EOF);
 +                              fprintf(o->file, "%s:%d: %s.\n",
 +                                      data.filename, blank_at_eof, err);
 +                              data.status = 1; /* report errors */
 +                      }
                }
        }
   free_and_return:
@@@ -2130,48 -1846,30 +2130,48 @@@ void diff_free_filespec_data(struct dif
        s->cnt_data = NULL;
  }
  
 -static void prep_temp_blob(struct diff_tempfile *temp,
 +static void prep_temp_blob(const char *path, struct diff_tempfile *temp,
                           void *blob,
                           unsigned long size,
                           const unsigned char *sha1,
                           int mode)
  {
        int fd;
 +      struct strbuf buf = STRBUF_INIT;
 +      struct strbuf template = STRBUF_INIT;
 +      char *path_dup = xstrdup(path);
 +      const char *base = basename(path_dup);
  
 -      fd = git_mkstemp(temp->tmp_path, PATH_MAX, ".diff_XXXXXX");
 +      /* Generate "XXXXXX_basename.ext" */
 +      strbuf_addstr(&template, "XXXXXX_");
 +      strbuf_addstr(&template, base);
 +
 +      fd = git_mkstemps(temp->tmp_path, PATH_MAX, template.buf,
 +                      strlen(base) + 1);
        if (fd < 0)
 -              die("unable to create temp-file: %s", strerror(errno));
 +              die_errno("unable to create temp-file");
 +      if (convert_to_working_tree(path,
 +                      (const char *)blob, (size_t)size, &buf)) {
 +              blob = buf.buf;
 +              size = buf.len;
 +      }
        if (write_in_full(fd, blob, size) != size)
 -              die("unable to write temp-file");
 +              die_errno("unable to write temp-file");
        close(fd);
        temp->name = temp->tmp_path;
        strcpy(temp->hex, sha1_to_hex(sha1));
        temp->hex[40] = 0;
        sprintf(temp->mode, "%06o", mode);
 +      strbuf_release(&buf);
 +      strbuf_release(&template);
 +      free(path_dup);
  }
  
 -static void prepare_temp_file(const char *name,
 -                            struct diff_tempfile *temp,
 -                            struct diff_filespec *one)
 +static struct diff_tempfile *prepare_temp_file(const char *name,
 +              struct diff_filespec *one)
  {
 +      struct diff_tempfile *temp = claim_diff_tempfile();
 +
        if (!DIFF_FILE_VALID(one)) {
        not_a_valid_file:
                /* A '-' entry produces this for file-2, and
                temp->name = "/dev/null";
                strcpy(temp->hex, ".");
                strcpy(temp->mode, ".");
 -              return;
 +              return temp;
 +      }
 +
 +      if (!remove_tempfile_installed) {
 +              atexit(remove_tempfile);
 +              sigchain_push_common(remove_tempfile_on_signal);
 +              remove_tempfile_installed = 1;
        }
  
        if (!one->sha1_valid ||
                if (lstat(name, &st) < 0) {
                        if (errno == ENOENT)
                                goto not_a_valid_file;
 -                      die("stat(%s): %s", name, strerror(errno));
 +                      die_errno("stat(%s)", name);
                }
                if (S_ISLNK(st.st_mode)) {
 -                      int ret;
 -                      char buf[PATH_MAX + 1]; /* ought to be SYMLINK_MAX */
 -                      ret = readlink(name, buf, sizeof(buf));
 -                      if (ret < 0)
 -                              die("readlink(%s)", name);
 -                      if (ret == sizeof(buf))
 -                              die("symlink too long: %s", name);
 -                      prep_temp_blob(temp, buf, ret,
 +                      struct strbuf sb = STRBUF_INIT;
 +                      if (strbuf_readlink(&sb, name, st.st_size) < 0)
 +                              die_errno("readlink(%s)", name);
 +                      prep_temp_blob(name, temp, sb.buf, sb.len,
                                       (one->sha1_valid ?
                                        one->sha1 : null_sha1),
                                       (one->sha1_valid ?
                                        one->mode : S_IFLNK));
 +                      strbuf_release(&sb);
                }
                else {
                        /* we can borrow from the file in the work tree */
                         */
                        sprintf(temp->mode, "%06o", one->mode);
                }
 -              return;
 +              return temp;
        }
        else {
                if (diff_populate_filespec(one, 0))
                        die("cannot read data blob for %s", one->path);
 -              prep_temp_blob(temp, one->data, one->size,
 +              prep_temp_blob(name, temp, one->data, one->size,
                               one->sha1, one->mode);
        }
 -}
 -
 -static void remove_tempfile(void)
 -{
 -      int i;
 -
 -      for (i = 0; i < 2; i++)
 -              if (diff_temp[i].name == diff_temp[i].tmp_path) {
 -                      unlink(diff_temp[i].name);
 -                      diff_temp[i].name = NULL;
 -              }
 -}
 -
 -static void remove_tempfile_on_signal(int signo)
 -{
 -      remove_tempfile();
 -      signal(SIGINT, SIG_DFL);
 -      raise(signo);
 +      return temp;
  }
  
  /* An external diff command takes:
@@@ -2249,22 -1961,34 +2249,22 @@@ static void run_external_diff(const cha
                              int complete_rewrite)
  {
        const char *spawn_arg[10];
 -      struct diff_tempfile *temp = diff_temp;
        int retval;
 -      static int atexit_asked = 0;
 -      const char *othername;
        const char **arg = &spawn_arg[0];
  
 -      othername = (other? other : name);
 -      if (one && two) {
 -              prepare_temp_file(name, &temp[0], one);
 -              prepare_temp_file(othername, &temp[1], two);
 -              if (! atexit_asked &&
 -                  (temp[0].name == temp[0].tmp_path ||
 -                   temp[1].name == temp[1].tmp_path)) {
 -                      atexit_asked = 1;
 -                      atexit(remove_tempfile);
 -              }
 -              signal(SIGINT, remove_tempfile_on_signal);
 -      }
 -
        if (one && two) {
 +              struct diff_tempfile *temp_one, *temp_two;
 +              const char *othername = (other ? other : name);
 +              temp_one = prepare_temp_file(name, one);
 +              temp_two = prepare_temp_file(othername, two);
                *arg++ = pgm;
                *arg++ = name;
 -              *arg++ = temp[0].name;
 -              *arg++ = temp[0].hex;
 -              *arg++ = temp[0].mode;
 -              *arg++ = temp[1].name;
 -              *arg++ = temp[1].hex;
 -              *arg++ = temp[1].mode;
 +              *arg++ = temp_one->name;
 +              *arg++ = temp_one->hex;
 +              *arg++ = temp_one->mode;
 +              *arg++ = temp_two->name;
 +              *arg++ = temp_two->hex;
 +              *arg++ = temp_two->mode;
                if (other) {
                        *arg++ = other;
                        *arg++ = xfrm_msg;
@@@ -2393,9 -2117,9 +2393,9 @@@ static void diff_fill_sha1_info(struct 
                                return;
                        }
                        if (lstat(one->path, &st) < 0)
 -                              die("stat %s", one->path);
 +                              die_errno("stat '%s'", one->path);
                        if (index_path(one->sha1, one->path, &st, 0))
 -                              die("cannot hash %s\n", one->path);
 +                              die("cannot hash %s", one->path);
                }
        }
        else
@@@ -2522,12 -2246,15 +2522,12 @@@ void diff_setup(struct diff_options *op
        options->break_opt = -1;
        options->rename_limit = -1;
        options->dirstat_percent = 3;
 -      DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE);
        options->context = 3;
  
        options->change = diff_change;
        options->add_remove = diff_addremove;
        if (diff_use_color_default > 0)
                DIFF_OPT_SET(options, COLOR_DIFF);
 -      else
 -              DIFF_OPT_CLR(options, COLOR_DIFF);
        options->detect_rename = diff_detect_rename_default;
  
        if (!diff_mnemonic_prefix) {
@@@ -2763,13 -2490,11 +2763,13 @@@ int diff_opt_parse(struct diff_options 
  
        /* xdiff options */
        else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
 -              options->xdl_opts |= XDF_IGNORE_WHITESPACE;
 +              DIFF_XDL_SET(options, IGNORE_WHITESPACE);
        else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
 -              options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
 +              DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE);
        else if (!strcmp(arg, "--ignore-space-at-eol"))
 -              options->xdl_opts |= XDF_IGNORE_WHITESPACE_AT_EOL;
 +              DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
 +      else if (!strcmp(arg, "--patience"))
 +              DIFF_XDL_SET(options, PATIENCE_DIFF);
  
        /* flags options */
        else if (!strcmp(arg, "--binary")) {
                DIFF_OPT_SET(options, COLOR_DIFF);
        else if (!strcmp(arg, "--no-color"))
                DIFF_OPT_CLR(options, COLOR_DIFF);
 -      else if (!strcmp(arg, "--color-words"))
 -              options->flags |= DIFF_OPT_COLOR_DIFF | DIFF_OPT_COLOR_DIFF_WORDS;
 +      else if (!strcmp(arg, "--color-words")) {
 +              DIFF_OPT_SET(options, COLOR_DIFF);
 +              DIFF_OPT_SET(options, COLOR_DIFF_WORDS);
 +      }
 +      else if (!prefixcmp(arg, "--color-words=")) {
 +              DIFF_OPT_SET(options, COLOR_DIFF);
 +              DIFF_OPT_SET(options, COLOR_DIFF_WORDS);
 +              options->word_regex = arg + 14;
 +      }
        else if (!strcmp(arg, "--exit-code"))
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        else if (!strcmp(arg, "--quiet"))
                DIFF_OPT_CLR(options, ALLOW_TEXTCONV);
        else if (!strcmp(arg, "--ignore-submodules"))
                DIFF_OPT_SET(options, IGNORE_SUBMODULES);
 +      else if (!strcmp(arg, "--submodule"))
 +              DIFF_OPT_SET(options, SUBMODULE_LOG);
 +      else if (!prefixcmp(arg, "--submodule=")) {
 +              if (!strcmp(arg + 12, "log"))
 +                      DIFF_OPT_SET(options, SUBMODULE_LOG);
 +      }
  
        /* misc options */
        else if (!strcmp(arg, "-z"))
                options->b_prefix = arg + 13;
        else if (!strcmp(arg, "--no-prefix"))
                options->a_prefix = options->b_prefix = "";
 +      else if (opt_arg(arg, '\0', "inter-hunk-context",
 +                       &options->interhunkcontext))
 +              ;
        else if (!prefixcmp(arg, "--output=")) {
                options->file = fopen(arg + strlen("--output="), "w");
                options->close_file = 1;
@@@ -2870,7 -2579,7 +2870,7 @@@ static int parse_num(const char **cp_p
        num = 0;
        scale = 1;
        dot = 0;
 -      for(;;) {
 +      for (;;) {
                ch = *cp;
                if ( !dot && ch == '.' ) {
                        scale = 1;
@@@ -3759,15 -3468,15 +3759,15 @@@ void diff_unmerge(struct diff_options *
  static char *run_textconv(const char *pgm, struct diff_filespec *spec,
                size_t *outsize)
  {
 -      struct diff_tempfile temp;
 +      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);
 +      temp = prepare_temp_file(spec->path, spec);
        *arg++ = pgm;
 -      *arg++ = temp.name;
 +      *arg++ = temp->name;
        *arg = NULL;
  
        memset(&child, 0, sizeof(child));
        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);
+               close(child.out);
 +              strbuf_release(&buf);
 +              remove_tempfile();
                error("error running textconv command '%s'", pgm);
                return NULL;
        }
 -      if (temp.name == temp.tmp_path)
 -              unlink(temp.name);
+       close(child.out);
 +      remove_tempfile();
  
        return strbuf_detach(&buf, outsize);
  }
diff --combined t/t7102-reset.sh
index e85ff02c3e636567a80586e92fdba1c5382fc995,5f3916bf4ffe1f9747d9b863056d2f16dff15619..b8cf2603a195af406d3606712e45fd1195c1588f
@@@ -139,19 -139,19 +139,19 @@@ test_expect_success 
  test_expect_success \
        'resetting to HEAD with no changes should succeed and do nothing' '
        git reset --hard &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset --hard HEAD &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset --soft &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset --soft HEAD &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset --mixed &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset --mixed HEAD &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset HEAD &&
                check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
  '
@@@ -419,8 -419,7 +419,8 @@@ test_expect_success 'resetting an unmod
  '
  
  cat > expect << EOF
 -file2: locally modified
 +Unstaged changes after reset:
 +M     file2
  EOF
  
  test_expect_success '--mixed refreshes the index' '