summaryrefslogtreecommitdiff
path: root/sequencer.c
diff options
context:
space:
mode:
Diffstat (limited to 'sequencer.c')
-rw-r--r--sequencer.c697
1 files changed, 607 insertions, 90 deletions
diff --git a/sequencer.c b/sequencer.c
index 4d3f60594c..f10033b9dc 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1,13 +1,13 @@
#include "cache.h"
#include "config.h"
#include "lockfile.h"
-#include "sequencer.h"
#include "dir.h"
#include "object.h"
#include "commit.h"
+#include "sequencer.h"
#include "tag.h"
#include "run-command.h"
-#include "exec_cmd.h"
+#include "exec-cmd.h"
#include "utf8.h"
#include "cache-tree.h"
#include "diff.h"
@@ -21,12 +21,16 @@
#include "log-tree.h"
#include "wt-status.h"
#include "hashmap.h"
+#include "notes-utils.h"
+#include "sigchain.h"
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
const char sign_off_header[] = "Signed-off-by: ";
static const char cherry_picked_prefix[] = "(cherry picked from commit ";
+GIT_PATH_FUNC(git_path_commit_editmsg, "COMMIT_EDITMSG")
+
GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo")
@@ -123,6 +127,7 @@ static GIT_PATH_FUNC(rebase_path_rewritten_pending,
static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt")
static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head")
static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose")
+static GIT_PATH_FUNC(rebase_path_signoff, "rebase-merge/signoff")
static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name")
static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto")
static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash")
@@ -130,6 +135,51 @@ static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
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) ? xstrdup("") : 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;
@@ -233,7 +283,7 @@ struct commit_message {
static const char *short_commit_name(struct commit *commit)
{
- return find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV);
+ return find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV);
}
static int get_message(struct commit *commit, struct commit_message *out)
@@ -290,7 +340,7 @@ static void print_advice(int show_hint, struct replay_opts *opts)
static int write_message(const void *buf, size_t len, const char *filename,
int append_eol)
{
- static struct lock_file msg_file;
+ struct lock_file msg_file = LOCK_INIT;
int msg_fd = hold_lock_file_for_update(&msg_file, filename, 0);
if (msg_fd < 0)
@@ -303,10 +353,8 @@ static int write_message(const void *buf, size_t len, const char *filename,
rollback_lock_file(&msg_file);
return error_errno(_("could not write eol to '%s'"), filename);
}
- if (commit_lock_file(&msg_file) < 0) {
- rollback_lock_file(&msg_file);
- return error(_("failed to finalize '%s'."), filename);
- }
+ if (commit_lock_file(&msg_file) < 0)
+ return error(_("failed to finalize '%s'"), filename);
return 0;
}
@@ -436,7 +484,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
struct tree *result, *next_tree, *base_tree, *head_tree;
int clean;
char **xopt;
- static struct lock_file index_lock;
+ struct lock_file index_lock = LOCK_INIT;
if (hold_locked_index(&index_lock, LOCK_REPORT_ON_ERROR) < 0)
return -1;
@@ -452,8 +500,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
o.show_rename_progress = 1;
head_tree = parse_tree_indirect(head);
- next_tree = next ? next->tree : empty_tree();
- base_tree = base ? base->tree : empty_tree();
+ next_tree = next ? get_commit_tree(next) : empty_tree();
+ base_tree = base ? get_commit_tree(base) : empty_tree();
for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++)
parse_merge_opt(&o, *xopt);
@@ -465,21 +513,19 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
fputs(o.obuf.buf, stdout);
strbuf_release(&o.obuf);
diff_warn_rename_limit("merge.renamelimit", o.needed_rename_limit, 0);
- if (clean < 0)
+ if (clean < 0) {
+ rollback_lock_file(&index_lock);
return clean;
+ }
- if (active_cache_changed &&
- write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
+ if (write_locked_index(&the_index, &index_lock,
+ COMMIT_LOCK | SKIP_IF_UNCHANGED))
/*
* TRANSLATORS: %s will be "revert", "cherry-pick" or
* "rebase -i".
*/
return error(_("%s: Unable to write new index file"),
_(action_name(opts)));
- rollback_lock_file(&index_lock);
-
- if (opts->signoff)
- append_signoff(msgbuf, 0, 0);
if (!clean)
append_conflicts_hint(msgbuf);
@@ -516,7 +562,7 @@ static int is_index_unchanged(void)
return error(_("unable to update cache tree"));
return !oidcmp(&active_cache_tree->oid,
- &head_commit->tree->object.oid);
+ get_commit_tree_oid(head_commit));
}
static int write_author_script(const char *message)
@@ -596,6 +642,18 @@ static int read_env_script(struct argv_array *env)
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"
@@ -658,8 +716,6 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
argv_array_push(&cmd.args, "--amend");
if (opts->gpg_sign)
argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
- if (opts->signoff)
- argv_array_push(&cmd.args, "-s");
if (defmsg)
argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
if ((flags & CLEANUP_MSG))
@@ -694,6 +750,463 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
return run_command(&cmd);
}
+static int rest_is_empty(const struct strbuf *sb, int start)
+{
+ int i, eol;
+ const char *nl;
+
+ /* Check if the rest is just whitespace and Signed-off-by's. */
+ for (i = start; i < sb->len; i++) {
+ nl = memchr(sb->buf + i, '\n', sb->len - i);
+ if (nl)
+ eol = nl - sb->buf;
+ else
+ eol = sb->len;
+
+ if (strlen(sign_off_header) <= eol - i &&
+ starts_with(sb->buf + i, sign_off_header)) {
+ i = eol;
+ continue;
+ }
+ while (i < eol)
+ if (!isspace(sb->buf[i++]))
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * Find out if the message in the strbuf contains only whitespace and
+ * Signed-off-by lines.
+ */
+int message_is_empty(const struct strbuf *sb,
+ enum commit_msg_cleanup_mode cleanup_mode)
+{
+ if (cleanup_mode == COMMIT_MSG_CLEANUP_NONE && sb->len)
+ return 0;
+ return rest_is_empty(sb, 0);
+}
+
+/*
+ * See if the user edited the message in the editor or left what
+ * was in the template intact
+ */
+int template_untouched(const struct strbuf *sb, const char *template_file,
+ enum commit_msg_cleanup_mode cleanup_mode)
+{
+ struct strbuf tmpl = STRBUF_INIT;
+ const char *start;
+
+ if (cleanup_mode == COMMIT_MSG_CLEANUP_NONE && sb->len)
+ return 0;
+
+ if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0)
+ return 0;
+
+ strbuf_stripspace(&tmpl, cleanup_mode == COMMIT_MSG_CLEANUP_ALL);
+ if (!skip_prefix(sb->buf, tmpl.buf, &start))
+ start = sb->buf;
+ strbuf_release(&tmpl);
+ return rest_is_empty(sb, start - sb->buf);
+}
+
+int update_head_with_reflog(const struct commit *old_head,
+ const struct object_id *new_head,
+ const char *action, const struct strbuf *msg,
+ struct strbuf *err)
+{
+ struct ref_transaction *transaction;
+ struct strbuf sb = STRBUF_INIT;
+ const char *nl;
+ int ret = 0;
+
+ if (action) {
+ strbuf_addstr(&sb, action);
+ strbuf_addstr(&sb, ": ");
+ }
+
+ nl = strchr(msg->buf, '\n');
+ if (nl) {
+ strbuf_add(&sb, msg->buf, nl + 1 - msg->buf);
+ } else {
+ strbuf_addbuf(&sb, msg);
+ strbuf_addch(&sb, '\n');
+ }
+
+ transaction = ref_transaction_begin(err);
+ if (!transaction ||
+ ref_transaction_update(transaction, "HEAD", new_head,
+ old_head ? &old_head->object.oid : &null_oid,
+ 0, sb.buf, err) ||
+ ref_transaction_commit(transaction, err)) {
+ ret = -1;
+ }
+ ref_transaction_free(transaction);
+ strbuf_release(&sb);
+
+ return ret;
+}
+
+static int run_rewrite_hook(const struct object_id *oldoid,
+ const struct object_id *newoid)
+{
+ struct child_process proc = CHILD_PROCESS_INIT;
+ const char *argv[3];
+ int code;
+ struct strbuf sb = STRBUF_INIT;
+
+ argv[0] = find_hook("post-rewrite");
+ if (!argv[0])
+ return 0;
+
+ argv[1] = "amend";
+ argv[2] = NULL;
+
+ proc.argv = argv;
+ proc.in = -1;
+ proc.stdout_to_stderr = 1;
+
+ code = start_command(&proc);
+ if (code)
+ return code;
+ strbuf_addf(&sb, "%s %s\n", oid_to_hex(oldoid), oid_to_hex(newoid));
+ sigchain_push(SIGPIPE, SIG_IGN);
+ write_in_full(proc.in, sb.buf, sb.len);
+ close(proc.in);
+ strbuf_release(&sb);
+ sigchain_pop(SIGPIPE);
+ return finish_command(&proc);
+}
+
+void commit_post_rewrite(const struct commit *old_head,
+ const struct object_id *new_head)
+{
+ struct notes_rewrite_cfg *cfg;
+
+ cfg = init_copy_notes_for_rewrite("amend");
+ if (cfg) {
+ /* we are amending, so old_head is not NULL */
+ copy_note_for_rewrite(cfg, &old_head->object.oid, new_head);
+ finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'");
+ }
+ run_rewrite_hook(&old_head->object.oid, new_head);
+}
+
+static int run_prepare_commit_msg_hook(struct strbuf *msg, const char *commit)
+{
+ struct argv_array hook_env = ARGV_ARRAY_INIT;
+ int ret;
+ const char *name;
+
+ name = git_path_commit_editmsg();
+ if (write_message(msg->buf, msg->len, name, 0))
+ return -1;
+
+ argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", get_index_file());
+ argv_array_push(&hook_env, "GIT_EDITOR=:");
+ if (commit)
+ ret = run_hook_le(hook_env.argv, "prepare-commit-msg", name,
+ "commit", commit, NULL);
+ else
+ ret = run_hook_le(hook_env.argv, "prepare-commit-msg", name,
+ "message", NULL);
+ if (ret)
+ ret = error(_("'prepare-commit-msg' hook failed"));
+ argv_array_clear(&hook_env);
+
+ return ret;
+}
+
+static const char implicit_ident_advice_noconfig[] =
+N_("Your name and email address were configured automatically based\n"
+"on your username and hostname. Please check that they are accurate.\n"
+"You can suppress this message by setting them explicitly. Run the\n"
+"following command and follow the instructions in your editor to edit\n"
+"your configuration file:\n"
+"\n"
+" git config --global --edit\n"
+"\n"
+"After doing this, you may fix the identity used for this commit with:\n"
+"\n"
+" git commit --amend --reset-author\n");
+
+static const char implicit_ident_advice_config[] =
+N_("Your name and email address were configured automatically based\n"
+"on your username and hostname. Please check that they are accurate.\n"
+"You can suppress this message by setting them explicitly:\n"
+"\n"
+" git config --global user.name \"Your Name\"\n"
+" git config --global user.email you@example.com\n"
+"\n"
+"After doing this, you may fix the identity used for this commit with:\n"
+"\n"
+" git commit --amend --reset-author\n");
+
+static const char *implicit_ident_advice(void)
+{
+ char *user_config = expand_user_path("~/.gitconfig", 0);
+ char *xdg_config = xdg_config_home("config");
+ int config_exists = file_exists(user_config) || file_exists(xdg_config);
+
+ free(user_config);
+ free(xdg_config);
+
+ if (config_exists)
+ return _(implicit_ident_advice_config);
+ else
+ return _(implicit_ident_advice_noconfig);
+
+}
+
+void print_commit_summary(const char *prefix, const struct object_id *oid,
+ unsigned int flags)
+{
+ struct rev_info rev;
+ struct commit *commit;
+ struct strbuf format = STRBUF_INIT;
+ const char *head;
+ struct pretty_print_context pctx = {0};
+ struct strbuf author_ident = STRBUF_INIT;
+ struct strbuf committer_ident = STRBUF_INIT;
+
+ commit = lookup_commit(oid);
+ if (!commit)
+ die(_("couldn't look up newly created commit"));
+ if (parse_commit(commit))
+ die(_("could not parse newly created commit"));
+
+ strbuf_addstr(&format, "format:%h] %s");
+
+ format_commit_message(commit, "%an <%ae>", &author_ident, &pctx);
+ format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx);
+ if (strbuf_cmp(&author_ident, &committer_ident)) {
+ strbuf_addstr(&format, "\n Author: ");
+ strbuf_addbuf_percentquote(&format, &author_ident);
+ }
+ if (flags & SUMMARY_SHOW_AUTHOR_DATE) {
+ struct strbuf date = STRBUF_INIT;
+
+ format_commit_message(commit, "%ad", &date, &pctx);
+ strbuf_addstr(&format, "\n Date: ");
+ strbuf_addbuf_percentquote(&format, &date);
+ strbuf_release(&date);
+ }
+ if (!committer_ident_sufficiently_given()) {
+ strbuf_addstr(&format, "\n Committer: ");
+ strbuf_addbuf_percentquote(&format, &committer_ident);
+ if (advice_implicit_identity) {
+ strbuf_addch(&format, '\n');
+ strbuf_addstr(&format, implicit_ident_advice());
+ }
+ }
+ strbuf_release(&author_ident);
+ strbuf_release(&committer_ident);
+
+ init_revisions(&rev, prefix);
+ setup_revisions(0, NULL, &rev, NULL);
+
+ rev.diff = 1;
+ rev.diffopt.output_format =
+ DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY;
+
+ rev.verbose_header = 1;
+ rev.show_root_diff = 1;
+ get_commit_format(format.buf, &rev);
+ rev.always_show_header = 0;
+ rev.diffopt.detect_rename = DIFF_DETECT_RENAME;
+ rev.diffopt.break_opt = 0;
+ diff_setup_done(&rev.diffopt);
+
+ head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
+ if (!head)
+ die_errno(_("unable to resolve HEAD after creating commit"));
+ if (!strcmp(head, "HEAD"))
+ head = _("detached HEAD");
+ else
+ skip_prefix(head, "refs/heads/", &head);
+ printf("[%s%s ", head, (flags & SUMMARY_INITIAL_COMMIT) ?
+ _(" (root-commit)") : "");
+
+ if (!log_tree_commit(&rev, commit)) {
+ rev.always_show_header = 1;
+ rev.use_terminator = 1;
+ log_tree_commit(&rev, commit);
+ }
+
+ 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, &current_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 commit_msg = STRBUF_INIT;
+ char *amend_author = NULL;
+ const char *hook_commit = NULL;
+ enum commit_msg_cleanup_mode cleanup;
+ int res = 0;
+
+ if (parse_head(&current_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 = &commit_msg;
+ strbuf_addstr(msg, orig_message);
+ hook_commit = "HEAD";
+ }
+ 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);
+ }
+
+ if (write_cache_as_tree(&tree, 0, NULL)) {
+ res = error(_("git write-tree failed to write a tree"));
+ goto out;
+ }
+
+ if (!(flags & ALLOW_EMPTY) && !oidcmp(current_head ?
+ get_commit_tree_oid(current_head) :
+ &empty_tree_oid, &tree)) {
+ res = 1; /* run 'git commit' to display error message */
+ goto out;
+ }
+
+ if (find_hook("prepare-commit-msg")) {
+ res = run_prepare_commit_msg_hook(msg, hook_commit);
+ if (res)
+ goto out;
+ if (strbuf_read_file(&commit_msg, git_path_commit_editmsg(),
+ 2048) < 0) {
+ res = error_errno(_("unable to read commit message "
+ "from '%s'"),
+ git_path_commit_editmsg());
+ goto out;
+ }
+ msg = &commit_msg;
+ }
+
+ 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;
+ }
+
+ reset_ident_date();
+
+ if (commit_tree_extended(msg->buf, msg->len, &tree, parents,
+ oid, 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(&commit_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;
@@ -706,12 +1219,12 @@ static int is_original_commit_empty(struct commit *commit)
if (parse_commit(parent))
return error(_("could not parse parent commit %s"),
oid_to_hex(&parent->object.oid));
- ptree_oid = &parent->tree->object.oid;
+ ptree_oid = get_commit_tree_oid(parent);
} else {
ptree_oid = the_hash_algo->empty_tree; /* commit is root */
}
- return !oidcmp(ptree_oid, &commit->tree->object.oid);
+ return !oidcmp(ptree_oid, get_commit_tree_oid(commit));
}
/*
@@ -952,6 +1465,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
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;
@@ -963,7 +1477,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
* that represents the "current" state for merge-recursive
* to work on.
*/
- if (write_cache_as_tree(head.hash, 0, NULL))
+ if (write_cache_as_tree(&head, 0, NULL))
return error(_("your index file is unmerged."));
} else {
unborn = get_oid("HEAD", &head);
@@ -1066,6 +1580,8 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
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)
@@ -1091,6 +1607,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
}
}
+ if (opts->signoff && !is_fixup(command))
+ append_signoff(&msgbuf, 0, 0);
+
if (is_rebase_i(opts) && write_author_script(msg.message) < 0)
res = -1;
else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) {
@@ -1148,9 +1667,13 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
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());
@@ -1159,6 +1682,7 @@ fast_forward_edit:
leave:
free_message(commit, &msg);
+ free(author);
update_abort_safety_file();
return res;
@@ -1183,7 +1707,7 @@ static int prepare_revs(struct replay_opts *opts)
static int read_and_refresh_cache(struct replay_opts *opts)
{
- static struct lock_file index_lock;
+ struct lock_file index_lock = LOCK_INIT;
int index_fd = hold_locked_index(&index_lock, 0);
if (read_index_preload(&the_index, NULL) < 0) {
rollback_lock_file(&index_lock);
@@ -1191,13 +1715,13 @@ static int read_and_refresh_cache(struct replay_opts *opts)
_(action_name(opts)));
}
refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
- if (the_index.cache_changed && index_fd >= 0) {
- if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) {
+ if (index_fd >= 0) {
+ if (write_locked_index(&the_index, &index_lock,
+ COMMIT_LOCK | SKIP_IF_UNCHANGED)) {
return error(_("git %s: failed to refresh the index"),
_(action_name(opts)));
}
}
- rollback_lock_file(&index_lock);
return 0;
}
@@ -1347,22 +1871,31 @@ static int count_commands(struct todo_list *todo_list)
return count;
}
+static ssize_t strbuf_read_file_or_whine(struct strbuf *sb, const char *path)
+{
+ int fd;
+ ssize_t len;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return error_errno(_("could not open '%s'"), path);
+ len = strbuf_read(sb, fd, 0);
+ close(fd);
+ if (len < 0)
+ return error(_("could not read '%s'."), path);
+ return len;
+}
+
static int read_populate_todo(struct todo_list *todo_list,
struct replay_opts *opts)
{
struct stat st;
const char *todo_file = get_todo_path(opts);
- int fd, res;
+ int res;
strbuf_reset(&todo_list->buf);
- fd = open(todo_file, O_RDONLY);
- if (fd < 0)
- return error_errno(_("could not open '%s'"), todo_file);
- if (strbuf_read(&todo_list->buf, fd, 0) < 0) {
- close(fd);
- return error(_("could not read '%s'."), todo_file);
- }
- close(fd);
+ if (strbuf_read_file_or_whine(&todo_list->buf, todo_file) < 0)
+ return -1;
res = stat(todo_file, &st);
if (res)
@@ -1513,6 +2046,11 @@ static int read_populate_opts(struct replay_opts *opts)
if (file_exists(rebase_path_verbose()))
opts->verbose = 1;
+ if (file_exists(rebase_path_signoff())) {
+ opts->allow_ff = 0;
+ opts->signoff = 1;
+ }
+
read_strategy_opts(opts, &buf);
strbuf_release(&buf);
@@ -1577,16 +2115,14 @@ static int create_seq_dir(void)
static int save_head(const char *head)
{
- static struct lock_file head_lock;
+ struct lock_file head_lock = LOCK_INIT;
struct strbuf buf = STRBUF_INIT;
int fd;
ssize_t written;
fd = hold_lock_file_for_update(&head_lock, git_path_head_file(), 0);
- if (fd < 0) {
- rollback_lock_file(&head_lock);
+ if (fd < 0)
return error_errno(_("could not lock HEAD"));
- }
strbuf_addf(&buf, "%s\n", head);
written = write_in_full(fd, buf.buf, buf.len);
strbuf_release(&buf);
@@ -1595,10 +2131,8 @@ static int save_head(const char *head)
return error_errno(_("could not write to '%s'"),
git_path_head_file());
}
- if (commit_lock_file(&head_lock) < 0) {
- rollback_lock_file(&head_lock);
- return error(_("failed to finalize '%s'."), git_path_head_file());
- }
+ if (commit_lock_file(&head_lock) < 0)
+ return error(_("failed to finalize '%s'"), git_path_head_file());
return 0;
}
@@ -1702,7 +2236,7 @@ fail:
static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
{
- static struct lock_file todo_lock;
+ struct lock_file todo_lock = LOCK_INIT;
const char *todo_path = get_todo_path(opts);
int next = todo_list->current, offset, fd;
@@ -1722,7 +2256,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
todo_list->buf.len - offset) < 0)
return error_errno(_("could not write to '%s'"), todo_path);
if (commit_lock_file(&todo_lock) < 0)
- return error(_("failed to finalize '%s'."), todo_path);
+ return error(_("failed to finalize '%s'"), todo_path);
if (is_rebase_i(opts)) {
const char *done_path = rebase_path_done();
@@ -1792,6 +2326,9 @@ static int make_patch(struct commit *commit, struct replay_opts *opts)
p = short_commit_name(commit);
if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0)
return -1;
+ if (update_ref("rebase", "REBASE_HEAD", &commit->object.oid,
+ NULL, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
+ res |= error(_("could not update %s"), "REBASE_HEAD");
strbuf_addf(&buf, "%s/patch", get_dir(opts));
memset(&log_tree_opt, 0, sizeof(log_tree_opt));
@@ -2043,6 +2580,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
unlink(rebase_path_author_script());
unlink(rebase_path_stopped_sha());
unlink(rebase_path_amend());
+ delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
}
if (item->command <= TODO_SQUASH) {
if (is_rebase_i(opts))
@@ -2346,9 +2884,11 @@ int sequencer_pick_revisions(struct replay_opts *opts)
if (!get_oid(name, &oid)) {
if (!lookup_commit_reference_gently(&oid, 1)) {
- enum object_type type = sha1_object_info(oid.hash, NULL);
+ enum object_type type = oid_object_info(the_repository,
+ &oid,
+ NULL);
return error(_("%s: can't cherry-pick a %s"),
- name, typename(type));
+ name, type_name(type));
}
} else
return error(_("%s: bad revision"), name);
@@ -2470,7 +3010,7 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,
init_revisions(&revs, NULL);
revs.verbose_header = 1;
revs.max_parents = 1;
- revs.cherry_pick = 1;
+ revs.cherry_mark = 1;
revs.limited = 1;
revs.reverse = 1;
revs.right_only = 1;
@@ -2495,8 +3035,12 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,
return error(_("make_script: error preparing revisions"));
while ((commit = get_revision(&revs))) {
+ int is_empty = is_original_commit_empty(commit);
+
+ if (!is_empty && (commit->object.flags & PATCHSAME))
+ continue;
strbuf_reset(&buf);
- if (!keep_empty && is_original_commit_empty(commit))
+ if (!keep_empty && is_empty)
strbuf_addf(&buf, "%c ", comment_line_char);
strbuf_addf(&buf, "%s %s ", insn,
oid_to_hex(&commit->object.oid));
@@ -2629,20 +3173,13 @@ int check_todo_list(void)
struct strbuf todo_file = STRBUF_INIT;
struct todo_list todo_list = TODO_LIST_INIT;
struct strbuf missing = STRBUF_INIT;
- int advise_to_edit_todo = 0, res = 0, fd, i;
+ int advise_to_edit_todo = 0, res = 0, i;
strbuf_addstr(&todo_file, rebase_path_todo());
- fd = open(todo_file.buf, O_RDONLY);
- if (fd < 0) {
- res = error_errno(_("could not open '%s'"), todo_file.buf);
- goto leave_check;
- }
- if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
- close(fd);
- res = error(_("could not read '%s'."), todo_file.buf);
+ if (strbuf_read_file_or_whine(&todo_list.buf, todo_file.buf) < 0) {
+ res = -1;
goto leave_check;
}
- close(fd);
advise_to_edit_todo = res =
parse_insn_buffer(todo_list.buf.buf, &todo_list);
@@ -2658,17 +3195,10 @@ int check_todo_list(void)
todo_list_release(&todo_list);
strbuf_addstr(&todo_file, ".backup");
- fd = open(todo_file.buf, O_RDONLY);
- if (fd < 0) {
- res = error_errno(_("could not open '%s'"), todo_file.buf);
- goto leave_check;
- }
- if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
- close(fd);
- res = error(_("could not read '%s'."), todo_file.buf);
+ if (strbuf_read_file_or_whine(&todo_list.buf, todo_file.buf) < 0) {
+ res = -1;
goto leave_check;
}
- close(fd);
strbuf_release(&todo_file);
res = !!parse_insn_buffer(todo_list.buf.buf, &todo_list);
@@ -2749,15 +3279,8 @@ int skip_unnecessary_picks(void)
}
strbuf_release(&buf);
- fd = open(todo_file, O_RDONLY);
- if (fd < 0) {
- return error_errno(_("could not open '%s'"), todo_file);
- }
- if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
- close(fd);
- return error(_("could not read '%s'."), todo_file);
- }
- close(fd);
+ if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0)
+ return -1;
if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) {
todo_list_release(&todo_list);
return -1;
@@ -2848,17 +3371,11 @@ int rearrange_squash(void)
const char *todo_file = rebase_path_todo();
struct todo_list todo_list = TODO_LIST_INIT;
struct hashmap subject2item;
- int res = 0, rearranged = 0, *next, *tail, fd, i;
+ int res = 0, rearranged = 0, *next, *tail, i;
char **subjects;
- fd = open(todo_file, O_RDONLY);
- if (fd < 0)
- return error_errno(_("could not open '%s'"), todo_file);
- if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
- close(fd);
- return error(_("could not read '%s'."), todo_file);
- }
- close(fd);
+ if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0)
+ return -1;
if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) {
todo_list_release(&todo_list);
return -1;