submodule--helper: fix usage string for relative-path
[gitweb.git] / builtin / apply.c
index dd56a8e93adfffa6e40aec487829596184702099..1a488f9e888b6e9ebb1357a6cf3f7f007cf732fe 100644 (file)
@@ -34,10 +34,28 @@ enum ws_ignore {
        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 */
@@ -75,6 +93,7 @@ struct apply_state {
 
        /* 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
@@ -84,6 +103,12 @@ struct apply_state {
        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;
@@ -93,8 +118,6 @@ struct apply_state {
        int applied_after_fixing_ws;
 };
 
-static int newfd = -1;
-
 static const char * const apply_usage[] = {
        N_("git apply [<options>] [<patch>...]"),
        NULL
@@ -271,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;
@@ -448,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;
@@ -684,7 +700,7 @@ static char *find_name_common(struct apply_state *state,
                if (!end && isspace(c)) {
                        if (c == '\n')
                                break;
-                       if (name_terminate(start, line-start, c, terminate))
+                       if (name_terminate(c, terminate))
                                break;
                }
                line++;
@@ -3207,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;
 
@@ -3246,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;
 
@@ -3256,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;
        }
 
@@ -3265,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
@@ -3278,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;
@@ -3299,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;
 
@@ -3307,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;
 
@@ -3341,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)
@@ -3352,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;
@@ -3376,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);
@@ -3398,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);
                }
        }
 
@@ -3572,7 +3590,7 @@ static int apply_data(struct apply_state *state, struct patch *patch,
        }
        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)
@@ -3606,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);
@@ -3701,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);
        }
 }
 
@@ -3760,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)
@@ -3852,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
@@ -3929,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,
@@ -4540,8 +4548,8 @@ static int apply_patch(struct apply_state *state,
                state->apply = 0;
 
        state->update_index = state->check_index && state->apply;
-       if (state->update_index && newfd < 0)
-               newfd = hold_locked_index(&lock_file, 1);
+       if (state->update_index && state->newfd < 0)
+               state->newfd = hold_locked_index(state->lock_file, 1);
 
        if (state->check_index) {
                if (read_cache() < 0)
@@ -4574,7 +4582,7 @@ static int apply_patch(struct apply_state *state,
 
        free_patch_list(list);
        strbuf_release(&buf);
-       string_list_clear(&fn_table, 0);
+       string_list_clear(&state->fn_table, 0);
        return 0;
 }
 
@@ -4642,11 +4650,15 @@ static int option_parse_directory(const struct option *opt,
        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;
@@ -4655,6 +4667,9 @@ static void init_apply_state(struct apply_state *state, const char *prefix)
        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();
@@ -4667,17 +4682,117 @@ static void init_apply_state(struct apply_state *state, const char *prefix)
 static void clear_apply_state(struct apply_state *state)
 {
        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;
 
        struct option builtin_apply_options[] = {
@@ -4749,91 +4864,16 @@ int cmd_apply(int argc, const char **argv, const char *prefix)
                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 && 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;
-
-       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);
-       }
+       check_apply_state(&state, force_apply);
 
-       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;
 }