Merge branch 'nd/fetch-ref-summary'
[gitweb.git] / builtin / apply.c
index d940125ee20d02e52a16065832d3d681d3e44f61..1a488f9e888b6e9ebb1357a6cf3f7f007cf732fe 100644 (file)
 #include "ll-merge.h"
 #include "rerere.h"
 
+enum ws_error_action {
+       nowarn_ws_error,
+       warn_on_ws_error,
+       die_on_ws_error,
+       correct_ws_error
+};
+
+
+enum ws_ignore {
+       ignore_ws_none,
+       ignore_ws_change
+};
+
+/*
+ * We need to keep track of how symlinks in the preimage are
+ * manipulated by the patches.  A patch to add a/b/c where a/b
+ * is a symlink should not be allowed to affect the directory
+ * the symlink points at, but if the same patch removes a/b,
+ * it is perfectly fine, as the patch removes a/b to make room
+ * to create a directory a/b so that a/b/c can be created.
+ *
+ * See also "struct string_list symlink_changes" in "struct
+ * apply_state".
+ */
+#define SYMLINK_GOES_AWAY 01
+#define SYMLINK_IN_RESULT 02
+
 struct apply_state {
        const char *prefix;
        int prefix_length;
 
+       /* These are lock_file related */
+       struct lock_file *lock_file;
+       int newfd;
+
        /* These control what gets looked at and modified */
+       int apply; /* this is not a dry-run */
        int cached; /* apply to the index only */
        int check; /* preimage must match working tree, don't actually apply */
        int check_index; /* preimage must match the indexed version */
@@ -33,121 +65,116 @@ struct apply_state {
 
        /* These control cosmetic aspect of the output */
        int diffstat; /* just show a diffstat, and don't actually apply */
+       int numstat; /* just show a numeric diffstat, and don't actually apply */
+       int summary; /* just report creation, deletion, etc, and don't actually apply */
 
        /* These boolean parameters control how the apply is done */
        int allow_overlap;
        int apply_in_reverse;
        int apply_with_reject;
        int apply_verbosely;
+       int no_add;
+       int threeway;
        int unidiff_zero;
+       int unsafe_paths;
+
+       /* Other non boolean parameters */
+       const char *fake_ancestor;
+       const char *patch_input_file;
+       int line_termination;
+       struct strbuf root;
+       int p_value;
+       int p_value_known;
+       unsigned int p_context;
+
+       /* Exclude and include path parameters */
+       struct string_list limit_by_name;
+       int has_include;
+
+       /* Various "current state" */
+       int linenr; /* current line number */
+       struct string_list symlink_changes; /* we have to track symlinks */
+
+       /*
+        * For "diff-stat" like behaviour, we keep track of the biggest change
+        * we've seen, and the longest filename. That allows us to do simple
+        * scaling.
+        */
+       int max_change;
+       int max_len;
+
+       /*
+        * Records filenames that have been touched, in order to handle
+        * the case where more than one patches touch the same file.
+        */
+       struct string_list fn_table;
+
+       /* These control whitespace errors */
+       enum ws_error_action ws_error_action;
+       enum ws_ignore ws_ignore_action;
+       const char *whitespace_option;
+       int whitespace_error;
+       int squelch_whitespace_errors;
+       int applied_after_fixing_ws;
 };
 
-/*
- *  --numstat does numeric diffstat, and doesn't actually apply
- *  --index-info shows the old and new index info for paths if available.
- */
-static int newfd = -1;
-
-static int state_p_value = 1;
-static int p_value_known;
-static int numstat;
-static int summary;
-static int apply = 1;
-static int no_add;
-static int threeway;
-static int unsafe_paths;
-static const char *fake_ancestor;
-static int line_termination = '\n';
-static unsigned int p_context = UINT_MAX;
 static const char * const apply_usage[] = {
        N_("git apply [<options>] [<patch>...]"),
        NULL
 };
 
-static enum ws_error_action {
-       nowarn_ws_error,
-       warn_on_ws_error,
-       die_on_ws_error,
-       correct_ws_error
-} ws_error_action = warn_on_ws_error;
-static int whitespace_error;
-static int squelch_whitespace_errors = 5;
-static int applied_after_fixing_ws;
-
-static enum ws_ignore {
-       ignore_ws_none,
-       ignore_ws_change
-} ws_ignore_action = ignore_ws_none;
-
-
-static const char *patch_input_file;
-static struct strbuf root = STRBUF_INIT;
-
-static void parse_whitespace_option(const char *option)
+static void parse_whitespace_option(struct apply_state *state, const char *option)
 {
        if (!option) {
-               ws_error_action = warn_on_ws_error;
+               state->ws_error_action = warn_on_ws_error;
                return;
        }
        if (!strcmp(option, "warn")) {
-               ws_error_action = warn_on_ws_error;
+               state->ws_error_action = warn_on_ws_error;
                return;
        }
        if (!strcmp(option, "nowarn")) {
-               ws_error_action = nowarn_ws_error;
+               state->ws_error_action = nowarn_ws_error;
                return;
        }
        if (!strcmp(option, "error")) {
-               ws_error_action = die_on_ws_error;
+               state->ws_error_action = die_on_ws_error;
                return;
        }
        if (!strcmp(option, "error-all")) {
-               ws_error_action = die_on_ws_error;
-               squelch_whitespace_errors = 0;
+               state->ws_error_action = die_on_ws_error;
+               state->squelch_whitespace_errors = 0;
                return;
        }
        if (!strcmp(option, "strip") || !strcmp(option, "fix")) {
-               ws_error_action = correct_ws_error;
+               state->ws_error_action = correct_ws_error;
                return;
        }
        die(_("unrecognized whitespace option '%s'"), option);
 }
 
-static void parse_ignorewhitespace_option(const char *option)
+static void parse_ignorewhitespace_option(struct apply_state *state,
+                                         const char *option)
 {
        if (!option || !strcmp(option, "no") ||
            !strcmp(option, "false") || !strcmp(option, "never") ||
            !strcmp(option, "none")) {
-               ws_ignore_action = ignore_ws_none;
+               state->ws_ignore_action = ignore_ws_none;
                return;
        }
        if (!strcmp(option, "change")) {
-               ws_ignore_action = ignore_ws_change;
+               state->ws_ignore_action = ignore_ws_change;
                return;
        }
        die(_("unrecognized whitespace ignore option '%s'"), option);
 }
 
-static void set_default_whitespace_mode(const char *whitespace_option)
+static void set_default_whitespace_mode(struct apply_state *state)
 {
-       if (!whitespace_option && !apply_default_whitespace)
-               ws_error_action = (apply ? warn_on_ws_error : nowarn_ws_error);
+       if (!state->whitespace_option && !apply_default_whitespace)
+               state->ws_error_action = (state->apply ? warn_on_ws_error : nowarn_ws_error);
 }
 
-/*
- * For "diff-stat" like behaviour, we keep track of the biggest change
- * we've seen, and the longest filename. That allows us to do simple
- * scaling.
- */
-static int max_change, max_len;
-
-/*
- * Various "current state", notably line numbers and what
- * file (and how) we're patching right now.. The "is_xxxx"
- * things are flags, where -1 means "don't know yet".
- */
-static int state_linenr = 1;
-
 /*
  * This represents one "hunk" from a patch, starting with
  * "@@ -oldpos,oldlines +newpos,newlines @@" marker.  The
@@ -267,13 +294,6 @@ struct image {
        struct line *line;
 };
 
-/*
- * Records filenames that have been touched, in order to handle
- * the case where more than one patches touch the same file.
- */
-
-static struct string_list fn_table;
-
 static uint32_t hash_line(const char *cp, size_t len)
 {
        size_t i;
@@ -444,7 +464,7 @@ static int is_dev_null(const char *str)
 #define TERM_SPACE     1
 #define TERM_TAB       2
 
-static int name_terminate(const char *name, int namelen, int c, int terminate)
+static int name_terminate(int c, int terminate)
 {
        if (c == ' ' && !(terminate & TERM_SPACE))
                return 0;
@@ -471,7 +491,10 @@ static char *squash_slash(char *name)
        return name;
 }
 
-static char *find_name_gnu(const char *line, const char *def, int p_value)
+static char *find_name_gnu(struct apply_state *state,
+                          const char *line,
+                          const char *def,
+                          int p_value)
 {
        struct strbuf name = STRBUF_INIT;
        char *cp;
@@ -495,8 +518,8 @@ static char *find_name_gnu(const char *line, const char *def, int p_value)
        }
 
        strbuf_remove(&name, 0, cp - name.buf);
-       if (root.len)
-               strbuf_insert(&name, 0, root.buf, root.len);
+       if (state->root.len)
+               strbuf_insert(&name, 0, state->root.buf, state->root.len);
        return squash_slash(strbuf_detach(&name, NULL));
 }
 
@@ -659,8 +682,12 @@ static size_t diff_timestamp_len(const char *line, size_t len)
        return line + len - end;
 }
 
-static char *find_name_common(const char *line, const char *def,
-                             int p_value, const char *end, int terminate)
+static char *find_name_common(struct apply_state *state,
+                             const char *line,
+                             const char *def,
+                             int p_value,
+                             const char *end,
+                             int terminate)
 {
        int len;
        const char *start = NULL;
@@ -673,7 +700,7 @@ static char *find_name_common(const char *line, const char *def,
                if (!end && isspace(c)) {
                        if (c == '\n')
                                break;
-                       if (name_terminate(start, line-start, c, terminate))
+                       if (name_terminate(c, terminate))
                                break;
                }
                line++;
@@ -698,32 +725,39 @@ static char *find_name_common(const char *line, const char *def,
                        return squash_slash(xstrdup(def));
        }
 
-       if (root.len) {
-               char *ret = xstrfmt("%s%.*s", root.buf, len, start);
+       if (state->root.len) {
+               char *ret = xstrfmt("%s%.*s", state->root.buf, len, start);
                return squash_slash(ret);
        }
 
        return squash_slash(xmemdupz(start, len));
 }
 
-static char *find_name(const char *line, char *def, int p_value, int terminate)
+static char *find_name(struct apply_state *state,
+                      const char *line,
+                      char *def,
+                      int p_value,
+                      int terminate)
 {
        if (*line == '"') {
-               char *name = find_name_gnu(line, def, p_value);
+               char *name = find_name_gnu(state, line, def, p_value);
                if (name)
                        return name;
        }
 
-       return find_name_common(line, def, p_value, NULL, terminate);
+       return find_name_common(state, line, def, p_value, NULL, terminate);
 }
 
-static char *find_name_traditional(const char *line, char *def, int p_value)
+static char *find_name_traditional(struct apply_state *state,
+                                  const char *line,
+                                  char *def,
+                                  int p_value)
 {
        size_t len;
        size_t date_len;
 
        if (*line == '"') {
-               char *name = find_name_gnu(line, def, p_value);
+               char *name = find_name_gnu(state, line, def, p_value);
                if (name)
                        return name;
        }
@@ -731,10 +765,10 @@ static char *find_name_traditional(const char *line, char *def, int p_value)
        len = strchrnul(line, '\n') - line;
        date_len = diff_timestamp_len(line, len);
        if (!date_len)
-               return find_name_common(line, def, p_value, NULL, TERM_TAB);
+               return find_name_common(state, line, def, p_value, NULL, TERM_TAB);
        len -= date_len;
 
-       return find_name_common(line, def, p_value, line + len, 0);
+       return find_name_common(state, line, def, p_value, line + len, 0);
 }
 
 static int count_slashes(const char *cp)
@@ -759,7 +793,7 @@ static int guess_p_value(struct apply_state *state, const char *nameline)
 
        if (is_dev_null(nameline))
                return -1;
-       name = find_name_traditional(nameline, NULL, 0);
+       name = find_name_traditional(state, nameline, NULL, 0);
        if (!name)
                return -1;
        cp = strchr(name, '/');
@@ -871,30 +905,30 @@ static void parse_traditional_patch(struct apply_state *state,
 
        first += 4;     /* skip "--- " */
        second += 4;    /* skip "+++ " */
-       if (!p_value_known) {
+       if (!state->p_value_known) {
                int p, q;
                p = guess_p_value(state, first);
                q = guess_p_value(state, second);
                if (p < 0) p = q;
                if (0 <= p && p == q) {
-                       state_p_value = p;
-                       p_value_known = 1;
+                       state->p_value = p;
+                       state->p_value_known = 1;
                }
        }
        if (is_dev_null(first)) {
                patch->is_new = 1;
                patch->is_delete = 0;
-               name = find_name_traditional(second, NULL, state_p_value);
+               name = find_name_traditional(state, second, NULL, state->p_value);
                patch->new_name = name;
        } else if (is_dev_null(second)) {
                patch->is_new = 0;
                patch->is_delete = 1;
-               name = find_name_traditional(first, NULL, state_p_value);
+               name = find_name_traditional(state, first, NULL, state->p_value);
                patch->old_name = name;
        } else {
                char *first_name;
-               first_name = find_name_traditional(first, NULL, state_p_value);
-               name = find_name_traditional(second, first_name, state_p_value);
+               first_name = find_name_traditional(state, first, NULL, state->p_value);
+               name = find_name_traditional(state, second, first_name, state->p_value);
                free(first_name);
                if (has_epoch_timestamp(first)) {
                        patch->is_new = 1;
@@ -910,10 +944,12 @@ static void parse_traditional_patch(struct apply_state *state,
                }
        }
        if (!name)
-               die(_("unable to find filename in patch at line %d"), state_linenr);
+               die(_("unable to find filename in patch at line %d"), state->linenr);
 }
 
-static int gitdiff_hdrend(const char *line, struct patch *patch)
+static int gitdiff_hdrend(struct apply_state *state,
+                         const char *line,
+                         struct patch *patch)
 {
        return -1;
 }
@@ -930,10 +966,14 @@ static int gitdiff_hdrend(const char *line, struct patch *patch)
 #define DIFF_OLD_NAME 0
 #define DIFF_NEW_NAME 1
 
-static void gitdiff_verify_name(const char *line, int isnull, char **name, int side)
+static void gitdiff_verify_name(struct apply_state *state,
+                               const char *line,
+                               int isnull,
+                               char **name,
+                               int side)
 {
        if (!*name && !isnull) {
-               *name = find_name(line, NULL, state_p_value, TERM_TAB);
+               *name = find_name(state, line, NULL, state->p_value, TERM_TAB);
                return;
        }
 
@@ -942,95 +982,119 @@ static void gitdiff_verify_name(const char *line, int isnull, char **name, int s
                char *another;
                if (isnull)
                        die(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"),
-                           *name, state_linenr);
-               another = find_name(line, NULL, state_p_value, TERM_TAB);
+                           *name, state->linenr);
+               another = find_name(state, line, NULL, state->p_value, TERM_TAB);
                if (!another || memcmp(another, *name, len + 1))
                        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"), state_linenr);
+                           _("git apply: bad git-diff - inconsistent old filename on line %d"), state->linenr);
                free(another);
        } 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"), state_linenr);
+                       die(_("git apply: bad git-diff - expected /dev/null on line %d"), state->linenr);
        }
 }
 
-static int gitdiff_oldname(const char *line, struct patch *patch)
+static int gitdiff_oldname(struct apply_state *state,
+                          const char *line,
+                          struct patch *patch)
 {
-       gitdiff_verify_name(line, patch->is_new, &patch->old_name,
+       gitdiff_verify_name(state, line,
+                           patch->is_new, &patch->old_name,
                            DIFF_OLD_NAME);
        return 0;
 }
 
-static int gitdiff_newname(const char *line, struct patch *patch)
+static int gitdiff_newname(struct apply_state *state,
+                          const char *line,
+                          struct patch *patch)
 {
-       gitdiff_verify_name(line, patch->is_delete, &patch->new_name,
+       gitdiff_verify_name(state, line,
+                           patch->is_delete, &patch->new_name,
                            DIFF_NEW_NAME);
        return 0;
 }
 
-static int gitdiff_oldmode(const char *line, struct patch *patch)
+static int gitdiff_oldmode(struct apply_state *state,
+                          const char *line,
+                          struct patch *patch)
 {
        patch->old_mode = strtoul(line, NULL, 8);
        return 0;
 }
 
-static int gitdiff_newmode(const char *line, struct patch *patch)
+static int gitdiff_newmode(struct apply_state *state,
+                          const char *line,
+                          struct patch *patch)
 {
        patch->new_mode = strtoul(line, NULL, 8);
        return 0;
 }
 
-static int gitdiff_delete(const char *line, struct patch *patch)
+static int gitdiff_delete(struct apply_state *state,
+                         const char *line,
+                         struct patch *patch)
 {
        patch->is_delete = 1;
        free(patch->old_name);
        patch->old_name = xstrdup_or_null(patch->def_name);
-       return gitdiff_oldmode(line, patch);
+       return gitdiff_oldmode(state, line, patch);
 }
 
-static int gitdiff_newfile(const char *line, struct patch *patch)
+static int gitdiff_newfile(struct apply_state *state,
+                          const char *line,
+                          struct patch *patch)
 {
        patch->is_new = 1;
        free(patch->new_name);
        patch->new_name = xstrdup_or_null(patch->def_name);
-       return gitdiff_newmode(line, patch);
+       return gitdiff_newmode(state, line, patch);
 }
 
-static int gitdiff_copysrc(const char *line, struct patch *patch)
+static int gitdiff_copysrc(struct apply_state *state,
+                          const char *line,
+                          struct patch *patch)
 {
        patch->is_copy = 1;
        free(patch->old_name);
-       patch->old_name = find_name(line, NULL, state_p_value ? state_p_value - 1 : 0, 0);
+       patch->old_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
        return 0;
 }
 
-static int gitdiff_copydst(const char *line, struct patch *patch)
+static int gitdiff_copydst(struct apply_state *state,
+                          const char *line,
+                          struct patch *patch)
 {
        patch->is_copy = 1;
        free(patch->new_name);
-       patch->new_name = find_name(line, NULL, state_p_value ? state_p_value - 1 : 0, 0);
+       patch->new_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
        return 0;
 }
 
-static int gitdiff_renamesrc(const char *line, struct patch *patch)
+static int gitdiff_renamesrc(struct apply_state *state,
+                            const char *line,
+                            struct patch *patch)
 {
        patch->is_rename = 1;
        free(patch->old_name);
-       patch->old_name = find_name(line, NULL, state_p_value ? state_p_value - 1 : 0, 0);
+       patch->old_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
        return 0;
 }
 
-static int gitdiff_renamedst(const char *line, struct patch *patch)
+static int gitdiff_renamedst(struct apply_state *state,
+                            const char *line,
+                            struct patch *patch)
 {
        patch->is_rename = 1;
        free(patch->new_name);
-       patch->new_name = find_name(line, NULL, state_p_value ? state_p_value - 1 : 0, 0);
+       patch->new_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
        return 0;
 }
 
-static int gitdiff_similarity(const char *line, struct patch *patch)
+static int gitdiff_similarity(struct apply_state *state,
+                             const char *line,
+                             struct patch *patch)
 {
        unsigned long val = strtoul(line, NULL, 10);
        if (val <= 100)
@@ -1038,7 +1102,9 @@ static int gitdiff_similarity(const char *line, struct patch *patch)
        return 0;
 }
 
-static int gitdiff_dissimilarity(const char *line, struct patch *patch)
+static int gitdiff_dissimilarity(struct apply_state *state,
+                                const char *line,
+                                struct patch *patch)
 {
        unsigned long val = strtoul(line, NULL, 10);
        if (val <= 100)
@@ -1046,7 +1112,9 @@ static int gitdiff_dissimilarity(const char *line, struct patch *patch)
        return 0;
 }
 
-static int gitdiff_index(const char *line, struct patch *patch)
+static int gitdiff_index(struct apply_state *state,
+                        const char *line,
+                        struct patch *patch)
 {
        /*
         * index line is N hexadecimal, "..", N hexadecimal,
@@ -1083,7 +1151,9 @@ static int gitdiff_index(const char *line, struct patch *patch)
  * This is normal for a diff that doesn't change anything: we'll fall through
  * into the next diff. Tell the parser to break out.
  */
-static int gitdiff_unrecognized(const char *line, struct patch *patch)
+static int gitdiff_unrecognized(struct apply_state *state,
+                               const char *line,
+                               struct patch *patch)
 {
        return -1;
 }
@@ -1092,15 +1162,17 @@ static int gitdiff_unrecognized(const char *line, struct patch *patch)
  * 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)
+static const char *skip_tree_prefix(struct apply_state *state,
+                                   const char *line,
+                                   int llen)
 {
        int nslash;
        int i;
 
-       if (!state_p_value)
+       if (!state->p_value)
                return (llen && line[0] == '/') ? NULL : line;
 
-       nslash = state_p_value;
+       nslash = state->p_value;
        for (i = 0; i < llen; i++) {
                int ch = line[i];
                if (ch == '/' && --nslash <= 0)
@@ -1117,7 +1189,9 @@ static const char *skip_tree_prefix(const char *line, int llen)
  * 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(const char *line, int llen)
+static char *git_header_name(struct apply_state *state,
+                            const char *line,
+                            int llen)
 {
        const char *name;
        const char *second = NULL;
@@ -1135,7 +1209,7 @@ static char *git_header_name(const char *line, int llen)
                        goto free_and_fail1;
 
                /* strip the a/b prefix including trailing slash */
-               cp = skip_tree_prefix(first.buf, first.len);
+               cp = skip_tree_prefix(state, first.buf, first.len);
                if (!cp)
                        goto free_and_fail1;
                strbuf_remove(&first, 0, cp - first.buf);
@@ -1152,7 +1226,7 @@ static char *git_header_name(const char *line, int llen)
                if (*second == '"') {
                        if (unquote_c_style(&sp, second, NULL))
                                goto free_and_fail1;
-                       cp = skip_tree_prefix(sp.buf, sp.len);
+                       cp = skip_tree_prefix(state, sp.buf, sp.len);
                        if (!cp)
                                goto free_and_fail1;
                        /* They must match, otherwise ignore */
@@ -1163,7 +1237,7 @@ static char *git_header_name(const char *line, int llen)
                }
 
                /* unquoted second */
-               cp = skip_tree_prefix(second, line + llen - second);
+               cp = skip_tree_prefix(state, second, line + llen - second);
                if (!cp)
                        goto free_and_fail1;
                if (line + llen - cp != first.len ||
@@ -1178,7 +1252,7 @@ static char *git_header_name(const char *line, int llen)
        }
 
        /* unquoted first name */
-       name = skip_tree_prefix(line, llen);
+       name = skip_tree_prefix(state, line, llen);
        if (!name)
                return NULL;
 
@@ -1194,7 +1268,7 @@ static char *git_header_name(const char *line, int llen)
                        if (unquote_c_style(&sp, second, NULL))
                                goto free_and_fail2;
 
-                       np = skip_tree_prefix(sp.buf, sp.len);
+                       np = skip_tree_prefix(state, sp.buf, sp.len);
                        if (!np)
                                goto free_and_fail2;
 
@@ -1238,7 +1312,7 @@ static char *git_header_name(const char *line, int llen)
                         */
                        if (!name[len + 1])
                                return NULL; /* no postimage name */
-                       second = skip_tree_prefix(name + len + 1,
+                       second = skip_tree_prefix(state, name + len + 1,
                                                  line_len - (len + 1));
                        if (!second)
                                return NULL;
@@ -1254,7 +1328,11 @@ static char *git_header_name(const char *line, int llen)
 }
 
 /* Verify that we recognize the lines following a git header */
-static int parse_git_header(const char *line, int len, unsigned int size, struct patch *patch)
+static int parse_git_header(struct apply_state *state,
+                           const char *line,
+                           int len,
+                           unsigned int size,
+                           struct patch *patch)
 {
        unsigned long offset;
 
@@ -1268,20 +1346,20 @@ static int parse_git_header(const char *line, int len, unsigned int size, struct
         * or removing or adding empty files), so we get
         * the default name from the header.
         */
-       patch->def_name = git_header_name(line, len);
-       if (patch->def_name && root.len) {
-               char *s = xstrfmt("%s%s", root.buf, patch->def_name);
+       patch->def_name = git_header_name(state, line, len);
+       if (patch->def_name && state->root.len) {
+               char *s = xstrfmt("%s%s", state->root.buf, patch->def_name);
                free(patch->def_name);
                patch->def_name = s;
        }
 
        line += len;
        size -= len;
-       state_linenr++;
-       for (offset = len ; size > 0 ; offset += len, size -= len, line += len, state_linenr++) {
+       state->linenr++;
+       for (offset = len ; size > 0 ; offset += len, size -= len, line += len, state->linenr++) {
                static const struct opentry {
                        const char *str;
-                       int (*fn)(const char *, struct patch *);
+                       int (*fn)(struct apply_state *, const char *, struct patch *);
                } optable[] = {
                        { "@@ -", gitdiff_hdrend },
                        { "--- ", gitdiff_oldname },
@@ -1311,7 +1389,7 @@ static int parse_git_header(const char *line, int len, unsigned int size, struct
                        int oplen = strlen(p->str);
                        if (len < oplen || memcmp(p->str, line, oplen))
                                continue;
-                       if (p->fn(line + oplen, patch) < 0)
+                       if (p->fn(state, line + oplen, patch) < 0)
                                return offset;
                        break;
                }
@@ -1449,7 +1527,7 @@ static int find_header(struct apply_state *state,
        patch->is_new = patch->is_delete = -1;
        patch->old_mode = patch->new_mode = 0;
        patch->old_name = patch->new_name = NULL;
-       for (offset = 0; size > 0; offset += len, size -= len, line += len, state_linenr++) {
+       for (offset = 0; size > 0; offset += len, size -= len, line += len, state->linenr++) {
                unsigned long nextlen;
 
                len = linelen(line, size);
@@ -1470,7 +1548,7 @@ static int find_header(struct apply_state *state,
                        if (parse_fragment_header(line, len, &dummy) < 0)
                                continue;
                        die(_("patch fragment without header at line %d: %.*s"),
-                           state_linenr, (int)len-1, line);
+                           state->linenr, (int)len-1, line);
                }
 
                if (size < len + 6)
@@ -1481,7 +1559,7 @@ static int find_header(struct apply_state *state,
                 * or mode change, so we handle that specially
                 */
                if (!memcmp("diff --git ", line, 11)) {
-                       int git_hdr_len = parse_git_header(line, len, size, patch);
+                       int git_hdr_len = parse_git_header(state, line, len, size, patch);
                        if (git_hdr_len <= len)
                                continue;
                        if (!patch->old_name && !patch->new_name) {
@@ -1490,14 +1568,14 @@ static int find_header(struct apply_state *state,
                                               "%d leading pathname component (line %d)",
                                               "git diff header lacks filename information when removing "
                                               "%d leading pathname components (line %d)",
-                                              state_p_value),
-                                           state_p_value, state_linenr);
+                                              state->p_value),
+                                           state->p_value, state->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 "
-                                   "(line %d)", state_linenr);
+                                   "(line %d)", state->linenr);
                        patch->is_toplevel_relative = 1;
                        *hdrsize = git_hdr_len;
                        return offset;
@@ -1519,35 +1597,42 @@ static int find_header(struct apply_state *state,
                /* Ok, we'll consider it a patch */
                parse_traditional_patch(state, line, line+len, patch);
                *hdrsize = len + nextlen;
-               state_linenr += 2;
+               state->linenr += 2;
                return offset;
        }
        return -1;
 }
 
-static void record_ws_error(unsigned result, const char *line, int len, int linenr)
+static void record_ws_error(struct apply_state *state,
+                           unsigned result,
+                           const char *line,
+                           int len,
+                           int linenr)
 {
        char *err;
 
        if (!result)
                return;
 
-       whitespace_error++;
-       if (squelch_whitespace_errors &&
-           squelch_whitespace_errors < whitespace_error)
+       state->whitespace_error++;
+       if (state->squelch_whitespace_errors &&
+           state->squelch_whitespace_errors < state->whitespace_error)
                return;
 
        err = whitespace_error_string(result);
        fprintf(stderr, "%s:%d: %s.\n%.*s\n",
-               patch_input_file, linenr, err, len, line);
+               state->patch_input_file, linenr, err, len, line);
        free(err);
 }
 
-static void check_whitespace(const char *line, int len, unsigned ws_rule)
+static void check_whitespace(struct apply_state *state,
+                            const char *line,
+                            int len,
+                            unsigned ws_rule)
 {
        unsigned result = ws_check(line + 1, len - 1, ws_rule);
 
-       record_ws_error(result, line + 1, len - 2, state_linenr);
+       record_ws_error(state, result, line + 1, len - 2, state->linenr);
 }
 
 /*
@@ -1580,11 +1665,11 @@ static int parse_fragment(struct apply_state *state,
        /* Parse the thing.. */
        line += len;
        size -= len;
-       state_linenr++;
+       state->linenr++;
        added = deleted = 0;
        for (offset = len;
             0 < size;
-            offset += len, size -= len, line += len, state_linenr++) {
+            offset += len, size -= len, line += len, state->linenr++) {
                if (!oldlines && !newlines)
                        break;
                len = linelen(line, size);
@@ -1601,21 +1686,21 @@ static int parse_fragment(struct apply_state *state,
                                leading++;
                        trailing++;
                        if (!state->apply_in_reverse &&
-                           ws_error_action == correct_ws_error)
-                               check_whitespace(line, len, patch->ws_rule);
+                           state->ws_error_action == correct_ws_error)
+                               check_whitespace(state, line, len, patch->ws_rule);
                        break;
                case '-':
                        if (state->apply_in_reverse &&
-                           ws_error_action != nowarn_ws_error)
-                               check_whitespace(line, len, patch->ws_rule);
+                           state->ws_error_action != nowarn_ws_error)
+                               check_whitespace(state, line, len, patch->ws_rule);
                        deleted++;
                        oldlines--;
                        trailing = 0;
                        break;
                case '+':
                        if (!state->apply_in_reverse &&
-                           ws_error_action != nowarn_ws_error)
-                               check_whitespace(line, len, patch->ws_rule);
+                           state->ws_error_action != nowarn_ws_error)
+                               check_whitespace(state, line, len, patch->ws_rule);
                        added++;
                        newlines--;
                        trailing = 0;
@@ -1683,10 +1768,10 @@ static int parse_single_patch(struct apply_state *state,
                int len;
 
                fragment = xcalloc(1, sizeof(*fragment));
-               fragment->linenr = state_linenr;
+               fragment->linenr = state->linenr;
                len = parse_fragment(state, line, size, patch, fragment);
                if (len <= 0)
-                       die(_("corrupt patch at line %d"), state_linenr);
+                       die(_("corrupt patch at line %d"), state->linenr);
                fragment->patch = line;
                fragment->size = len;
                oldlines += fragment->oldlines;
@@ -1772,7 +1857,8 @@ static char *inflate_it(const void *data, unsigned long size,
  * 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,
+static struct fragment *parse_binary_hunk(struct apply_state *state,
+                                         char **buf_p,
                                          unsigned long *sz_p,
                                          int *status_p,
                                          int *used_p)
@@ -1814,13 +1900,13 @@ static struct fragment *parse_binary_hunk(char **buf_p,
        else
                return NULL;
 
-       state_linenr++;
+       state->linenr++;
        buffer += llen;
        while (1) {
                int byte_length, max_byte_length, newsize;
                llen = linelen(buffer, size);
                used += llen;
-               state_linenr++;
+               state->linenr++;
                if (llen == 1) {
                        /* consume the blank line */
                        buffer++;
@@ -1874,7 +1960,7 @@ static struct fragment *parse_binary_hunk(char **buf_p,
        free(data);
        *status_p = -1;
        error(_("corrupt binary patch at line %d: %.*s"),
-             state_linenr-1, llen-1, buffer);
+             state->linenr-1, llen-1, buffer);
        return NULL;
 }
 
@@ -1883,7 +1969,10 @@ static struct fragment *parse_binary_hunk(char **buf_p,
  *   -1 in case of error,
  *   the length of the parsed binary patch otherwise
  */
-static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
+static int parse_binary(struct apply_state *state,
+                       char *buffer,
+                       unsigned long size,
+                       struct patch *patch)
 {
        /*
         * We have read "GIT binary patch\n"; what follows is a line
@@ -1904,15 +1993,15 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
        int status;
        int used, used_1;
 
-       forward = parse_binary_hunk(&buffer, &size, &status, &used);
+       forward = parse_binary_hunk(state, &buffer, &size, &status, &used);
        if (!forward && !status)
                /* there has to be one hunk (forward hunk) */
-               return error(_("unrecognized binary patch at line %d"), state_linenr-1);
+               return error(_("unrecognized binary patch at line %d"), state->linenr-1);
        if (status)
                /* otherwise we already gave an error message */
                return status;
 
-       reverse = parse_binary_hunk(&buffer, &size, &status, &used_1);
+       reverse = parse_binary_hunk(state, &buffer, &size, &status, &used_1);
        if (reverse)
                used += used_1;
        else if (status) {
@@ -1951,13 +2040,13 @@ static void prefix_patch(struct apply_state *state, struct patch *p)
  * include/exclude
  */
 
-static struct string_list limit_by_name;
-static int has_include;
-static void add_name_limit(const char *name, int exclude)
+static void add_name_limit(struct apply_state *state,
+                          const char *name,
+                          int exclude)
 {
        struct string_list_item *it;
 
-       it = string_list_append(&limit_by_name, name);
+       it = string_list_append(&state->limit_by_name, name);
        it->util = exclude ? NULL : (void *) 1;
 }
 
@@ -1975,8 +2064,8 @@ static int use_patch(struct apply_state *state, struct patch *p)
        }
 
        /* See if it matches any of exclude/include rule */
-       for (i = 0; i < limit_by_name.nr; i++) {
-               struct string_list_item *it = &limit_by_name.items[i];
+       for (i = 0; i < state->limit_by_name.nr; i++) {
+               struct string_list_item *it = &state->limit_by_name.items[i];
                if (!wildmatch(it->string, pathname, 0, NULL))
                        return (it->util != NULL);
        }
@@ -1986,7 +2075,7 @@ static int use_patch(struct apply_state *state, struct patch *p)
         * not used.  Otherwise, we saw bunch of exclude rules (or none)
         * and such a path is used.
         */
-       return !has_include;
+       return !state->has_include;
 }
 
 
@@ -2027,8 +2116,8 @@ static int parse_chunk(struct apply_state *state, char *buffer, unsigned long si
                if (llen == sizeof(git_binary) - 1 &&
                    !memcmp(git_binary, buffer + hd, llen)) {
                        int used;
-                       state_linenr++;
-                       used = parse_binary(buffer + hd + llen,
+                       state->linenr++;
+                       used = parse_binary(state, buffer + hd + llen,
                                            size - hd - llen, patch);
                        if (used < 0)
                                return -1;
@@ -2048,7 +2137,7 @@ static int parse_chunk(struct apply_state *state, char *buffer, unsigned long si
                                int len = strlen(binhdr[i]);
                                if (len < size - hd &&
                                    !memcmp(binhdr[i], buffer + hd, len)) {
-                                       state_linenr++;
+                                       state->linenr++;
                                        patch->is_binary = 1;
                                        patchsize = llen;
                                        break;
@@ -2060,9 +2149,9 @@ static int parse_chunk(struct apply_state *state, char *buffer, unsigned long si
                 * without metadata change.  A binary patch appears
                 * empty to us here.
                 */
-               if ((apply || state->check) &&
+               if ((state->apply || state->check) &&
                    (!patch->is_binary && !metadata_changes(patch)))
-                       die(_("patch with only garbage at line %d"), state_linenr);
+                       die(_("patch with only garbage at line %d"), state->linenr);
        }
 
        return offset + hdrsize + patchsize;
@@ -2100,7 +2189,7 @@ static const char pluses[] =
 static const char minuses[]=
 "----------------------------------------------------------------------";
 
-static void show_stats(struct patch *patch)
+static void show_stats(struct apply_state *state, struct patch *patch)
 {
        struct strbuf qname = STRBUF_INIT;
        char *cp = patch->new_name ? patch->new_name : patch->old_name;
@@ -2111,7 +2200,7 @@ static void show_stats(struct patch *patch)
        /*
         * "scale" the filename
         */
-       max = max_len;
+       max = state->max_len;
        if (max > 50)
                max = 50;
 
@@ -2134,13 +2223,13 @@ static void show_stats(struct patch *patch)
        /*
         * scale the add/delete
         */
-       max = max + max_change > 70 ? 70 - max : max_change;
+       max = max + state->max_change > 70 ? 70 - max : state->max_change;
        add = patch->lines_added;
        del = patch->lines_deleted;
 
-       if (max_change > 0) {
-               int total = ((add + del) * max + max_change / 2) / max_change;
-               add = (add * max + max_change / 2) / max_change;
+       if (state->max_change > 0) {
+               int total = ((add + del) * max + state->max_change / 2) / state->max_change;
+               add = (add * max + state->max_change / 2) / state->max_change;
                del = total - add;
        }
        printf("%5d %.*s%.*s\n", patch->lines_added + patch->lines_deleted,
@@ -2327,7 +2416,8 @@ static int line_by_line_fuzzy_match(struct image *img,
        return 1;
 }
 
-static int match_fragment(struct image *img,
+static int match_fragment(struct apply_state *state,
+                         struct image *img,
                          struct image *preimage,
                          struct image *postimage,
                          unsigned long try,
@@ -2348,7 +2438,7 @@ static int match_fragment(struct image *img,
                preimage_limit = preimage->nr;
                if (match_end && (preimage->nr + try_lno != img->nr))
                        return 0;
-       } else if (ws_error_action == correct_ws_error &&
+       } else if (state->ws_error_action == correct_ws_error &&
                   (ws_rule & WS_BLANK_AT_EOF)) {
                /*
                 * This hunk extends beyond the end of img, and we are
@@ -2416,11 +2506,11 @@ static int match_fragment(struct image *img,
         * fuzzy matching. We collect all the line length information because
         * we need it to adjust whitespace if we match.
         */
-       if (ws_ignore_action == ignore_ws_change)
+       if (state->ws_ignore_action == ignore_ws_change)
                return line_by_line_fuzzy_match(img, preimage, postimage,
                                                try, try_lno, preimage_limit);
 
-       if (ws_error_action != correct_ws_error)
+       if (state->ws_error_action != correct_ws_error)
                return 0;
 
        /*
@@ -2532,7 +2622,8 @@ static int match_fragment(struct image *img,
        return 0;
 }
 
-static int find_pos(struct image *img,
+static int find_pos(struct apply_state *state,
+                   struct image *img,
                    struct image *preimage,
                    struct image *postimage,
                    int line,
@@ -2576,7 +2667,7 @@ static int find_pos(struct image *img,
        try_lno = line;
 
        for (i = 0; ; i++) {
-               if (match_fragment(img, preimage, postimage,
+               if (match_fragment(state, img, preimage, postimage,
                                   try, try_lno, ws_rule,
                                   match_beginning, match_end))
                        return try_lno;
@@ -2783,17 +2874,17 @@ static int apply_one_fragment(struct apply_state *state,
                /* Fall-through for ' ' */
                case '+':
                        /* --no-add does not add new lines */
-                       if (first == '+' && no_add)
+                       if (first == '+' && state->no_add)
                                break;
 
                        start = newlines.len;
                        if (first != '+' ||
-                           !whitespace_error ||
-                           ws_error_action != correct_ws_error) {
+                           !state->whitespace_error ||
+                           state->ws_error_action != correct_ws_error) {
                                strbuf_add(&newlines, patch + 1, plen);
                        }
                        else {
-                               ws_fix_copy(&newlines, patch + 1, plen, ws_rule, &applied_after_fixing_ws);
+                               ws_fix_copy(&newlines, patch + 1, plen, ws_rule, &state->applied_after_fixing_ws);
                        }
                        add_line_info(&postimage, newlines.buf + start, newlines.len - start,
                                      (first == '+' ? 0 : LINE_COMMON));
@@ -2867,14 +2958,14 @@ static int apply_one_fragment(struct apply_state *state,
 
        for (;;) {
 
-               applied_pos = find_pos(img, &preimage, &postimage, pos,
+               applied_pos = find_pos(state, img, &preimage, &postimage, pos,
                                       ws_rule, match_beginning, match_end);
 
                if (applied_pos >= 0)
                        break;
 
                /* Am I at my context limits? */
-               if ((leading <= p_context) && (trailing <= p_context))
+               if ((leading <= state->p_context) && (trailing <= state->p_context))
                        break;
                if (match_beginning || match_end) {
                        match_beginning = match_end = 0;
@@ -2903,10 +2994,10 @@ static int apply_one_fragment(struct apply_state *state,
                if (new_blank_lines_at_end &&
                    preimage.nr + applied_pos >= img->nr &&
                    (ws_rule & WS_BLANK_AT_EOF) &&
-                   ws_error_action != nowarn_ws_error) {
-                       record_ws_error(WS_BLANK_AT_EOF, "+", 1,
+                   state->ws_error_action != nowarn_ws_error) {
+                       record_ws_error(state, WS_BLANK_AT_EOF, "+", 1,
                                        found_new_blank_lines_at_end);
-                       if (ws_error_action == correct_ws_error) {
+                       if (state->ws_error_action == correct_ws_error) {
                                while (new_blank_lines_at_end--)
                                        remove_last_line(&postimage);
                        }
@@ -2917,8 +3008,8 @@ static int apply_one_fragment(struct apply_state *state,
                         * apply_patch->check_patch_list->check_patch->
                         * apply_data->apply_fragments->apply_one_fragment
                         */
-                       if (ws_error_action == die_on_ws_error)
-                               apply = 0;
+                       if (state->ws_error_action == die_on_ws_error)
+                               state->apply = 0;
                }
 
                if (state->apply_verbosely && applied_pos != pos) {
@@ -3132,14 +3223,14 @@ static int read_file_or_gitlink(const struct cache_entry *ce, struct strbuf *buf
        return read_blob_object(buf, ce->sha1, ce->ce_mode);
 }
 
-static struct patch *in_fn_table(const char *name)
+static struct patch *in_fn_table(struct apply_state *state, const char *name)
 {
        struct string_list_item *item;
 
        if (name == NULL)
                return NULL;
 
-       item = string_list_lookup(&fn_table, name);
+       item = string_list_lookup(&state->fn_table, name);
        if (item != NULL)
                return (struct patch *)item->util;
 
@@ -3171,7 +3262,7 @@ static int was_deleted(struct patch *patch)
        return patch == PATH_WAS_DELETED;
 }
 
-static void add_to_fn_table(struct patch *patch)
+static void add_to_fn_table(struct apply_state *state, struct patch *patch)
 {
        struct string_list_item *item;
 
@@ -3181,7 +3272,7 @@ static void add_to_fn_table(struct patch *patch)
         * file creations and copies
         */
        if (patch->new_name != NULL) {
-               item = string_list_insert(&fn_table, patch->new_name);
+               item = string_list_insert(&state->fn_table, patch->new_name);
                item->util = patch;
        }
 
@@ -3190,12 +3281,12 @@ static void add_to_fn_table(struct patch *patch)
         * later chunks shouldn't patch old names
         */
        if ((patch->new_name == NULL) || (patch->is_rename)) {
-               item = string_list_insert(&fn_table, patch->old_name);
+               item = string_list_insert(&state->fn_table, patch->old_name);
                item->util = PATH_WAS_DELETED;
        }
 }
 
-static void prepare_fn_table(struct patch *patch)
+static void prepare_fn_table(struct apply_state *state, struct patch *patch)
 {
        /*
         * store information about incoming file deletion
@@ -3203,7 +3294,7 @@ static void prepare_fn_table(struct patch *patch)
        while (patch) {
                if ((patch->new_name == NULL) || (patch->is_rename)) {
                        struct string_list_item *item;
-                       item = string_list_insert(&fn_table, patch->old_name);
+                       item = string_list_insert(&state->fn_table, patch->old_name);
                        item->util = PATH_TO_BE_DELETED;
                }
                patch = patch->next;
@@ -3224,7 +3315,9 @@ static int checkout_target(struct index_state *istate,
        return 0;
 }
 
-static struct patch *previous_patch(struct patch *patch, int *gone)
+static struct patch *previous_patch(struct apply_state *state,
+                                   struct patch *patch,
+                                   int *gone)
 {
        struct patch *previous;
 
@@ -3232,7 +3325,7 @@ static struct patch *previous_patch(struct patch *patch, int *gone)
        if (patch->is_copy || patch->is_rename)
                return NULL; /* "git" patches do not depend on the order */
 
-       previous = in_fn_table(patch->old_name);
+       previous = in_fn_table(state, patch->old_name);
        if (!previous)
                return NULL;
 
@@ -3266,7 +3359,7 @@ static int load_patch_target(struct apply_state *state,
 {
        if (state->cached || state->check_index) {
                if (read_file_or_gitlink(ce, buf))
-                       return error(_("read of %s failed"), name);
+                       return error(_("failed to read %s"), name);
        } else if (name) {
                if (S_ISGITLINK(expected_mode)) {
                        if (ce)
@@ -3277,7 +3370,7 @@ static int load_patch_target(struct apply_state *state,
                        return error(_("reading from '%s' beyond a symbolic link"), name);
                } else {
                        if (read_old_data(st, name, buf))
-                               return error(_("read of %s failed"), name);
+                               return error(_("failed to read %s"), name);
                }
        }
        return 0;
@@ -3301,7 +3394,7 @@ static int load_preimage(struct apply_state *state,
        struct patch *previous;
        int status;
 
-       previous = previous_patch(patch, &status);
+       previous = previous_patch(state, patch, &status);
        if (status)
                return error(_("path %s has been renamed/deleted"),
                             patch->old_name);
@@ -3323,7 +3416,7 @@ static int load_preimage(struct apply_state *state,
                        free_fragment_list(patch->fragments);
                        patch->fragments = NULL;
                } else if (status) {
-                       return error(_("read of %s failed"), patch->old_name);
+                       return error(_("failed to read %s"), patch->old_name);
                }
        }
 
@@ -3492,12 +3585,12 @@ static int apply_data(struct apply_state *state, struct patch *patch,
        if (patch->direct_to_threeway ||
            apply_fragments(state, &image, patch) < 0) {
                /* Note: with --reject, apply_fragments() returns 0 */
-               if (!threeway || try_threeway(state, &image, patch, st, ce) < 0)
+               if (!state->threeway || try_threeway(state, &image, patch, st, ce) < 0)
                        return -1;
        }
        patch->result = image.buf;
        patch->resultsize = image.len;
-       add_to_fn_table(patch);
+       add_to_fn_table(state, patch);
        free(image.line_allocated);
 
        if (0 < patch->is_delete && patch->resultsize)
@@ -3531,7 +3624,7 @@ static int check_preimage(struct apply_state *state,
                return 0;
 
        assert(patch->is_new <= 0);
-       previous = previous_patch(patch, &status);
+       previous = previous_patch(state, patch, &status);
 
        if (status)
                return error(_("path %s has been renamed/deleted"), old_name);
@@ -3626,52 +3719,42 @@ static int check_to_create(struct apply_state *state,
        return 0;
 }
 
-/*
- * We need to keep track of how symlinks in the preimage are
- * manipulated by the patches.  A patch to add a/b/c where a/b
- * is a symlink should not be allowed to affect the directory
- * the symlink points at, but if the same patch removes a/b,
- * it is perfectly fine, as the patch removes a/b to make room
- * to create a directory a/b so that a/b/c can be created.
- */
-static struct string_list symlink_changes;
-#define SYMLINK_GOES_AWAY 01
-#define SYMLINK_IN_RESULT 02
-
-static uintptr_t register_symlink_changes(const char *path, uintptr_t what)
+static uintptr_t register_symlink_changes(struct apply_state *state,
+                                         const char *path,
+                                         uintptr_t what)
 {
        struct string_list_item *ent;
 
-       ent = string_list_lookup(&symlink_changes, path);
+       ent = string_list_lookup(&state->symlink_changes, path);
        if (!ent) {
-               ent = string_list_insert(&symlink_changes, path);
+               ent = string_list_insert(&state->symlink_changes, path);
                ent->util = (void *)0;
        }
        ent->util = (void *)(what | ((uintptr_t)ent->util));
        return (uintptr_t)ent->util;
 }
 
-static uintptr_t check_symlink_changes(const char *path)
+static uintptr_t check_symlink_changes(struct apply_state *state, const char *path)
 {
        struct string_list_item *ent;
 
-       ent = string_list_lookup(&symlink_changes, path);
+       ent = string_list_lookup(&state->symlink_changes, path);
        if (!ent)
                return 0;
        return (uintptr_t)ent->util;
 }
 
-static void prepare_symlink_changes(struct patch *patch)
+static void prepare_symlink_changes(struct apply_state *state, struct patch *patch)
 {
        for ( ; patch; patch = patch->next) {
                if ((patch->old_name && S_ISLNK(patch->old_mode)) &&
                    (patch->is_rename || patch->is_delete))
                        /* the symlink at patch->old_name is removed */
-                       register_symlink_changes(patch->old_name, SYMLINK_GOES_AWAY);
+                       register_symlink_changes(state, patch->old_name, SYMLINK_GOES_AWAY);
 
                if (patch->new_name && S_ISLNK(patch->new_mode))
                        /* the symlink at patch->new_name is created or remains */
-                       register_symlink_changes(patch->new_name, SYMLINK_IN_RESULT);
+                       register_symlink_changes(state, patch->new_name, SYMLINK_IN_RESULT);
        }
 }
 
@@ -3685,7 +3768,7 @@ static int path_is_beyond_symlink_1(struct apply_state *state, struct strbuf *na
                if (!name->len)
                        break;
                name->buf[name->len] = '\0';
-               change = check_symlink_changes(name->buf);
+               change = check_symlink_changes(state, name->buf);
                if (change & SYMLINK_IN_RESULT)
                        return 1;
                if (change & SYMLINK_GOES_AWAY)
@@ -3777,7 +3860,7 @@ static int check_patch(struct apply_state *state, struct patch *patch)
         * B and rename from A to B is handled the same way by asking
         * was_deleted().
         */
-       if ((tpatch = in_fn_table(new_name)) &&
+       if ((tpatch = in_fn_table(state, new_name)) &&
            (was_deleted(tpatch) || to_be_deleted(tpatch)))
                ok_if_exists = 1;
        else
@@ -3787,7 +3870,7 @@ static int check_patch(struct apply_state *state, struct patch *patch)
            ((0 < patch->is_new) || patch->is_rename || patch->is_copy)) {
                int err = check_to_create(state, new_name, ok_if_exists);
 
-               if (err && threeway) {
+               if (err && state->threeway) {
                        patch->direct_to_threeway = 1;
                } else switch (err) {
                case 0:
@@ -3828,7 +3911,7 @@ static int check_patch(struct apply_state *state, struct patch *patch)
                }
        }
 
-       if (!unsafe_paths)
+       if (!state->unsafe_paths)
                die_on_unsafe_path(patch);
 
        /*
@@ -3854,8 +3937,8 @@ static int check_patch_list(struct apply_state *state, struct patch *patch)
 {
        int err = 0;
 
-       prepare_symlink_changes(patch);
-       prepare_fn_table(patch);
+       prepare_symlink_changes(state, patch);
+       prepare_fn_table(state, patch);
        while (patch) {
                if (state->apply_verbosely)
                        say_patch_name(stderr,
@@ -3964,7 +4047,7 @@ static void build_fake_ancestor(struct patch *list, const char *filename)
        discard_index(&result);
 }
 
-static void stat_patch_list(struct patch *patch)
+static void stat_patch_list(struct apply_state *state, struct patch *patch)
 {
        int files, adds, dels;
 
@@ -3972,13 +4055,14 @@ static void stat_patch_list(struct patch *patch)
                files++;
                adds += patch->lines_added;
                dels += patch->lines_deleted;
-               show_stats(patch);
+               show_stats(state, patch);
        }
 
        print_stat_summary(stdout, files, adds, dels);
 }
 
-static void numstat_patch_list(struct patch *patch)
+static void numstat_patch_list(struct apply_state *state,
+                              struct patch *patch)
 {
        for ( ; patch; patch = patch->next) {
                const char *name;
@@ -3987,7 +4071,7 @@ static void numstat_patch_list(struct patch *patch)
                        printf("-\t-\t");
                else
                        printf("%d\t%d\t", patch->lines_added, patch->lines_deleted);
-               write_name_quoted(name, stdout, line_termination);
+               write_name_quoted(name, stdout, state->line_termination);
        }
 }
 
@@ -4069,25 +4153,25 @@ static void summary_patch_list(struct patch *patch)
        }
 }
 
-static void patch_stats(struct patch *patch)
+static void patch_stats(struct apply_state *state, struct patch *patch)
 {
        int lines = patch->lines_added + patch->lines_deleted;
 
-       if (lines > max_change)
-               max_change = lines;
+       if (lines > state->max_change)
+               state->max_change = lines;
        if (patch->old_name) {
                int len = quote_c_style(patch->old_name, NULL, NULL, 0);
                if (!len)
                        len = strlen(patch->old_name);
-               if (len > max_len)
-                       max_len = len;
+               if (len > state->max_len)
+                       state->max_len = len;
        }
        if (patch->new_name) {
                int len = quote_c_style(patch->new_name, NULL, NULL, 0);
                if (!len)
                        len = strlen(patch->new_name);
-               if (len > max_len)
-                       max_len = len;
+               if (len > state->max_len)
+                       state->max_len = len;
        }
 }
 
@@ -4426,7 +4510,7 @@ static int apply_patch(struct apply_state *state,
        struct patch *list = NULL, **listp = &list;
        int skipped_patch = 0;
 
-       patch_input_file = filename;
+       state->patch_input_file = filename;
        read_patch_file(&buf, fd);
        offset = 0;
        while (offset < buf.len) {
@@ -4444,7 +4528,7 @@ static int apply_patch(struct apply_state *state,
                if (state->apply_in_reverse)
                        reverse_patches(patch);
                if (use_patch(state, patch)) {
-                       patch_stats(patch);
+                       patch_stats(state, patch);
                        *listp = patch;
                        listp = &patch->next;
                }
@@ -4460,45 +4544,45 @@ static int apply_patch(struct apply_state *state,
        if (!list && !skipped_patch)
                die(_("unrecognized input"));
 
-       if (whitespace_error && (ws_error_action == die_on_ws_error))
-               apply = 0;
+       if (state->whitespace_error && (state->ws_error_action == die_on_ws_error))
+               state->apply = 0;
 
-       state->update_index = state->check_index && apply;
-       if (state->update_index && newfd < 0)
-               newfd = hold_locked_index(&lock_file, 1);
+       state->update_index = state->check_index && state->apply;
+       if (state->update_index && state->newfd < 0)
+               state->newfd = hold_locked_index(state->lock_file, 1);
 
        if (state->check_index) {
                if (read_cache() < 0)
                        die(_("unable to read index file"));
        }
 
-       if ((state->check || apply) &&
+       if ((state->check || state->apply) &&
            check_patch_list(state, list) < 0 &&
            !state->apply_with_reject)
                exit(1);
 
-       if (apply && write_out_results(state, list)) {
+       if (state->apply && write_out_results(state, list)) {
                if (state->apply_with_reject)
                        exit(1);
                /* with --3way, we still need to write the index out */
                return 1;
        }
 
-       if (fake_ancestor)
-               build_fake_ancestor(list, fake_ancestor);
+       if (state->fake_ancestor)
+               build_fake_ancestor(list, state->fake_ancestor);
 
        if (state->diffstat)
-               stat_patch_list(list);
+               stat_patch_list(state, list);
 
-       if (numstat)
-               numstat_patch_list(list);
+       if (state->numstat)
+               numstat_patch_list(state, list);
 
-       if (summary)
+       if (state->summary)
                summary_patch_list(list);
 
        free_patch_list(list);
        strbuf_release(&buf);
-       string_list_clear(&fn_table, 0);
+       string_list_clear(&state->fn_table, 0);
        return 0;
 }
 
@@ -4512,104 +4596,224 @@ static void git_apply_config(void)
 static int option_parse_exclude(const struct option *opt,
                                const char *arg, int unset)
 {
-       add_name_limit(arg, 1);
+       struct apply_state *state = opt->value;
+       add_name_limit(state, arg, 1);
        return 0;
 }
 
 static int option_parse_include(const struct option *opt,
                                const char *arg, int unset)
 {
-       add_name_limit(arg, 0);
-       has_include = 1;
+       struct apply_state *state = opt->value;
+       add_name_limit(state, arg, 0);
+       state->has_include = 1;
        return 0;
 }
 
 static int option_parse_p(const struct option *opt,
-                         const char *arg, int unset)
+                         const char *arg,
+                         int unset)
 {
-       state_p_value = atoi(arg);
-       p_value_known = 1;
+       struct apply_state *state = opt->value;
+       state->p_value = atoi(arg);
+       state->p_value_known = 1;
        return 0;
 }
 
 static int option_parse_space_change(const struct option *opt,
-                         const char *arg, int unset)
+                                    const char *arg, int unset)
 {
+       struct apply_state *state = opt->value;
        if (unset)
-               ws_ignore_action = ignore_ws_none;
+               state->ws_ignore_action = ignore_ws_none;
        else
-               ws_ignore_action = ignore_ws_change;
+               state->ws_ignore_action = ignore_ws_change;
        return 0;
 }
 
 static int option_parse_whitespace(const struct option *opt,
                                   const char *arg, int unset)
 {
-       const char **whitespace_option = opt->value;
-
-       *whitespace_option = arg;
-       parse_whitespace_option(arg);
+       struct apply_state *state = opt->value;
+       state->whitespace_option = arg;
+       parse_whitespace_option(state, arg);
        return 0;
 }
 
 static int option_parse_directory(const struct option *opt,
                                  const char *arg, int unset)
 {
-       strbuf_reset(&root);
-       strbuf_addstr(&root, arg);
-       strbuf_complete(&root, '/');
+       struct apply_state *state = opt->value;
+       strbuf_reset(&state->root);
+       strbuf_addstr(&state->root, arg);
+       strbuf_complete(&state->root, '/');
        return 0;
 }
 
-static void init_apply_state(struct apply_state *state, const char *prefix)
+static void init_apply_state(struct apply_state *state,
+                            const char *prefix,
+                            struct lock_file *lock_file)
 {
        memset(state, 0, sizeof(*state));
        state->prefix = prefix;
        state->prefix_length = state->prefix ? strlen(state->prefix) : 0;
+       state->lock_file = lock_file;
+       state->newfd = -1;
+       state->apply = 1;
+       state->line_termination = '\n';
+       state->p_value = 1;
+       state->p_context = UINT_MAX;
+       state->squelch_whitespace_errors = 5;
+       state->ws_error_action = warn_on_ws_error;
+       state->ws_ignore_action = ignore_ws_none;
+       state->linenr = 1;
+       string_list_init(&state->fn_table, 0);
+       string_list_init(&state->limit_by_name, 0);
+       string_list_init(&state->symlink_changes, 0);
+       strbuf_init(&state->root, 0);
 
        git_apply_config();
        if (apply_default_whitespace)
-               parse_whitespace_option(apply_default_whitespace);
+               parse_whitespace_option(state, apply_default_whitespace);
        if (apply_default_ignorewhitespace)
-               parse_ignorewhitespace_option(apply_default_ignorewhitespace);
+               parse_ignorewhitespace_option(state, apply_default_ignorewhitespace);
 }
 
 static void clear_apply_state(struct apply_state *state)
 {
-       /* empty for now */
+       string_list_clear(&state->limit_by_name, 0);
+       string_list_clear(&state->symlink_changes, 0);
+       strbuf_release(&state->root);
+
+       /* &state->fn_table is cleared at the end of apply_patch() */
 }
 
-int cmd_apply(int argc, const char **argv, const char *prefix)
+static void check_apply_state(struct apply_state *state, int force_apply)
+{
+       int is_not_gitdir = !startup_info->have_repository;
+
+       if (state->apply_with_reject && state->threeway)
+               die("--reject and --3way cannot be used together.");
+       if (state->cached && state->threeway)
+               die("--cached and --3way cannot be used together.");
+       if (state->threeway) {
+               if (is_not_gitdir)
+                       die(_("--3way outside a repository"));
+               state->check_index = 1;
+       }
+       if (state->apply_with_reject)
+               state->apply = state->apply_verbosely = 1;
+       if (!force_apply && (state->diffstat || state->numstat || state->summary || state->check || state->fake_ancestor))
+               state->apply = 0;
+       if (state->check_index && is_not_gitdir)
+               die(_("--index outside a repository"));
+       if (state->cached) {
+               if (is_not_gitdir)
+                       die(_("--cached outside a repository"));
+               state->check_index = 1;
+       }
+       if (state->check_index)
+               state->unsafe_paths = 0;
+       if (!state->lock_file)
+               die("BUG: state->lock_file should not be NULL");
+}
+
+static int apply_all_patches(struct apply_state *state,
+                            int argc,
+                            const char **argv,
+                            int options)
 {
        int i;
        int errs = 0;
-       int is_not_gitdir = !startup_info->have_repository;
+       int read_stdin = 1;
+
+       for (i = 0; i < argc; i++) {
+               const char *arg = argv[i];
+               int fd;
+
+               if (!strcmp(arg, "-")) {
+                       errs |= apply_patch(state, 0, "<stdin>", options);
+                       read_stdin = 0;
+                       continue;
+               } else if (0 < state->prefix_length)
+                       arg = prefix_filename(state->prefix,
+                                             state->prefix_length,
+                                             arg);
+
+               fd = open(arg, O_RDONLY);
+               if (fd < 0)
+                       die_errno(_("can't open patch '%s'"), arg);
+               read_stdin = 0;
+               set_default_whitespace_mode(state);
+               errs |= apply_patch(state, fd, arg, options);
+               close(fd);
+       }
+       set_default_whitespace_mode(state);
+       if (read_stdin)
+               errs |= apply_patch(state, 0, "<stdin>", options);
+
+       if (state->whitespace_error) {
+               if (state->squelch_whitespace_errors &&
+                   state->squelch_whitespace_errors < state->whitespace_error) {
+                       int squelched =
+                               state->whitespace_error - state->squelch_whitespace_errors;
+                       warning(Q_("squelched %d whitespace error",
+                                  "squelched %d whitespace errors",
+                                  squelched),
+                               squelched);
+               }
+               if (state->ws_error_action == die_on_ws_error)
+                       die(Q_("%d line adds whitespace errors.",
+                              "%d lines add whitespace errors.",
+                              state->whitespace_error),
+                           state->whitespace_error);
+               if (state->applied_after_fixing_ws && state->apply)
+                       warning("%d line%s applied after"
+                               " fixing whitespace errors.",
+                               state->applied_after_fixing_ws,
+                               state->applied_after_fixing_ws == 1 ? "" : "s");
+               else if (state->whitespace_error)
+                       warning(Q_("%d line adds whitespace errors.",
+                                  "%d lines add whitespace errors.",
+                                  state->whitespace_error),
+                               state->whitespace_error);
+       }
+
+       if (state->update_index) {
+               if (write_locked_index(&the_index, state->lock_file, COMMIT_LOCK))
+                       die(_("Unable to write new index file"));
+               state->newfd = -1;
+       }
+
+       return !!errs;
+}
+
+int cmd_apply(int argc, const char **argv, const char *prefix)
+{
        int force_apply = 0;
        int options = 0;
-       int read_stdin = 1;
+       int ret;
        struct apply_state state;
 
-       const char *whitespace_option = NULL;
-
        struct option builtin_apply_options[] = {
-               { OPTION_CALLBACK, 0, "exclude", NULL, N_("path"),
+               { OPTION_CALLBACK, 0, "exclude", &state, N_("path"),
                        N_("don't apply changes matching the given path"),
                        0, option_parse_exclude },
-               { OPTION_CALLBACK, 0, "include", NULL, N_("path"),
+               { OPTION_CALLBACK, 0, "include", &state, N_("path"),
                        N_("apply changes matching the given path"),
                        0, option_parse_include },
-               { OPTION_CALLBACK, 'p', NULL, NULL, N_("num"),
+               { OPTION_CALLBACK, 'p', NULL, &state, N_("num"),
                        N_("remove <num> leading slashes from traditional diff paths"),
                        0, option_parse_p },
-               OPT_BOOL(0, "no-add", &no_add,
+               OPT_BOOL(0, "no-add", &state.no_add,
                        N_("ignore additions made by the patch")),
                OPT_BOOL(0, "stat", &state.diffstat,
                        N_("instead of applying the patch, output diffstat for the input")),
                OPT_NOOP_NOARG(0, "allow-binary-replacement"),
                OPT_NOOP_NOARG(0, "binary"),
-               OPT_BOOL(0, "numstat", &numstat,
+               OPT_BOOL(0, "numstat", &state.numstat,
                        N_("show number of added and deleted lines in decimal notation")),
-               OPT_BOOL(0, "summary", &summary,
+               OPT_BOOL(0, "summary", &state.summary,
                        N_("instead of applying the patch, output a summary for the input")),
                OPT_BOOL(0, "check", &state.check,
                        N_("instead of applying the patch, see if the patch is applicable")),
@@ -4617,26 +4821,26 @@ int cmd_apply(int argc, const char **argv, const char *prefix)
                        N_("make sure the patch is applicable to the current index")),
                OPT_BOOL(0, "cached", &state.cached,
                        N_("apply a patch without touching the working tree")),
-               OPT_BOOL(0, "unsafe-paths", &unsafe_paths,
+               OPT_BOOL(0, "unsafe-paths", &state.unsafe_paths,
                        N_("accept a patch that touches outside the working area")),
                OPT_BOOL(0, "apply", &force_apply,
                        N_("also apply the patch (use with --stat/--summary/--check)")),
-               OPT_BOOL('3', "3way", &threeway,
+               OPT_BOOL('3', "3way", &state.threeway,
                         N_( "attempt three-way merge if a patch does not apply")),
-               OPT_FILENAME(0, "build-fake-ancestor", &fake_ancestor,
+               OPT_FILENAME(0, "build-fake-ancestor", &state.fake_ancestor,
                        N_("build a temporary index based on embedded index information")),
                /* Think twice before adding "--nul" synonym to this */
-               OPT_SET_INT('z', NULL, &line_termination,
+               OPT_SET_INT('z', NULL, &state.line_termination,
                        N_("paths are separated with NUL character"), '\0'),
-               OPT_INTEGER('C', NULL, &p_context,
+               OPT_INTEGER('C', NULL, &state.p_context,
                                N_("ensure at least <n> lines of context match")),
-               { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, N_("action"),
+               { OPTION_CALLBACK, 0, "whitespace", &state, N_("action"),
                        N_("detect new or modified lines that have whitespace errors"),
                        0, option_parse_whitespace },
-               { OPTION_CALLBACK, 0, "ignore-space-change", NULL, NULL,
+               { OPTION_CALLBACK, 0, "ignore-space-change", &state, NULL,
                        N_("ignore changes in whitespace when finding context"),
                        PARSE_OPT_NOARG, option_parse_space_change },
-               { OPTION_CALLBACK, 0, "ignore-whitespace", NULL, NULL,
+               { OPTION_CALLBACK, 0, "ignore-whitespace", &state, NULL,
                        N_("ignore changes in whitespace when finding context"),
                        PARSE_OPT_NOARG, option_parse_space_change },
                OPT_BOOL('R', "reverse", &state.apply_in_reverse,
@@ -4654,97 +4858,22 @@ int cmd_apply(int argc, const char **argv, const char *prefix)
                OPT_BIT(0, "recount", &options,
                        N_("do not trust the line counts in the hunk headers"),
                        RECOUNT),
-               { OPTION_CALLBACK, 0, "directory", NULL, N_("root"),
+               { OPTION_CALLBACK, 0, "directory", &state, N_("root"),
                        N_("prepend <root> to all filenames"),
                        0, option_parse_directory },
                OPT_END()
        };
 
-       init_apply_state(&state, prefix);
+       init_apply_state(&state, prefix, &lock_file);
 
        argc = parse_options(argc, argv, state.prefix, builtin_apply_options,
                        apply_usage, 0);
 
-       if (state.apply_with_reject && threeway)
-               die("--reject and --3way cannot be used together.");
-       if (state.cached && threeway)
-               die("--cached and --3way cannot be used together.");
-       if (threeway) {
-               if (is_not_gitdir)
-                       die(_("--3way outside a repository"));
-               state.check_index = 1;
-       }
-       if (state.apply_with_reject)
-               apply = state.apply_verbosely = 1;
-       if (!force_apply && (state.diffstat || numstat || summary || state.check || fake_ancestor))
-               apply = 0;
-       if (state.check_index && is_not_gitdir)
-               die(_("--index outside a repository"));
-       if (state.cached) {
-               if (is_not_gitdir)
-                       die(_("--cached outside a repository"));
-               state.check_index = 1;
-       }
-       if (state.check_index)
-               unsafe_paths = 0;
+       check_apply_state(&state, force_apply);
 
-       for (i = 0; i < argc; i++) {
-               const char *arg = argv[i];
-               int fd;
-
-               if (!strcmp(arg, "-")) {
-                       errs |= apply_patch(&state, 0, "<stdin>", options);
-                       read_stdin = 0;
-                       continue;
-               } else if (0 < state.prefix_length)
-                       arg = prefix_filename(state.prefix,
-                                             state.prefix_length,
-                                             arg);
-
-               fd = open(arg, O_RDONLY);
-               if (fd < 0)
-                       die_errno(_("can't open patch '%s'"), arg);
-               read_stdin = 0;
-               set_default_whitespace_mode(whitespace_option);
-               errs |= apply_patch(&state, fd, arg, options);
-               close(fd);
-       }
-       set_default_whitespace_mode(whitespace_option);
-       if (read_stdin)
-               errs |= apply_patch(&state, 0, "<stdin>", options);
-       if (whitespace_error) {
-               if (squelch_whitespace_errors &&
-                   squelch_whitespace_errors < whitespace_error) {
-                       int squelched =
-                               whitespace_error - squelch_whitespace_errors;
-                       warning(Q_("squelched %d whitespace error",
-                                  "squelched %d whitespace errors",
-                                  squelched),
-                               squelched);
-               }
-               if (ws_error_action == die_on_ws_error)
-                       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(Q_("%d line adds whitespace errors.",
-                                  "%d lines add whitespace errors.",
-                                  whitespace_error),
-                               whitespace_error);
-       }
-
-       if (state.update_index) {
-               if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
-                       die(_("Unable to write new index file"));
-       }
+       ret = apply_all_patches(&state, argc, argv, options);
 
        clear_apply_state(&state);
 
-       return !!errs;
+       return ret;
 }