builtin-am: invoke pre-applypatch hook
[gitweb.git] / builtin / am.c
index 84d3e0519e6501408c5cc792f31c8db4bebc64e7..7a7da943a398d97f53e5f842ca06440dffd6bd83 100644 (file)
@@ -23,6 +23,7 @@
 #include "merge-recursive.h"
 #include "revision.h"
 #include "log-tree.h"
+#include "notes-utils.h"
 
 /**
  * Returns 1 if the file is empty or does not exist, 0 otherwise.
@@ -95,6 +96,9 @@ struct am_state {
        char *msg;
        size_t msg_len;
 
+       /* when --rebasing, records the original commit the patch came from */
+       unsigned char orig_commit[GIT_SHA1_RAWSZ];
+
        /* number of digits in patch filename */
        int prec;
 
@@ -108,7 +112,9 @@ struct am_state {
        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;
 };
 
@@ -118,6 +124,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);
@@ -132,6 +140,9 @@ static void am_state_init(struct am_state *state, const char *dir)
        state->scissors = SCISSORS_UNSET;
 
        argv_array_init(&state->git_apply_opts);
+
+       if (!git_config_get_bool("commit.gpgsign", &gpgsign))
+               state->sign_commit = gpgsign ? "" : NULL;
 }
 
 /**
@@ -385,6 +396,11 @@ static void am_load(struct am_state *state)
 
        read_commit_msg(state);
 
+       if (read_state_file(&sb, state, "original-commit", 1) < 0)
+               hashclr(state->orig_commit);
+       else if (get_sha1_hex(sb.buf, state->orig_commit) < 0)
+               die(_("could not parse %s"), am_path(state, "original-commit"));
+
        read_state_file(&sb, state, "threeway", 1);
        state->threeway = !strcmp(sb.buf, "t");
 
@@ -439,6 +455,109 @@ static void am_destroy(const struct am_state *state)
        strbuf_release(&sb);
 }
 
+/**
+ * Runs applypatch-msg hook. Returns its exit code.
+ */
+static int run_applypatch_msg_hook(struct am_state *state)
+{
+       int ret;
+
+       assert(state->msg);
+       ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL);
+
+       if (!ret) {
+               free(state->msg);
+               state->msg = NULL;
+               if (read_commit_msg(state) < 0)
+                       die(_("'%s' was deleted by the applypatch-msg hook"),
+                               am_path(state, "final-commit"));
+       }
+
+       return ret;
+}
+
+/**
+ * Runs post-rewrite hook. Returns it exit code.
+ */
+static int run_post_rewrite_hook(const struct am_state *state)
+{
+       struct child_process cp = CHILD_PROCESS_INIT;
+       const char *hook = find_hook("post-rewrite");
+       int ret;
+
+       if (!hook)
+               return 0;
+
+       argv_array_push(&cp.args, hook);
+       argv_array_push(&cp.args, "rebase");
+
+       cp.in = xopen(am_path(state, "rewritten"), O_RDONLY);
+       cp.stdout_to_stderr = 1;
+
+       ret = run_command(&cp);
+
+       close(cp.in);
+       return ret;
+}
+
+/**
+ * Reads the state directory's "rewritten" file, and copies notes from the old
+ * commits listed in the file to their rewritten commits.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int copy_notes_for_rebase(const struct am_state *state)
+{
+       struct notes_rewrite_cfg *c;
+       struct strbuf sb = STRBUF_INIT;
+       const char *invalid_line = _("Malformed input line: '%s'.");
+       const char *msg = "Notes added by 'git rebase'";
+       FILE *fp;
+       int ret = 0;
+
+       assert(state->rebasing);
+
+       c = init_copy_notes_for_rewrite("rebase");
+       if (!c)
+               return 0;
+
+       fp = xfopen(am_path(state, "rewritten"), "r");
+
+       while (!strbuf_getline(&sb, fp, '\n')) {
+               unsigned char from_obj[GIT_SHA1_RAWSZ], to_obj[GIT_SHA1_RAWSZ];
+
+               if (sb.len != GIT_SHA1_HEXSZ * 2 + 1) {
+                       ret = error(invalid_line, sb.buf);
+                       goto finish;
+               }
+
+               if (get_sha1_hex(sb.buf, from_obj)) {
+                       ret = error(invalid_line, sb.buf);
+                       goto finish;
+               }
+
+               if (sb.buf[GIT_SHA1_HEXSZ] != ' ') {
+                       ret = error(invalid_line, sb.buf);
+                       goto finish;
+               }
+
+               if (get_sha1_hex(sb.buf + GIT_SHA1_HEXSZ + 1, to_obj)) {
+                       ret = error(invalid_line, sb.buf);
+                       goto finish;
+               }
+
+               if (copy_note_for_rewrite(c, from_obj, to_obj))
+                       ret = error(_("Failed to copy notes from '%s' to '%s'"),
+                                       sha1_to_hex(from_obj), sha1_to_hex(to_obj));
+       }
+
+finish:
+       finish_copy_notes_for_rewrite(c, msg);
+       fclose(fp);
+       strbuf_release(&sb);
+       return ret;
+}
+
 /**
  * Determines if the file looks like a piece of RFC2822 mail by grabbing all
  * non-indented lines and checking if they look like they begin with valid
@@ -713,6 +832,9 @@ static void am_next(struct am_state *state)
        unlink(am_path(state, "author-script"));
        unlink(am_path(state, "final-commit"));
 
+       hashclr(state->orig_commit);
+       unlink(am_path(state, "original-commit"));
+
        if (!get_sha1("HEAD", head))
                write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(head));
        else
@@ -1031,6 +1153,8 @@ static void write_commit_patch(const struct am_state *state, struct commit *comm
  * directly. This is used in --rebasing mode to bypass git-mailinfo's munging
  * of patches.
  *
+ * state->orig_commit will be set to the original commit ID.
+ *
  * Will always return 0 as the patch should never be skipped.
  */
 static int parse_mail_rebase(struct am_state *state, const char *mail)
@@ -1047,6 +1171,10 @@ static int parse_mail_rebase(struct am_state *state, const char *mail)
 
        write_commit_patch(state, commit);
 
+       hashcpy(state->orig_commit, commit_sha1);
+       write_file(am_path(state, "original-commit"), 1, "%s",
+                       sha1_to_hex(commit_sha1));
+
        return 0;
 }
 
@@ -1206,6 +1334,9 @@ static void do_commit(const struct am_state *state)
        const char *reflog_msg, *author;
        struct strbuf sb = STRBUF_INIT;
 
+       if (run_hook_le(NULL, "pre-applypatch", NULL))
+               exit(1);
+
        if (write_cache_as_tree(tree, 0, NULL))
                die(_("git write-tree failed to write a tree"));
 
@@ -1221,8 +1352,12 @@ static void do_commit(const struct am_state *state)
                        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");
@@ -1234,6 +1369,15 @@ static void do_commit(const struct am_state *state)
 
        update_ref(sb.buf, "HEAD", commit, ptr, 0, UPDATE_REFS_DIE_ON_ERR);
 
+       if (state->rebasing) {
+               FILE *fp = xfopen(am_path(state, "rewritten"), "a");
+
+               assert(!is_null_sha1(state->orig_commit));
+               fprintf(fp, "%s ", sha1_to_hex(state->orig_commit));
+               fprintf(fp, "%s\n", sha1_to_hex(commit));
+               fclose(fp);
+       }
+
        strbuf_release(&sb);
 }
 
@@ -1300,6 +1444,9 @@ static void am_run(struct am_state *state, int resume)
                        write_commit_msg(state);
                }
 
+               if (run_applypatch_msg_hook(state))
+                       exit(1);
+
                say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
 
                apply_status = run_apply(state, NULL);
@@ -1342,6 +1489,12 @@ static void am_run(struct am_state *state, int resume)
                am_next(state);
        }
 
+       if (!is_empty_file(am_path(state, "rewritten"))) {
+               assert(state->rebasing);
+               copy_notes_for_rebase(state);
+               run_post_rewrite_hook(state);
+       }
+
        /*
         * In rebasing mode, it's up to the caller to take care of
         * housekeeping.
@@ -1663,8 +1816,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()