summaryrefslogtreecommitdiff
path: root/sequencer.c
diff options
context:
space:
mode:
Diffstat (limited to 'sequencer.c')
-rw-r--r--sequencer.c282
1 files changed, 242 insertions, 40 deletions
diff --git a/sequencer.c b/sequencer.c
index d2332d3e17..b4c63ae207 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1726,13 +1726,183 @@ static int is_pick_or_similar(enum todo_command command)
}
}
+enum todo_item_flags {
+ TODO_EDIT_MERGE_MSG = (1 << 0),
+ TODO_REPLACE_FIXUP_MSG = (1 << 1),
+ TODO_EDIT_FIXUP_MSG = (1 << 2),
+};
+
+static const char first_commit_msg_str[] = N_("This is the 1st commit message:");
+static const char nth_commit_msg_fmt[] = N_("This is the commit message #%d:");
+static const char skip_first_commit_msg_str[] = N_("The 1st commit message will be skipped:");
+static const char skip_nth_commit_msg_fmt[] = N_("The commit message #%d will be skipped:");
+static const char combined_commit_msg_fmt[] = N_("This is a combination of %d commits.");
+
+static int is_fixup_flag(enum todo_command command, unsigned flag)
+{
+ return command == TODO_FIXUP && ((flag & TODO_REPLACE_FIXUP_MSG) ||
+ (flag & TODO_EDIT_FIXUP_MSG));
+}
+
+/*
+ * Wrapper around strbuf_add_commented_lines() which avoids double
+ * commenting commit subjects.
+ */
+static void add_commented_lines(struct strbuf *buf, const void *str, size_t len)
+{
+ const char *s = str;
+ while (len > 0 && s[0] == comment_line_char) {
+ size_t count;
+ const char *n = memchr(s, '\n', len);
+ if (!n)
+ count = len;
+ else
+ count = n - s + 1;
+ strbuf_add(buf, s, count);
+ s += count;
+ len -= count;
+ }
+ strbuf_add_commented_lines(buf, s, len);
+}
+
+/* Does the current fixup chain contain a squash command? */
+static int seen_squash(struct replay_opts *opts)
+{
+ return starts_with(opts->current_fixups.buf, "squash") ||
+ strstr(opts->current_fixups.buf, "\nsquash");
+}
+
+static void update_comment_bufs(struct strbuf *buf1, struct strbuf *buf2, int n)
+{
+ strbuf_setlen(buf1, 2);
+ strbuf_addf(buf1, _(nth_commit_msg_fmt), n);
+ strbuf_addch(buf1, '\n');
+ strbuf_setlen(buf2, 2);
+ strbuf_addf(buf2, _(skip_nth_commit_msg_fmt), n);
+ strbuf_addch(buf2, '\n');
+}
+
+/*
+ * Comment out any un-commented commit messages, updating the message comments
+ * to say they will be skipped but do not comment out the empty lines that
+ * surround commit messages and their comments.
+ */
+static void update_squash_message_for_fixup(struct strbuf *msg)
+{
+ void (*copy_lines)(struct strbuf *, const void *, size_t) = strbuf_add;
+ struct strbuf buf1 = STRBUF_INIT, buf2 = STRBUF_INIT;
+ const char *s, *start;
+ char *orig_msg;
+ size_t orig_msg_len;
+ int i = 1;
+
+ strbuf_addf(&buf1, "# %s\n", _(first_commit_msg_str));
+ strbuf_addf(&buf2, "# %s\n", _(skip_first_commit_msg_str));
+ s = start = orig_msg = strbuf_detach(msg, &orig_msg_len);
+ while (s) {
+ const char *next;
+ size_t off;
+ if (skip_prefix(s, buf1.buf, &next)) {
+ /*
+ * Copy the last message, preserving the blank line
+ * preceding the current line
+ */
+ off = (s > start + 1 && s[-2] == '\n') ? 1 : 0;
+ copy_lines(msg, start, s - start - off);
+ if (off)
+ strbuf_addch(msg, '\n');
+ /*
+ * The next message needs to be commented out but the
+ * message header is already commented out so just copy
+ * it and the blank line that follows it.
+ */
+ strbuf_addbuf(msg, &buf2);
+ if (*next == '\n')
+ strbuf_addch(msg, *next++);
+ start = s = next;
+ copy_lines = add_commented_lines;
+ update_comment_bufs(&buf1, &buf2, ++i);
+ } else if (skip_prefix(s, buf2.buf, &next)) {
+ off = (s > start + 1 && s[-2] == '\n') ? 1 : 0;
+ copy_lines(msg, start, s - start - off);
+ start = s - off;
+ s = next;
+ copy_lines = strbuf_add;
+ update_comment_bufs(&buf1, &buf2, ++i);
+ } else {
+ s = strchr(s, '\n');
+ if (s)
+ s++;
+ }
+ }
+ copy_lines(msg, start, orig_msg_len - (start - orig_msg));
+ free(orig_msg);
+ strbuf_release(&buf1);
+ strbuf_release(&buf2);
+}
+
+static int append_squash_message(struct strbuf *buf, const char *body,
+ enum todo_command command, struct replay_opts *opts,
+ unsigned flag)
+{
+ const char *fixup_msg;
+ size_t commented_len = 0, fixup_off;
+ /*
+ * amend is non-interactive and not normally used with fixup!
+ * or squash! commits, so only comment out those subjects when
+ * squashing commit messages.
+ */
+ if (starts_with(body, "amend!") ||
+ ((command == TODO_SQUASH || seen_squash(opts)) &&
+ (starts_with(body, "squash!") || starts_with(body, "fixup!"))))
+ commented_len = commit_subject_length(body);
+
+ strbuf_addf(buf, "\n%c ", comment_line_char);
+ strbuf_addf(buf, _(nth_commit_msg_fmt),
+ ++opts->current_fixup_count + 1);
+ strbuf_addstr(buf, "\n\n");
+ strbuf_add_commented_lines(buf, body, commented_len);
+ /* buf->buf may be reallocated so store an offset into the buffer */
+ fixup_off = buf->len;
+ strbuf_addstr(buf, body + commented_len);
+
+ /* fixup -C after squash behaves like squash */
+ if (is_fixup_flag(command, flag) && !seen_squash(opts)) {
+ /*
+ * We're replacing the commit message so we need to
+ * append the Signed-off-by: trailer if the user
+ * requested '--signoff'.
+ */
+ if (opts->signoff)
+ append_signoff(buf, 0, 0);
+
+ if ((command == TODO_FIXUP) &&
+ (flag & TODO_REPLACE_FIXUP_MSG) &&
+ (file_exists(rebase_path_fixup_msg()) ||
+ !file_exists(rebase_path_squash_msg()))) {
+ fixup_msg = skip_blank_lines(buf->buf + fixup_off);
+ if (write_message(fixup_msg, strlen(fixup_msg),
+ rebase_path_fixup_msg(), 0) < 0)
+ return error(_("cannot write '%s'"),
+ rebase_path_fixup_msg());
+ } else {
+ unlink(rebase_path_fixup_msg());
+ }
+ } else {
+ unlink(rebase_path_fixup_msg());
+ }
+
+ return 0;
+}
+
static int update_squash_messages(struct repository *r,
enum todo_command command,
struct commit *commit,
- struct replay_opts *opts)
+ struct replay_opts *opts,
+ unsigned flag)
{
struct strbuf buf = STRBUF_INIT;
- int res;
+ int res = 0;
const char *message, *body;
const char *encoding = get_commit_output_encoding();
@@ -1748,10 +1918,12 @@ static int update_squash_messages(struct repository *r,
buf.buf : strchrnul(buf.buf, '\n');
strbuf_addf(&header, "%c ", comment_line_char);
- strbuf_addf(&header, _("This is a combination of %d commits."),
+ strbuf_addf(&header, _(combined_commit_msg_fmt),
opts->current_fixup_count + 2);
strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
strbuf_release(&header);
+ if (is_fixup_flag(command, flag) && !seen_squash(opts))
+ update_squash_message_for_fixup(&buf);
} else {
struct object_id head;
struct commit *head_commit;
@@ -1765,19 +1937,22 @@ static int update_squash_messages(struct repository *r,
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)) {
+ if (command == TODO_FIXUP && !flag && write_message(body, strlen(body),
+ rebase_path_fixup_msg(), 0) < 0) {
unuse_commit_buffer(head_commit, head_message);
- return error(_("cannot write '%s'"),
- rebase_path_fixup_msg());
+ return error(_("cannot write '%s'"), rebase_path_fixup_msg());
}
-
strbuf_addf(&buf, "%c ", comment_line_char);
- strbuf_addf(&buf, _("This is a combination of %d commits."), 2);
+ strbuf_addf(&buf, _(combined_commit_msg_fmt), 2);
strbuf_addf(&buf, "\n%c ", comment_line_char);
- strbuf_addstr(&buf, _("This is the 1st commit message:"));
+ strbuf_addstr(&buf, is_fixup_flag(command, flag) ?
+ _(skip_first_commit_msg_str) :
+ _(first_commit_msg_str));
strbuf_addstr(&buf, "\n\n");
- strbuf_addstr(&buf, body);
+ if (is_fixup_flag(command, flag))
+ strbuf_add_commented_lines(&buf, body, strlen(body));
+ else
+ strbuf_addstr(&buf, body);
unuse_commit_buffer(head_commit, head_message);
}
@@ -1787,16 +1962,11 @@ static int update_squash_messages(struct repository *r,
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:"),
- ++opts->current_fixup_count + 1);
- strbuf_addstr(&buf, "\n\n");
- strbuf_addstr(&buf, body);
+ if (command == TODO_SQUASH || is_fixup_flag(command, flag)) {
+ res = append_squash_message(&buf, body, command, opts, flag);
} else if (command == TODO_FIXUP) {
strbuf_addf(&buf, "\n%c ", comment_line_char);
- strbuf_addf(&buf, _("The commit message #%d will be skipped:"),
+ strbuf_addf(&buf, _(skip_nth_commit_msg_fmt),
++opts->current_fixup_count + 1);
strbuf_addstr(&buf, "\n\n");
strbuf_add_commented_lines(&buf, body, strlen(body));
@@ -1804,7 +1974,9 @@ static int update_squash_messages(struct repository *r,
return error(_("unknown command: %d"), command);
unuse_commit_buffer(commit, message);
- res = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0);
+ if (!res)
+ res = write_message(buf.buf, buf.len, rebase_path_squash_msg(),
+ 0);
strbuf_release(&buf);
if (!res) {
@@ -1861,8 +2033,7 @@ static void record_in_rewritten(struct object_id *oid,
}
static int do_pick_commit(struct repository *r,
- enum todo_command command,
- struct commit *commit,
+ struct todo_item *item,
struct replay_opts *opts,
int final_fixup, int *check_todo)
{
@@ -1875,6 +2046,8 @@ static int do_pick_commit(struct repository *r,
struct commit_message msg = { NULL, NULL, NULL, NULL };
struct strbuf msgbuf = STRBUF_INIT;
int res, unborn = 0, reword = 0, allow, drop_commit;
+ enum todo_command command = item->command;
+ struct commit *commit = item->commit;
if (opts->no_commit) {
/*
@@ -2004,7 +2177,8 @@ static int do_pick_commit(struct repository *r,
if (command == TODO_REWORD)
reword = 1;
else if (is_fixup(command)) {
- if (update_squash_messages(r, command, commit, opts))
+ if (update_squash_messages(r, command, commit,
+ opts, item->flags))
return -1;
flags |= AMEND_MSG;
if (!final_fixup)
@@ -2169,10 +2343,6 @@ static int read_and_refresh_cache(struct repository *r,
return 0;
}
-enum todo_item_flags {
- TODO_EDIT_MERGE_MSG = 1
-};
-
void todo_list_release(struct todo_list *todo_list)
{
strbuf_release(&todo_list->buf);
@@ -2259,6 +2429,18 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,
return 0;
}
+ if (item->command == TODO_FIXUP) {
+ if (skip_prefix(bol, "-C", &bol) &&
+ (*bol == ' ' || *bol == '\t')) {
+ bol += strspn(bol, " \t");
+ item->flags |= TODO_REPLACE_FIXUP_MSG;
+ } else if (skip_prefix(bol, "-c", &bol) &&
+ (*bol == ' ' || *bol == '\t')) {
+ bol += strspn(bol, " \t");
+ item->flags |= TODO_EDIT_FIXUP_MSG;
+ }
+ }
+
if (item->command == TODO_MERGE) {
if (skip_prefix(bol, "-C", &bol))
bol += strspn(bol, " \t");
@@ -4124,8 +4306,8 @@ static int pick_commits(struct repository *r,
setenv(GIT_REFLOG_ACTION, reflog_message(opts,
command_to_string(item->command), NULL),
1);
- res = do_pick_commit(r, item->command, item->commit,
- opts, is_final_fixup(todo_list),
+ res = do_pick_commit(r, item, opts,
+ is_final_fixup(todo_list),
&check_todo);
if (is_rebase_i(opts))
setenv(GIT_REFLOG_ACTION, prev_reflog_action, 1);
@@ -4587,11 +4769,14 @@ static int single_pick(struct repository *r,
struct replay_opts *opts)
{
int check_todo;
+ struct todo_item item;
+
+ item.command = opts->action == REPLAY_PICK ?
+ TODO_PICK : TODO_REVERT;
+ item.commit = cmit;
setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
- return do_pick_commit(r, opts->action == REPLAY_PICK ?
- TODO_PICK : TODO_REVERT, cmit, opts, 0,
- &check_todo);
+ return do_pick_commit(r, &item, opts, 0, &check_todo);
}
int sequencer_pick_revisions(struct repository *r,
@@ -5171,7 +5356,7 @@ void todo_list_add_exec_commands(struct todo_list *todo_list,
int i, insert, nr = 0, alloc = 0;
struct todo_item *items = NULL, *base_items = NULL;
- base_items = xcalloc(commands->nr, sizeof(struct todo_item));
+ CALLOC_ARRAY(base_items, commands->nr);
for (i = 0; i < commands->nr; i++) {
size_t command_len = strlen(commands->items[i].string);
@@ -5262,6 +5447,14 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis
short_commit_name(item->commit) :
oid_to_hex(&item->commit->object.oid);
+ if (item->command == TODO_FIXUP) {
+ if (item->flags & TODO_EDIT_FIXUP_MSG)
+ strbuf_addstr(buf, " -c");
+ else if (item->flags & TODO_REPLACE_FIXUP_MSG) {
+ strbuf_addstr(buf, " -C");
+ }
+ }
+
if (item->command == TODO_MERGE) {
if (item->flags & TODO_EDIT_MERGE_MSG)
strbuf_addstr(buf, " -c");
@@ -5462,6 +5655,12 @@ static int subject2item_cmp(const void *fndata,
define_commit_slab(commit_todo_item, struct todo_item *);
+static int skip_fixupish(const char *subject, const char **p) {
+ return skip_prefix(subject, "fixup! ", p) ||
+ skip_prefix(subject, "amend! ", p) ||
+ skip_prefix(subject, "squash! ", p);
+}
+
/*
* 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
@@ -5520,15 +5719,13 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
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))) {
+ if (skip_fixupish(subject, &p)) {
struct commit *commit2;
for (;;) {
while (isspace(*p))
p++;
- if (!skip_prefix(p, "fixup! ", &p) &&
- !skip_prefix(p, "squash! ", &p))
+ if (!skip_fixupish(p, &p))
break;
}
@@ -5558,9 +5755,14 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
}
if (i2 >= 0) {
rearranged = 1;
- todo_list->items[i].command =
- starts_with(subject, "fixup!") ?
- TODO_FIXUP : TODO_SQUASH;
+ if (starts_with(subject, "fixup!")) {
+ todo_list->items[i].command = TODO_FIXUP;
+ } else if (starts_with(subject, "amend!")) {
+ todo_list->items[i].command = TODO_FIXUP;
+ todo_list->items[i].flags = TODO_REPLACE_FIXUP_MSG;
+ } else {
+ todo_list->items[i].command = TODO_SQUASH;
+ }
if (tail[i2] < 0) {
next[i] = next[i2];
next[i2] = i;