Merge branch 'maint-1.6.1' into maint-1.6.2
authorJunio C Hamano <gitster@pobox.com>
Sun, 10 Jan 2010 08:49:47 +0000 (00:49 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sun, 10 Jan 2010 08:49:47 +0000 (00:49 -0800)
* maint-1.6.1:
base85: Make the code more obvious instead of explaining the non-obvious
base85: encode_85() does not use the decode table
base85 debug code: Fix length byte calculation
checkout -m: do not try to fall back to --merge from an unborn branch
branch: die explicitly why when calling "git branch [-a|-r] branchname".
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:
diff.c

1  2 
builtin-branch.c
builtin-checkout.c
builtin-commit.c
builtin-count-objects.c
diff.c
diff --combined builtin-branch.c
index 07166b8856b2a2637a55b20221a929d6dbea1e0c,7c615871100aa8c9e637bde41197419bfb04d68a..74c404bd3f72d8931d42c3ecd841e25a5b2edec1
@@@ -99,7 -99,6 +99,7 @@@ static int delete_branches(int argc, co
        const char *fmt, *remote;
        int i;
        int ret = 0;
 +      struct strbuf bname = STRBUF_INIT;
  
        switch (kinds) {
        case REF_REMOTE_BRANCH:
                if (!head_rev)
                        die("Couldn't look up commit object for HEAD");
        }
 -      for (i = 0; i < argc; i++) {
 -              if (kinds == REF_LOCAL_BRANCH && !strcmp(head, argv[i])) {
 +      for (i = 0; i < argc; i++, strbuf_release(&bname)) {
 +              int len = strlen(argv[i]);
 +
 +              if (interpret_nth_last_branch(argv[i], &bname) != len)
 +                      strbuf_add(&bname, argv[i], len);
 +
 +              if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) {
                        error("Cannot delete the branch '%s' "
 -                              "which you are currently on.", argv[i]);
 +                            "which you are currently on.", bname.buf);
                        ret = 1;
                        continue;
                }
  
                free(name);
  
 -              name = xstrdup(mkpath(fmt, argv[i]));
 +              name = xstrdup(mkpath(fmt, bname.buf));
                if (!resolve_ref(name, sha1, 1, NULL)) {
                        error("%sbranch '%s' not found.",
 -                                      remote, argv[i]);
 +                                      remote, bname.buf);
                        ret = 1;
                        continue;
                }
                if (!force &&
                    !in_merge_bases(rev, &head_rev, 1)) {
                        error("The branch '%s' is not an ancestor of "
 -                              "your current HEAD.\n"
 -                              "If you are sure you want to delete it, "
 -                              "run 'git branch -D %s'.", argv[i], argv[i]);
 +                            "your current HEAD.\n"
 +                            "If you are sure you want to delete it, "
 +                            "run 'git branch -D %s'.", bname.buf, bname.buf);
                        ret = 1;
                        continue;
                }
  
                if (delete_ref(name, sha1, 0)) {
                        error("Error deleting %sbranch '%s'", remote,
 -                             argv[i]);
 +                            bname.buf);
                        ret = 1;
                } else {
                        struct strbuf buf = STRBUF_INIT;
 -                      printf("Deleted %sbranch %s (was %s).\n", remote, argv[i],
 -                              find_unique_abbrev(sha1, DEFAULT_ABBREV));
 -                      strbuf_addf(&buf, "branch.%s", argv[i]);
 +                      printf("Deleted %sbranch %s (was %s).\n", remote,
 +                             bname.buf,
 +                             find_unique_abbrev(sha1, DEFAULT_ABBREV));
 +                      strbuf_addf(&buf, "branch.%s", bname.buf);
                        if (git_config_rename_section(buf.buf, NULL) < 0)
                                warning("Update of config-file failed");
                        strbuf_release(&buf);
@@@ -200,6 -193,21 +200,6 @@@ struct ref_list 
        int kinds;
  };
  
 -static int has_commit(struct commit *commit, struct commit_list *with_commit)
 -{
 -      if (!with_commit)
 -              return 1;
 -      while (with_commit) {
 -              struct commit *other;
 -
 -              other = with_commit->item;
 -              with_commit = with_commit->next;
 -              if (in_merge_bases(other, &commit, 1))
 -                      return 1;
 -      }
 -      return 0;
 -}
 -
  static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
  {
        struct ref_list *ref_list = (struct ref_list*)(cb_data);
                return error("branch '%s' does not point at a commit", refname);
  
        /* Filter with with_commit if specified */
 -      if (!has_commit(commit, ref_list->with_commit))
 +      if (!is_descendant_of(commit, ref_list->with_commit))
                return 0;
  
        /* Don't add types the caller doesn't want */
@@@ -393,8 -401,7 +393,8 @@@ static void print_ref_list(int kinds, i
        qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
  
        detached = (detached && (kinds & REF_LOCAL_BRANCH));
 -      if (detached && head_commit && has_commit(head_commit, with_commit)) {
 +      if (detached && head_commit &&
 +          is_descendant_of(head_commit, with_commit)) {
                struct ref_item item;
                item.name = xstrdup("(no branch)");
                item.kind = REF_LOCAL_BRANCH;
@@@ -459,6 -466,22 +459,6 @@@ static void rename_branch(const char *o
        strbuf_release(&newsection);
  }
  
 -static int opt_parse_with_commit(const struct option *opt, const char *arg, int unset)
 -{
 -      unsigned char sha1[20];
 -      struct commit *commit;
 -
 -      if (!arg)
 -              return -1;
 -      if (get_sha1(arg, sha1))
 -              die("malformed object name %s", arg);
 -      commit = lookup_commit_reference(sha1);
 -      if (!commit)
 -              die("no such commit %s", arg);
 -      commit_list_insert(commit, opt->value);
 -      return 0;
 -}
 -
  static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset)
  {
        merge_filter = ((opt->long_name[0] == 'n')
@@@ -494,13 -517,13 +494,13 @@@ int cmd_branch(int argc, const char **a
                        OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
                        "print only branches that contain the commit",
                        PARSE_OPT_LASTARG_DEFAULT,
 -                      opt_parse_with_commit, (intptr_t)"HEAD",
 +                      parse_opt_with_commit, (intptr_t)"HEAD",
                },
                {
                        OPTION_CALLBACK, 0, "with", &with_commit, "commit",
                        "print only branches that contain the commit",
                        PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT,
 -                      opt_parse_with_commit, (intptr_t) "HEAD",
 +                      parse_opt_with_commit, (intptr_t) "HEAD",
                },
                OPT__ABBREV(&abbrev),
  
                rename_branch(head, argv[0], rename > 1);
        else if (rename && (argc == 2))
                rename_branch(argv[0], argv[1], rename > 1);
-       else if (argc <= 2)
+       else if (argc <= 2) {
+               if (kinds != REF_LOCAL_BRANCH)
+                       die("-a and -r options to 'git branch' do not make sense with a branch name");
                create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
                              force_create, reflog, track);
-       else
+       else
                usage_with_options(builtin_branch_usage, options);
  
        return 0;
diff --combined builtin-checkout.c
index ffdb33aef596740890362a0e237d33131a906f45,f32b14838d718907b105bcefd4e015f31aed1403..e0bbea58ffe16a0eb0edab8e4c260e23e2ef1b80
@@@ -5,7 -5,6 +5,7 @@@
  #include "commit.h"
  #include "tree.h"
  #include "tree-walk.h"
 +#include "cache-tree.h"
  #include "unpack-trees.h"
  #include "dir.h"
  #include "run-command.h"
@@@ -39,13 -38,23 +39,13 @@@ struct checkout_opts 
  static int post_checkout_hook(struct commit *old, struct commit *new,
                              int changed)
  {
 -      struct child_process proc;
 -      const char *name = git_path("hooks/post-checkout");
 -      const char *argv[5];
 +      return run_hook(NULL, "post-checkout",
 +                      sha1_to_hex(old ? old->object.sha1 : null_sha1),
 +                      sha1_to_hex(new ? new->object.sha1 : null_sha1),
 +                      changed ? "1" : "0", NULL);
 +      /* "new" can be NULL when checking out from the index before
 +         a commit exists. */
  
 -      if (access(name, X_OK) < 0)
 -              return 0;
 -
 -      memset(&proc, 0, sizeof(proc));
 -      argv[0] = name;
 -      argv[1] = xstrdup(sha1_to_hex(old ? old->object.sha1 : null_sha1));
 -      argv[2] = xstrdup(sha1_to_hex(new->object.sha1));
 -      argv[3] = changed ? "1" : "0";
 -      argv[4] = NULL;
 -      proc.argv = argv;
 -      proc.no_stdin = 1;
 -      proc.stdout_to_stderr = 1;
 -      return run_command(&proc);
  }
  
  static int update_some(const unsigned char *sha1, const char *base, int baselen,
@@@ -54,6 -63,9 +54,6 @@@
        int len;
        struct cache_entry *ce;
  
 -      if (S_ISGITLINK(mode))
 -              return 0;
 -
        if (S_ISDIR(mode))
                return READ_TREE_RECURSIVE;
  
@@@ -228,7 -240,7 +228,7 @@@ static int checkout_paths(struct tree *
  
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
 -              pathspec_match(pathspec, ps_matched, ce->name, 0);
 +              match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched);
        }
  
        if (report_path_error(ps_matched, pathspec, 0))
        /* Any unmerged paths? */
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
 -              if (pathspec_match(pathspec, NULL, ce->name, 0)) {
 +              if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
                        if (!ce_stage(ce))
                                continue;
                        if (opts->force) {
        state.refresh_cache = 1;
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
 -              if (pathspec_match(pathspec, NULL, ce->name, 0)) {
 +              if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
                        if (!ce_stage(ce)) {
                                errs |= checkout_entry(ce, &state, NULL);
                                continue;
@@@ -349,16 -361,8 +349,16 @@@ struct branch_info 
  static void setup_branch_path(struct branch_info *branch)
  {
        struct strbuf buf = STRBUF_INIT;
 -      strbuf_addstr(&buf, "refs/heads/");
 -      strbuf_addstr(&buf, branch->name);
 +      int ret;
 +
 +      if ((ret = interpret_nth_last_branch(branch->name, &buf))
 +          && ret == strlen(branch->name)) {
 +              branch->name = xstrdup(buf.buf);
 +              strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
 +      } else {
 +              strbuf_addstr(&buf, "refs/heads/");
 +              strbuf_addstr(&buf, branch->name);
 +      }
        branch->path = strbuf_detach(&buf, NULL);
  }
  
@@@ -399,7 -403,7 +399,7 @@@ static int merge_working_tree(struct ch
                topts.initial_checkout = is_cache_unborn();
                topts.update = 1;
                topts.merge = 1;
-               topts.gently = opts->merge;
+               topts.gently = opts->merge && old->commit;
                topts.verbose_update = !opts->quiet;
                topts.fn = twoway_merge;
                topts.dir = xcalloc(1, sizeof(*topts.dir));
                        struct merge_options o;
                        if (!opts->merge)
                                return 1;
-                       parse_commit(old->commit);
+                       /*
+                        * Without old->commit, the below is the same as
+                        * the two-tree unpack we already tried and failed.
+                        */
+                       if (!old->commit)
+                               return 1;
  
                        /* Do more real merge */
  
@@@ -499,10 -509,10 +505,10 @@@ static void update_refs_for_switch(stru
                create_symref("HEAD", new->path, msg.buf);
                if (!opts->quiet) {
                        if (old->path && !strcmp(new->path, old->path))
 -                              fprintf(stderr, "Already on \"%s\"\n",
 +                              fprintf(stderr, "Already on '%s'\n",
                                        new->name);
                        else
 -                              fprintf(stderr, "Switched to%s branch \"%s\"\n",
 +                              fprintf(stderr, "Switched to%s branch '%s'\n",
                                        opts->new_branch ? " a new" : "",
                                        new->name);
                }
                           REF_NODEREF, DIE_ON_ERR);
                if (!opts->quiet) {
                        if (old->path)
 -                              fprintf(stderr, "Note: moving to \"%s\" which isn't a local branch\nIf you want to create a new branch from this checkout, you may do so\n(now or later) by using -b with the checkout command again. Example:\n  git checkout -b <new_branch_name>\n", new->name);
 +                              fprintf(stderr, "Note: moving to '%s' which isn't a local branch\nIf you want to create a new branch from this checkout, you may do so\n(now or later) by using -b with the checkout command again. Example:\n  git checkout -b <new_branch_name>\n", new->name);
                        describe_detached_head("HEAD is now at", new->commit);
                }
        }
@@@ -667,9 -677,6 +673,9 @@@ int cmd_checkout(int argc, const char *
                arg = argv[0];
                has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
  
 +              if (!strcmp(arg, "-"))
 +                      arg = "@{-1}";
 +
                if (get_sha1(arg, rev)) {
                        if (has_dash_dash)          /* case (1) */
                                die("invalid reference: %s", arg);
diff --combined builtin-commit.c
index 81371b1d2698a48dba36046d7ff9d849f830a762,bf01ae776be92fcdf4878a008464777185a08e4b..0871ad5d66ea07361e263a1c2c38554d72e5af1a
@@@ -86,8 -86,8 +86,8 @@@ 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_GROUP("Commit message options"),
        OPT_STRING('F', "file", &logfile, "FILE", "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),
@@@ -96,6 -96,8 +96,8 @@@
        OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
        OPT_STRING('t', "template", &template_file, "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, "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()
  };
@@@ -166,7 -168,7 +168,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);
        }
@@@ -362,6 -364,40 +364,6 @@@ static int run_status(FILE *fp, const c
        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);
 -}
 -
  static int is_a_merge(const unsigned char *sha1)
  {
        struct commit *commit = lookup_commit(sha1);
@@@ -562,6 -598,7 +564,6 @@@ static int prepare_to_commit(const cha
                commitable = run_status(fp, index_file, prefix, 1);
                wt_status_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);
@@@ -841,7 -886,7 +843,7 @@@ static void print_summary(const char *p
  {
        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") ?
diff --combined builtin-count-objects.c
index b814fe5070873f5c87fc6bbfde480e3b0a83e397,80b2614fd4dab25e4ecd5e43d9bdccc34fa1d48a..433afd85770dde2ac22689f42ec806485016ffaa
@@@ -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,7 -78,7 +77,7 @@@ 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(),
        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 4f980aded6aa0f61b0a6354b0157b8711a2c4627,69147b802f73203ab27352ac1b3513138fa50417..9ec5767709898af88d83256c6ea98d74863042d8
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -12,7 -12,6 +12,7 @@@
  #include "run-command.h"
  #include "utf8.h"
  #include "userdiff.h"
 +#include "sigchain.h"
  
  #ifdef NO_FAST_WORKING_DIRECTORY
  #define FAST_WORKING_DIRECTORY 0
@@@ -24,7 -23,6 +24,7 @@@ 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;
@@@ -94,8 -92,6 +94,8 @@@ int git_diff_ui_config(const char *var
        }
        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);
  }
@@@ -171,33 -167,6 +171,33 @@@ static struct diff_tempfile 
        char tmp_path[PATH_MAX];
  } diff_temp[2];
  
 +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(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 int count_lines(const char *data, int size)
  {
        int count, ch, completely_empty = 1, nl_just_seen = 0;
@@@ -352,138 -321,82 +352,138 @@@ static int fill_mmfile(mmfile_t *mf, st
  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;
 +
 +      /* find the end of the word */
 +      *end = *begin + 1;
 +      while (*end < buffer->size && !isspace(buffer->ptr[*end]))
 +              (*end)++;
  
 -      if (eol) {
 -              if (suppress_newline)
 -                      buffer->suppressed_newline = 1;
 -              else
 -                      putc('\n', file);
 -      }
 +      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;
        }
  }
  
@@@ -494,36 -407,38 +494,36 @@@ 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);
@@@ -547,10 -462,7 +547,10 @@@ static void free_diff_words_data(struc
                        diff_words_show(ecbdata->diff_words);
  
                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;
        }
@@@ -1413,12 -1325,6 +1413,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)
@@@ -1565,7 -1471,6 +1565,7 @@@ static void builtin_diff(const char *na
                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);
@@@ -1956,23 -1846,17 +1956,23 @@@ 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;
  
        fd = git_mkstemp(temp->tmp_path, PATH_MAX, ".diff_XXXXXX");
        if (fd < 0)
                die("unable to create temp-file: %s", strerror(errno));
 +      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");
        close(fd);
        strcpy(temp->hex, sha1_to_hex(sha1));
        temp->hex[40] = 0;
        sprintf(temp->mode, "%06o", mode);
 +      strbuf_release(&buf);
  }
  
 -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 ||
                                die("readlink(%s)", name);
                        if (ret == sizeof(buf))
                                die("symlink too long: %s", name);
 -                      prep_temp_blob(temp, buf, ret,
 +                      prep_temp_blob(name, temp, buf, ret,
                                       (one->sha1_valid ?
                                        one->sha1 : null_sha1),
                                       (one->sha1_valid ?
                         */
                        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:
@@@ -2068,22 -1961,34 +2068,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;
@@@ -2214,7 -2119,7 +2214,7 @@@ static void diff_fill_sha1_info(struct 
                        if (lstat(one->path, &st) < 0)
                                die("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
@@@ -2341,12 -2246,15 +2341,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) {
@@@ -2587,8 -2495,6 +2587,8 @@@ int diff_opt_parse(struct diff_options 
                options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
        else if (!strcmp(arg, "--ignore-space-at-eol"))
                options->xdl_opts |= XDF_IGNORE_WHITESPACE_AT_EOL;
 +      else if (!strcmp(arg, "--patience"))
 +              options->xdl_opts |= XDF_PATIENCE_DIFF;
  
        /* flags options */
        else if (!strcmp(arg, "--binary")) {
                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 (!prefixcmp(arg, "--color-words=")) {
 +              options->flags |= DIFF_OPT_COLOR_DIFF | DIFF_OPT_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"))
                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;
@@@ -3569,15 -3468,15 +3569,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);
 +              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);
  }