builtin-am: implement -S/--gpg-sign, commit.gpgsign
[gitweb.git] / builtin / am.c
index 8e978395b2721f7ab71f07dc38d939722f1ea058..18611fa0c396bdf951cb66aca8377f3b11aed960 100644 (file)
@@ -74,6 +74,12 @@ enum keep_type {
        KEEP_NON_PATCH  /* pass -b flag to git-mailinfo */
 };
 
+enum scissors_type {
+       SCISSORS_UNSET = -1,
+       SCISSORS_FALSE = 0,  /* pass --no-scissors to git-mailinfo */
+       SCISSORS_TRUE        /* pass --scissors to git-mailinfo */
+};
+
 struct am_state {
        /* state directory path */
        char *dir;
@@ -99,7 +105,12 @@ struct am_state {
        int utf8;
        int keep; /* enum keep_type */
        int message_id;
+       int scissors; /* enum scissors_type */
+       struct argv_array git_apply_opts;
        const char *resolvemsg;
+       int committer_date_is_author_date;
+       int ignore_date;
+       const char *sign_commit;
        int rebasing;
 };
 
@@ -109,6 +120,8 @@ struct am_state {
  */
 static void am_state_init(struct am_state *state, const char *dir)
 {
+       int gpgsign;
+
        memset(state, 0, sizeof(*state));
 
        assert(dir);
@@ -119,6 +132,13 @@ static void am_state_init(struct am_state *state, const char *dir)
        state->utf8 = 1;
 
        git_config_get_bool("am.messageid", &state->message_id);
+
+       state->scissors = SCISSORS_UNSET;
+
+       argv_array_init(&state->git_apply_opts);
+
+       if (!git_config_get_bool("commit.gpgsign", &gpgsign))
+               state->sign_commit = gpgsign ? "" : NULL;
 }
 
 /**
@@ -131,6 +151,7 @@ static void am_state_release(struct am_state *state)
        free(state->author_email);
        free(state->author_date);
        free(state->msg);
+       argv_array_clear(&state->git_apply_opts);
 }
 
 /**
@@ -394,6 +415,19 @@ static void am_load(struct am_state *state)
        read_state_file(&sb, state, "messageid", 1);
        state->message_id = !strcmp(sb.buf, "t");
 
+       read_state_file(&sb, state, "scissors", 1);
+       if (!strcmp(sb.buf, "t"))
+               state->scissors = SCISSORS_TRUE;
+       else if (!strcmp(sb.buf, "f"))
+               state->scissors = SCISSORS_FALSE;
+       else
+               state->scissors = SCISSORS_UNSET;
+
+       read_state_file(&sb, state, "apply-opt", 1);
+       argv_array_clear(&state->git_apply_opts);
+       if (sq_dequote_to_argv_array(sb.buf, &state->git_apply_opts) < 0)
+               die(_("could not parse %s"), am_path(state, "apply-opt"));
+
        state->rebasing = !!file_exists(am_path(state, "rebasing"));
 
        strbuf_release(&sb);
@@ -502,7 +536,7 @@ static int detect_patch_format(const char **paths)
  * Splits out individual email patches from `paths`, where each path is either
  * a mbox file or a Maildir. Returns 0 on success, -1 on failure.
  */
-static int split_mail_mbox(struct am_state *state, const char **paths)
+static int split_mail_mbox(struct am_state *state, const char **paths, int keep_cr)
 {
        struct child_process cp = CHILD_PROCESS_INIT;
        struct strbuf last = STRBUF_INIT;
@@ -512,6 +546,8 @@ static int split_mail_mbox(struct am_state *state, const char **paths)
        argv_array_pushf(&cp.args, "-d%d", state->prec);
        argv_array_pushf(&cp.args, "-o%s", state->dir);
        argv_array_push(&cp.args, "-b");
+       if (keep_cr)
+               argv_array_push(&cp.args, "--keep-cr");
        argv_array_push(&cp.args, "--");
        argv_array_pushv(&cp.args, paths);
 
@@ -536,14 +572,22 @@ static int split_mail_mbox(struct am_state *state, const char **paths)
  * state->cur will be set to the index of the first mail, and state->last will
  * be set to the index of the last mail.
  *
+ * Set keep_cr to 0 to convert all lines ending with \r\n to end with \n, 1
+ * to disable this behavior, -1 to use the default configured setting.
+ *
  * Returns 0 on success, -1 on failure.
  */
 static int split_mail(struct am_state *state, enum patch_format patch_format,
-                       const char **paths)
+                       const char **paths, int keep_cr)
 {
+       if (keep_cr < 0) {
+               keep_cr = 0;
+               git_config_get_bool("am.keepcr", &keep_cr);
+       }
+
        switch (patch_format) {
        case PATCH_FORMAT_MBOX:
-               return split_mail_mbox(state, paths);
+               return split_mail_mbox(state, paths, keep_cr);
        default:
                die("BUG: invalid patch_format");
        }
@@ -554,10 +598,11 @@ static int split_mail(struct am_state *state, enum patch_format patch_format,
  * Setup a new am session for applying patches
  */
 static void am_setup(struct am_state *state, enum patch_format patch_format,
-                       const char **paths)
+                       const char **paths, int keep_cr)
 {
        unsigned char curr_head[GIT_SHA1_RAWSZ];
        const char *str;
+       struct strbuf sb = STRBUF_INIT;
 
        if (!patch_format)
                patch_format = detect_patch_format(paths);
@@ -570,7 +615,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
        if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
                die_errno(_("failed to create directory '%s'"), state->dir);
 
-       if (split_mail(state, patch_format, paths) < 0) {
+       if (split_mail(state, patch_format, paths, keep_cr) < 0) {
                am_destroy(state);
                die(_("Failed to split patches."));
        }
@@ -604,6 +649,25 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
 
        write_file(am_path(state, "messageid"), 1, state->message_id ? "t" : "f");
 
+       switch (state->scissors) {
+       case SCISSORS_UNSET:
+               str = "";
+               break;
+       case SCISSORS_FALSE:
+               str = "f";
+               break;
+       case SCISSORS_TRUE:
+               str = "t";
+               break;
+       default:
+               die("BUG: invalid value for state->scissors");
+       }
+
+       write_file(am_path(state, "scissors"), 1, "%s", str);
+
+       sq_quote_argv(&sb, state->git_apply_opts.argv, 0);
+       write_file(am_path(state, "apply-opt"), 1, "%s", sb.buf);
+
        if (state->rebasing)
                write_file(am_path(state, "rebasing"), 1, "%s", "");
        else
@@ -628,6 +692,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
        write_file(am_path(state, "next"), 1, "%d", state->cur);
 
        write_file(am_path(state, "last"), 1, "%d", state->last);
+
+       strbuf_release(&sb);
 }
 
 /**
@@ -788,6 +854,19 @@ static int parse_mail(struct am_state *state, const char *mail)
        if (state->message_id)
                argv_array_push(&cp.args, "-m");
 
+       switch (state->scissors) {
+       case SCISSORS_UNSET:
+               break;
+       case SCISSORS_FALSE:
+               argv_array_push(&cp.args, "--no-scissors");
+               break;
+       case SCISSORS_TRUE:
+               argv_array_push(&cp.args, "--scissors");
+               break;
+       default:
+               die("BUG: invalid value for state->scissors");
+       }
+
        argv_array_push(&cp.args, am_path(state, "msg"));
        argv_array_push(&cp.args, am_path(state, "patch"));
 
@@ -1002,6 +1081,8 @@ static int run_apply(const struct am_state *state, const char *index_file)
 
        argv_array_push(&cp.args, "apply");
 
+       argv_array_pushv(&cp.args, state->git_apply_opts.argv);
+
        if (index_file)
                argv_array_push(&cp.args, "--cached");
        else
@@ -1028,6 +1109,7 @@ static int build_fake_ancestor(const struct am_state *state, const char *index_f
 
        cp.git_cmd = 1;
        argv_array_push(&cp.args, "apply");
+       argv_array_pushv(&cp.args, state->git_apply_opts.argv);
        argv_array_pushf(&cp.args, "--build-fake-ancestor=%s", index_file);
        argv_array_push(&cp.args, am_path(state, "patch"));
 
@@ -1143,10 +1225,15 @@ static void do_commit(const struct am_state *state)
        }
 
        author = fmt_ident(state->author_name, state->author_email,
-                       state->author_date, IDENT_STRICT);
+                       state->ignore_date ? NULL : state->author_date,
+                       IDENT_STRICT);
+
+       if (state->committer_date_is_author_date)
+               setenv("GIT_COMMITTER_DATE",
+                       state->ignore_date ? "" : state->author_date, 1);
 
        if (commit_tree(state->msg, state->msg_len, tree, parents, commit,
-                               author, NULL))
+                               author, state->sign_commit))
                die(_("failed to write commit object"));
 
        reflog_msg = getenv("GIT_REFLOG_ACTION");
@@ -1511,6 +1598,7 @@ enum resume_mode {
 int cmd_am(int argc, const char **argv, const char *prefix)
 {
        struct am_state state;
+       int keep_cr = -1;
        int patch_format = PATCH_FORMAT_UNKNOWN;
        enum resume_mode resume = RESUME_FALSE;
 
@@ -1534,9 +1622,44 @@ int cmd_am(int argc, const char **argv, const char *prefix)
                        N_("pass -b flag to git-mailinfo"), KEEP_NON_PATCH),
                OPT_BOOL('m', "message-id", &state.message_id,
                        N_("pass -m flag to git-mailinfo")),
+               { OPTION_SET_INT, 0, "keep-cr", &keep_cr, NULL,
+                 N_("pass --keep-cr flag to git-mailsplit for mbox format"),
+                 PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1},
+               { OPTION_SET_INT, 0, "no-keep-cr", &keep_cr, NULL,
+                 N_("do not pass --keep-cr flag to git-mailsplit independent of am.keepcr"),
+                 PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
+               OPT_BOOL('c', "scissors", &state.scissors,
+                       N_("strip everything before a scissors line")),
+               OPT_PASSTHRU_ARGV(0, "whitespace", &state.git_apply_opts, N_("action"),
+                       N_("pass it through git-apply"),
+                       0),
+               OPT_PASSTHRU_ARGV(0, "ignore-space-change", &state.git_apply_opts, NULL,
+                       N_("pass it through git-apply"),
+                       PARSE_OPT_NOARG),
+               OPT_PASSTHRU_ARGV(0, "ignore-whitespace", &state.git_apply_opts, NULL,
+                       N_("pass it through git-apply"),
+                       PARSE_OPT_NOARG),
+               OPT_PASSTHRU_ARGV(0, "directory", &state.git_apply_opts, N_("root"),
+                       N_("pass it through git-apply"),
+                       0),
+               OPT_PASSTHRU_ARGV(0, "exclude", &state.git_apply_opts, N_("path"),
+                       N_("pass it through git-apply"),
+                       0),
+               OPT_PASSTHRU_ARGV(0, "include", &state.git_apply_opts, N_("path"),
+                       N_("pass it through git-apply"),
+                       0),
+               OPT_PASSTHRU_ARGV('C', NULL, &state.git_apply_opts, N_("n"),
+                       N_("pass it through git-apply"),
+                       0),
+               OPT_PASSTHRU_ARGV('p', NULL, &state.git_apply_opts, N_("num"),
+                       N_("pass it through git-apply"),
+                       0),
                OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
                        N_("format the patch(es) are in"),
                        parse_opt_patchformat),
+               OPT_PASSTHRU_ARGV(0, "reject", &state.git_apply_opts, NULL,
+                       N_("pass it through git-apply"),
+                       PARSE_OPT_NOARG),
                OPT_STRING(0, "resolvemsg", &state.resolvemsg, NULL,
                        N_("override error message when patch failure occurs")),
                OPT_CMDMODE(0, "continue", &resume,
@@ -1551,6 +1674,14 @@ int cmd_am(int argc, const char **argv, const char *prefix)
                OPT_CMDMODE(0, "abort", &resume,
                        N_("restore the original branch and abort the patching operation."),
                        RESUME_ABORT),
+               OPT_BOOL(0, "committer-date-is-author-date",
+                       &state.committer_date_is_author_date,
+                       N_("lie about committer date")),
+               OPT_BOOL(0, "ignore-date", &state.ignore_date,
+                       N_("use current timestamp for author date")),
+               { OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
+                 N_("GPG-sign commits"),
+                 PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
                OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
                        N_("(internal use for git-rebase)")),
                OPT_END()
@@ -1631,7 +1762,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
                                argv_array_push(&paths, mkpath("%s/%s", prefix, argv[i]));
                }
 
-               am_setup(&state, patch_format, paths.argv);
+               am_setup(&state, patch_format, paths.argv, keep_cr);
 
                argv_array_clear(&paths);
        }