Merge branch 'jc/apply-binary-p0' into maint-1.7.11
authorJunio C Hamano <gitster@pobox.com>
Wed, 12 Sep 2012 21:00:52 +0000 (14:00 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 12 Sep 2012 21:00:53 +0000 (14:00 -0700)
"git apply -p0" did not parse pathnames on "diff --git" line
correctly. This caused patches that had pathnames in no other
places to be mistakenly rejected (most notably, binary patch that
does not rename nor change mode). Textual patches, renames or mode
changes have preimage and postimage pathnames in different places in
a form that can be parsed unambiguously and did not suffer from this
problem.

* jc/apply-binary-p0:
apply: compute patch->def_name correctly under -p0

1  2 
builtin/apply.c
t/t4103-apply-binary.sh
diff --combined builtin/apply.c
index ca54ff3aaa76457d2838d39228b385661040dbd1,2ad8c4820720944f19207c4c09e87e9946116cf0..d2180b03744e093c51ffe61168391d4c4f814212
@@@ -14,7 -14,6 +14,7 @@@
  #include "builtin.h"
  #include "string-list.h"
  #include "dir.h"
 +#include "diff.h"
  #include "parse-options.h"
  
  /*
@@@ -50,7 -49,7 +50,7 @@@ static const char *fake_ancestor
  static int line_termination = '\n';
  static unsigned int p_context = UINT_MAX;
  static const char * const apply_usage[] = {
 -      "git apply [options] [<patch>...]",
 +      N_("git apply [options] [<patch>...]"),
        NULL
  };
  
@@@ -103,7 -102,7 +103,7 @@@ static void parse_whitespace_option(con
                ws_error_action = correct_ws_error;
                return;
        }
 -      die("unrecognized whitespace option '%s'", option);
 +      die(_("unrecognized whitespace option '%s'"), option);
  }
  
  static void parse_ignorewhitespace_option(const char *option)
                ws_ignore_action = ignore_ws_change;
                return;
        }
 -      die("unrecognized whitespace ignore option '%s'", option);
 +      die(_("unrecognized whitespace ignore option '%s'"), option);
  }
  
  static void set_default_whitespace_mode(const char *whitespace_option)
@@@ -152,14 -151,9 +152,14 @@@ struct fragment 
        unsigned long leading, trailing;
        unsigned long oldpos, oldlines;
        unsigned long newpos, newlines;
 +      /*
 +       * 'patch' is usually borrowed from buf in apply_patch(),
 +       * but some codepaths store an allocated buffer.
 +       */
        const char *patch;
 +      unsigned free_patch:1,
 +              rejected:1;
        int size;
 -      int rejected;
        int linenr;
        struct fragment *next;
  };
@@@ -184,6 -178,7 +184,6 @@@ struct patch 
        int is_new, is_delete;  /* -1 = unknown, 0 = false, 1 = true */
        int rejected;
        unsigned ws_rule;
 -      unsigned long deflate_origlen;
        int lines_added, lines_deleted;
        int score;
        unsigned int is_toplevel_relative:1;
        struct patch *next;
  };
  
 +static void free_fragment_list(struct fragment *list)
 +{
 +      while (list) {
 +              struct fragment *next = list->next;
 +              if (list->free_patch)
 +                      free((char *)list->patch);
 +              free(list);
 +              list = next;
 +      }
 +}
 +
 +static void free_patch(struct patch *patch)
 +{
 +      free_fragment_list(patch->fragments);
 +      free(patch->def_name);
 +      free(patch->old_name);
 +      free(patch->new_name);
 +      free(patch->result);
 +      free(patch);
 +}
 +
 +static void free_patch_list(struct patch *list)
 +{
 +      while (list) {
 +              struct patch *next = list->next;
 +              free_patch(list);
 +              list = next;
 +      }
 +}
 +
  /*
   * A line in a file, len-bytes long (includes the terminating LF,
   * except for an incomplete line at the end if the file ends with
@@@ -336,11 -301,6 +336,11 @@@ static void add_line_info(struct image 
        img->nr++;
  }
  
 +/*
 + * "buf" has the file contents to be patched (read from various sources).
 + * attach it to "image" and add line-based index to it.
 + * "image" now owns the "buf".
 + */
  static void prepare_image(struct image *image, char *buf, size_t len,
                          int prepare_linetable)
  {
@@@ -374,27 -334,25 +374,27 @@@ static void clear_image(struct image *i
        image->len = 0;
  }
  
 -static void say_patch_name(FILE *output, const char *pre,
 -                         struct patch *patch, const char *post)
 +/* fmt must contain _one_ %s and no other substitution */
 +static void say_patch_name(FILE *output, const char *fmt, struct patch *patch)
  {
 -      fputs(pre, output);
 +      struct strbuf sb = STRBUF_INIT;
 +
        if (patch->old_name && patch->new_name &&
            strcmp(patch->old_name, patch->new_name)) {
 -              quote_c_style(patch->old_name, NULL, output, 0);
 -              fputs(" => ", output);
 -              quote_c_style(patch->new_name, NULL, output, 0);
 +              quote_c_style(patch->old_name, &sb, NULL, 0);
 +              strbuf_addstr(&sb, " => ");
 +              quote_c_style(patch->new_name, &sb, NULL, 0);
        } else {
                const char *n = patch->new_name;
                if (!n)
                        n = patch->old_name;
 -              quote_c_style(n, NULL, output, 0);
 +              quote_c_style(n, &sb, NULL, 0);
        }
 -      fputs(post, output);
 +      fprintf(output, fmt, sb.buf);
 +      fputc('\n', output);
 +      strbuf_release(&sb);
  }
  
 -#define CHUNKSIZE (8192)
  #define SLOP (16)
  
  static void read_patch_file(struct strbuf *sb, int fd)
@@@ -457,7 -415,7 +457,7 @@@ static char *squash_slash(char *name
        return name;
  }
  
 -static char *find_name_gnu(const char *line, char *def, int p_value)
 +static char *find_name_gnu(const char *line, const char *def, int p_value)
  {
        struct strbuf name = STRBUF_INIT;
        char *cp;
                cp++;
        }
  
 -      /* name can later be freed, so we need
 -       * to memmove, not just return cp
 -       */
        strbuf_remove(&name, 0, cp - name.buf);
 -      free(def);
        if (root)
                strbuf_insert(&name, 0, root, root_len);
        return squash_slash(strbuf_detach(&name, NULL));
@@@ -645,13 -607,8 +645,13 @@@ static size_t diff_timestamp_len(const 
        return line + len - end;
  }
  
 -static char *find_name_common(const char *line, char *def, int p_value,
 -                              const char *end, int terminate)
 +static char *null_strdup(const char *s)
 +{
 +      return s ? xstrdup(s) : NULL;
 +}
 +
 +static char *find_name_common(const char *line, const char *def,
 +                            int p_value, const char *end, int terminate)
  {
        int len;
        const char *start = NULL;
                        start = line;
        }
        if (!start)
 -              return squash_slash(def);
 +              return squash_slash(null_strdup(def));
        len = line - start;
        if (!len)
 -              return squash_slash(def);
 +              return squash_slash(null_strdup(def));
  
        /*
         * Generally we prefer the shorter name, especially
        if (def) {
                int deflen = strlen(def);
                if (deflen < len && !strncmp(start, def, deflen))
 -                      return squash_slash(def);
 -              free(def);
 +                      return squash_slash(xstrdup(def));
        }
  
        if (root) {
@@@ -811,7 -769,7 +811,7 @@@ static int has_epoch_timestamp(const ch
        if (!stamp) {
                stamp = xmalloc(sizeof(*stamp));
                if (regcomp(stamp, stamp_regexp, REG_EXTENDED)) {
 -                      warning("Cannot prepare timestamp regexp %s",
 +                      warning(_("Cannot prepare timestamp regexp %s"),
                                stamp_regexp);
                        return 0;
                }
        status = regexec(stamp, timestamp, ARRAY_SIZE(m), m, 0);
        if (status) {
                if (status != REG_NOMATCH)
 -                      warning("regexec returned %d for input: %s",
 +                      warning(_("regexec returned %d for input: %s"),
                                status, timestamp);
                return 0;
        }
@@@ -883,10 -841,8 +883,10 @@@ static void parse_traditional_patch(con
                name = find_name_traditional(first, NULL, p_value);
                patch->old_name = name;
        } else {
 -              name = find_name_traditional(first, NULL, p_value);
 -              name = find_name_traditional(second, name, p_value);
 +              char *first_name;
 +              first_name = find_name_traditional(first, NULL, p_value);
 +              name = find_name_traditional(second, first_name, p_value);
 +              free(first_name);
                if (has_epoch_timestamp(first)) {
                        patch->is_new = 1;
                        patch->is_delete = 0;
                        patch->is_delete = 1;
                        patch->old_name = name;
                } else {
 -                      patch->old_name = patch->new_name = name;
 +                      patch->old_name = name;
 +                      patch->new_name = xstrdup(name);
                }
        }
        if (!name)
 -              die("unable to find filename in patch at line %d", linenr);
 +              die(_("unable to find filename in patch at line %d"), linenr);
  }
  
  static int gitdiff_hdrend(const char *line, struct patch *patch)
   * their names against any previous information, just
   * to make sure..
   */
 -static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew)
 +#define DIFF_OLD_NAME 0
 +#define DIFF_NEW_NAME 1
 +
 +static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, int side)
  {
        if (!orig_name && !isnull)
                return find_name(line, NULL, p_value, TERM_TAB);
                name = orig_name;
                len = strlen(name);
                if (isnull)
 -                      die("git apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
 +                      die(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"), name, linenr);
                another = find_name(line, NULL, p_value, TERM_TAB);
                if (!another || memcmp(another, name, len + 1))
 -                      die("git apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
 +                      die((side == DIFF_NEW_NAME) ?
 +                          _("git apply: bad git-diff - inconsistent new filename on line %d") :
 +                          _("git apply: bad git-diff - inconsistent old filename on line %d"), linenr);
                free(another);
                return orig_name;
        }
        else {
                /* expect "/dev/null" */
                if (memcmp("/dev/null", line, 9) || line[9] != '\n')
 -                      die("git apply: bad git-diff - expected /dev/null on line %d", linenr);
 +                      die(_("git apply: bad git-diff - expected /dev/null on line %d"), linenr);
                return NULL;
        }
  }
  
  static int gitdiff_oldname(const char *line, struct patch *patch)
  {
 -      patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, "old");
 +      char *orig = patch->old_name;
 +      patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name,
 +                                            DIFF_OLD_NAME);
 +      if (orig != patch->old_name)
 +              free(orig);
        return 0;
  }
  
  static int gitdiff_newname(const char *line, struct patch *patch)
  {
 -      patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, "new");
 +      char *orig = patch->new_name;
 +      patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name,
 +                                            DIFF_NEW_NAME);
 +      if (orig != patch->new_name)
 +              free(orig);
        return 0;
  }
  
@@@ -985,23 -927,20 +985,23 @@@ static int gitdiff_newmode(const char *
  static int gitdiff_delete(const char *line, struct patch *patch)
  {
        patch->is_delete = 1;
 -      patch->old_name = patch->def_name;
 +      free(patch->old_name);
 +      patch->old_name = null_strdup(patch->def_name);
        return gitdiff_oldmode(line, patch);
  }
  
  static int gitdiff_newfile(const char *line, struct patch *patch)
  {
        patch->is_new = 1;
 -      patch->new_name = patch->def_name;
 +      free(patch->new_name);
 +      patch->new_name = null_strdup(patch->def_name);
        return gitdiff_newmode(line, patch);
  }
  
  static int gitdiff_copysrc(const char *line, struct patch *patch)
  {
        patch->is_copy = 1;
 +      free(patch->old_name);
        patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
        return 0;
  }
  static int gitdiff_copydst(const char *line, struct patch *patch)
  {
        patch->is_copy = 1;
 +      free(patch->new_name);
        patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
        return 0;
  }
  static int gitdiff_renamesrc(const char *line, struct patch *patch)
  {
        patch->is_rename = 1;
 +      free(patch->old_name);
        patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
        return 0;
  }
  static int gitdiff_renamedst(const char *line, struct patch *patch)
  {
        patch->is_rename = 1;
 +      free(patch->new_name);
        patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
        return 0;
  }
@@@ -1086,15 -1022,23 +1086,23 @@@ static int gitdiff_unrecognized(const c
        return -1;
  }
  
- static const char *stop_at_slash(const char *line, int llen)
+ /*
+  * Skip p_value leading components from "line"; as we do not accept
+  * absolute paths, return NULL in that case.
+  */
+ static const char *skip_tree_prefix(const char *line, int llen)
  {
-       int nslash = p_value;
+       int nslash;
        int i;
  
+       if (!p_value)
+               return (llen && line[0] == '/') ? NULL : line;
+       nslash = p_value;
        for (i = 0; i < llen; i++) {
                int ch = line[i];
                if (ch == '/' && --nslash <= 0)
-                       return &line[i];
+                       return (i == 0) ? NULL : &line[i + 1];
        }
        return NULL;
  }
   * creation or deletion of an empty file.  In any of these cases,
   * both sides are the same name under a/ and b/ respectively.
   */
 -static char *git_header_name(char *line, int llen)
 +static char *git_header_name(const char *line, int llen)
  {
        const char *name;
        const char *second = NULL;
                if (unquote_c_style(&first, line, &second))
                        goto free_and_fail1;
  
-               /* advance to the first slash */
-               cp = stop_at_slash(first.buf, first.len);
-               /* we do not accept absolute paths */
-               if (!cp || cp == first.buf)
+               /* strip the a/b prefix including trailing slash */
+               cp = skip_tree_prefix(first.buf, first.len);
+               if (!cp)
                        goto free_and_fail1;
-               strbuf_remove(&first, 0, cp + 1 - first.buf);
+               strbuf_remove(&first, 0, cp - first.buf);
  
                /*
                 * second points at one past closing dq of name.
                if (*second == '"') {
                        if (unquote_c_style(&sp, second, NULL))
                                goto free_and_fail1;
-                       cp = stop_at_slash(sp.buf, sp.len);
-                       if (!cp || cp == sp.buf)
+                       cp = skip_tree_prefix(sp.buf, sp.len);
+                       if (!cp)
                                goto free_and_fail1;
                        /* They must match, otherwise ignore */
-                       if (strcmp(cp + 1, first.buf))
+                       if (strcmp(cp, first.buf))
                                goto free_and_fail1;
                        strbuf_release(&sp);
                        return strbuf_detach(&first, NULL);
                }
  
                /* unquoted second */
-               cp = stop_at_slash(second, line + llen - second);
-               if (!cp || cp == second)
+               cp = skip_tree_prefix(second, line + llen - second);
+               if (!cp)
                        goto free_and_fail1;
-               cp++;
-               if (line + llen - cp != first.len + 1 ||
+               if (line + llen - cp != first.len ||
                    memcmp(first.buf, cp, first.len))
                        goto free_and_fail1;
                return strbuf_detach(&first, NULL);
        }
  
        /* unquoted first name */
-       name = stop_at_slash(line, llen);
-       if (!name || name == line)
+       name = skip_tree_prefix(line, llen);
+       if (!name)
                return NULL;
-       name++;
  
        /*
         * since the first name is unquoted, a dq if exists must be
                        if (unquote_c_style(&sp, second, NULL))
                                goto free_and_fail2;
  
-                       np = stop_at_slash(sp.buf, sp.len);
-                       if (!np || np == sp.buf)
+                       np = skip_tree_prefix(sp.buf, sp.len);
+                       if (!np)
                                goto free_and_fail2;
-                       np++;
  
                        len = sp.buf + sp.len - np;
                        if (len < second - name &&
                case '\n':
                        return NULL;
                case '\t': case ' ':
-                       second = stop_at_slash(name + len, line_len - len);
+                       /*
+                        * Is this the separator between the preimage
+                        * and the postimage pathname?  Again, we are
+                        * only interested in the case where there is
+                        * no rename, as this is only to set def_name
+                        * and a rename patch has the names elsewhere
+                        * in an unambiguous form.
+                        */
+                       if (!name[len + 1])
+                               return NULL; /* no postimage name */
+                       second = skip_tree_prefix(name + len + 1,
+                                                 line_len - (len + 1));
                        if (!second)
                                return NULL;
-                       second++;
-                       if (second[len] == '\n' && !strncmp(name, second, len)) {
+                       /*
+                        * Does len bytes starting at "name" and "second"
+                        * (that are separated by one HT or SP we just
+                        * found) exactly match?
+                        */
+                       if (second[len] == '\n' && !strncmp(name, second, len))
                                return xmemdupz(name, len);
-                       }
                }
        }
  }
  
  /* Verify that we recognize the lines following a git header */
 -static int parse_git_header(char *line, int len, unsigned int size, struct patch *patch)
 +static int parse_git_header(const char *line, int len, unsigned int size, struct patch *patch)
  {
        unsigned long offset;
  
@@@ -1350,7 -1304,7 +1368,7 @@@ static int parse_range(const char *line
        return offset + ex;
  }
  
 -static void recount_diff(char *line, int size, struct fragment *fragment)
 +static void recount_diff(const char *line, int size, struct fragment *fragment)
  {
        int oldlines = 0, newlines = 0, ret = 0;
  
                        break;
                }
                if (ret) {
 -                      warning("recount: unexpected line: %.*s",
 +                      warning(_("recount: unexpected line: %.*s"),
                                (int)linelen(line, size), line);
                        return;
                }
   * Parse a unified diff fragment header of the
   * form "@@ -a,b +c,d @@"
   */
 -static int parse_fragment_header(char *line, int len, struct fragment *fragment)
 +static int parse_fragment_header(const char *line, int len, struct fragment *fragment)
  {
        int offset;
  
        return offset;
  }
  
 -static int find_header(char *line, unsigned long size, int *hdrsize, struct patch *patch)
 +static int find_header(const char *line, unsigned long size, int *hdrsize, struct patch *patch)
  {
        unsigned long offset, len;
  
                        struct fragment dummy;
                        if (parse_fragment_header(line, len, &dummy) < 0)
                                continue;
 -                      die("patch fragment without header at line %d: %.*s",
 +                      die(_("patch fragment without header at line %d: %.*s"),
                            linenr, (int)len-1, line);
                }
  
                                continue;
                        if (!patch->old_name && !patch->new_name) {
                                if (!patch->def_name)
 -                                      die("git diff header lacks filename information when removing "
 -                                          "%d leading pathname components (line %d)" , p_value, linenr);
 -                              patch->old_name = patch->new_name = patch->def_name;
 +                                      die(Q_("git diff header lacks filename information when removing "
 +                                             "%d leading pathname component (line %d)",
 +                                             "git diff header lacks filename information when removing "
 +                                             "%d leading pathname components (line %d)",
 +                                             p_value),
 +                                          p_value, linenr);
 +                              patch->old_name = xstrdup(patch->def_name);
 +                              patch->new_name = xstrdup(patch->def_name);
                        }
                        if (!patch->is_delete && !patch->new_name)
                                die("git diff header lacks filename information "
@@@ -1534,7 -1483,7 +1552,7 @@@ static void check_whitespace(const cha
   * between a "---" that is part of a patch, and a "---" that starts
   * the next patch is to look at the line counts..
   */
 -static int parse_fragment(char *line, unsigned long size,
 +static int parse_fragment(const char *line, unsigned long size,
                          struct patch *patch, struct fragment *fragment)
  {
        int added, deleted;
        patch->lines_deleted += deleted;
  
        if (0 < patch->is_new && oldlines)
 -              return error("new file depends on old contents");
 +              return error(_("new file depends on old contents"));
        if (0 < patch->is_delete && newlines)
 -              return error("deleted file still has contents");
 +              return error(_("deleted file still has contents"));
        return offset;
  }
  
 -static int parse_single_patch(char *line, unsigned long size, struct patch *patch)
 +/*
 + * We have seen "diff --git a/... b/..." header (or a traditional patch
 + * header).  Read hunks that belong to this patch into fragments and hang
 + * them to the given patch structure.
 + *
 + * The (fragment->patch, fragment->size) pair points into the memory given
 + * by the caller, not a copy, when we return.
 + */
 +static int parse_single_patch(const char *line, unsigned long size, struct patch *patch)
  {
        unsigned long offset = 0;
        unsigned long oldlines = 0, newlines = 0, context = 0;
                fragment->linenr = linenr;
                len = parse_fragment(line, size, patch, fragment);
                if (len <= 0)
 -                      die("corrupt patch at line %d", linenr);
 +                      die(_("corrupt patch at line %d"), linenr);
                fragment->patch = line;
                fragment->size = len;
                oldlines += fragment->oldlines;
                patch->is_delete = 0;
  
        if (0 < patch->is_new && oldlines)
 -              die("new file %s depends on old contents", patch->new_name);
 +              die(_("new file %s depends on old contents"), patch->new_name);
        if (0 < patch->is_delete && newlines)
 -              die("deleted file %s still has contents", patch->old_name);
 +              die(_("deleted file %s still has contents"), patch->old_name);
        if (!patch->is_delete && !newlines && context)
 -              fprintf(stderr, "** warning: file %s becomes empty but "
 -                      "is not deleted\n", patch->new_name);
 +              fprintf_ln(stderr,
 +                         _("** warning: "
 +                           "file %s becomes empty but is not deleted"),
 +                         patch->new_name);
  
        return offset;
  }
@@@ -1733,11 -1672,6 +1751,11 @@@ static char *inflate_it(const void *dat
        return out;
  }
  
 +/*
 + * Read a binary hunk and return a new fragment; fragment->patch
 + * points at an allocated memory that the caller must free, so
 + * it is marked as "->free_patch = 1".
 + */
  static struct fragment *parse_binary_hunk(char **buf_p,
                                          unsigned long *sz_p,
                                          int *status_p,
  
        frag = xcalloc(1, sizeof(*frag));
        frag->patch = inflate_it(data, hunk_size, origlen);
 +      frag->free_patch = 1;
        if (!frag->patch)
                goto corrupt;
        free(data);
   corrupt:
        free(data);
        *status_p = -1;
 -      error("corrupt binary patch at line %d: %.*s",
 +      error(_("corrupt binary patch at line %d: %.*s"),
              linenr-1, llen-1, buffer);
        return NULL;
  }
@@@ -1868,7 -1801,7 +1886,7 @@@ static int parse_binary(char *buffer, u
        forward = parse_binary_hunk(&buffer, &size, &status, &used);
        if (!forward && !status)
                /* there has to be one hunk (forward hunk) */
 -              return error("unrecognized binary patch at line %d", linenr-1);
 +              return error(_("unrecognized binary patch at line %d"), linenr-1);
        if (status)
                /* otherwise we already gave an error message */
                return status;
        return used;
  }
  
 +/*
 + * Read the patch text in "buffer" taht extends for "size" bytes; stop
 + * reading after seeing a single patch (i.e. changes to a single file).
 + * Create fragments (i.e. patch hunks) and hang them to the given patch.
 + * Return the number of bytes consumed, so that the caller can call us
 + * again for the next patch.
 + */
  static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
  {
        int hdrsize, patchsize;
                 */
                if ((apply || check) &&
                    (!patch->is_binary && !metadata_changes(patch)))
 -                      die("patch with only garbage at line %d", linenr);
 +                      die(_("patch with only garbage at line %d"), linenr);
        }
  
        return offset + hdrsize + patchsize;
@@@ -2044,11 -1970,11 +2062,11 @@@ static int read_old_data(struct stat *s
        switch (st->st_mode & S_IFMT) {
        case S_IFLNK:
                if (strbuf_readlink(buf, path, st->st_size) < 0)
 -                      return error("unable to read symlink %s", path);
 +                      return error(_("unable to read symlink %s"), path);
                return 0;
        case S_IFREG:
                if (strbuf_read_file(buf, path, st->st_size) != st->st_size)
 -                      return error("unable to open or read %s", path);
 +                      return error(_("unable to open or read %s"), path);
                convert_to_git(path, buf->buf, buf->len, buf, 0);
                return 0;
        default:
@@@ -2119,7 -2045,7 +2137,7 @@@ static void update_pre_post_images(stru
                        ctx++;
                }
                if (preimage->nr <= ctx)
 -                      die("oops");
 +                      die(_("oops"));
  
                /* and copy it in, while fixing the line length */
                len = preimage->line[ctx].len;
@@@ -2458,11 -2384,6 +2476,11 @@@ static void remove_last_line(struct ima
        img->len -= img->line[--img->nr].len;
  }
  
 +/*
 + * The change from "preimage" and "postimage" has been found to
 + * apply at applied_pos (counts in line numbers) in "img".
 + * Update "img" to remove "preimage" and replace it with "postimage".
 + */
  static void update_image(struct image *img,
                         int applied_pos,
                         struct image *preimage,
        img->nr = nr;
  }
  
 +/*
 + * Use the patch-hunk text in "frag" to prepare two images (preimage and
 + * postimage) for the hunk.  Find lines that match "preimage" in "img" and
 + * replace the part of "img" with "postimage" text.
 + */
  static int apply_one_fragment(struct image *img, struct fragment *frag,
                              int inaccurate_eof, unsigned ws_rule,
                              int nth_fragment)
                        break;
                default:
                        if (apply_verbosely)
 -                              error("invalid start of line: '%c'", first);
 +                              error(_("invalid start of line: '%c'"), first);
                        return -1;
                }
                if (added_blank_line) {
                        int offset = applied_pos - pos;
                        if (apply_in_reverse)
                                offset = 0 - offset;
 -                      fprintf(stderr,
 -                              "Hunk #%d succeeded at %d (offset %d lines).\n",
 -                              nth_fragment, applied_pos + 1, offset);
 +                      fprintf_ln(stderr,
 +                                 Q_("Hunk #%d succeeded at %d (offset %d line).",
 +                                    "Hunk #%d succeeded at %d (offset %d lines).",
 +                                    offset),
 +                                 nth_fragment, applied_pos + 1, offset);
                }
  
                /*
                 */
                if ((leading != frag->leading) ||
                    (trailing != frag->trailing))
 -                      fprintf(stderr, "Context reduced to (%ld/%ld)"
 -                              " to apply fragment at %d\n",
 -                              leading, trailing, applied_pos+1);
 +                      fprintf_ln(stderr, _("Context reduced to (%ld/%ld)"
 +                                           " to apply fragment at %d"),
 +                                 leading, trailing, applied_pos+1);
                update_image(img, applied_pos, &preimage, &postimage);
        } else {
                if (apply_verbosely)
 -                      error("while searching for:\n%.*s",
 +                      error(_("while searching for:\n%.*s"),
                              (int)(old - oldlines), oldlines);
        }
  
@@@ -2796,7 -2710,7 +2814,7 @@@ static int apply_binary_fragment(struc
        void *dst;
  
        if (!fragment)
 -              return error("missing binary patch data for '%s'",
 +              return error(_("missing binary patch data for '%s'"),
                             patch->new_name ?
                             patch->new_name :
                             patch->old_name);
        return -1;
  }
  
 +/*
 + * Replace "img" with the result of applying the binary patch.
 + * The binary patch data itself in patch->fragment is still kept
 + * but the preimage prepared by the caller in "img" is freed here
 + * or in the helper function apply_binary_fragment() this calls.
 + */
  static int apply_binary(struct image *img, struct patch *patch)
  {
        const char *name = patch->old_name ? patch->old_name : patch->new_name;
                 * in the patch->fragments->{patch,size}.
                 */
                if (apply_binary_fragment(img, patch))
 -                      return error("binary patch does not apply to '%s'",
 +                      return error(_("binary patch does not apply to '%s'"),
                                     name);
  
                /* verify that the result matches */
                hash_sha1_file(img->buf, img->len, blob_type, sha1);
                if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix))
 -                      return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)",
 +                      return error(_("binary patch to '%s' creates incorrect result (expecting %s, got %s)"),
                                name, patch->new_sha1_prefix, sha1_to_hex(sha1));
        }
  
@@@ -2926,7 -2834,7 +2944,7 @@@ static int apply_fragments(struct imag
        while (frag) {
                nth++;
                if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule, nth)) {
 -                      error("patch failed: %s:%ld", name, frag->oldpos);
 +                      error(_("patch failed: %s:%ld"), name, frag->oldpos);
                        if (!apply_with_reject)
                                return -1;
                        frag->rejected = 1;
@@@ -3041,14 -2949,14 +3059,14 @@@ static int apply_data(struct patch *pat
        if (!(patch->is_copy || patch->is_rename) &&
            (tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) {
                if (was_deleted(tpatch)) {
 -                      return error("patch %s has been renamed/deleted",
 +                      return error(_("patch %s has been renamed/deleted"),
                                patch->old_name);
                }
 -              /* We have a patched copy in memory use that */
 +              /* We have a patched copy in memory; use that. */
                strbuf_add(&buf, tpatch->result, tpatch->resultsize);
        } else if (cached) {
                if (read_file_or_gitlink(ce, &buf))
 -                      return error("read of %s failed", patch->old_name);
 +                      return error(_("read of %s failed"), patch->old_name);
        } else if (patch->old_name) {
                if (S_ISGITLINK(patch->old_mode)) {
                        if (ce) {
                                /*
                                 * There is no way to apply subproject
                                 * patch without looking at the index.
 +                               * NEEDSWORK: shouldn't this be flagged
 +                               * as an error???
                                 */
 +                              free_fragment_list(patch->fragments);
                                patch->fragments = NULL;
                        }
                } else {
                        if (read_old_data(st, patch->old_name, &buf))
 -                              return error("read of %s failed", patch->old_name);
 +                              return error(_("read of %s failed"), patch->old_name);
                }
        }
  
        free(image.line_allocated);
  
        if (0 < patch->is_delete && patch->resultsize)
 -              return error("removal patch leaves file contents");
 +              return error(_("removal patch leaves file contents"));
  
        return 0;
  }
@@@ -3101,7 -3006,7 +3119,7 @@@ static int check_to_create_blob(const c
                if (has_symlink_leading_path(new_name, strlen(new_name)))
                        return 0;
  
 -              return error("%s: already exists in working directory", new_name);
 +              return error(_("%s: already exists in working directory"), new_name);
        }
        else if ((errno != ENOENT) && (errno != ENOTDIR))
                return error("%s: %s", new_name, strerror(errno));
@@@ -3139,12 -3044,12 +3157,12 @@@ static int check_preimage(struct patch 
        if (!(patch->is_copy || patch->is_rename) &&
            (tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) {
                if (was_deleted(tpatch))
 -                      return error("%s: has been deleted/renamed", old_name);
 +                      return error(_("%s: has been deleted/renamed"), old_name);
                st_mode = tpatch->new_mode;
        } else if (!cached) {
                stat_ret = lstat(old_name, st);
                if (stat_ret && errno != ENOENT)
 -                      return error("%s: %s", old_name, strerror(errno));
 +                      return error(_("%s: %s"), old_name, strerror(errno));
        }
  
        if (to_be_deleted(tpatch))
                if (pos < 0) {
                        if (patch->is_new < 0)
                                goto is_new;
 -                      return error("%s: does not exist in index", old_name);
 +                      return error(_("%s: does not exist in index"), old_name);
                }
                *ce = active_cache[pos];
                if (stat_ret < 0) {
                                return -1;
                }
                if (!cached && verify_index_match(*ce, st))
 -                      return error("%s: does not match index", old_name);
 +                      return error(_("%s: does not match index"), old_name);
                if (cached)
                        st_mode = (*ce)->ce_mode;
        } else if (stat_ret < 0) {
                if (patch->is_new < 0)
                        goto is_new;
 -              return error("%s: %s", old_name, strerror(errno));
 +              return error(_("%s: %s"), old_name, strerror(errno));
        }
  
        if (!cached && !tpatch)
        if (!patch->old_mode)
                patch->old_mode = st_mode;
        if ((st_mode ^ patch->old_mode) & S_IFMT)
 -              return error("%s: wrong type", old_name);
 +              return error(_("%s: wrong type"), old_name);
        if (st_mode != patch->old_mode)
 -              warning("%s has type %o, expected %o",
 +              warning(_("%s has type %o, expected %o"),
                        old_name, st_mode, patch->old_mode);
        if (!patch->new_mode && !patch->is_delete)
                patch->new_mode = st_mode;
   is_new:
        patch->is_new = 1;
        patch->is_delete = 0;
 +      free(patch->old_name);
        patch->old_name = NULL;
        return 0;
  }
  
 +/*
 + * Check and apply the patch in-core; leave the result in patch->result
 + * for the caller to write it out to the final destination.
 + */
  static int check_patch(struct patch *patch)
  {
        struct stat st;
                if (check_index &&
                    cache_name_pos(new_name, strlen(new_name)) >= 0 &&
                    !ok_if_exists)
 -                      return error("%s: already exists in index", new_name);
 +                      return error(_("%s: already exists in index"), new_name);
                if (!cached) {
                        int err = check_to_create_blob(new_name, ok_if_exists);
                        if (err)
                int same = !strcmp(old_name, new_name);
                if (!patch->new_mode)
                        patch->new_mode = patch->old_mode;
 -              if ((patch->old_mode ^ patch->new_mode) & S_IFMT)
 -                      return error("new mode (%o) of %s does not match old mode (%o)%s%s",
 -                              patch->new_mode, new_name, patch->old_mode,
 -                              same ? "" : " of ", same ? "" : old_name);
 +              if ((patch->old_mode ^ patch->new_mode) & S_IFMT) {
 +                      if (same)
 +                              return error(_("new mode (%o) of %s does not "
 +                                             "match old mode (%o)"),
 +                                      patch->new_mode, new_name,
 +                                      patch->old_mode);
 +                      else
 +                              return error(_("new mode (%o) of %s does not "
 +                                             "match old mode (%o) of %s"),
 +                                      patch->new_mode, new_name,
 +                                      patch->old_mode, old_name);
 +              }
        }
  
        if (apply_data(patch, &st, ce) < 0)
 -              return error("%s: patch does not apply", name);
 +              return error(_("%s: patch does not apply"), name);
        patch->rejected = 0;
        return 0;
  }
@@@ -3289,7 -3181,7 +3307,7 @@@ static int check_patch_list(struct patc
        while (patch) {
                if (apply_verbosely)
                        say_patch_name(stderr,
 -                                     "Checking patch ", patch, "...\n");
 +                                     _("Checking patch %s..."), patch);
                err |= check_patch(patch);
                patch = patch->next;
        }
@@@ -3344,7 -3236,7 +3362,7 @@@ static void build_fake_ancestor(struct 
  
                ce = make_cache_entry(patch->old_mode, sha1_ptr, name, 0, 0);
                if (!ce)
 -                      die("make_cache_entry failed for path '%s'", name);
 +                      die(_("make_cache_entry failed for path '%s'"), name);
                if (add_index_entry(&result, ce, ADD_CACHE_OK_TO_ADD))
                        die ("Could not add %s to temporary index", name);
        }
@@@ -3367,7 -3259,7 +3385,7 @@@ static void stat_patch_list(struct patc
                show_stats(patch);
        }
  
 -      printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels);
 +      print_stat_summary(stdout, files, adds, dels);
  }
  
  static void numstat_patch_list(struct patch *patch)
@@@ -3487,7 -3379,7 +3505,7 @@@ static void remove_file(struct patch *p
  {
        if (update_index) {
                if (remove_file_from_cache(patch->old_name) < 0)
 -                      die("unable to remove %s from index", patch->old_name);
 +                      die(_("unable to remove %s from index"), patch->old_name);
        }
        if (!cached) {
                if (!remove_or_warn(patch->old_mode, patch->old_name) && rmdir_empty) {
@@@ -3514,19 -3406,19 +3532,19 @@@ static void add_index_file(const char *
                const char *s = buf;
  
                if (get_sha1_hex(s + strlen("Subproject commit "), ce->sha1))
 -                      die("corrupt patch for subproject %s", path);
 +                      die(_("corrupt patch for subproject %s"), path);
        } else {
                if (!cached) {
                        if (lstat(path, &st) < 0)
 -                              die_errno("unable to stat newly created file '%s'",
 +                              die_errno(_("unable to stat newly created file '%s'"),
                                          path);
                        fill_stat_cache_info(ce, &st);
                }
                if (write_sha1_file(buf, size, blob_type, ce->sha1) < 0)
 -                      die("unable to create backing store for newly created file %s", path);
 +                      die(_("unable to create backing store for newly created file %s"), path);
        }
        if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
 -              die("unable to add cache entry for %s", path);
 +              die(_("unable to add cache entry for %s"), path);
  }
  
  static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
        strbuf_release(&nbuf);
  
        if (close(fd) < 0)
 -              die_errno("closing file '%s'", path);
 +              die_errno(_("closing file '%s'"), path);
        return 0;
  }
  
@@@ -3608,7 -3500,7 +3626,7 @@@ static void create_one_file(char *path
                        ++nr;
                }
        }
 -      die_errno("unable to write file '%s' mode %o", path, mode);
 +      die_errno(_("unable to write file '%s' mode %o"), path, mode);
  }
  
  static void create_file(struct patch *patch)
@@@ -3653,7 -3545,6 +3671,7 @@@ static int write_out_one_reject(struct 
        char namebuf[PATH_MAX];
        struct fragment *frag;
        int cnt = 0;
 +      struct strbuf sb = STRBUF_INIT;
  
        for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) {
                if (!frag->rejected)
        if (!cnt) {
                if (apply_verbosely)
                        say_patch_name(stderr,
 -                                     "Applied patch ", patch, " cleanly.\n");
 +                                     _("Applied patch %s cleanly."), patch);
                return 0;
        }
  
         * contents are marked "rejected" at the patch level.
         */
        if (!patch->new_name)
 -              die("internal error");
 +              die(_("internal error"));
  
        /* Say this even without --verbose */
 -      say_patch_name(stderr, "Applying patch ", patch, " with");
 -      fprintf(stderr, " %d rejects...\n", cnt);
 +      strbuf_addf(&sb, Q_("Applying patch %%s with %d reject...",
 +                          "Applying patch %%s with %d rejects...",
 +                          cnt),
 +                  cnt);
 +      say_patch_name(stderr, sb.buf, patch);
 +      strbuf_release(&sb);
  
        cnt = strlen(patch->new_name);
        if (ARRAY_SIZE(namebuf) <= cnt + 5) {
                cnt = ARRAY_SIZE(namebuf) - 5;
 -              warning("truncating .rej filename to %.*s.rej",
 +              warning(_("truncating .rej filename to %.*s.rej"),
                        cnt - 1, patch->new_name);
        }
        memcpy(namebuf, patch->new_name, cnt);
  
        rej = fopen(namebuf, "w");
        if (!rej)
 -              return error("cannot open %s: %s", namebuf, strerror(errno));
 +              return error(_("cannot open %s: %s"), namebuf, strerror(errno));
  
        /* Normal git tools never deal with .rej, so do not pretend
         * this is a git patch by saying --git nor give extended
             frag;
             cnt++, frag = frag->next) {
                if (!frag->rejected) {
 -                      fprintf(stderr, "Hunk #%d applied cleanly.\n", cnt);
 +                      fprintf_ln(stderr, _("Hunk #%d applied cleanly."), cnt);
                        continue;
                }
 -              fprintf(stderr, "Rejected hunk #%d.\n", cnt);
 +              fprintf_ln(stderr, _("Rejected hunk #%d."), cnt);
                fprintf(rej, "%.*s", frag->size, frag->patch);
                if (frag->patch[frag->size-1] != '\n')
                        fputc('\n', rej);
@@@ -3795,8 -3682,15 +3813,8 @@@ static void prefix_patches(struct patc
        if (!prefix || p->is_toplevel_relative)
                return;
        for ( ; p; p = p->next) {
 -              if (p->new_name == p->old_name) {
 -                      char *prefixed = p->new_name;
 -                      prefix_one(&prefixed);
 -                      p->new_name = p->old_name = prefixed;
 -              }
 -              else {
 -                      prefix_one(&p->new_name);
 -                      prefix_one(&p->old_name);
 -              }
 +              prefix_one(&p->new_name);
 +              prefix_one(&p->old_name);
        }
  }
  
  static int apply_patch(int fd, const char *filename, int options)
  {
        size_t offset;
 -      struct strbuf buf = STRBUF_INIT;
 +      struct strbuf buf = STRBUF_INIT; /* owns the patch text */
        struct patch *list = NULL, **listp = &list;
        int skipped_patch = 0;
  
 -      /* FIXME - memory leak when using multiple patch files as inputs */
 -      memset(&fn_table, 0, sizeof(struct string_list));
        patch_input_file = filename;
        read_patch_file(&buf, fd);
        offset = 0;
                        listp = &patch->next;
                }
                else {
 -                      /* perhaps free it a bit better? */
 -                      free(patch);
 +                      free_patch(patch);
                        skipped_patch++;
                }
                offset += nr;
        }
  
        if (!list && !skipped_patch)
 -              die("unrecognized input");
 +              die(_("unrecognized input"));
  
        if (whitespace_error && (ws_error_action == die_on_ws_error))
                apply = 0;
  
        if (check_index) {
                if (read_cache() < 0)
 -                      die("unable to read index file");
 +                      die(_("unable to read index file"));
        }
  
        if ((check || apply) &&
        if (summary)
                summary_patch_list(list);
  
 +      free_patch_list(list);
        strbuf_release(&buf);
 +      string_list_clear(&fn_table, 0);
        return 0;
  }
  
@@@ -3966,66 -3861,66 +3984,66 @@@ int cmd_apply(int argc, const char **ar
        const char *whitespace_option = NULL;
  
        struct option builtin_apply_options[] = {
 -              { OPTION_CALLBACK, 0, "exclude", NULL, "path",
 -                      "don't apply changes matching the given path",
 +              { OPTION_CALLBACK, 0, "exclude", NULL, N_("path"),
 +                      N_("don't apply changes matching the given path"),
                        0, option_parse_exclude },
 -              { OPTION_CALLBACK, 0, "include", NULL, "path",
 -                      "apply changes matching the given path",
 +              { OPTION_CALLBACK, 0, "include", NULL, N_("path"),
 +                      N_("apply changes matching the given path"),
                        0, option_parse_include },
 -              { OPTION_CALLBACK, 'p', NULL, NULL, "num",
 -                      "remove <num> leading slashes from traditional diff paths",
 +              { OPTION_CALLBACK, 'p', NULL, NULL, N_("num"),
 +                      N_("remove <num> leading slashes from traditional diff paths"),
                        0, option_parse_p },
                OPT_BOOLEAN(0, "no-add", &no_add,
 -                      "ignore additions made by the patch"),
 +                      N_("ignore additions made by the patch")),
                OPT_BOOLEAN(0, "stat", &diffstat,
 -                      "instead of applying the patch, output diffstat for the input"),
 +                      N_("instead of applying the patch, output diffstat for the input")),
                OPT_NOOP_NOARG(0, "allow-binary-replacement"),
                OPT_NOOP_NOARG(0, "binary"),
                OPT_BOOLEAN(0, "numstat", &numstat,
 -                      "shows number of added and deleted lines in decimal notation"),
 +                      N_("shows number of added and deleted lines in decimal notation")),
                OPT_BOOLEAN(0, "summary", &summary,
 -                      "instead of applying the patch, output a summary for the input"),
 +                      N_("instead of applying the patch, output a summary for the input")),
                OPT_BOOLEAN(0, "check", &check,
 -                      "instead of applying the patch, see if the patch is applicable"),
 +                      N_("instead of applying the patch, see if the patch is applicable")),
                OPT_BOOLEAN(0, "index", &check_index,
 -                      "make sure the patch is applicable to the current index"),
 +                      N_("make sure the patch is applicable to the current index")),
                OPT_BOOLEAN(0, "cached", &cached,
 -                      "apply a patch without touching the working tree"),
 +                      N_("apply a patch without touching the working tree")),
                OPT_BOOLEAN(0, "apply", &force_apply,
 -                      "also apply the patch (use with --stat/--summary/--check)"),
 +                      N_("also apply the patch (use with --stat/--summary/--check)")),
                OPT_FILENAME(0, "build-fake-ancestor", &fake_ancestor,
 -                      "build a temporary index based on embedded index information"),
 +                      N_("build a temporary index based on embedded index information")),
                { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
 -                      "paths are separated with NUL character",
 +                      N_("paths are separated with NUL character"),
                        PARSE_OPT_NOARG, option_parse_z },
                OPT_INTEGER('C', NULL, &p_context,
 -                              "ensure at least <n> lines of context match"),
 -              { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action",
 -                      "detect new or modified lines that have whitespace errors",
 +                              N_("ensure at least <n> lines of context match")),
 +              { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, N_("action"),
 +                      N_("detect new or modified lines that have whitespace errors"),
                        0, option_parse_whitespace },
                { OPTION_CALLBACK, 0, "ignore-space-change", NULL, NULL,
 -                      "ignore changes in whitespace when finding context",
 +                      N_("ignore changes in whitespace when finding context"),
                        PARSE_OPT_NOARG, option_parse_space_change },
                { OPTION_CALLBACK, 0, "ignore-whitespace", NULL, NULL,
 -                      "ignore changes in whitespace when finding context",
 +                      N_("ignore changes in whitespace when finding context"),
                        PARSE_OPT_NOARG, option_parse_space_change },
                OPT_BOOLEAN('R', "reverse", &apply_in_reverse,
 -                      "apply the patch in reverse"),
 +                      N_("apply the patch in reverse")),
                OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero,
 -                      "don't expect at least one line of context"),
 +                      N_("don't expect at least one line of context")),
                OPT_BOOLEAN(0, "reject", &apply_with_reject,
 -                      "leave the rejected hunks in corresponding *.rej files"),
 +                      N_("leave the rejected hunks in corresponding *.rej files")),
                OPT_BOOLEAN(0, "allow-overlap", &allow_overlap,
 -                      "allow overlapping hunks"),
 -              OPT__VERBOSE(&apply_verbosely, "be verbose"),
 +                      N_("allow overlapping hunks")),
 +              OPT__VERBOSE(&apply_verbosely, N_("be verbose")),
                OPT_BIT(0, "inaccurate-eof", &options,
 -                      "tolerate incorrectly detected missing new-line at the end of file",
 +                      N_("tolerate incorrectly detected missing new-line at the end of file"),
                        INACCURATE_EOF),
                OPT_BIT(0, "recount", &options,
 -                      "do not trust the line counts in the hunk headers",
 +                      N_("do not trust the line counts in the hunk headers"),
                        RECOUNT),
 -              { OPTION_CALLBACK, 0, "directory", NULL, "root",
 -                      "prepend <root> to all filenames",
 +              { OPTION_CALLBACK, 0, "directory", NULL, N_("root"),
 +                      N_("prepend <root> to all filenames"),
                        0, option_parse_directory },
                OPT_END()
        };
        if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor))
                apply = 0;
        if (check_index && is_not_gitdir)
 -              die("--index outside a repository");
 +              die(_("--index outside a repository"));
        if (cached) {
                if (is_not_gitdir)
 -                      die("--cached outside a repository");
 +                      die(_("--cached outside a repository"));
                check_index = 1;
        }
        for (i = 0; i < argc; i++) {
  
                fd = open(arg, O_RDONLY);
                if (fd < 0)
 -                      die_errno("can't open patch '%s'", arg);
 +                      die_errno(_("can't open patch '%s'"), arg);
                read_stdin = 0;
                set_default_whitespace_mode(whitespace_option);
                errs |= apply_patch(fd, arg, options);
                    squelch_whitespace_errors < whitespace_error) {
                        int squelched =
                                whitespace_error - squelch_whitespace_errors;
 -                      warning("squelched %d "
 -                              "whitespace error%s",
 -                              squelched,
 -                              squelched == 1 ? "" : "s");
 +                      warning(Q_("squelched %d whitespace error",
 +                                 "squelched %d whitespace errors",
 +                                 squelched),
 +                              squelched);
                }
                if (ws_error_action == die_on_ws_error)
 -                      die("%d line%s add%s whitespace errors.",
 -                          whitespace_error,
 -                          whitespace_error == 1 ? "" : "s",
 -                          whitespace_error == 1 ? "s" : "");
 +                      die(Q_("%d line adds whitespace errors.",
 +                             "%d lines add whitespace errors.",
 +                             whitespace_error),
 +                          whitespace_error);
                if (applied_after_fixing_ws && apply)
                        warning("%d line%s applied after"
                                " fixing whitespace errors.",
                                applied_after_fixing_ws,
                                applied_after_fixing_ws == 1 ? "" : "s");
                else if (whitespace_error)
 -                      warning("%d line%s add%s whitespace errors.",
 -                              whitespace_error,
 -                              whitespace_error == 1 ? "" : "s",
 -                              whitespace_error == 1 ? "s" : "");
 +                      warning(Q_("%d line adds whitespace errors.",
 +                                 "%d lines add whitespace errors.",
 +                                 whitespace_error),
 +                              whitespace_error);
        }
  
        if (update_index) {
                if (write_cache(newfd, active_cache, active_nr) ||
                    commit_locked_index(&lock_file))
 -                      die("Unable to write new index file");
 +                      die(_("Unable to write new index file"));
        }
  
        return !!errs;
diff --combined t/t4103-apply-binary.sh
index 99627bc6d69f17a8dce0ad318764e32cd9d9f507,1b420e3b5fc2a7130d2b10bcd5660670b9d780c8..b1b906b1bb58ad9f96ee8f6146a2d49ec999e3f2
@@@ -8,30 -8,28 +8,28 @@@ test_description='git apply handling bi
  '
  . ./test-lib.sh
  
- # setup
- cat >file1 <<EOF
- A quick brown fox jumps over the lazy dog.
- A tiny little penguin runs around in circles.
- There is a flag with Linux written on it.
- A slow black-and-white panda just sits there,
- munching on his bamboo.
- EOF
- cat file1 >file2
- cat file1 >file4
- test_expect_success 'setup' "
+ test_expect_success 'setup' '
+       cat >file1 <<-\EOF &&
+       A quick brown fox jumps over the lazy dog.
+       A tiny little penguin runs around in circles.
+       There is a flag with Linux written on it.
+       A slow black-and-white panda just sits there,
+       munching on his bamboo.
+       EOF
+       cat file1 >file2 &&
+       cat file1 >file4 &&
        git update-index --add --remove file1 file2 file4 &&
-       git commit -m 'Initial Version' 2>/dev/null &&
+       git commit -m "Initial Version" 2>/dev/null &&
  
        git checkout -b binary &&
-       "$PERL_PATH" -pe 'y/x/\000/' <file1 >file3 &&
 -      perl -pe "y/x/\000/" <file1 >file3 &&
++      "$PERL_PATH" -pe "y/x/\000/" <file1 >file3 &&
        cat file3 >file4 &&
        git add file2 &&
-       "$PERL_PATH" -pe 'y/\000/v/' <file3 >file1 &&
 -      perl -pe "y/\000/v/" <file3 >file1 &&
++      "$PERL_PATH" -pe "y/\000/v/" <file3 >file1 &&
        rm -f file2 &&
        git update-index --add --remove file1 file2 file3 file4 &&
-       git commit -m 'Second Version' &&
+       git commit -m "Second Version" &&
  
        git diff-tree -p master binary >B.diff &&
        git diff-tree -p -C master binary >C.diff &&
        git diff-tree -p --full-index master binary >B-index.diff &&
        git diff-tree -p -C --full-index master binary >C-index.diff &&
  
+       git diff-tree -p --binary --no-prefix master binary -- file3 >B0.diff &&
        git init other-repo &&
-       (cd other-repo &&
-        git fetch .. master &&
-        git reset --hard FETCH_HEAD
+       (
+               cd other-repo &&
+               git fetch .. master &&
+               git reset --hard FETCH_HEAD
        )
- "
+ '
  
  test_expect_success 'stat binary diff -- should not fail.' \
        'git checkout master &&
         git apply --stat --summary B.diff'
  
+ test_expect_success 'stat binary -p0 diff -- should not fail.' '
+        git checkout master &&
+        git apply --stat -p0 B0.diff
+ '
  test_expect_success 'stat binary diff (copy) -- should not fail.' \
        'git checkout master &&
         git apply --stat --summary C.diff'
@@@ -143,4 -149,10 +149,10 @@@ test_expect_success 'apply binary diff 
         git apply --allow-binary-replacement --index CF.diff &&
         test -z "$(git diff --name-status binary)"'
  
+ test_expect_success 'apply binary -p0 diff' '
+       do_reset &&
+       git apply -p0 --index B0.diff &&
+       test -z "$(git diff --name-status binary -- file3)"
+ '
  test_done