apply: make init_apply_state() return -1 instead of exit()ing
[gitweb.git] / builtin / apply.c
index 1a488f9e888b6e9ebb1357a6cf3f7f007cf732fe..61fd3163638f892f238c9e7f8140cf1f207db3bd 100644 (file)
 #include "xdiff-interface.h"
 #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 */
-       int update_index; /* check_index && apply */
-
-       /* 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;
-};
+#include "apply.h"
 
 static const char * const apply_usage[] = {
        N_("git apply [<options>] [<patch>...]"),
        NULL
 };
 
-static void parse_whitespace_option(struct apply_state *state, const char *option)
-{
-       if (!option) {
-               state->ws_error_action = warn_on_ws_error;
-               return;
-       }
-       if (!strcmp(option, "warn")) {
-               state->ws_error_action = warn_on_ws_error;
-               return;
-       }
-       if (!strcmp(option, "nowarn")) {
-               state->ws_error_action = nowarn_ws_error;
-               return;
-       }
-       if (!strcmp(option, "error")) {
-               state->ws_error_action = die_on_ws_error;
-               return;
-       }
-       if (!strcmp(option, "error-all")) {
-               state->ws_error_action = die_on_ws_error;
-               state->squelch_whitespace_errors = 0;
-               return;
-       }
-       if (!strcmp(option, "strip") || !strcmp(option, "fix")) {
-               state->ws_error_action = correct_ws_error;
-               return;
-       }
-       die(_("unrecognized whitespace option '%s'"), 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")) {
-               state->ws_ignore_action = ignore_ws_none;
-               return;
-       }
-       if (!strcmp(option, "change")) {
-               state->ws_ignore_action = ignore_ws_change;
-               return;
-       }
-       die(_("unrecognized whitespace ignore option '%s'"), option);
-}
-
 static void set_default_whitespace_mode(struct apply_state *state)
 {
        if (!state->whitespace_option && !apply_default_whitespace)
@@ -431,10 +289,10 @@ static void say_patch_name(FILE *output, const char *fmt, struct patch *patch)
 
 #define SLOP (16)
 
-static void read_patch_file(struct strbuf *sb, int fd)
+static int read_patch_file(struct strbuf *sb, int fd)
 {
        if (strbuf_read(sb, fd, 0) < 0)
-               die_errno("git apply: failed to read");
+               return error_errno("git apply: failed to read");
 
        /*
         * Make sure that we have some slop in the buffer
@@ -443,6 +301,7 @@ static void read_patch_file(struct strbuf *sb, int fd)
         */
        strbuf_grow(sb, SLOP);
        memset(sb->buf + sb->len, 0, SLOP);
+       return 0;
 }
 
 static unsigned long linelen(const char *buffer, unsigned long size)
@@ -1514,6 +1373,14 @@ static int parse_fragment_header(const char *line, int len, struct fragment *fra
        return offset;
 }
 
+/*
+ * Find file diff header
+ *
+ * Returns:
+ *  -1 if no header was found
+ *  -128 in case of error
+ *   the size of the header in bytes (called "offset") otherwise
+ */
 static int find_header(struct apply_state *state,
                       const char *line,
                       unsigned long size,
@@ -1547,8 +1414,9 @@ static int find_header(struct apply_state *state,
                        struct fragment dummy;
                        if (parse_fragment_header(line, len, &dummy) < 0)
                                continue;
-                       die(_("patch fragment without header at line %d: %.*s"),
-                           state->linenr, (int)len-1, line);
+                       error(_("patch fragment without header at line %d: %.*s"),
+                                    state->linenr, (int)len-1, line);
+                       return -128;
                }
 
                if (size < len + 6)
@@ -1563,19 +1431,23 @@ static int find_header(struct apply_state *state,
                        if (git_hdr_len <= len)
                                continue;
                        if (!patch->old_name && !patch->new_name) {
-                               if (!patch->def_name)
-                                       die(Q_("git diff header lacks filename information when removing "
-                                              "%d leading pathname component (line %d)",
-                                              "git diff header lacks filename information when removing "
-                                              "%d leading pathname components (line %d)",
-                                              state->p_value),
-                                           state->p_value, state->linenr);
+                               if (!patch->def_name) {
+                                       error(Q_("git diff header lacks filename information when removing "
+                                                       "%d leading pathname component (line %d)",
+                                                       "git diff header lacks filename information when removing "
+                                                       "%d leading pathname components (line %d)",
+                                                       state->p_value),
+                                                    state->p_value, state->linenr);
+                                       return -128;
+                               }
                                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);
+                       if (!patch->is_delete && !patch->new_name) {
+                               error("git diff header lacks filename information "
+                                            "(line %d)", state->linenr);
+                               return -128;
+                       }
                        patch->is_toplevel_relative = 1;
                        *hdrsize = git_hdr_len;
                        return offset;
@@ -1753,6 +1625,10 @@ static int parse_fragment(struct apply_state *state,
  *
  * The (fragment->patch, fragment->size) pair points into the memory given
  * by the caller, not a copy, when we return.
+ *
+ * Returns:
+ *   -1 in case of error,
+ *   the number of bytes in the patch otherwise.
  */
 static int parse_single_patch(struct apply_state *state,
                              const char *line,
@@ -1770,8 +1646,10 @@ static int parse_single_patch(struct apply_state *state,
                fragment = xcalloc(1, sizeof(*fragment));
                fragment->linenr = state->linenr;
                len = parse_fragment(state, line, size, patch, fragment);
-               if (len <= 0)
-                       die(_("corrupt patch at line %d"), state->linenr);
+               if (len <= 0) {
+                       free(fragment);
+                       return error(_("corrupt patch at line %d"), state->linenr);
+               }
                fragment->patch = line;
                fragment->size = len;
                oldlines += fragment->oldlines;
@@ -1807,9 +1685,9 @@ static int parse_single_patch(struct apply_state *state,
                patch->is_delete = 0;
 
        if (0 < patch->is_new && oldlines)
-               die(_("new file %s depends on old contents"), patch->new_name);
+               return error(_("new file %s depends on old contents"), patch->new_name);
        if (0 < patch->is_delete && newlines)
-               die(_("deleted file %s still has contents"), patch->old_name);
+               return error(_("deleted file %s still has contents"), patch->old_name);
        if (!patch->is_delete && !newlines && context)
                fprintf_ln(stderr,
                           _("** warning: "
@@ -2078,13 +1956,16 @@ static int use_patch(struct apply_state *state, struct patch *p)
        return !state->has_include;
 }
 
-
 /*
  * Read the patch text in "buffer" that extends for "size" bytes; stop
  * reading after seeing a single patch (i.e. changes to a single file).
  * Create fragments (i.e. patch hunks) and hang them to the given patch.
- * Return the number of bytes consumed, so that the caller can call us
- * again for the next patch.
+ *
+ * Returns:
+ *   -1 if no header was found or parse_binary() failed,
+ *   -128 on another error,
+ *   the number of bytes consumed otherwise,
+ *     so that the caller can call us again for the next patch.
  */
 static int parse_chunk(struct apply_state *state, char *buffer, unsigned long size, struct patch *patch)
 {
@@ -2108,6 +1989,9 @@ static int parse_chunk(struct apply_state *state, char *buffer, unsigned long si
                                       size - offset - hdrsize,
                                       patch);
 
+       if (patchsize < 0)
+               return -128;
+
        if (!patchsize) {
                static const char git_binary[] = "GIT binary patch\n";
                int hd = hdrsize + offset;
@@ -2150,8 +2034,10 @@ static int parse_chunk(struct apply_state *state, char *buffer, unsigned long si
                 * empty to us here.
                 */
                if ((state->apply || state->check) &&
-                   (!patch->is_binary && !metadata_changes(patch)))
-                       die(_("patch with only garbage at line %d"), state->linenr);
+                   (!patch->is_binary && !metadata_changes(patch))) {
+                       error(_("patch with only garbage at line %d"), state->linenr);
+                       return -128;
+               }
        }
 
        return offset + hdrsize + patchsize;
@@ -3750,11 +3636,11 @@ static void prepare_symlink_changes(struct apply_state *state, struct patch *pat
                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(state, patch->old_name, SYMLINK_GOES_AWAY);
+                       register_symlink_changes(state, patch->old_name, APPLY_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(state, patch->new_name, SYMLINK_IN_RESULT);
+                       register_symlink_changes(state, patch->new_name, APPLY_SYMLINK_IN_RESULT);
        }
 }
 
@@ -3769,9 +3655,9 @@ static int path_is_beyond_symlink_1(struct apply_state *state, struct strbuf *na
                        break;
                name->buf[name->len] = '\0';
                change = check_symlink_changes(state, name->buf);
-               if (change & SYMLINK_IN_RESULT)
+               if (change & APPLY_SYMLINK_IN_RESULT)
                        return 1;
-               if (change & SYMLINK_GOES_AWAY)
+               if (change & APPLY_SYMLINK_GOES_AWAY)
                        /*
                         * This cannot be "return 0", because we may
                         * see a new one created at a higher level.
@@ -4500,6 +4386,15 @@ static struct lock_file lock_file;
 #define INACCURATE_EOF (1<<0)
 #define RECOUNT                (1<<1)
 
+/*
+ * Try to apply a patch.
+ *
+ * Returns:
+ *  -128 if a bad error happened (like patch unreadable)
+ *  -1 if patch did not apply and user cannot deal with it
+ *   0 if the patch applied
+ *   1 if the patch did not apply but user might fix it
+ */
 static int apply_patch(struct apply_state *state,
                       int fd,
                       const char *filename,
@@ -4509,9 +4404,11 @@ static int apply_patch(struct apply_state *state,
        struct strbuf buf = STRBUF_INIT; /* owns the patch text */
        struct patch *list = NULL, **listp = &list;
        int skipped_patch = 0;
+       int res = 0;
 
        state->patch_input_file = filename;
-       read_patch_file(&buf, fd);
+       if (read_patch_file(&buf, fd) < 0)
+               return -128;
        offset = 0;
        while (offset < buf.len) {
                struct patch *patch;
@@ -4523,6 +4420,10 @@ static int apply_patch(struct apply_state *state,
                nr = parse_chunk(state, buf.buf + offset, buf.len - offset, patch);
                if (nr < 0) {
                        free_patch(patch);
+                       if (nr == -128) {
+                               res = -128;
+                               goto end;
+                       }
                        break;
                }
                if (state->apply_in_reverse)
@@ -4541,8 +4442,11 @@ static int apply_patch(struct apply_state *state,
                offset += nr;
        }
 
-       if (!list && !skipped_patch)
-               die(_("unrecognized input"));
+       if (!list && !skipped_patch) {
+               error(_("unrecognized input"));
+               res = -128;
+               goto end;
+       }
 
        if (state->whitespace_error && (state->ws_error_action == die_on_ws_error))
                state->apply = 0;
@@ -4551,21 +4455,23 @@ static int apply_patch(struct apply_state *state,
        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_index && read_cache() < 0) {
+               error(_("unable to read index file"));
+               res = -128;
+               goto end;
        }
 
        if ((state->check || state->apply) &&
            check_patch_list(state, list) < 0 &&
-           !state->apply_with_reject)
-               exit(1);
+           !state->apply_with_reject) {
+               res = -1;
+               goto end;
+       }
 
        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;
+               res = state->apply_with_reject ? -1 : 1;
+               goto end;
        }
 
        if (state->fake_ancestor)
@@ -4580,17 +4486,11 @@ static int apply_patch(struct apply_state *state,
        if (state->summary)
                summary_patch_list(list);
 
+end:
        free_patch_list(list);
        strbuf_release(&buf);
        string_list_clear(&state->fn_table, 0);
-       return 0;
-}
-
-static void git_apply_config(void)
-{
-       git_config_get_string_const("apply.whitespace", &apply_default_whitespace);
-       git_config_get_string_const("apply.ignorewhitespace", &apply_default_ignorewhitespace);
-       git_config(git_default_config, NULL);
+       return res;
 }
 
 static int option_parse_exclude(const struct option *opt,
@@ -4636,7 +4536,8 @@ static int option_parse_whitespace(const struct option *opt,
 {
        struct apply_state *state = opt->value;
        state->whitespace_option = arg;
-       parse_whitespace_option(state, arg);
+       if (parse_whitespace_option(state, arg))
+               exit(1);
        return 0;
 }
 
@@ -4650,44 +4551,6 @@ static int option_parse_directory(const struct option *opt,
        return 0;
 }
 
-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(state, apply_default_whitespace);
-       if (apply_default_ignorewhitespace)
-               parse_ignorewhitespace_option(state, apply_default_ignorewhitespace);
-}
-
-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() */
-}
-
 static void check_apply_state(struct apply_state *state, int force_apply)
 {
        int is_not_gitdir = !startup_info->have_repository;
@@ -4724,6 +4587,7 @@ static int apply_all_patches(struct apply_state *state,
                             int options)
 {
        int i;
+       int res;
        int errs = 0;
        int read_stdin = 1;
 
@@ -4732,7 +4596,10 @@ static int apply_all_patches(struct apply_state *state,
                int fd;
 
                if (!strcmp(arg, "-")) {
-                       errs |= apply_patch(state, 0, "<stdin>", options);
+                       res = apply_patch(state, 0, "<stdin>", options);
+                       if (res < 0)
+                               goto end;
+                       errs |= res;
                        read_stdin = 0;
                        continue;
                } else if (0 < state->prefix_length)
@@ -4745,12 +4612,19 @@ static int apply_all_patches(struct apply_state *state,
                        die_errno(_("can't open patch '%s'"), arg);
                read_stdin = 0;
                set_default_whitespace_mode(state);
-               errs |= apply_patch(state, fd, arg, options);
+               res = apply_patch(state, fd, arg, options);
+               if (res < 0)
+                       goto end;
+               errs |= res;
                close(fd);
        }
        set_default_whitespace_mode(state);
-       if (read_stdin)
-               errs |= apply_patch(state, 0, "<stdin>", options);
+       if (read_stdin) {
+               res = apply_patch(state, 0, "<stdin>", options);
+               if (res < 0)
+                       goto end;
+               errs |= res;
+       }
 
        if (state->whitespace_error) {
                if (state->squelch_whitespace_errors &&
@@ -4786,6 +4660,9 @@ static int apply_all_patches(struct apply_state *state,
        }
 
        return !!errs;
+
+end:
+       exit(res == -1 ? 1 : 128);
 }
 
 int cmd_apply(int argc, const char **argv, const char *prefix)
@@ -4864,7 +4741,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
 
-       init_apply_state(&state, prefix, &lock_file);
+       if (init_apply_state(&state, prefix, &lock_file))
+               exit(128);
 
        argc = parse_options(argc, argv, state.prefix, builtin_apply_options,
                        apply_usage, 0);