@ -46,6 +46,35 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")
* actions.
* actions.
*/
*/
static GIT_PATH_FUNC(rebase_path_done, "rebase-merge/done")
static GIT_PATH_FUNC(rebase_path_done, "rebase-merge/done")
/*
* The commit message that is planned to be used for any changes that
* need to be committed following a user interaction.
*/
static GIT_PATH_FUNC(rebase_path_message, "rebase-merge/message")
/*
* The file into which is accumulated the suggested commit message for
* squash/fixup commands. When the first of a series of squash/fixups
* is seen, the file is created and the commit message from the
* previous commit and from the first squash/fixup commit are written
* to it. The commit message for each subsequent squash/fixup commit
* is appended to the file as it is processed.
*
* The first line of the file is of the form
* # This is a combination of $count commits.
* where $count is the number of commits whose messages have been
* written to the file so far (including the initial "pick" commit).
* Each time that a commit message is processed, this line is read and
* updated. It is deleted just before the combined commit is made.
*/
static GIT_PATH_FUNC(rebase_path_squash_msg, "rebase-merge/message-squash")
/*
* If the current series of squash/fixups has not yet included a squash
* command, then this file exists and holds the commit message of the
* original "pick" commit. (If the series ends without a "squash"
* command, then this can be used as the commit message of the combined
* commit without opening the editor.)
*/
static GIT_PATH_FUNC(rebase_path_fixup_msg, "rebase-merge/message-fixup")
/*
/*
* A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
* A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
* GIT_AUTHOR_DATE that will be used for the commit that is currently
* GIT_AUTHOR_DATE that will be used for the commit that is currently
@ -641,6 +670,8 @@ enum todo_command {
TODO_PICK = 0,
TODO_PICK = 0,
TODO_REVERT,
TODO_REVERT,
TODO_EDIT,
TODO_EDIT,
TODO_FIXUP,
TODO_SQUASH,
/* commands that do something else than handling a single commit */
/* commands that do something else than handling a single commit */
TODO_EXEC,
TODO_EXEC,
/* commands that do nothing but are counted for reporting progress */
/* commands that do nothing but are counted for reporting progress */
@ -651,6 +682,8 @@ static const char *todo_command_strings[] = {
"pick",
"pick",
"revert",
"revert",
"edit",
"edit",
"fixup",
"squash",
"exec",
"exec",
"noop"
"noop"
};
};
@ -667,15 +700,114 @@ static int is_noop(const enum todo_command command)
return TODO_NOOP <= (size_t)command;
return TODO_NOOP <= (size_t)command;
}
}
static int is_fixup(enum todo_command command)
{
return command == TODO_FIXUP || command == TODO_SQUASH;
}
static int update_squash_messages(enum todo_command command,
struct commit *commit, struct replay_opts *opts)
{
struct strbuf buf = STRBUF_INIT;
int count, res;
const char *message, *body;
if (file_exists(rebase_path_squash_msg())) {
struct strbuf header = STRBUF_INIT;
char *eol, *p;
if (strbuf_read_file(&buf, rebase_path_squash_msg(), 2048) <= 0)
return error(_("could not read '%s'"),
rebase_path_squash_msg());
p = buf.buf + 1;
eol = strchrnul(buf.buf, '\n');
if (buf.buf[0] != comment_line_char ||
(p += strcspn(p, "0123456789\n")) == eol)
return error(_("unexpected 1st line of squash message:"
"\n\n\t%.*s"),
(int)(eol - buf.buf), buf.buf);
count = strtol(p, NULL, 10);
if (count < 1)
return error(_("invalid 1st line of squash message:\n"
"\n\t%.*s"),
(int)(eol - buf.buf), buf.buf);
strbuf_addf(&header, "%c ", comment_line_char);
strbuf_addf(&header,
_("This is a combination of %d commits."), ++count);
strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
strbuf_release(&header);
} else {
unsigned char head[20];
struct commit *head_commit;
const char *head_message, *body;
if (get_sha1("HEAD", head))
return error(_("need a HEAD to fixup"));
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"));
find_commit_subject(head_message, &body);
if (write_message(body, strlen(body),
rebase_path_fixup_msg(), 0)) {
unuse_commit_buffer(head_commit, head_message);
return error(_("cannot write '%s'"),
rebase_path_fixup_msg());
}
count = 2;
strbuf_addf(&buf, "%c ", comment_line_char);
strbuf_addf(&buf, _("This is a combination of %d commits."),
count);
strbuf_addf(&buf, "\n%c ", comment_line_char);
strbuf_addstr(&buf, _("This is the 1st commit message:"));
strbuf_addstr(&buf, "\n\n");
strbuf_addstr(&buf, body);
unuse_commit_buffer(head_commit, head_message);
}
if (!(message = get_commit_buffer(commit, NULL)))
return error(_("could not read commit message of %s"),
oid_to_hex(&commit->object.oid));
find_commit_subject(message, &body);
if (command == TODO_SQUASH) {
unlink(rebase_path_fixup_msg());
strbuf_addf(&buf, "\n%c ", comment_line_char);
strbuf_addf(&buf, _("This is the commit message #%d:"), count);
strbuf_addstr(&buf, "\n\n");
strbuf_addstr(&buf, body);
} else if (command == TODO_FIXUP) {
strbuf_addf(&buf, "\n%c ", comment_line_char);
strbuf_addf(&buf, _("The commit message #%d will be skipped:"),
count);
strbuf_addstr(&buf, "\n\n");
strbuf_add_commented_lines(&buf, body, strlen(body));
} else
return error(_("unknown command: %d"), command);
unuse_commit_buffer(commit, message);
res = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0);
strbuf_release(&buf);
return res;
}
static int do_pick_commit(enum todo_command command, struct commit *commit,
static int do_pick_commit(enum todo_command command, struct commit *commit,
struct replay_opts *opts)
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 char head[20];
struct commit *base, *next, *parent;
struct commit *base, *next, *parent;
const char *base_label, *next_label;
const char *base_label, *next_label;
struct commit_message msg = { NULL, NULL, NULL, NULL };
struct commit_message msg = { NULL, NULL, NULL, NULL };
struct strbuf msgbuf = STRBUF_INIT;
struct strbuf msgbuf = STRBUF_INIT;
int res, unborn = 0, allow;
int res, unborn = 0, amend = 0, allow;
if (opts->no_commit) {
if (opts->no_commit) {
/*
/*
@ -720,7 +852,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
else
else
parent = commit->parents->item;
parent = commit->parents->item;
if (opts->allow_ff &&
if (opts->allow_ff && !is_fixup(command) &&
((parent && !hashcmp(parent->object.oid.hash, head)) ||
((parent && !hashcmp(parent->object.oid.hash, head)) ||
(!parent && unborn)))
(!parent && unborn)))
return fast_forward_to(commit->object.oid.hash, head, unborn, opts);
return fast_forward_to(commit->object.oid.hash, head, unborn, opts);
@ -779,6 +911,27 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
}
}
}
}
if (is_fixup(command)) {
if (update_squash_messages(command, commit, opts))
return -1;
amend = 1;
if (!final_fixup)
msg_file = rebase_path_squash_msg();
else if (file_exists(rebase_path_fixup_msg())) {
cleanup_commit_message = 1;
msg_file = rebase_path_fixup_msg();
} else {
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"));
msg_file = dest;
edit = 1;
}
}
if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) {
if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) {
res = do_recursive_merge(base, next, base_label, next_label,
res = do_recursive_merge(base, next, base_label, next_label,
head, &msgbuf, opts);
head, &msgbuf, opts);
@ -834,8 +987,13 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
goto leave;
goto leave;
}
}
if (!opts->no_commit)
if (!opts->no_commit)
res = run_git_commit(opts->edit ? NULL : git_path_merge_msg(),
res = run_git_commit(msg_file, opts, allow, edit, amend,
opts, allow, opts->edit, 0, 0);
cleanup_commit_message);
if (!res && final_fixup) {
unlink(rebase_path_fixup_msg());
unlink(rebase_path_squash_msg());
}
leave:
leave:
free_message(commit, &msg);
free_message(commit, &msg);
@ -976,7 +1134,7 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list)
{
{
struct todo_item *item;
struct todo_item *item;
char *p = buf, *next_p;
char *p = buf, *next_p;
int i, res = 0;
int i, res = 0, fixup_okay = file_exists(rebase_path_done());
for (i = 1; *p; i++, p = next_p) {
for (i = 1; *p; i++, p = next_p) {
char *eol = strchrnul(p, '\n');
char *eol = strchrnul(p, '\n');
@ -991,8 +1149,16 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list)
if (parse_insn_line(item, p, eol)) {
if (parse_insn_line(item, p, eol)) {
res = error(_("invalid line %d: %.*s"),
res = error(_("invalid line %d: %.*s"),
i, (int)(eol - p), p);
i, (int)(eol - p), p);
item->command = -1;
item->command = TODO_NOOP;
}
}
if (fixup_okay)
; /* do nothing */
else if (is_fixup(item->command))
return error(_("cannot '%s' without a previous commit"),
command_to_string(item->command));
else if (!is_noop(item->command))
fixup_okay = 1;
}
}
if (!todo_list->nr)
if (!todo_list->nr)
return error(_("no commits parsed."));
return error(_("no commits parsed."));
@ -1435,6 +1601,20 @@ static int error_with_patch(struct commit *commit,
return exit_code;
return exit_code;
}
}
static int error_failed_squash(struct commit *commit,
struct replay_opts *opts, int subject_len, const char *subject)
{
if (rename(rebase_path_squash_msg(), rebase_path_message()))
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))
return error(_("could not copy '%s' to '%s'"),
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)
static int do_exec(const char *command_line)
{
{
const char *child_argv[] = { NULL, NULL };
const char *child_argv[] = { NULL, NULL };
@ -1475,6 +1655,21 @@ static int do_exec(const char *command_line)
return status;
return status;
}
}
static int is_final_fixup(struct todo_list *todo_list)
{
int i = todo_list->current;
if (!is_fixup(todo_list->items[i].command))
return 0;
while (++i < todo_list->nr)
if (is_fixup(todo_list->items[i].command))
return 0;
else if (!is_noop(todo_list->items[i].command))
break;
return 1;
}
static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
{
{
int res = 0;
int res = 0;
@ -1490,9 +1685,15 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
struct todo_item *item = todo_list->items + todo_list->current;
struct todo_item *item = todo_list->items + todo_list->current;
if (save_todo(todo_list, opts))
if (save_todo(todo_list, opts))
return -1;
return -1;
if (item->command <= TODO_EDIT) {
if (is_rebase_i(opts)) {
unlink(rebase_path_message());
unlink(rebase_path_author_script());
unlink(rebase_path_stopped_sha());
unlink(rebase_path_amend());
}
if (item->command <= TODO_SQUASH) {
res = do_pick_commit(item->command, item->commit,
res = do_pick_commit(item->command, item->commit,
opts);
opts, is_final_fixup(todo_list));
if (item->command == TODO_EDIT) {
if (item->command == TODO_EDIT) {
struct commit *commit = item->commit;
struct commit *commit = item->commit;
if (!res)
if (!res)
@ -1503,6 +1704,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
item->arg, item->arg_len, opts, res,
item->arg, item->arg_len, opts, res,
!res);
!res);
}
}
if (res && is_fixup(item->command)) {
if (res == 1)
intend_to_amend();
return error_failed_squash(item->commit, opts,
item->arg_len, item->arg);
}
} else if (item->command == TODO_EXEC) {
} else if (item->command == TODO_EXEC) {
char *end_of_arg = (char *)(item->arg + item->arg_len);
char *end_of_arg = (char *)(item->arg + item->arg_len);
int saved = *end_of_arg;
int saved = *end_of_arg;
@ -1601,7 +1808,7 @@ static int single_pick(struct commit *cmit, struct replay_opts *opts)
{
{
setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
return do_pick_commit(opts->action == REPLAY_PICK ?
return do_pick_commit(opts->action == REPLAY_PICK ?
TODO_PICK : TODO_REVERT, cmit, opts);
TODO_PICK : TODO_REVERT, cmit, opts, 0);
}
}
int sequencer_pick_revisions(struct replay_opts *opts)
int sequencer_pick_revisions(struct replay_opts *opts)