static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
+static int git_sequencer_config(const char *k, const char *v, void *cb)
+{
+ struct replay_opts *opts = cb;
+ int status;
+
+ if (!strcmp(k, "commit.cleanup")) {
+ const char *s;
+
+ status = git_config_string(&s, k, v);
+ if (status)
+ return status;
+
+ if (!strcmp(s, "verbatim"))
+ opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_NONE;
+ else if (!strcmp(s, "whitespace"))
+ opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_SPACE;
+ else if (!strcmp(s, "strip"))
+ opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_ALL;
+ else if (!strcmp(s, "scissors"))
+ opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_SPACE;
+ else
+ warning(_("invalid commit message cleanup mode '%s'"),
+ s);
+
+ return status;
+ }
+
+ if (!strcmp(k, "commit.gpgsign")) {
+ opts->gpg_sign = git_config_bool(k, v) ? "" : NULL;
+ return 0;
+ }
+
+ status = git_gpg_config(k, v, NULL);
+ if (status)
+ return status;
+
+ return git_diff_basic_config(k, v, NULL);
+}
+
+void sequencer_init_config(struct replay_opts *opts)
+{
+ opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_NONE;
+ git_config(git_sequencer_config, opts);
+}
+
static inline int is_rebase_i(const struct replay_opts *opts)
{
return opts->action == REPLAY_INTERACTIVE_REBASE;
return 0;
}
+static char *get_author(const char *message)
+{
+ size_t len;
+ const char *a;
+
+ a = find_commit_header(message, "author", &len);
+ if (a)
+ return xmemdupz(a, len);
+
+ return NULL;
+}
+
static const char staged_changes_advice[] =
N_("you have staged changes in your working tree\n"
"If these changes are meant to be squashed into the previous commit, run:\n"
return run_command(&cmd);
}
-static enum commit_msg_cleanup_mode default_msg_cleanup =
- COMMIT_MSG_CLEANUP_NONE;
-static char *default_gpg_sign;
-
-int git_sequencer_config(const char *k, const char *v, void *cb)
-{
- if (!strcmp(k, "commit.cleanup")) {
- int status;
- const char *s;
-
- status = git_config_string(&s, k, v);
- if (status)
- return status;
-
- if (!strcmp(s, "verbatim"))
- default_msg_cleanup = COMMIT_MSG_CLEANUP_NONE;
- else if (!strcmp(s, "whitespace"))
- default_msg_cleanup = COMMIT_MSG_CLEANUP_SPACE;
- else if (!strcmp(s, "strip"))
- default_msg_cleanup = COMMIT_MSG_CLEANUP_ALL;
- else if (!strcmp(s, "scissors"))
- default_msg_cleanup = COMMIT_MSG_CLEANUP_SPACE;
-
- return status;
- }
-
- if (!strcmp(k, "commit.gpgsign")) {
- default_gpg_sign = git_config_bool(k, v) ? "" : NULL;
- return 0;
- }
-
- return git_gpg_config(k, v, NULL);
-}
-
static int rest_is_empty(const struct strbuf *sb, int start)
{
int i, eol;
strbuf_release(&format);
}
+static int parse_head(struct commit **head)
+{
+ struct commit *current_head;
+ struct object_id oid;
+
+ if (get_oid("HEAD", &oid)) {
+ current_head = NULL;
+ } else {
+ current_head = lookup_commit_reference(&oid);
+ if (!current_head)
+ return error(_("could not parse HEAD"));
+ if (oidcmp(&oid, ¤t_head->object.oid)) {
+ warning(_("HEAD %s is not a commit!"),
+ oid_to_hex(&oid));
+ }
+ if (parse_commit(current_head))
+ return error(_("could not parse HEAD commit"));
+ }
+ *head = current_head;
+
+ return 0;
+}
+
+/*
+ * Try to commit without forking 'git commit'. In some cases we need
+ * to run 'git commit' to display an error message
+ *
+ * Returns:
+ * -1 - error unable to commit
+ * 0 - success
+ * 1 - run 'git commit'
+ */
+static int try_to_commit(struct strbuf *msg, const char *author,
+ struct replay_opts *opts, unsigned int flags,
+ struct object_id *oid)
+{
+ struct object_id tree;
+ struct commit *current_head;
+ struct commit_list *parents = NULL;
+ struct commit_extra_header *extra = NULL;
+ struct strbuf err = STRBUF_INIT;
+ struct strbuf amend_msg = STRBUF_INIT;
+ char *amend_author = NULL;
+ enum commit_msg_cleanup_mode cleanup;
+ int res = 0;
+
+ if (parse_head(¤t_head))
+ return -1;
+
+ if (flags & AMEND_MSG) {
+ const char *exclude_gpgsig[] = { "gpgsig", NULL };
+ const char *out_enc = get_commit_output_encoding();
+ const char *message = logmsg_reencode(current_head, NULL,
+ out_enc);
+
+ if (!msg) {
+ const char *orig_message = NULL;
+
+ find_commit_subject(message, &orig_message);
+ msg = &amend_msg;
+ strbuf_addstr(msg, orig_message);
+ }
+ author = amend_author = get_author(message);
+ unuse_commit_buffer(current_head, message);
+ if (!author) {
+ res = error(_("unable to parse commit author"));
+ goto out;
+ }
+ parents = copy_commit_list(current_head->parents);
+ extra = read_commit_extra_headers(current_head, exclude_gpgsig);
+ } else if (current_head) {
+ commit_list_insert(current_head, &parents);
+ }
+
+ cleanup = (flags & CLEANUP_MSG) ? COMMIT_MSG_CLEANUP_ALL :
+ opts->default_msg_cleanup;
+
+ if (cleanup != COMMIT_MSG_CLEANUP_NONE)
+ strbuf_stripspace(msg, cleanup == COMMIT_MSG_CLEANUP_ALL);
+ if (!opts->allow_empty_message && message_is_empty(msg, cleanup)) {
+ res = 1; /* run 'git commit' to display error message */
+ goto out;
+ }
+
+ if (write_cache_as_tree(tree.hash, 0, NULL)) {
+ res = error(_("git write-tree failed to write a tree"));
+ goto out;
+ }
+
+ if (!(flags & ALLOW_EMPTY) && !oidcmp(current_head ?
+ ¤t_head->tree->object.oid :
+ &empty_tree_oid, &tree)) {
+ res = 1; /* run 'git commit' to display error message */
+ goto out;
+ }
+
+ if (commit_tree_extended(msg->buf, msg->len, tree.hash, parents,
+ oid->hash, author, opts->gpg_sign, extra)) {
+ res = error(_("failed to write commit object"));
+ goto out;
+ }
+
+ if (update_head_with_reflog(current_head, oid,
+ getenv("GIT_REFLOG_ACTION"), msg, &err)) {
+ res = error("%s", err.buf);
+ goto out;
+ }
+
+ if (flags & AMEND_MSG)
+ commit_post_rewrite(current_head, oid);
+
+out:
+ free_commit_extra_headers(extra);
+ strbuf_release(&err);
+ strbuf_release(&amend_msg);
+ free(amend_author);
+
+ return res;
+}
+
+static int do_commit(const char *msg_file, const char *author,
+ struct replay_opts *opts, unsigned int flags)
+{
+ int res = 1;
+
+ if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG)) {
+ struct object_id oid;
+ struct strbuf sb = STRBUF_INIT;
+
+ if (msg_file && strbuf_read_file(&sb, msg_file, 2048) < 0)
+ return error_errno(_("unable to read commit message "
+ "from '%s'"),
+ msg_file);
+
+ res = try_to_commit(msg_file ? &sb : NULL, author, opts, flags,
+ &oid);
+ strbuf_release(&sb);
+ if (!res) {
+ unlink(git_path_cherry_pick_head());
+ unlink(git_path_merge_msg());
+ if (!is_rebase_i(opts))
+ print_commit_summary(NULL, &oid,
+ SUMMARY_SHOW_AUTHOR_DATE);
+ return res;
+ }
+ }
+ if (res == 1)
+ return run_git_commit(msg_file, opts, flags);
+
+ return res;
+}
+
static int is_original_commit_empty(struct commit *commit)
{
const struct object_id *ptree_oid;
struct object_id head;
struct commit *base, *next, *parent;
const char *base_label, *next_label;
+ char *author = NULL;
struct commit_message msg = { NULL, NULL, NULL, NULL };
struct strbuf msgbuf = STRBUF_INIT;
int res, unborn = 0, allow;
strbuf_addstr(&msgbuf, oid_to_hex(&commit->object.oid));
strbuf_addstr(&msgbuf, ")\n");
}
+ if (!is_fixup(command))
+ author = get_author(msg.message);
}
if (command == TODO_REWORD)
goto leave;
} else if (allow)
flags |= ALLOW_EMPTY;
- if (!opts->no_commit)
+ if (!opts->no_commit) {
fast_forward_edit:
- res = run_git_commit(msg_file, opts, flags);
+ if (author || command == TODO_REVERT || (flags & AMEND_MSG))
+ res = do_commit(msg_file, author, opts, flags);
+ else
+ res = error(_("unable to parse commit author"));
+ }
if (!res && final_fixup) {
unlink(rebase_path_fixup_msg());
leave:
free_message(commit, &msg);
+ free(author);
update_abort_safety_file();
return res;