packaging: Add contrib installation
[platform/upstream/git.git] / add-patch.c
index 2c46fe5..2fad92c 100644 (file)
 #include "add-interactive.h"
 #include "strbuf.h"
 #include "run-command.h"
-#include "argv-array.h"
+#include "strvec.h"
 #include "pathspec.h"
 #include "color.h"
 #include "diff.h"
+#include "compat/terminal.h"
+#include "prompt.h"
 
 enum prompt_mode_type {
-       PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_HUNK
+       PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_ADDITION, PROMPT_HUNK,
+       PROMPT_MODE_MAX, /* must be last */
 };
 
-static const char *prompt_mode[] = {
-       N_("Stage mode change [y,n,a,q,d%s,?]? "),
-       N_("Stage deletion [y,n,a,q,d%s,?]? "),
-       N_("Stage this hunk [y,n,a,q,d%s,?]? ")
+struct patch_mode {
+       /*
+        * The magic constant 4 is chosen such that all patch modes
+        * provide enough space for three command-line arguments followed by a
+        * trailing `NULL`.
+        */
+       const char *diff_cmd[4], *apply_args[4], *apply_check_args[4];
+       unsigned is_reverse:1, index_only:1, apply_for_checkout:1;
+       const char *prompt_mode[PROMPT_MODE_MAX];
+       const char *edit_hunk_hint, *help_patch_text;
+};
+
+static struct patch_mode patch_mode_add = {
+       .diff_cmd = { "diff-files", NULL },
+       .apply_args = { "--cached", NULL },
+       .apply_check_args = { "--cached", NULL },
+       .prompt_mode = {
+               N_("Stage mode change [y,n,q,a,d%s,?]? "),
+               N_("Stage deletion [y,n,q,a,d%s,?]? "),
+               N_("Stage addition [y,n,q,a,d%s,?]? "),
+               N_("Stage this hunk [y,n,q,a,d%s,?]? ")
+       },
+       .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+                            "will immediately be marked for staging."),
+       .help_patch_text =
+               N_("y - stage this hunk\n"
+                  "n - do not stage this hunk\n"
+                  "q - quit; do not stage this hunk or any of the remaining "
+                       "ones\n"
+                  "a - stage this hunk and all later hunks in the file\n"
+                  "d - do not stage this hunk or any of the later hunks in "
+                       "the file\n")
+};
+
+static struct patch_mode patch_mode_stash = {
+       .diff_cmd = { "diff-index", "HEAD", NULL },
+       .apply_args = { "--cached", NULL },
+       .apply_check_args = { "--cached", NULL },
+       .prompt_mode = {
+               N_("Stash mode change [y,n,q,a,d%s,?]? "),
+               N_("Stash deletion [y,n,q,a,d%s,?]? "),
+               N_("Stash addition [y,n,q,a,d%s,?]? "),
+               N_("Stash this hunk [y,n,q,a,d%s,?]? "),
+       },
+       .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+                            "will immediately be marked for stashing."),
+       .help_patch_text =
+               N_("y - stash this hunk\n"
+                  "n - do not stash this hunk\n"
+                  "q - quit; do not stash this hunk or any of the remaining "
+                       "ones\n"
+                  "a - stash this hunk and all later hunks in the file\n"
+                  "d - do not stash this hunk or any of the later hunks in "
+                       "the file\n"),
+};
+
+static struct patch_mode patch_mode_reset_head = {
+       .diff_cmd = { "diff-index", "--cached", NULL },
+       .apply_args = { "-R", "--cached", NULL },
+       .apply_check_args = { "-R", "--cached", NULL },
+       .is_reverse = 1,
+       .index_only = 1,
+       .prompt_mode = {
+               N_("Unstage mode change [y,n,q,a,d%s,?]? "),
+               N_("Unstage deletion [y,n,q,a,d%s,?]? "),
+               N_("Unstage addition [y,n,q,a,d%s,?]? "),
+               N_("Unstage this hunk [y,n,q,a,d%s,?]? "),
+       },
+       .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+                            "will immediately be marked for unstaging."),
+       .help_patch_text =
+               N_("y - unstage this hunk\n"
+                  "n - do not unstage this hunk\n"
+                  "q - quit; do not unstage this hunk or any of the remaining "
+                       "ones\n"
+                  "a - unstage this hunk and all later hunks in the file\n"
+                  "d - do not unstage this hunk or any of the later hunks in "
+                       "the file\n"),
+};
+
+static struct patch_mode patch_mode_reset_nothead = {
+       .diff_cmd = { "diff-index", "-R", "--cached", NULL },
+       .apply_args = { "--cached", NULL },
+       .apply_check_args = { "--cached", NULL },
+       .index_only = 1,
+       .prompt_mode = {
+               N_("Apply mode change to index [y,n,q,a,d%s,?]? "),
+               N_("Apply deletion to index [y,n,q,a,d%s,?]? "),
+               N_("Apply addition to index [y,n,q,a,d%s,?]? "),
+               N_("Apply this hunk to index [y,n,q,a,d%s,?]? "),
+       },
+       .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+                            "will immediately be marked for applying."),
+       .help_patch_text =
+               N_("y - apply this hunk to index\n"
+                  "n - do not apply this hunk to index\n"
+                  "q - quit; do not apply this hunk or any of the remaining "
+                       "ones\n"
+                  "a - apply this hunk and all later hunks in the file\n"
+                  "d - do not apply this hunk or any of the later hunks in "
+                       "the file\n"),
+};
+
+static struct patch_mode patch_mode_checkout_index = {
+       .diff_cmd = { "diff-files", NULL },
+       .apply_args = { "-R", NULL },
+       .apply_check_args = { "-R", NULL },
+       .is_reverse = 1,
+       .prompt_mode = {
+               N_("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
+               N_("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
+               N_("Discard addition from worktree [y,n,q,a,d%s,?]? "),
+               N_("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
+       },
+       .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+                            "will immediately be marked for discarding."),
+       .help_patch_text =
+               N_("y - discard this hunk from worktree\n"
+                  "n - do not discard this hunk from worktree\n"
+                  "q - quit; do not discard this hunk or any of the remaining "
+                       "ones\n"
+                  "a - discard this hunk and all later hunks in the file\n"
+                  "d - do not discard this hunk or any of the later hunks in "
+                       "the file\n"),
+};
+
+static struct patch_mode patch_mode_checkout_head = {
+       .diff_cmd = { "diff-index", NULL },
+       .apply_for_checkout = 1,
+       .apply_check_args = { "-R", NULL },
+       .is_reverse = 1,
+       .prompt_mode = {
+               N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Discard addition from index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
+       },
+       .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+                            "will immediately be marked for discarding."),
+       .help_patch_text =
+               N_("y - discard this hunk from index and worktree\n"
+                  "n - do not discard this hunk from index and worktree\n"
+                  "q - quit; do not discard this hunk or any of the remaining "
+                       "ones\n"
+                  "a - discard this hunk and all later hunks in the file\n"
+                  "d - do not discard this hunk or any of the later hunks in "
+                       "the file\n"),
+};
+
+static struct patch_mode patch_mode_checkout_nothead = {
+       .diff_cmd = { "diff-index", "-R", NULL },
+       .apply_for_checkout = 1,
+       .apply_check_args = { NULL },
+       .prompt_mode = {
+               N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Apply addition to index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
+       },
+       .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+                            "will immediately be marked for applying."),
+       .help_patch_text =
+               N_("y - apply this hunk to index and worktree\n"
+                  "n - do not apply this hunk to index and worktree\n"
+                  "q - quit; do not apply this hunk or any of the remaining "
+                       "ones\n"
+                  "a - apply this hunk and all later hunks in the file\n"
+                  "d - do not apply this hunk or any of the later hunks in "
+                       "the file\n"),
+};
+
+static struct patch_mode patch_mode_worktree_head = {
+       .diff_cmd = { "diff-index", NULL },
+       .apply_args = { "-R", NULL },
+       .apply_check_args = { "-R", NULL },
+       .is_reverse = 1,
+       .prompt_mode = {
+               N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Discard addition from index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
+       },
+       .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+                            "will immediately be marked for discarding."),
+       .help_patch_text =
+               N_("y - discard this hunk from worktree\n"
+                  "n - do not discard this hunk from worktree\n"
+                  "q - quit; do not discard this hunk or any of the remaining "
+                       "ones\n"
+                  "a - discard this hunk and all later hunks in the file\n"
+                  "d - do not discard this hunk or any of the later hunks in "
+                       "the file\n"),
+};
+
+static struct patch_mode patch_mode_worktree_nothead = {
+       .diff_cmd = { "diff-index", "-R", NULL },
+       .apply_args = { NULL },
+       .apply_check_args = { NULL },
+       .prompt_mode = {
+               N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Apply addition to index and worktree [y,n,q,a,d%s,?]? "),
+               N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
+       },
+       .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
+                            "will immediately be marked for applying."),
+       .help_patch_text =
+               N_("y - apply this hunk to worktree\n"
+                  "n - do not apply this hunk to worktree\n"
+                  "q - quit; do not apply this hunk or any of the remaining "
+                       "ones\n"
+                  "a - apply this hunk and all later hunks in the file\n"
+                  "d - do not apply this hunk or any of the later hunks in "
+                       "the file\n"),
 };
 
 struct hunk_header {
@@ -44,11 +257,29 @@ struct add_p_state {
                struct hunk head;
                struct hunk *hunk;
                size_t hunk_nr, hunk_alloc;
-               unsigned deleted:1, mode_change:1,binary:1;
+               unsigned deleted:1, added:1, mode_change:1,binary:1;
        } *file_diff;
        size_t file_diff_nr;
+
+       /* patch mode */
+       struct patch_mode *mode;
+       const char *revision;
 };
 
+static void add_p_state_clear(struct add_p_state *s)
+{
+       size_t i;
+
+       strbuf_release(&s->answer);
+       strbuf_release(&s->buf);
+       strbuf_release(&s->plain);
+       strbuf_release(&s->colored);
+       for (i = 0; i < s->file_diff_nr; i++)
+               free(s->file_diff[i].hunk);
+       free(s->file_diff);
+       clear_add_i_state(&s->s);
+}
+
 static void err(struct add_p_state *s, const char *fmt, ...)
 {
        va_list args;
@@ -69,12 +300,12 @@ static void setup_child_process(struct add_p_state *s,
 
        va_start(ap, cp);
        while ((arg = va_arg(ap, const char *)))
-               argv_array_push(&cp->args, arg);
+               strvec_push(&cp->args, arg);
        va_end(ap);
 
        cp->git_cmd = 1;
-       argv_array_pushf(&cp->env_array,
-                        INDEX_ENVIRONMENT "=%s", s->s.r->index_file);
+       strvec_pushf(&cp->env_array,
+                    INDEX_ENVIRONMENT "=%s", s->s.r->index_file);
 }
 
 static int parse_range(const char **p,
@@ -153,7 +384,8 @@ static int is_octal(const char *p, size_t len)
 
 static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
 {
-       struct argv_array args = ARGV_ARRAY_INIT;
+       struct strvec args = STRVEC_INIT;
+       const char *diff_algorithm = s->s.interactive_diff_algorithm;
        struct strbuf *plain = &s->plain, *colored = NULL;
        struct child_process cp = CHILD_PROCESS_INIT;
        char *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\0';
@@ -162,41 +394,71 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
        struct hunk *hunk = NULL;
        int res;
 
+       strvec_pushv(&args, s->mode->diff_cmd);
+       if (diff_algorithm)
+               strvec_pushf(&args, "--diff-algorithm=%s", diff_algorithm);
+       if (s->revision) {
+               struct object_id oid;
+               strvec_push(&args,
+                           /* could be on an unborn branch */
+                           !strcmp("HEAD", s->revision) &&
+                           get_oid("HEAD", &oid) ?
+                           empty_tree_oid_hex() : s->revision);
+       }
+       color_arg_index = args.nr;
        /* Use `--no-color` explicitly, just in case `diff.color = always`. */
-       argv_array_pushl(&args, "diff-files", "-p", "--no-color", "--", NULL);
-       color_arg_index = args.argc - 2;
+       strvec_pushl(&args, "--no-color", "-p", "--", NULL);
        for (i = 0; i < ps->nr; i++)
-               argv_array_push(&args, ps->items[i].original);
+               strvec_push(&args, ps->items[i].original);
 
        setup_child_process(s, &cp, NULL);
-       cp.argv = args.argv;
+       cp.argv = args.v;
        res = capture_command(&cp, plain, 0);
        if (res) {
-               argv_array_clear(&args);
+               strvec_clear(&args);
                return error(_("could not parse diff"));
        }
        if (!plain->len) {
-               argv_array_clear(&args);
+               strvec_clear(&args);
                return 0;
        }
        strbuf_complete_line(plain);
 
        if (want_color_fd(1, -1)) {
                struct child_process colored_cp = CHILD_PROCESS_INIT;
+               const char *diff_filter = s->s.interactive_diff_filter;
 
                setup_child_process(s, &colored_cp, NULL);
-               xsnprintf((char *)args.argv[color_arg_index], 8, "--color");
-               colored_cp.argv = args.argv;
+               xsnprintf((char *)args.v[color_arg_index], 8, "--color");
+               colored_cp.argv = args.v;
                colored = &s->colored;
                res = capture_command(&colored_cp, colored, 0);
-               argv_array_clear(&args);
+               strvec_clear(&args);
                if (res)
                        return error(_("could not parse colored diff"));
+
+               if (diff_filter) {
+                       struct child_process filter_cp = CHILD_PROCESS_INIT;
+
+                       setup_child_process(s, &filter_cp,
+                                           diff_filter, NULL);
+                       filter_cp.git_cmd = 0;
+                       filter_cp.use_shell = 1;
+                       strbuf_reset(&s->buf);
+                       if (pipe_command(&filter_cp,
+                                        colored->buf, colored->len,
+                                        &s->buf, colored->len,
+                                        NULL, 0) < 0)
+                               return error(_("failed to run '%s'"),
+                                            diff_filter);
+                       strbuf_swap(colored, &s->buf);
+               }
+
                strbuf_complete_line(colored);
                colored_p = colored->buf;
                colored_pend = colored_p + colored->len;
        }
-       argv_array_clear(&args);
+       strvec_clear(&args);
 
        /* parse files and hunks */
        p = plain->buf;
@@ -209,11 +471,9 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
                        eol = pend;
 
                if (starts_with(p, "diff ")) {
-                       s->file_diff_nr++;
-                       ALLOC_GROW(s->file_diff, s->file_diff_nr,
+                       ALLOC_GROW_BY(s->file_diff, s->file_diff_nr, 1,
                                   file_diff_alloc);
                        file_diff = s->file_diff + s->file_diff_nr - 1;
-                       memset(file_diff, 0, sizeof(*file_diff));
                        hunk = &file_diff->head;
                        hunk->start = p - plain->buf;
                        if (colored_p)
@@ -226,7 +486,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
                        ; /* keep the rest of the file in a single "hunk" */
                else if (starts_with(p, "@@ ") ||
                         (hunk == &file_diff->head &&
-                         skip_prefix(p, "deleted file", &deleted))) {
+                         (skip_prefix(p, "deleted file", &deleted)))) {
                        if (marker == '-' || marker == '+')
                                /*
                                 * Should not happen; previous hunk did not end
@@ -234,11 +494,9 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
                                 */
                                hunk->splittable_into++;
 
-                       file_diff->hunk_nr++;
-                       ALLOC_GROW(file_diff->hunk, file_diff->hunk_nr,
+                       ALLOC_GROW_BY(file_diff->hunk, file_diff->hunk_nr, 1,
                                   file_diff->hunk_alloc);
                        hunk = file_diff->hunk + file_diff->hunk_nr - 1;
-                       memset(hunk, 0, sizeof(*hunk));
 
                        hunk->start = p - plain->buf;
                        if (colored)
@@ -255,12 +513,15 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
                         */
                        marker = *p;
                } else if (hunk == &file_diff->head &&
+                          starts_with(p, "new file")) {
+                       file_diff->added = 1;
+               } else if (hunk == &file_diff->head &&
                           skip_prefix(p, "old mode ", &mode_change) &&
                           is_octal(mode_change, eol - mode_change)) {
                        if (file_diff->mode_change)
                                BUG("double mode change?\n\n%.*s",
                                    (int)(eol - plain->buf), plain->buf);
-                       if (file_diff->hunk_nr++)
+                       if (file_diff->hunk_nr)
                                BUG("mode change in the middle?\n\n%.*s",
                                    (int)(eol - plain->buf), plain->buf);
 
@@ -269,9 +530,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
                         * is _part of_ the header "hunk".
                         */
                        file_diff->mode_change = 1;
-                       ALLOC_GROW(file_diff->hunk, file_diff->hunk_nr,
+                       ALLOC_GROW_BY(file_diff->hunk, file_diff->hunk_nr, 1,
                                   file_diff->hunk_alloc);
-                       memset(file_diff->hunk, 0, sizeof(struct hunk));
                        file_diff->hunk->start = p - plain->buf;
                        if (colored_p)
                                file_diff->hunk->colored_start =
@@ -298,8 +558,10 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
                           starts_with(p, "Binary files "))
                        file_diff->binary = 1;
 
-               if (file_diff->deleted && file_diff->mode_change)
-                       BUG("diff contains delete *and* a mode change?!?\n%.*s",
+               if (!!file_diff->deleted + !!file_diff->added +
+                   !!file_diff->mode_change > 1)
+                       BUG("diff can only contain delete *or* add *or* a "
+                           "mode change?!?\n%.*s",
                            (int)(eol - (plain->buf + file_diff->head.start)),
                            plain->buf + file_diff->head.start);
 
@@ -316,6 +578,9 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
                                                   colored_pend - colored_p);
                        if (colored_eol)
                                colored_p = colored_eol + 1;
+                       else if (p != pend)
+                               /* colored shorter than non-colored? */
+                               goto mismatched_output;
                        else
                                colored_p = colored_pend;
 
@@ -340,6 +605,15 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
                 */
                hunk->splittable_into++;
 
+       /* non-colored shorter than colored? */
+       if (colored_p != colored_pend) {
+mismatched_output:
+               error(_("mismatched output from interactive.diffFilter"));
+               advise(_("Your filter must maintain a one-to-one correspondence\n"
+                        "between its input and output lines."));
+               return -1;
+       }
+
        return 0;
 }
 
@@ -382,15 +656,23 @@ static void render_hunk(struct add_p_state *s, struct hunk *hunk,
                                - header->colored_extra_start;
                }
 
-               new_offset += delta;
+               if (s->mode->is_reverse)
+                       old_offset -= delta;
+               else
+                       new_offset += delta;
+
+               strbuf_addf(out, "@@ -%lu", old_offset);
+               if (header->old_count != 1)
+                       strbuf_addf(out, ",%lu", header->old_count);
+               strbuf_addf(out, " +%lu", new_offset);
+               if (header->new_count != 1)
+                       strbuf_addf(out, ",%lu", header->new_count);
+               strbuf_addstr(out, " @@");
 
-               strbuf_addf(out, "@@ -%lu,%lu +%lu,%lu @@",
-                           old_offset, header->old_count,
-                           new_offset, header->new_count);
                if (len)
                        strbuf_add(out, p, len);
                else if (colored)
-                       strbuf_addf(out, "%s\n", GIT_COLOR_RESET);
+                       strbuf_addf(out, "%s\n", s->s.reset_color);
                else
                        strbuf_addch(out, '\n');
        }
@@ -783,7 +1065,7 @@ static void recolor_hunk(struct add_p_state *s, struct hunk *hunk)
                              s->s.file_new_color :
                              s->s.context_color);
                strbuf_add(&s->colored, plain + current, eol - current);
-               strbuf_addstr(&s->colored, GIT_COLOR_RESET);
+               strbuf_addstr(&s->colored, s->s.reset_color);
                if (next > eol)
                        strbuf_add(&s->colored, plain + eol, next - eol);
                current = next;
@@ -805,11 +1087,10 @@ static int edit_hunk_manually(struct add_p_state *s, struct hunk *hunk)
                                "(context).\n"
                                "To remove '%c' lines, delete them.\n"
                                "Lines starting with %c will be removed.\n"),
-                             '-', '+', comment_line_char);
-       strbuf_commented_addf(&s->buf,
-                             _("If the patch applies cleanly, the edited hunk "
-                               "will immediately be\n"
-                               "marked for staging.\n"));
+                             s->mode->is_reverse ? '+' : '-',
+                             s->mode->is_reverse ? '-' : '+',
+                             comment_line_char);
+       strbuf_commented_addf(&s->buf, "%s", _(s->mode->edit_hunk_hint));
        /*
         * TRANSLATORS: 'it' refers to the patch mentioned in the previous
         * messages.
@@ -890,21 +1171,34 @@ static int run_apply_check(struct add_p_state *s,
        reassemble_patch(s, file_diff, 1, &s->buf);
 
        setup_child_process(s, &cp,
-                           "apply", "--cached", "--check", NULL);
+                           "apply", "--check", NULL);
+       strvec_pushv(&cp.args, s->mode->apply_check_args);
        if (pipe_command(&cp, s->buf.buf, s->buf.len, NULL, 0, NULL, 0))
                return error(_("'git apply --cached' failed"));
 
        return 0;
 }
 
+static int read_single_character(struct add_p_state *s)
+{
+       if (s->s.use_single_key) {
+               int res = read_key_without_echo(&s->answer);
+               printf("%s\n", res == EOF ? "" : s->answer.buf);
+               return res;
+       }
+
+       if (git_read_line_interactively(&s->answer) == EOF)
+               return EOF;
+       return 0;
+}
+
 static int prompt_yesno(struct add_p_state *s, const char *prompt)
 {
        for (;;) {
                color_fprintf(stdout, s->s.prompt_color, "%s", _(prompt));
                fflush(stdout);
-               if (strbuf_getline(&s->answer, stdin) == EOF)
+               if (read_single_character(s) == EOF)
                        return -1;
-               strbuf_trim_trailing_newline(&s->answer);
                switch (tolower(s->answer.buf[0])) {
                case 'n': return 0;
                case 'y': return 1;
@@ -923,7 +1217,7 @@ static int edit_hunk_loop(struct add_p_state *s,
        for (;;) {
                int res = edit_hunk_manually(s, hunk);
                if (res == 0) {
-                       /* abandonded */
+                       /* abandoned */
                        *hunk = backup;
                        return -1;
                }
@@ -957,6 +1251,57 @@ static int edit_hunk_loop(struct add_p_state *s,
        }
 }
 
+static int apply_for_checkout(struct add_p_state *s, struct strbuf *diff,
+                             int is_reverse)
+{
+       const char *reverse = is_reverse ? "-R" : NULL;
+       struct child_process check_index = CHILD_PROCESS_INIT;
+       struct child_process check_worktree = CHILD_PROCESS_INIT;
+       struct child_process apply_index = CHILD_PROCESS_INIT;
+       struct child_process apply_worktree = CHILD_PROCESS_INIT;
+       int applies_index, applies_worktree;
+
+       setup_child_process(s, &check_index,
+                           "apply", "--cached", "--check", reverse, NULL);
+       applies_index = !pipe_command(&check_index, diff->buf, diff->len,
+                                     NULL, 0, NULL, 0);
+
+       setup_child_process(s, &check_worktree,
+                           "apply", "--check", reverse, NULL);
+       applies_worktree = !pipe_command(&check_worktree, diff->buf, diff->len,
+                                        NULL, 0, NULL, 0);
+
+       if (applies_worktree && applies_index) {
+               setup_child_process(s, &apply_index,
+                                   "apply", "--cached", reverse, NULL);
+               pipe_command(&apply_index, diff->buf, diff->len,
+                            NULL, 0, NULL, 0);
+
+               setup_child_process(s, &apply_worktree,
+                                   "apply", reverse, NULL);
+               pipe_command(&apply_worktree, diff->buf, diff->len,
+                            NULL, 0, NULL, 0);
+
+               return 1;
+       }
+
+       if (!applies_index) {
+               err(s, _("The selected hunks do not apply to the index!"));
+               if (prompt_yesno(s, _("Apply them to the worktree "
+                                         "anyway? ")) > 0) {
+                       setup_child_process(s, &apply_worktree,
+                                           "apply", reverse, NULL);
+                       return pipe_command(&apply_worktree, diff->buf,
+                                           diff->len, NULL, 0, NULL, 0);
+               }
+               err(s, _("Nothing was applied.\n"));
+       } else
+               /* As a last resort, show the diff to the user */
+               fwrite(diff->buf, diff->len, 1, stderr);
+
+       return 0;
+}
+
 #define SUMMARY_HEADER_WIDTH 20
 #define SUMMARY_LINE_WIDTH 80
 static void summarize_hunk(struct add_p_state *s, struct hunk *hunk,
@@ -1005,13 +1350,6 @@ static size_t display_hunks(struct add_p_state *s,
        return end_index;
 }
 
-static const char help_patch_text[] =
-N_("y - stage this hunk\n"
-   "n - do not stage this hunk\n"
-   "q - quit; do not stage this hunk or any of the remaining ones\n"
-   "a - stage this and all the remaining hunks\n"
-   "d - do not stage this hunk nor any of the remaining hunks\n");
-
 static const char help_patch_remainder[] =
 N_("j - leave this hunk undecided, see next undecided hunk\n"
    "J - leave this hunk undecided, see next hunk\n"
@@ -1033,8 +1371,18 @@ static int patch_update_file(struct add_p_state *s,
        struct child_process cp = CHILD_PROCESS_INIT;
        int colored = !!s->colored.len, quit = 0;
        enum prompt_mode_type prompt_mode_type;
-
-       if (!file_diff->hunk_nr)
+       enum {
+               ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0,
+               ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK = 1 << 1,
+               ALLOW_GOTO_NEXT_HUNK = 1 << 2,
+               ALLOW_GOTO_NEXT_UNDECIDED_HUNK = 1 << 3,
+               ALLOW_SEARCH_AND_GOTO = 1 << 4,
+               ALLOW_SPLIT = 1 << 5,
+               ALLOW_EDIT = 1 << 6
+       } permitted = 0;
+
+       /* Empty added files have no hunks */
+       if (!file_diff->hunk_nr && !file_diff->added)
                return 0;
 
        strbuf_reset(&s->buf);
@@ -1043,21 +1391,25 @@ static int patch_update_file(struct add_p_state *s,
        for (;;) {
                if (hunk_index >= file_diff->hunk_nr)
                        hunk_index = 0;
-               hunk = file_diff->hunk + hunk_index;
-
+               hunk = file_diff->hunk_nr
+                               ? file_diff->hunk + hunk_index
+                               : &file_diff->head;
                undecided_previous = -1;
-               for (i = hunk_index - 1; i >= 0; i--)
-                       if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
-                               undecided_previous = i;
-                               break;
-                       }
-
                undecided_next = -1;
-               for (i = hunk_index + 1; i < file_diff->hunk_nr; i++)
-                       if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
-                               undecided_next = i;
-                               break;
-                       }
+
+               if (file_diff->hunk_nr) {
+                       for (i = hunk_index - 1; i >= 0; i--)
+                               if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
+                                       undecided_previous = i;
+                                       break;
+                               }
+
+                       for (i = hunk_index + 1; i < file_diff->hunk_nr; i++)
+                               if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
+                                       undecided_next = i;
+                                       break;
+                               }
+               }
 
                /* Everything decided? */
                if (undecided_previous < 0 && undecided_next < 0 &&
@@ -1065,43 +1417,62 @@ static int patch_update_file(struct add_p_state *s,
                        break;
 
                strbuf_reset(&s->buf);
-               render_hunk(s, hunk, 0, colored, &s->buf);
-               fputs(s->buf.buf, stdout);
-
-               strbuf_reset(&s->buf);
-               if (undecided_previous >= 0)
-                       strbuf_addstr(&s->buf, ",k");
-               if (hunk_index)
-                       strbuf_addstr(&s->buf, ",K");
-               if (undecided_next >= 0)
-                       strbuf_addstr(&s->buf, ",j");
-               if (hunk_index + 1 < file_diff->hunk_nr)
-                       strbuf_addstr(&s->buf, ",J");
-               if (file_diff->hunk_nr > 1)
-                       strbuf_addstr(&s->buf, ",g,/");
-               if (hunk->splittable_into > 1)
-                       strbuf_addstr(&s->buf, ",s");
-               if (hunk_index + 1 > file_diff->mode_change &&
-                   !file_diff->deleted)
-                       strbuf_addstr(&s->buf, ",e");
-
+               if (file_diff->hunk_nr) {
+                       render_hunk(s, hunk, 0, colored, &s->buf);
+                       fputs(s->buf.buf, stdout);
+
+                       strbuf_reset(&s->buf);
+                       if (undecided_previous >= 0) {
+                               permitted |= ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK;
+                               strbuf_addstr(&s->buf, ",k");
+                       }
+                       if (hunk_index) {
+                               permitted |= ALLOW_GOTO_PREVIOUS_HUNK;
+                               strbuf_addstr(&s->buf, ",K");
+                       }
+                       if (undecided_next >= 0) {
+                               permitted |= ALLOW_GOTO_NEXT_UNDECIDED_HUNK;
+                               strbuf_addstr(&s->buf, ",j");
+                       }
+                       if (hunk_index + 1 < file_diff->hunk_nr) {
+                               permitted |= ALLOW_GOTO_NEXT_HUNK;
+                               strbuf_addstr(&s->buf, ",J");
+                       }
+                       if (file_diff->hunk_nr > 1) {
+                               permitted |= ALLOW_SEARCH_AND_GOTO;
+                               strbuf_addstr(&s->buf, ",g,/");
+                       }
+                       if (hunk->splittable_into > 1) {
+                               permitted |= ALLOW_SPLIT;
+                               strbuf_addstr(&s->buf, ",s");
+                       }
+                       if (hunk_index + 1 > file_diff->mode_change &&
+                           !file_diff->deleted) {
+                               permitted |= ALLOW_EDIT;
+                               strbuf_addstr(&s->buf, ",e");
+                       }
+               }
                if (file_diff->deleted)
                        prompt_mode_type = PROMPT_DELETION;
+               else if (file_diff->added)
+                       prompt_mode_type = PROMPT_ADDITION;
                else if (file_diff->mode_change && !hunk_index)
                        prompt_mode_type = PROMPT_MODE_CHANGE;
                else
                        prompt_mode_type = PROMPT_HUNK;
 
-               color_fprintf(stdout, s->s.prompt_color,
-                             "(%"PRIuMAX"/%"PRIuMAX") ",
+               printf("%s(%"PRIuMAX"/%"PRIuMAX") ", s->s.prompt_color,
                              (uintmax_t)hunk_index + 1,
-                             (uintmax_t)file_diff->hunk_nr);
-               color_fprintf(stdout, s->s.prompt_color,
-                             _(prompt_mode[prompt_mode_type]), s->buf.buf);
+                             (uintmax_t)(file_diff->hunk_nr
+                                               ? file_diff->hunk_nr
+                                               : 1));
+               printf(_(s->mode->prompt_mode[prompt_mode_type]),
+                      s->buf.buf);
+               if (*s->s.reset_color)
+                       fputs(s->s.reset_color, stdout);
                fflush(stdout);
-               if (strbuf_getline(&s->answer, stdin) == EOF)
+               if (read_single_character(s) == EOF)
                        break;
-               strbuf_trim_trailing_newline(&s->answer);
 
                if (!s->answer.len)
                        continue;
@@ -1115,38 +1486,46 @@ soft_increment:
                        hunk->use = SKIP_HUNK;
                        goto soft_increment;
                } else if (ch == 'a') {
-                       for (; hunk_index < file_diff->hunk_nr; hunk_index++) {
-                               hunk = file_diff->hunk + hunk_index;
-                               if (hunk->use == UNDECIDED_HUNK)
-                                       hunk->use = USE_HUNK;
+                       if (file_diff->hunk_nr) {
+                               for (; hunk_index < file_diff->hunk_nr; hunk_index++) {
+                                       hunk = file_diff->hunk + hunk_index;
+                                       if (hunk->use == UNDECIDED_HUNK)
+                                               hunk->use = USE_HUNK;
+                               }
+                       } else if (hunk->use == UNDECIDED_HUNK) {
+                               hunk->use = USE_HUNK;
                        }
                } else if (ch == 'd' || ch == 'q') {
-                       for (; hunk_index < file_diff->hunk_nr; hunk_index++) {
-                               hunk = file_diff->hunk + hunk_index;
-                               if (hunk->use == UNDECIDED_HUNK)
-                                       hunk->use = SKIP_HUNK;
+                       if (file_diff->hunk_nr) {
+                               for (; hunk_index < file_diff->hunk_nr; hunk_index++) {
+                                       hunk = file_diff->hunk + hunk_index;
+                                       if (hunk->use == UNDECIDED_HUNK)
+                                               hunk->use = SKIP_HUNK;
+                               }
+                       } else if (hunk->use == UNDECIDED_HUNK) {
+                               hunk->use = SKIP_HUNK;
                        }
                        if (ch == 'q') {
                                quit = 1;
                                break;
                        }
                } else if (s->answer.buf[0] == 'K') {
-                       if (hunk_index)
+                       if (permitted & ALLOW_GOTO_PREVIOUS_HUNK)
                                hunk_index--;
                        else
                                err(s, _("No previous hunk"));
                } else if (s->answer.buf[0] == 'J') {
-                       if (hunk_index + 1 < file_diff->hunk_nr)
+                       if (permitted & ALLOW_GOTO_NEXT_HUNK)
                                hunk_index++;
                        else
                                err(s, _("No next hunk"));
                } else if (s->answer.buf[0] == 'k') {
-                       if (undecided_previous >= 0)
+                       if (permitted & ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK)
                                hunk_index = undecided_previous;
                        else
                                err(s, _("No previous hunk"));
                } else if (s->answer.buf[0] == 'j') {
-                       if (undecided_next >= 0)
+                       if (permitted & ALLOW_GOTO_NEXT_UNDECIDED_HUNK)
                                hunk_index = undecided_next;
                        else
                                err(s, _("No next hunk"));
@@ -1154,7 +1533,7 @@ soft_increment:
                        char *pend;
                        unsigned long response;
 
-                       if (file_diff->hunk_nr < 2) {
+                       if (!(permitted & ALLOW_SEARCH_AND_GOTO)) {
                                err(s, _("No other hunks to goto"));
                                continue;
                        }
@@ -1191,7 +1570,7 @@ soft_increment:
                        regex_t regex;
                        int ret;
 
-                       if (file_diff->hunk_nr < 2) {
+                       if (!(permitted & ALLOW_SEARCH_AND_GOTO)) {
                                err(s, _("No other hunks to search"));
                                continue;
                        }
@@ -1236,7 +1615,7 @@ soft_increment:
                        hunk_index = i;
                } else if (s->answer.buf[0] == 's') {
                        size_t splittable_into = hunk->splittable_into;
-                       if (splittable_into < 2)
+                       if (!(permitted & ALLOW_SPLIT))
                                err(s, _("Sorry, cannot split this hunk"));
                        else if (!split_hunk(s, file_diff,
                                             hunk - file_diff->hunk))
@@ -1244,7 +1623,7 @@ soft_increment:
                                                 _("Split into %d hunks."),
                                                 (int)splittable_into);
                } else if (s->answer.buf[0] == 'e') {
-                       if (hunk_index + 1 == file_diff->mode_change)
+                       if (!(permitted & ALLOW_EDIT))
                                err(s, _("Sorry, cannot edit this hunk"));
                        else if (edit_hunk_loop(s, file_diff, hunk) >= 0) {
                                hunk->use = USE_HUNK;
@@ -1254,7 +1633,7 @@ soft_increment:
                        const char *p = _(help_patch_remainder), *eol = p;
 
                        color_fprintf(stdout, s->s.help_color, "%s",
-                                     _(help_patch_text));
+                                     _(s->mode->help_patch_text));
 
                        /*
                         * Show only those lines of the remainder that are
@@ -1282,17 +1661,24 @@ soft_increment:
                if (file_diff->hunk[i].use == USE_HUNK)
                        break;
 
-       if (i < file_diff->hunk_nr) {
+       if (i < file_diff->hunk_nr ||
+           (!file_diff->hunk_nr && file_diff->head.use == USE_HUNK)) {
                /* At least one hunk selected: apply */
                strbuf_reset(&s->buf);
                reassemble_patch(s, file_diff, 0, &s->buf);
 
                discard_index(s->s.r->index);
-               setup_child_process(s, &cp, "apply", "--cached", NULL);
-               if (pipe_command(&cp, s->buf.buf, s->buf.len,
-                                NULL, 0, NULL, 0))
-                       error(_("'git apply --cached' failed"));
-               if (!repo_read_index(s->s.r))
+               if (s->mode->apply_for_checkout)
+                       apply_for_checkout(s, &s->buf,
+                                          s->mode->is_reverse);
+               else {
+                       setup_child_process(s, &cp, "apply", NULL);
+                       strvec_pushv(&cp.args, s->mode->apply_args);
+                       if (pipe_command(&cp, s->buf.buf, s->buf.len,
+                                        NULL, 0, NULL, 0))
+                               error(_("'git apply' failed"));
+               }
+               if (repo_read_index(s->s.r) >= 0)
                        repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0,
                                                     1, NULL, NULL, NULL);
        }
@@ -1301,7 +1687,8 @@ soft_increment:
        return quit;
 }
 
-int run_add_p(struct repository *r, const struct pathspec *ps)
+int run_add_p(struct repository *r, enum add_p_mode mode,
+             const char *revision, const struct pathspec *ps)
 {
        struct add_p_state s = {
                { r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
@@ -1310,12 +1697,45 @@ int run_add_p(struct repository *r, const struct pathspec *ps)
 
        init_add_i_state(&s.s, r);
 
+       if (mode == ADD_P_STASH)
+               s.mode = &patch_mode_stash;
+       else if (mode == ADD_P_RESET) {
+               /*
+                * NEEDSWORK: Instead of comparing to the literal "HEAD",
+                * compare the commit objects instead so that other ways of
+                * saying the same thing (such as "@") are also handled
+                * appropriately.
+                *
+                * This applies to the cases below too.
+                */
+               if (!revision || !strcmp(revision, "HEAD"))
+                       s.mode = &patch_mode_reset_head;
+               else
+                       s.mode = &patch_mode_reset_nothead;
+       } else if (mode == ADD_P_CHECKOUT) {
+               if (!revision)
+                       s.mode = &patch_mode_checkout_index;
+               else if (!strcmp(revision, "HEAD"))
+                       s.mode = &patch_mode_checkout_head;
+               else
+                       s.mode = &patch_mode_checkout_nothead;
+       } else if (mode == ADD_P_WORKTREE) {
+               if (!revision)
+                       s.mode = &patch_mode_checkout_index;
+               else if (!strcmp(revision, "HEAD"))
+                       s.mode = &patch_mode_worktree_head;
+               else
+                       s.mode = &patch_mode_worktree_nothead;
+       } else
+               s.mode = &patch_mode_add;
+       s.revision = revision;
+
        if (discard_index(r->index) < 0 || repo_read_index(r) < 0 ||
-           repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
-                                        NULL, NULL, NULL) < 0 ||
+           (!s.mode->index_only &&
+            repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
+                                         NULL, NULL, NULL) < 0) ||
            parse_diff(&s, ps) < 0) {
-               strbuf_release(&s.plain);
-               strbuf_release(&s.colored);
+               add_p_state_clear(&s);
                return -1;
        }
 
@@ -1330,9 +1750,6 @@ int run_add_p(struct repository *r, const struct pathspec *ps)
        else if (binary_count == s.file_diff_nr)
                fprintf(stderr, _("Only binary files changed.\n"));
 
-       strbuf_release(&s.answer);
-       strbuf_release(&s.buf);
-       strbuf_release(&s.plain);
-       strbuf_release(&s.colored);
+       add_p_state_clear(&s);
        return 0;
 }