Merge branch 'dt/untracked-sparse'
[gitweb.git] / builtin / am.c
index 98c10a0b710f1f73f47abef8410c18428c4c3e4b..1399c8dd880f86190534b9c9e0a5e84f48ebeceb 100644 (file)
@@ -25,6 +25,7 @@
 #include "log-tree.h"
 #include "notes-utils.h"
 #include "rerere.h"
+#include "prompt.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -119,6 +120,7 @@ struct am_state {
        int prec;
 
        /* various operating modes and command line options */
+       int interactive;
        int threeway;
        int quiet;
        int signoff;
@@ -150,6 +152,8 @@ static void am_state_init(struct am_state *state, const char *dir)
 
        state->prec = 4;
 
+       git_config_get_bool("am.threeway", &state->threeway);
+
        state->utf8 = 1;
 
        git_config_get_bool("am.messageid", &state->message_id);
@@ -1171,7 +1175,7 @@ static void NORETURN die_user_resolve(const struct am_state *state)
        if (state->resolvemsg) {
                printf_ln("%s", state->resolvemsg);
        } else {
-               const char *cmdline = "git am";
+               const char *cmdline = state->interactive ? "git am -i" : "git am";
 
                printf_ln(_("When you have resolved this problem, run \"%s --continue\"."), cmdline);
                printf_ln(_("If you prefer to skip this patch, run \"%s --skip\" instead."), cmdline);
@@ -1403,6 +1407,36 @@ static void write_commit_patch(const struct am_state *state, struct commit *comm
        log_tree_commit(&rev_info, commit);
 }
 
+/**
+ * Writes the diff of the index against HEAD as a patch to the state
+ * directory's "patch" file.
+ */
+static void write_index_patch(const struct am_state *state)
+{
+       struct tree *tree;
+       unsigned char head[GIT_SHA1_RAWSZ];
+       struct rev_info rev_info;
+       FILE *fp;
+
+       if (!get_sha1_tree("HEAD", head))
+               tree = lookup_tree(head);
+       else
+               tree = lookup_tree(EMPTY_TREE_SHA1_BIN);
+
+       fp = xfopen(am_path(state, "patch"), "w");
+       init_revisions(&rev_info, NULL);
+       rev_info.diff = 1;
+       rev_info.disable_stdin = 1;
+       rev_info.no_commit_id = 1;
+       rev_info.diffopt.output_format = DIFF_FORMAT_PATCH;
+       rev_info.diffopt.use_color = 0;
+       rev_info.diffopt.file = fp;
+       rev_info.diffopt.close_file = 1;
+       add_pending_object(&rev_info, &tree->object, "");
+       diff_setup_done(&rev_info.diffopt);
+       run_diff_index(&rev_info, 1);
+}
+
 /**
  * Like parse_mail(), but parses the mail by looking up its commit ID
  * directly. This is used in --rebasing mode to bypass git-mailinfo's munging
@@ -1654,6 +1688,65 @@ static void validate_resume_state(const struct am_state *state)
                        am_path(state, "author-script"));
 }
 
+/**
+ * Interactively prompt the user on whether the current patch should be
+ * applied.
+ *
+ * Returns 0 if the user chooses to apply the patch, 1 if the user chooses to
+ * skip it.
+ */
+static int do_interactive(struct am_state *state)
+{
+       assert(state->msg);
+
+       if (!isatty(0))
+               die(_("cannot be interactive without stdin connected to a terminal."));
+
+       for (;;) {
+               const char *reply;
+
+               puts(_("Commit Body is:"));
+               puts("--------------------------");
+               printf("%s", state->msg);
+               puts("--------------------------");
+
+               /*
+                * TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+                * in your translation. The program will only accept English
+                * input at this point.
+                */
+               reply = git_prompt(_("Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all: "), PROMPT_ECHO);
+
+               if (!reply) {
+                       continue;
+               } else if (*reply == 'y' || *reply == 'Y') {
+                       return 0;
+               } else if (*reply == 'a' || *reply == 'A') {
+                       state->interactive = 0;
+                       return 0;
+               } else if (*reply == 'n' || *reply == 'N') {
+                       return 1;
+               } else if (*reply == 'e' || *reply == 'E') {
+                       struct strbuf msg = STRBUF_INIT;
+
+                       if (!launch_editor(am_path(state, "final-commit"), &msg, NULL)) {
+                               free(state->msg);
+                               state->msg = strbuf_detach(&msg, &state->msg_len);
+                       }
+                       strbuf_release(&msg);
+               } else if (*reply == 'v' || *reply == 'V') {
+                       const char *pager = git_pager(1);
+                       struct child_process cp = CHILD_PROCESS_INIT;
+
+                       if (!pager)
+                               pager = "cat";
+                       argv_array_push(&cp.args, pager);
+                       argv_array_push(&cp.args, am_path(state, "patch"));
+                       run_command(&cp);
+               }
+       }
+}
+
 /**
  * Applies all queued mail.
  *
@@ -1702,6 +1795,9 @@ static void am_run(struct am_state *state, int resume)
                        write_commit_msg(state);
                }
 
+               if (state->interactive && do_interactive(state))
+                       goto next;
+
                if (run_applypatch_msg_hook(state))
                        exit(1);
 
@@ -1787,10 +1883,17 @@ static void am_resolve(struct am_state *state)
                die_user_resolve(state);
        }
 
+       if (state->interactive) {
+               write_index_patch(state);
+               if (do_interactive(state))
+                       goto next;
+       }
+
        rerere(0);
 
        do_commit(state);
 
+next:
        am_next(state);
        am_run(state, 0);
 }
@@ -2025,6 +2128,7 @@ enum resume_mode {
 int cmd_am(int argc, const char **argv, const char *prefix)
 {
        struct am_state state;
+       int binary = -1;
        int keep_cr = -1;
        int patch_format = PATCH_FORMAT_UNKNOWN;
        enum resume_mode resume = RESUME_FALSE;
@@ -2036,6 +2140,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)
        };
 
        struct option options[] = {
+               OPT_BOOL('i', "interactive", &state.interactive,
+                       N_("run interactively")),
+               OPT_HIDDEN_BOOL('b', "binary", &binary,
+                       N_("(historical option -- no-op")),
                OPT_BOOL('3', "3way", &state.threeway,
                        N_("allow fall back on 3way merging if needed")),
                OPT__QUIET(&state.quiet, N_("be quiet")),
@@ -2115,27 +2223,19 @@ int cmd_am(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
 
-       /*
-        * NEEDSWORK: Once all the features of git-am.sh have been
-        * re-implemented in builtin/am.c, this preamble can be removed.
-        */
-       if (!getenv("_GIT_USE_BUILTIN_AM")) {
-               const char *path = mkpath("%s/git-am", git_exec_path());
-
-               if (sane_execvp(path, (char **)argv) < 0)
-                       die_errno("could not exec %s", path);
-       } else {
-               prefix = setup_git_directory();
-               trace_repo_setup(prefix);
-               setup_work_tree();
-       }
-
        git_config(git_default_config, NULL);
 
        am_state_init(&state, git_path("rebase-apply"));
 
        argc = parse_options(argc, argv, prefix, options, usage, 0);
 
+       if (binary >= 0)
+               fprintf_ln(stderr, _("The -b/--binary option has been a no-op for long time, and\n"
+                               "it will be removed. Please do not use it anymore."));
+
+       /* Ensure a valid committer ident can be constructed */
+       git_committer_info(IDENT_STRICT);
+
        if (read_index_preload(&the_index, NULL) < 0)
                die(_("failed to read the index"));