summaryrefslogtreecommitdiff
path: root/sequencer.c
diff options
context:
space:
mode:
Diffstat (limited to 'sequencer.c')
-rw-r--r--sequencer.c828
1 files changed, 704 insertions, 124 deletions
diff --git a/sequencer.c b/sequencer.c
index 1f729b053b..e90bc316bb 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1,4 +1,5 @@
#include "cache.h"
+#include "config.h"
#include "lockfile.h"
#include "sequencer.h"
#include "dir.h"
@@ -19,6 +20,7 @@
#include "trailer.h"
#include "log-tree.h"
#include "wt-status.h"
+#include "hashmap.h"
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
@@ -126,6 +128,7 @@ static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto")
static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash")
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 inline int is_rebase_i(const struct replay_opts *opts)
{
@@ -201,7 +204,7 @@ int sequencer_remove_state(struct replay_opts *opts)
free(opts->xopts[i]);
free(opts->xopts);
- strbuf_addf(&dir, "%s", get_dir(opts));
+ strbuf_addstr(&dir, get_dir(opts));
remove_dir_recursively(&dir, 0);
strbuf_release(&dir);
@@ -344,7 +347,7 @@ static int read_oneliner(struct strbuf *buf,
static struct tree *empty_tree(void)
{
- return lookup_tree(EMPTY_TREE_SHA1_BIN);
+ return lookup_tree(the_hash_algo->empty_tree);
}
static int error_dirty_index(struct replay_opts *opts)
@@ -374,7 +377,7 @@ static void update_abort_safety_file(void)
write_file(git_path_abort_safety_file(), "%s", "");
}
-static int fast_forward_to(const unsigned char *to, const unsigned char *from,
+static int fast_forward_to(const struct object_id *to, const struct object_id *from,
int unborn, struct replay_opts *opts)
{
struct ref_transaction *transaction;
@@ -390,7 +393,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from,
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, "HEAD",
- to, unborn ? null_sha1 : from,
+ to, unborn ? &null_oid : from,
0, sb.buf, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
@@ -426,7 +429,7 @@ void append_conflicts_hint(struct strbuf *msgbuf)
static int do_recursive_merge(struct commit *base, struct commit *next,
const char *base_label, const char *next_label,
- unsigned char *head, struct strbuf *msgbuf,
+ struct object_id *head, struct strbuf *msgbuf,
struct replay_opts *opts)
{
struct merge_options o;
@@ -435,7 +438,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
char **xopt;
static struct lock_file index_lock;
- hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR);
+ if (hold_locked_index(&index_lock, LOCK_REPORT_ON_ERROR) < 0)
+ return -1;
read_cache();
@@ -464,7 +468,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
if (active_cache_changed &&
write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
- /* TRANSLATORS: %s will be "revert", "cherry-pick" or
+ /*
+ * TRANSLATORS: %s will be "revert", "cherry-pick" or
* "rebase -i".
*/
return error(_("%s: Unable to write new index file"),
@@ -482,13 +487,13 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
static int is_index_unchanged(void)
{
- unsigned char head_sha1[20];
+ struct object_id head_oid;
struct commit *head_commit;
- if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL))
+ if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
return error(_("could not resolve HEAD commit\n"));
- head_commit = lookup_commit(head_sha1);
+ head_commit = lookup_commit(&head_oid);
/*
* If head_commit is NULL, check_commit, called from
@@ -508,7 +513,8 @@ static int is_index_unchanged(void)
if (cache_tree_update(&the_index, 0))
return error(_("unable to update cache tree\n"));
- return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.oid.hash);
+ return !oidcmp(&active_cache_tree->oid,
+ &head_commit->tree->object.oid);
}
static int write_author_script(const char *message)
@@ -602,6 +608,12 @@ N_("you have staged changes in your working tree\n"
"\n"
" git rebase --continue\n");
+#define ALLOW_EMPTY (1<<0)
+#define EDIT_MSG (1<<1)
+#define AMEND_MSG (1<<2)
+#define CLEANUP_MSG (1<<3)
+#define VERIFY_MSG (1<<4)
+
/*
* If we are cherry-pick, and if the merge did not result in
* hand-editing, we will hit this commit and inherit the original
@@ -615,8 +627,7 @@ N_("you have staged changes in your working tree\n"
* author metadata.
*/
static int run_git_commit(const char *defmsg, struct replay_opts *opts,
- int allow_empty, int edit, int amend,
- int cleanup_commit_message)
+ unsigned int flags)
{
struct child_process cmd = CHILD_PROCESS_INIT;
const char *value;
@@ -624,7 +635,7 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
cmd.git_cmd = 1;
if (is_rebase_i(opts)) {
- if (!edit) {
+ if (!(flags & EDIT_MSG)) {
cmd.stdout_to_stderr = 1;
cmd.err = -1;
}
@@ -638,9 +649,10 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
}
argv_array_push(&cmd.args, "commit");
- argv_array_push(&cmd.args, "-n");
- if (amend)
+ if (!(flags & VERIFY_MSG))
+ argv_array_push(&cmd.args, "-n");
+ if ((flags & AMEND_MSG))
argv_array_push(&cmd.args, "--amend");
if (opts->gpg_sign)
argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
@@ -648,16 +660,16 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
argv_array_push(&cmd.args, "-s");
if (defmsg)
argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
- if (cleanup_commit_message)
+ if ((flags & CLEANUP_MSG))
argv_array_push(&cmd.args, "--cleanup=strip");
- if (edit)
+ if ((flags & EDIT_MSG))
argv_array_push(&cmd.args, "-e");
- else if (!cleanup_commit_message &&
+ else if (!(flags & CLEANUP_MSG) &&
!opts->signoff && !opts->record_origin &&
git_config_get_value("commit.cleanup", &value))
argv_array_push(&cmd.args, "--cleanup=verbatim");
- if (allow_empty)
+ if ((flags & ALLOW_EMPTY))
argv_array_push(&cmd.args, "--allow-empty");
if (opts->allow_empty_message)
@@ -682,7 +694,7 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
static int is_original_commit_empty(struct commit *commit)
{
- const unsigned char *ptree_sha1;
+ const struct object_id *ptree_oid;
if (parse_commit(commit))
return error(_("could not parse commit %s\n"),
@@ -692,12 +704,12 @@ static int is_original_commit_empty(struct commit *commit)
if (parse_commit(parent))
return error(_("could not parse parent commit %s\n"),
oid_to_hex(&parent->object.oid));
- ptree_sha1 = parent->tree->object.oid.hash;
+ ptree_oid = &parent->tree->object.oid;
} else {
- ptree_sha1 = EMPTY_TREE_SHA1_BIN; /* commit is root */
+ ptree_oid = the_hash_algo->empty_tree; /* commit is root */
}
- return !hashcmp(ptree_sha1, commit->tree->object.oid.hash);
+ return !oidcmp(ptree_oid, &commit->tree->object.oid);
}
/*
@@ -828,13 +840,13 @@ static int update_squash_messages(enum todo_command command,
strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
strbuf_release(&header);
} else {
- unsigned char head[20];
+ struct object_id head;
struct commit *head_commit;
const char *head_message, *body;
- if (get_sha1("HEAD", head))
+ if (get_oid("HEAD", &head))
return error(_("need a HEAD to fixup"));
- if (!(head_commit = lookup_commit_reference(head)))
+ if (!(head_commit = lookup_commit_reference(&head)))
return error(_("could not read HEAD"));
if (!(head_message = get_commit_buffer(head_commit, NULL)))
return error(_("could not read HEAD's commit message"));
@@ -887,18 +899,18 @@ static int update_squash_messages(enum todo_command command,
static void flush_rewritten_pending(void) {
struct strbuf buf = STRBUF_INIT;
- unsigned char newsha1[20];
+ struct object_id newoid;
FILE *out;
- if (strbuf_read_file(&buf, rebase_path_rewritten_pending(), 82) > 0 &&
- !get_sha1("HEAD", newsha1) &&
- (out = fopen(rebase_path_rewritten_list(), "a"))) {
+ if (strbuf_read_file(&buf, rebase_path_rewritten_pending(), (GIT_MAX_HEXSZ + 1) * 2) > 0 &&
+ !get_oid("HEAD", &newoid) &&
+ (out = fopen_or_warn(rebase_path_rewritten_list(), "a"))) {
char *bol = buf.buf, *eol;
while (*bol) {
eol = strchrnul(bol, '\n');
fprintf(out, "%.*s %s\n", (int)(eol - bol),
- bol, sha1_to_hex(newsha1));
+ bol, oid_to_hex(&newoid));
if (!*eol)
break;
bol = eol + 1;
@@ -911,7 +923,7 @@ static void flush_rewritten_pending(void) {
static void record_in_rewritten(struct object_id *oid,
enum todo_command next_command) {
- FILE *out = fopen(rebase_path_rewritten_pending(), "a");
+ FILE *out = fopen_or_warn(rebase_path_rewritten_pending(), "a");
if (!out)
return;
@@ -926,14 +938,14 @@ static void record_in_rewritten(struct object_id *oid,
static int do_pick_commit(enum todo_command command, struct commit *commit,
struct replay_opts *opts, int final_fixup)
{
- int edit = opts->edit, cleanup_commit_message = 0;
- const char *msg_file = edit ? NULL : git_path_merge_msg();
- unsigned char head[20];
+ unsigned int flags = opts->edit ? EDIT_MSG : 0;
+ const char *msg_file = opts->edit ? NULL : git_path_merge_msg();
+ struct object_id head;
struct commit *base, *next, *parent;
const char *base_label, *next_label;
struct commit_message msg = { NULL, NULL, NULL, NULL };
struct strbuf msgbuf = STRBUF_INIT;
- int res, unborn = 0, amend = 0, allow = 0;
+ int res, unborn = 0, allow;
if (opts->no_commit) {
/*
@@ -942,13 +954,14 @@ 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, 0, NULL))
+ if (write_cache_as_tree(head.hash, 0, NULL))
return error(_("your index file is unmerged."));
} else {
- unborn = get_sha1("HEAD", head);
+ unborn = get_oid("HEAD", &head);
if (unborn)
- hashcpy(head, EMPTY_TREE_SHA1_BIN);
- if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", 0, 0))
+ oidcpy(&head, the_hash_algo->empty_tree);
+ if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD",
+ NULL, 0))
return error_dirty_index(opts);
}
discard_cache();
@@ -983,15 +996,17 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
oid_to_hex(&commit->object.oid));
if (opts->allow_ff && !is_fixup(command) &&
- ((parent && !hashcmp(parent->object.oid.hash, head)) ||
+ ((parent && !oidcmp(&parent->object.oid, &head)) ||
(!parent && unborn))) {
if (is_rebase_i(opts))
write_author_script(msg.message);
- res = fast_forward_to(commit->object.oid.hash, head, unborn,
+ res = fast_forward_to(&commit->object.oid, &head, unborn,
opts);
if (res || command != TODO_REWORD)
goto leave;
- edit = amend = 1;
+ flags |= EDIT_MSG | AMEND_MSG;
+ if (command == TODO_REWORD)
+ flags |= VERIFY_MSG;
msg_file = NULL;
goto fast_forward_edit;
}
@@ -1037,6 +1052,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
strbuf_addstr(&msgbuf, p);
if (opts->record_origin) {
+ strbuf_complete_line(&msgbuf);
if (!has_conforming_footer(&msgbuf, NULL, 0))
strbuf_addch(&msgbuf, '\n');
strbuf_addstr(&msgbuf, cherry_picked_prefix);
@@ -1046,25 +1062,25 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
}
if (command == TODO_REWORD)
- edit = 1;
+ flags |= EDIT_MSG | VERIFY_MSG;
else if (is_fixup(command)) {
if (update_squash_messages(command, commit, opts))
return -1;
- amend = 1;
+ flags |= AMEND_MSG;
if (!final_fixup)
msg_file = rebase_path_squash_msg();
else if (file_exists(rebase_path_fixup_msg())) {
- cleanup_commit_message = 1;
+ flags |= CLEANUP_MSG;
msg_file = rebase_path_fixup_msg();
} else {
- const char *dest = git_path("SQUASH_MSG");
+ const char *dest = git_path_squash_msg();
unlink(dest);
if (copy_file(dest, rebase_path_squash_msg(), 0666))
return error(_("could not rename '%s' to '%s'"),
rebase_path_squash_msg(), dest);
- unlink(git_path("MERGE_MSG"));
+ unlink(git_path_merge_msg());
msg_file = dest;
- edit = 1;
+ flags |= EDIT_MSG;
}
}
@@ -1072,7 +1088,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
res = -1;
else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) {
res = do_recursive_merge(base, next, base_label, next_label,
- head, &msgbuf, opts);
+ &head, &msgbuf, opts);
if (res < 0)
return res;
res |= write_message(msgbuf.buf, msgbuf.len,
@@ -1088,7 +1104,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
commit_list_insert(next, &remotes);
res |= try_merge_command(opts->strategy,
opts->xopts_nr, (const char **)opts->xopts,
- common, sha1_to_hex(head), remotes);
+ common, oid_to_hex(&head), remotes);
free_commit_list(common);
free_commit_list(remotes);
}
@@ -1101,12 +1117,12 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
* write it at all.
*/
if (command == TODO_PICK && !opts->no_commit && (res == 0 || res == 1) &&
- update_ref(NULL, "CHERRY_PICK_HEAD", commit->object.oid.hash, NULL,
- REF_NODEREF, UPDATE_REFS_MSG_ON_ERR))
+ update_ref(NULL, "CHERRY_PICK_HEAD", &commit->object.oid, NULL,
+ REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
res = -1;
if (command == TODO_REVERT && ((opts->no_commit && res == 0) || res == 1) &&
- update_ref(NULL, "REVERT_HEAD", commit->object.oid.hash, NULL,
- REF_NODEREF, UPDATE_REFS_MSG_ON_ERR))
+ update_ref(NULL, "REVERT_HEAD", &commit->object.oid, NULL,
+ REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
res = -1;
if (res) {
@@ -1123,11 +1139,11 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
if (allow < 0) {
res = allow;
goto leave;
- }
+ } else if (allow)
+ flags |= ALLOW_EMPTY;
if (!opts->no_commit)
fast_forward_edit:
- res = run_git_commit(msg_file, opts, allow, edit, amend,
- cleanup_commit_message);
+ res = run_git_commit(msg_file, opts, flags);
if (!res && final_fixup) {
unlink(rebase_path_fixup_msg());
@@ -1170,7 +1186,6 @@ static int read_and_refresh_cache(struct replay_opts *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)) {
- rollback_lock_file(&index_lock);
return error(_("git %s: failed to refresh the index"),
_(action_name(opts)));
}
@@ -1192,6 +1207,7 @@ struct todo_list {
struct todo_item *items;
int nr, alloc, current;
int done_nr, total_nr;
+ struct stat_data stat;
};
#define TODO_LIST_INIT { STRBUF_INIT }
@@ -1199,8 +1215,7 @@ struct todo_list {
static void todo_list_release(struct todo_list *todo_list)
{
strbuf_release(&todo_list->buf);
- free(todo_list->items);
- todo_list->items = NULL;
+ FREE_AND_NULL(todo_list->items);
todo_list->nr = todo_list->alloc = 0;
}
@@ -1212,7 +1227,7 @@ static struct todo_item *append_new_todo(struct todo_list *todo_list)
static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
{
- unsigned char commit_sha1[20];
+ struct object_id commit_oid;
char *end_of_object_name;
int i, saved, status, padding;
@@ -1261,7 +1276,7 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
end_of_object_name = (char *) bol + strcspn(bol, " \t\n");
saved = *end_of_object_name;
*end_of_object_name = '\0';
- status = get_sha1(bol, commit_sha1);
+ status = get_oid(bol, &commit_oid);
*end_of_object_name = saved;
item->arg = end_of_object_name + strspn(end_of_object_name, " \t");
@@ -1270,7 +1285,7 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
if (status < 0)
return -1;
- item->commit = lookup_commit_reference(commit_sha1);
+ item->commit = lookup_commit_reference(&commit_oid);
return !item->commit;
}
@@ -1322,6 +1337,7 @@ static int count_commands(struct todo_list *todo_list)
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;
@@ -1335,6 +1351,11 @@ static int read_populate_todo(struct todo_list *todo_list,
}
close(fd);
+ res = stat(todo_file, &st);
+ if (res)
+ return error(_("could not stat '%s'"), todo_file);
+ fill_stat_data(&todo_list->stat, &st);
+
res = parse_insn_buffer(todo_list->buf.buf, todo_list);
if (res) {
if (is_rebase_i(opts))
@@ -1363,7 +1384,7 @@ static int read_populate_todo(struct todo_list *todo_list,
if (is_rebase_i(opts)) {
struct todo_list done = TODO_LIST_INIT;
- FILE *f = fopen(rebase_path_msgtotal(), "w");
+ FILE *f = fopen_or_warn(rebase_path_msgtotal(), "w");
if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 &&
!parse_insn_buffer(done.buf.buf, &done))
@@ -1420,7 +1441,11 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
else if (!strcmp(key, "options.strategy-option")) {
ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
opts->xopts[opts->xopts_nr++] = xstrdup(value);
- } else
+ } else if (!strcmp(key, "options.allow-rerere-auto"))
+ opts->allow_rerere_auto =
+ git_config_bool_or_int(key, value, &error_flag) ?
+ RERERE_AUTOUPDATE : RERERE_NOAUTOUPDATE;
+ else
return error(_("invalid key: %s"), key);
if (!error_flag)
@@ -1461,6 +1486,15 @@ static int read_populate_opts(struct replay_opts *opts)
free(opts->gpg_sign);
opts->gpg_sign = xstrdup(buf.buf + 2);
}
+ strbuf_reset(&buf);
+ }
+
+ if (read_oneliner(&buf, rebase_path_allow_rerere_autoupdate(), 1)) {
+ if (!strcmp(buf.buf, "--rerere-autoupdate"))
+ opts->allow_rerere_auto = RERERE_AUTOUPDATE;
+ else if (!strcmp(buf.buf, "--no-rerere-autoupdate"))
+ opts->allow_rerere_auto = RERERE_NOAUTOUPDATE;
+ strbuf_reset(&buf);
}
if (file_exists(rebase_path_verbose()))
@@ -1533,6 +1567,7 @@ static int save_head(const char *head)
static struct lock_file head_lock;
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) {
@@ -1540,7 +1575,9 @@ static int save_head(const char *head)
return error_errno(_("could not lock HEAD"));
}
strbuf_addf(&buf, "%s\n", head);
- if (write_in_full(fd, buf.buf, buf.len) < 0) {
+ written = write_in_full(fd, buf.buf, buf.len);
+ strbuf_release(&buf);
+ if (written < 0) {
rollback_lock_file(&head_lock);
return error_errno(_("could not write to '%s'"),
git_path_head_file());
@@ -1576,36 +1613,37 @@ static int rollback_is_safe(void)
return !oidcmp(&actual_head, &expected_head);
}
-static int reset_for_rollback(const unsigned char *sha1)
+static int reset_for_rollback(const struct object_id *oid)
{
const char *argv[4]; /* reset --merge <arg> + NULL */
argv[0] = "reset";
argv[1] = "--merge";
- argv[2] = sha1_to_hex(sha1);
+ argv[2] = oid_to_hex(oid);
argv[3] = NULL;
return run_command_v_opt(argv, RUN_GIT_CMD);
}
static int rollback_single_pick(void)
{
- unsigned char head_sha1[20];
+ struct object_id head_oid;
if (!file_exists(git_path_cherry_pick_head()) &&
!file_exists(git_path_revert_head()))
return error(_("no cherry-pick or revert in progress"));
- if (read_ref_full("HEAD", 0, head_sha1, NULL))
+ if (read_ref_full("HEAD", 0, &head_oid, NULL))
return error(_("cannot resolve HEAD"));
- if (is_null_sha1(head_sha1))
+ if (is_null_oid(&head_oid))
return error(_("cannot abort from a branch yet to be born"));
- return reset_for_rollback(head_sha1);
+ return reset_for_rollback(&head_oid);
}
int sequencer_rollback(struct replay_opts *opts)
{
FILE *f;
- unsigned char sha1[20];
+ struct object_id oid;
struct strbuf buf = STRBUF_INIT;
+ const char *p;
f = fopen(git_path_head_file(), "r");
if (!f && errno == ENOENT) {
@@ -1625,12 +1663,12 @@ int sequencer_rollback(struct replay_opts *opts)
goto fail;
}
fclose(f);
- if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') {
+ if (parse_oid_hex(buf.buf, &oid, &p) || *p != '\0') {
error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"),
git_path_head_file());
goto fail;
}
- if (is_null_sha1(sha1)) {
+ if (is_null_oid(&oid)) {
error(_("cannot abort from a branch yet to be born"));
goto fail;
}
@@ -1640,7 +1678,7 @@ int sequencer_rollback(struct replay_opts *opts)
warning(_("You seem to have moved HEAD. "
"Not rewinding, check your HEAD!"));
} else
- if (reset_for_rollback(sha1))
+ if (reset_for_rollback(&oid))
goto fail;
strbuf_release(&buf);
return sequencer_remove_state(opts);
@@ -1724,6 +1762,10 @@ static int save_opts(struct replay_opts *opts)
"options.strategy-option",
opts->xopts[i], "^$", 0);
}
+ if (opts->allow_rerere_auto)
+ res |= git_config_set_in_file_gently(opts_file, "options.allow-rerere-auto",
+ opts->allow_rerere_auto == RERERE_AUTOUPDATE ?
+ "true" : "false");
return res;
}
@@ -1770,13 +1812,13 @@ static int make_patch(struct commit *commit, struct replay_opts *opts)
static int intend_to_amend(void)
{
- unsigned char head[20];
+ struct object_id head;
char *p;
- if (get_sha1("HEAD", head))
+ if (get_oid("HEAD", &head))
return error(_("cannot read HEAD"));
- p = sha1_to_hex(head);
+ p = oid_to_hex(&head);
return write_message(p, strlen(p), rebase_path_amend(), 1);
}
@@ -1812,21 +1854,24 @@ static int error_failed_squash(struct commit *commit,
return error(_("could not rename '%s' to '%s'"),
rebase_path_squash_msg(), rebase_path_message());
unlink(rebase_path_fixup_msg());
- unlink(git_path("MERGE_MSG"));
- if (copy_file(git_path("MERGE_MSG"), rebase_path_message(), 0666))
+ unlink(git_path_merge_msg());
+ if (copy_file(git_path_merge_msg(), rebase_path_message(), 0666))
return error(_("could not copy '%s' to '%s'"),
- rebase_path_message(), git_path("MERGE_MSG"));
+ rebase_path_message(), git_path_merge_msg());
return error_with_patch(commit, subject, subject_len, opts, 1, 0);
}
static int do_exec(const char *command_line)
{
+ struct argv_array child_env = ARGV_ARRAY_INIT;
const char *child_argv[] = { NULL, NULL };
int dirty, status;
fprintf(stderr, "Executing: %s\n", command_line);
child_argv[0] = command_line;
- status = run_command_v_opt(child_argv, RUN_USING_SHELL);
+ argv_array_pushf(&child_env, "GIT_DIR=%s", absolute_path(get_git_dir()));
+ status = run_command_v_opt_cd_env(child_argv, RUN_USING_SHELL, NULL,
+ child_env.argv);
/* force re-reading of the cache */
if (discard_cache() < 0 || read_cache() < 0)
@@ -1856,6 +1901,8 @@ static int do_exec(const char *command_line)
status = 1;
}
+ argv_array_clear(&child_env);
+
return status;
}
@@ -1898,11 +1945,13 @@ static int apply_autostash(struct replay_opts *opts)
strbuf_trim(&stash_sha1);
child.git_cmd = 1;
+ child.no_stdout = 1;
+ child.no_stderr = 1;
argv_array_push(&child.args, "stash");
argv_array_push(&child.args, "apply");
argv_array_push(&child.args, stash_sha1.buf);
if (!run_command(&child))
- printf(_("Applied autostash."));
+ fprintf(stderr, _("Applied autostash.\n"));
else {
struct child_process store = CHILD_PROCESS_INIT;
@@ -1916,10 +1965,11 @@ static int apply_autostash(struct replay_opts *opts)
if (run_command(&store))
ret = error(_("cannot store %s"), stash_sha1.buf);
else
- printf(_("Applying autostash resulted in conflicts.\n"
- "Your changes are safe in the stash.\n"
- "You can run \"git stash pop\" or"
- " \"git stash drop\" at any time.\n"));
+ fprintf(stderr,
+ _("Applying autostash resulted in conflicts.\n"
+ "Your changes are safe in the stash.\n"
+ "You can run \"git stash pop\" or"
+ " \"git stash drop\" at any time.\n"));
}
strbuf_release(&stash_sha1);
@@ -1997,7 +2047,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
if (item->command == TODO_EDIT) {
struct commit *commit = item->commit;
if (!res)
- warning(_("stopped at %s... %.*s"),
+ fprintf(stderr,
+ _("Stopped at %s... %.*s\n"),
short_commit_name(commit),
item->arg_len, item->arg);
return error_with_patch(commit,
@@ -2019,10 +2070,25 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
} else if (item->command == TODO_EXEC) {
char *end_of_arg = (char *)(item->arg + item->arg_len);
int saved = *end_of_arg;
+ struct stat st;
*end_of_arg = '\0';
res = do_exec(item->arg);
*end_of_arg = saved;
+
+ /* Reread the todo file if it has changed. */
+ if (res)
+ ; /* fall through */
+ else if (stat(get_todo_path(opts), &st))
+ res = error_errno(_("could not stat '%s'"),
+ get_todo_path(opts));
+ else if (match_stat_data(&todo_list->stat, &st)) {
+ todo_list_release(todo_list);
+ if (read_populate_todo(todo_list, opts))
+ res = -1; /* message was printed */
+ /* `current` will be incremented below */
+ todo_list->current = -1;
+ }
} else if (!is_noop(item->command))
return error(_("unknown command %d"), item->command);
@@ -2042,10 +2108,10 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
if (read_oneliner(&head_ref, rebase_path_head_name(), 0) &&
starts_with(head_ref.buf, "refs/")) {
const char *msg;
- unsigned char head[20], orig[20];
+ struct object_id head, orig;
int res;
- if (get_sha1("HEAD", head)) {
+ if (get_oid("HEAD", &head)) {
res = error(_("cannot read HEAD"));
cleanup_head_ref:
strbuf_release(&head_ref);
@@ -2053,18 +2119,19 @@ cleanup_head_ref:
return res;
}
if (!read_oneliner(&buf, rebase_path_orig_head(), 0) ||
- get_sha1_hex(buf.buf, orig)) {
+ get_oid_hex(buf.buf, &orig)) {
res = error(_("could not read orig-head"));
goto cleanup_head_ref;
}
+ strbuf_reset(&buf);
if (!read_oneliner(&buf, rebase_path_onto(), 0)) {
res = error(_("could not read 'onto'"));
goto cleanup_head_ref;
}
msg = reflog_message(opts, "finish", "%s onto %s",
head_ref.buf, buf.buf);
- if (update_ref(msg, head_ref.buf, head, orig,
- REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) {
+ if (update_ref(msg, head_ref.buf, &head, &orig,
+ REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
res = error(_("could not update %s"),
head_ref.buf);
goto cleanup_head_ref;
@@ -2091,10 +2158,10 @@ cleanup_head_ref:
log_tree_opt.disable_stdin = 1;
if (read_oneliner(&buf, rebase_path_orig_head(), 0) &&
- !get_sha1(buf.buf, orig.hash) &&
- !get_sha1("HEAD", head.hash)) {
- diff_tree_sha1(orig.hash, head.hash,
- "", &log_tree_opt.diffopt);
+ !get_oid(buf.buf, &orig) &&
+ !get_oid("HEAD", &head)) {
+ diff_tree_oid(&orig, &head, "",
+ &log_tree_opt.diffopt);
log_tree_diff_flush(&log_tree_opt);
}
}
@@ -2153,12 +2220,12 @@ static int continue_single_pick(void)
static int commit_staged_changes(struct replay_opts *opts)
{
- int amend = 0;
+ unsigned int flags = ALLOW_EMPTY | EDIT_MSG;
if (has_unstaged_changes(1))
return error(_("cannot rebase: You have unstaged changes."));
if (!has_uncommitted_changes(0)) {
- const char *cherry_pick_head = git_path("CHERRY_PICK_HEAD");
+ const char *cherry_pick_head = git_path_cherry_pick_head();
if (file_exists(cherry_pick_head) && unlink(cherry_pick_head))
return error(_("could not remove CHERRY_PICK_HEAD"));
@@ -2167,26 +2234,26 @@ static int commit_staged_changes(struct replay_opts *opts)
if (file_exists(rebase_path_amend())) {
struct strbuf rev = STRBUF_INIT;
- unsigned char head[20], to_amend[20];
+ struct object_id head, to_amend;
- if (get_sha1("HEAD", head))
+ if (get_oid("HEAD", &head))
return error(_("cannot amend non-existing commit"));
if (!read_oneliner(&rev, rebase_path_amend(), 0))
return error(_("invalid file: '%s'"), rebase_path_amend());
- if (get_sha1_hex(rev.buf, to_amend))
+ if (get_oid_hex(rev.buf, &to_amend))
return error(_("invalid contents: '%s'"),
rebase_path_amend());
- if (hashcmp(head, to_amend))
+ if (oidcmp(&head, &to_amend))
return error(_("\nYou have uncommitted changes in your "
"working tree. Please, commit them\n"
"first and then run 'git rebase "
"--continue' again."));
strbuf_release(&rev);
- amend = 1;
+ flags |= AMEND_MSG;
}
- if (run_git_commit(rebase_path_message(), opts, 1, 1, amend, 0))
+ if (run_git_commit(rebase_path_message(), opts, flags))
return error(_("could not commit staged changes."));
unlink(rebase_path_amend());
return 0;
@@ -2218,7 +2285,7 @@ int sequencer_continue(struct replay_opts *opts)
if (res)
goto release_todo_list;
}
- if (index_differs_from("HEAD", 0, 0)) {
+ if (index_differs_from("HEAD", NULL, 0)) {
res = error_dirty_index(opts);
goto release_todo_list;
}
@@ -2228,7 +2295,7 @@ int sequencer_continue(struct replay_opts *opts)
struct object_id oid;
if (read_oneliner(&buf, rebase_path_stopped_sha(), 1) &&
- !get_sha1_committish(buf.buf, oid.hash))
+ !get_oid_committish(buf.buf, &oid))
record_in_rewritten(&oid, peek_command(&todo_list, 0));
strbuf_release(&buf);
}
@@ -2249,7 +2316,7 @@ static int single_pick(struct commit *cmit, struct replay_opts *opts)
int sequencer_pick_revisions(struct replay_opts *opts)
{
struct todo_list todo_list = TODO_LIST_INIT;
- unsigned char sha1[20];
+ struct object_id oid;
int i, res;
assert(opts->revs);
@@ -2257,16 +2324,16 @@ int sequencer_pick_revisions(struct replay_opts *opts)
return -1;
for (i = 0; i < opts->revs->pending.nr; i++) {
- unsigned char sha1[20];
+ struct object_id oid;
const char *name = opts->revs->pending.objects[i].name;
/* This happens when using --stdin. */
if (!strlen(name))
continue;
- if (!get_sha1(name, sha1)) {
- if (!lookup_commit_reference_gently(sha1, 1)) {
- enum object_type type = sha1_object_info(sha1, NULL);
+ if (!get_oid(name, &oid)) {
+ if (!lookup_commit_reference_gently(&oid, 1)) {
+ enum object_type type = sha1_object_info(oid.hash, NULL);
return error(_("%s: can't cherry-pick a %s"),
name, typename(type));
}
@@ -2303,9 +2370,9 @@ int sequencer_pick_revisions(struct replay_opts *opts)
if (walk_revs_populate_todo(&todo_list, opts) ||
create_seq_dir() < 0)
return -1;
- if (get_sha1("HEAD", sha1) && (opts->action == REPLAY_REVERT))
+ if (get_oid("HEAD", &oid) && (opts->action == REPLAY_REVERT))
return error(_("can't revert as initial commit"));
- if (save_head(sha1_to_hex(sha1)))
+ if (save_head(oid_to_hex(&oid)))
return -1;
if (save_opts(opts))
return -1;
@@ -2326,6 +2393,9 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
getenv("GIT_COMMITTER_EMAIL")));
strbuf_addch(&sob, '\n');
+ if (!ignore_footer)
+ strbuf_complete_line(msgbuf);
+
/*
* If the whole message buffer is equal to the sob, pretend that we
* found a conforming footer with a matching sob
@@ -2346,13 +2416,6 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
* the title and body to be filled in by the user.
*/
append_newlines = "\n\n";
- } else if (msgbuf->buf[len - 1] != '\n') {
- /*
- * Incomplete line. Complete the line and add a
- * blank one so that there is an empty line between
- * the message body and the sob.
- */
- append_newlines = "\n\n";
} else if (len == 1) {
/*
* Buffer contains a single newline. Add another
@@ -2379,3 +2442,520 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
strbuf_release(&sob);
}
+
+int sequencer_make_script(int keep_empty, FILE *out,
+ int argc, const char **argv)
+{
+ char *format = NULL;
+ struct pretty_print_context pp = {0};
+ struct strbuf buf = STRBUF_INIT;
+ struct rev_info revs;
+ struct commit *commit;
+
+ init_revisions(&revs, NULL);
+ revs.verbose_header = 1;
+ revs.max_parents = 1;
+ revs.cherry_pick = 1;
+ revs.limited = 1;
+ revs.reverse = 1;
+ revs.right_only = 1;
+ revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
+ revs.topo_order = 1;
+
+ revs.pretty_given = 1;
+ git_config_get_string("rebase.instructionFormat", &format);
+ if (!format || !*format) {
+ free(format);
+ format = xstrdup("%s");
+ }
+ get_commit_format(format, &revs);
+ free(format);
+ pp.fmt = revs.commit_format;
+ pp.output_encoding = get_log_output_encoding();
+
+ if (setup_revisions(argc, argv, &revs, NULL) > 1)
+ return error(_("make_script: unhandled options"));
+
+ if (prepare_revision_walk(&revs) < 0)
+ return error(_("make_script: error preparing revisions"));
+
+ while ((commit = get_revision(&revs))) {
+ strbuf_reset(&buf);
+ if (!keep_empty && is_original_commit_empty(commit))
+ strbuf_addf(&buf, "%c ", comment_line_char);
+ strbuf_addf(&buf, "pick %s ", oid_to_hex(&commit->object.oid));
+ pretty_print_commit(&pp, commit, &buf);
+ strbuf_addch(&buf, '\n');
+ fputs(buf.buf, out);
+ }
+ strbuf_release(&buf);
+ return 0;
+}
+
+
+int transform_todo_ids(int shorten_ids)
+{
+ const char *todo_file = rebase_path_todo();
+ struct todo_list todo_list = TODO_LIST_INIT;
+ int fd, res, i;
+ FILE *out;
+
+ 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);
+
+ res = parse_insn_buffer(todo_list.buf.buf, &todo_list);
+ if (res) {
+ todo_list_release(&todo_list);
+ return error(_("unusable todo list: '%s'"), todo_file);
+ }
+
+ out = fopen(todo_file, "w");
+ if (!out) {
+ todo_list_release(&todo_list);
+ return error(_("unable to open '%s' for writing"), todo_file);
+ }
+ for (i = 0; i < todo_list.nr; i++) {
+ struct todo_item *item = todo_list.items + i;
+ int bol = item->offset_in_buf;
+ const char *p = todo_list.buf.buf + bol;
+ int eol = i + 1 < todo_list.nr ?
+ todo_list.items[i + 1].offset_in_buf :
+ todo_list.buf.len;
+
+ if (item->command >= TODO_EXEC && item->command != TODO_DROP)
+ fwrite(p, eol - bol, 1, out);
+ else {
+ const char *id = shorten_ids ?
+ short_commit_name(item->commit) :
+ oid_to_hex(&item->commit->object.oid);
+ int len;
+
+ p += strspn(p, " \t"); /* left-trim command */
+ len = strcspn(p, " \t"); /* length of command */
+
+ fprintf(out, "%.*s %s %.*s\n",
+ len, p, id, item->arg_len, item->arg);
+ }
+ }
+ fclose(out);
+ todo_list_release(&todo_list);
+ return 0;
+}
+
+enum check_level {
+ CHECK_IGNORE = 0, CHECK_WARN, CHECK_ERROR
+};
+
+static enum check_level get_missing_commit_check_level(void)
+{
+ const char *value;
+
+ if (git_config_get_value("rebase.missingcommitscheck", &value) ||
+ !strcasecmp("ignore", value))
+ return CHECK_IGNORE;
+ if (!strcasecmp("warn", value))
+ return CHECK_WARN;
+ if (!strcasecmp("error", value))
+ return CHECK_ERROR;
+ warning(_("unrecognized setting %s for option "
+ "rebase.missingCommitsCheck. Ignoring."), value);
+ return CHECK_IGNORE;
+}
+
+/*
+ * Check if the user dropped some commits by mistake
+ * Behaviour determined by rebase.missingCommitsCheck.
+ * Check if there is an unrecognized command or a
+ * bad SHA-1 in a command.
+ */
+int check_todo_list(void)
+{
+ enum check_level check_level = get_missing_commit_check_level();
+ 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;
+
+ 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);
+ goto leave_check;
+ }
+ close(fd);
+ advise_to_edit_todo = res =
+ parse_insn_buffer(todo_list.buf.buf, &todo_list);
+
+ if (res || check_level == CHECK_IGNORE)
+ goto leave_check;
+
+ /* Mark the commits in git-rebase-todo as seen */
+ for (i = 0; i < todo_list.nr; i++) {
+ struct commit *commit = todo_list.items[i].commit;
+ if (commit)
+ commit->util = (void *)1;
+ }
+
+ 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);
+ goto leave_check;
+ }
+ close(fd);
+ strbuf_release(&todo_file);
+ res = !!parse_insn_buffer(todo_list.buf.buf, &todo_list);
+
+ /* Find commits in git-rebase-todo.backup yet unseen */
+ for (i = todo_list.nr - 1; i >= 0; i--) {
+ struct todo_item *item = todo_list.items + i;
+ struct commit *commit = item->commit;
+ if (commit && !commit->util) {
+ strbuf_addf(&missing, " - %s %.*s\n",
+ short_commit_name(commit),
+ item->arg_len, item->arg);
+ commit->util = (void *)1;
+ }
+ }
+
+ /* Warn about missing commits */
+ if (!missing.len)
+ goto leave_check;
+
+ if (check_level == CHECK_ERROR)
+ advise_to_edit_todo = res = 1;
+
+ fprintf(stderr,
+ _("Warning: some commits may have been dropped accidentally.\n"
+ "Dropped commits (newer to older):\n"));
+
+ /* Make the list user-friendly and display */
+ fputs(missing.buf, stderr);
+ strbuf_release(&missing);
+
+ fprintf(stderr, _("To avoid this message, use \"drop\" to "
+ "explicitly remove a commit.\n\n"
+ "Use 'git config rebase.missingCommitsCheck' to change "
+ "the level of warnings.\n"
+ "The possible behaviours are: ignore, warn, error.\n\n"));
+
+leave_check:
+ strbuf_release(&todo_file);
+ todo_list_release(&todo_list);
+
+ if (advise_to_edit_todo)
+ fprintf(stderr,
+ _("You can fix this with 'git rebase --edit-todo' "
+ "and then run 'git rebase --continue'.\n"
+ "Or you can abort the rebase with 'git rebase"
+ " --abort'.\n"));
+
+ return res;
+}
+
+static int rewrite_file(const char *path, const char *buf, size_t len)
+{
+ int rc = 0;
+ int fd = open(path, O_WRONLY | O_TRUNC);
+ if (fd < 0)
+ return error_errno(_("could not open '%s' for writing"), path);
+ if (write_in_full(fd, buf, len) < 0)
+ rc = error_errno(_("could not write to '%s'"), path);
+ if (close(fd) && !rc)
+ rc = error_errno(_("could not close '%s'"), path);
+ return rc;
+}
+
+/* skip picking commits whose parents are unchanged */
+int skip_unnecessary_picks(void)
+{
+ const char *todo_file = rebase_path_todo();
+ struct strbuf buf = STRBUF_INIT;
+ struct todo_list todo_list = TODO_LIST_INIT;
+ struct object_id onto_oid, *oid = &onto_oid, *parent_oid;
+ int fd, i;
+
+ if (!read_oneliner(&buf, rebase_path_onto(), 0))
+ return error(_("could not read 'onto'"));
+ if (get_oid(buf.buf, &onto_oid)) {
+ strbuf_release(&buf);
+ return error(_("need a HEAD to fixup"));
+ }
+ 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 (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) {
+ todo_list_release(&todo_list);
+ return -1;
+ }
+
+ for (i = 0; i < todo_list.nr; i++) {
+ struct todo_item *item = todo_list.items + i;
+
+ if (item->command >= TODO_NOOP)
+ continue;
+ if (item->command != TODO_PICK)
+ break;
+ if (parse_commit(item->commit)) {
+ todo_list_release(&todo_list);
+ return error(_("could not parse commit '%s'"),
+ oid_to_hex(&item->commit->object.oid));
+ }
+ if (!item->commit->parents)
+ break; /* root commit */
+ if (item->commit->parents->next)
+ break; /* merge commit */
+ parent_oid = &item->commit->parents->item->object.oid;
+ if (hashcmp(parent_oid->hash, oid->hash))
+ break;
+ oid = &item->commit->object.oid;
+ }
+ if (i > 0) {
+ int offset = i < todo_list.nr ?
+ todo_list.items[i].offset_in_buf : todo_list.buf.len;
+ const char *done_path = rebase_path_done();
+
+ fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);
+ if (fd < 0) {
+ error_errno(_("could not open '%s' for writing"),
+ done_path);
+ todo_list_release(&todo_list);
+ return -1;
+ }
+ if (write_in_full(fd, todo_list.buf.buf, offset) < 0) {
+ error_errno(_("could not write to '%s'"), done_path);
+ todo_list_release(&todo_list);
+ close(fd);
+ return -1;
+ }
+ close(fd);
+
+ if (rewrite_file(rebase_path_todo(), todo_list.buf.buf + offset,
+ todo_list.buf.len - offset) < 0) {
+ todo_list_release(&todo_list);
+ return -1;
+ }
+
+ todo_list.current = i;
+ if (is_fixup(peek_command(&todo_list, 0)))
+ record_in_rewritten(oid, peek_command(&todo_list, 0));
+ }
+
+ todo_list_release(&todo_list);
+ printf("%s\n", oid_to_hex(oid));
+
+ return 0;
+}
+
+struct subject2item_entry {
+ struct hashmap_entry entry;
+ int i;
+ char subject[FLEX_ARRAY];
+};
+
+static int subject2item_cmp(const void *fndata,
+ const struct subject2item_entry *a,
+ const struct subject2item_entry *b, const void *key)
+{
+ return key ? strcmp(a->subject, key) : strcmp(a->subject, b->subject);
+}
+
+/*
+ * Rearrange the todo list that has both "pick commit-id msg" and "pick
+ * commit-id fixup!/squash! msg" in it so that the latter is put immediately
+ * after the former, and change "pick" to "fixup"/"squash".
+ *
+ * Note that if the config has specified a custom instruction format, each log
+ * message will have to be retrieved from the commit (as the oneline in the
+ * script cannot be trusted) in order to normalize the autosquash arrangement.
+ */
+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;
+ 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 (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) {
+ todo_list_release(&todo_list);
+ return -1;
+ }
+
+ /*
+ * The hashmap maps onelines to the respective todo list index.
+ *
+ * If any items need to be rearranged, the next[i] value will indicate
+ * which item was moved directly after the i'th.
+ *
+ * In that case, last[i] will indicate the index of the latest item to
+ * be moved to appear after the i'th.
+ */
+ hashmap_init(&subject2item, (hashmap_cmp_fn) subject2item_cmp,
+ NULL, todo_list.nr);
+ ALLOC_ARRAY(next, todo_list.nr);
+ ALLOC_ARRAY(tail, todo_list.nr);
+ ALLOC_ARRAY(subjects, todo_list.nr);
+ for (i = 0; i < todo_list.nr; i++) {
+ struct strbuf buf = STRBUF_INIT;
+ struct todo_item *item = todo_list.items + i;
+ const char *commit_buffer, *subject, *p;
+ size_t subject_len;
+ int i2 = -1;
+ struct subject2item_entry *entry;
+
+ next[i] = tail[i] = -1;
+ if (item->command >= TODO_EXEC) {
+ subjects[i] = NULL;
+ continue;
+ }
+
+ if (is_fixup(item->command)) {
+ todo_list_release(&todo_list);
+ return error(_("the script was already rearranged."));
+ }
+
+ item->commit->util = item;
+
+ parse_commit(item->commit);
+ commit_buffer = get_commit_buffer(item->commit, NULL);
+ find_commit_subject(commit_buffer, &subject);
+ format_subject(&buf, subject, " ");
+ subject = subjects[i] = strbuf_detach(&buf, &subject_len);
+ unuse_commit_buffer(item->commit, commit_buffer);
+ if ((skip_prefix(subject, "fixup! ", &p) ||
+ skip_prefix(subject, "squash! ", &p))) {
+ struct commit *commit2;
+
+ for (;;) {
+ while (isspace(*p))
+ p++;
+ if (!skip_prefix(p, "fixup! ", &p) &&
+ !skip_prefix(p, "squash! ", &p))
+ break;
+ }
+
+ if ((entry = hashmap_get_from_hash(&subject2item,
+ strhash(p), p)))
+ /* found by title */
+ i2 = entry->i;
+ else if (!strchr(p, ' ') &&
+ (commit2 =
+ lookup_commit_reference_by_name(p)) &&
+ commit2->util)
+ /* found by commit name */
+ i2 = (struct todo_item *)commit2->util
+ - todo_list.items;
+ else {
+ /* copy can be a prefix of the commit subject */
+ for (i2 = 0; i2 < i; i2++)
+ if (subjects[i2] &&
+ starts_with(subjects[i2], p))
+ break;
+ if (i2 == i)
+ i2 = -1;
+ }
+ }
+ if (i2 >= 0) {
+ rearranged = 1;
+ todo_list.items[i].command =
+ starts_with(subject, "fixup!") ?
+ TODO_FIXUP : TODO_SQUASH;
+ if (next[i2] < 0)
+ next[i2] = i;
+ else
+ next[tail[i2]] = i;
+ tail[i2] = i;
+ } else if (!hashmap_get_from_hash(&subject2item,
+ strhash(subject), subject)) {
+ FLEX_ALLOC_MEM(entry, subject, subject, subject_len);
+ entry->i = i;
+ hashmap_entry_init(entry, strhash(entry->subject));
+ hashmap_put(&subject2item, entry);
+ }
+ }
+
+ if (rearranged) {
+ struct strbuf buf = STRBUF_INIT;
+
+ for (i = 0; i < todo_list.nr; i++) {
+ enum todo_command command = todo_list.items[i].command;
+ int cur = i;
+
+ /*
+ * Initially, all commands are 'pick's. If it is a
+ * fixup or a squash now, we have rearranged it.
+ */
+ if (is_fixup(command))
+ continue;
+
+ while (cur >= 0) {
+ int offset = todo_list.items[cur].offset_in_buf;
+ int end_offset = cur + 1 < todo_list.nr ?
+ todo_list.items[cur + 1].offset_in_buf :
+ todo_list.buf.len;
+ char *bol = todo_list.buf.buf + offset;
+ char *eol = todo_list.buf.buf + end_offset;
+
+ /* replace 'pick', by 'fixup' or 'squash' */
+ command = todo_list.items[cur].command;
+ if (is_fixup(command)) {
+ strbuf_addstr(&buf,
+ todo_command_info[command].str);
+ bol += strcspn(bol, " \t");
+ }
+
+ strbuf_add(&buf, bol, eol - bol);
+
+ cur = next[cur];
+ }
+ }
+
+ res = rewrite_file(todo_file, buf.buf, buf.len);
+ strbuf_release(&buf);
+ }
+
+ free(next);
+ free(tail);
+ for (i = 0; i < todo_list.nr; i++)
+ free(subjects[i]);
+ free(subjects);
+ hashmap_free(&subject2item, 1);
+ todo_list_release(&todo_list);
+
+ return res;
+}