Merge branch 'cm/rebase-i'
"rebase -i" is getting cleaned up and also enhanced. * cm/rebase-i: doc/git-rebase: add documentation for fixup [-C|-c] options rebase -i: teach --autosquash to work with amend! t3437: test script for fixup [-C|-c] options in interactive rebase rebase -i: add fixup [-C | -c] command sequencer: use const variable for commit message comments sequencer: pass todo_item to do_pick_commit() rebase -i: comment out squash!/fixup! subjects from squash message sequencer: factor out code to append squash message rebase -i: only write fixup-message when it's neededmaint
commit
ce4296cf2b
|
@ -887,9 +887,17 @@ If you want to fold two or more commits into one, replace the command
|
|||
"pick" for the second and subsequent commits with "squash" or "fixup".
|
||||
If the commits had different authors, the folded commit will be
|
||||
attributed to the author of the first commit. The suggested commit
|
||||
message for the folded commit is the concatenation of the commit
|
||||
messages of the first commit and of those with the "squash" command,
|
||||
but omits the commit messages of commits with the "fixup" command.
|
||||
message for the folded commit is the concatenation of the first
|
||||
commit's message with those identified by "squash" commands, omitting the
|
||||
messages of commits identified by "fixup" commands, unless "fixup -c"
|
||||
is used. In that case the suggested commit message is only the message
|
||||
of the "fixup -c" commit, and an editor is opened allowing you to edit
|
||||
the message. The contents (patch) of the "fixup -c" commit are still
|
||||
incorporated into the folded commit. If there is more than one "fixup -c"
|
||||
commit, the message from the last last one is used. You can also use
|
||||
"fixup -C" to get the same behavior as "fixup -c" except without opening
|
||||
an editor.
|
||||
|
||||
|
||||
'git rebase' will stop when "pick" has been replaced with "edit" or
|
||||
when a command fails due to merge errors. When you are done editing
|
||||
|
|
|
@ -44,7 +44,9 @@ void append_todo_help(int command_count,
|
|||
"r, reword <commit> = use commit, but edit the commit message\n"
|
||||
"e, edit <commit> = use commit, but stop for amending\n"
|
||||
"s, squash <commit> = use commit, but meld into previous commit\n"
|
||||
"f, fixup <commit> = like \"squash\", but discard this commit's log message\n"
|
||||
"f, fixup [-C | -c] <commit> = like \"squash\", but discard this\n"
|
||||
" commit's log message. Use -C to replace with this\n"
|
||||
" commit message or -c to edit the commit message\n"
|
||||
"x, exec <command> = run command (the rest of the line) using shell\n"
|
||||
"b, break = stop here (continue rebase later with 'git rebase --continue')\n"
|
||||
"d, drop <commit> = remove commit\n"
|
||||
|
|
295
sequencer.c
295
sequencer.c
|
@ -1726,13 +1726,198 @@ 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 size_t subject_length(const char *body)
|
||||
{
|
||||
const char *p = body;
|
||||
while (*p) {
|
||||
const char *next = skip_blank_lines(p);
|
||||
if (next != p)
|
||||
break;
|
||||
p = strchrnul(p, '\n');
|
||||
if (*p)
|
||||
p++;
|
||||
}
|
||||
return p - body;
|
||||
}
|
||||
|
||||
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 check_fixup_flag(enum todo_command command,
|
||||
enum todo_item_flags 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,
|
||||
enum todo_item_flags 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 = 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 (check_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,
|
||||
enum todo_item_flags flag)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int res;
|
||||
int res = 0;
|
||||
const char *message, *body;
|
||||
const char *encoding = get_commit_output_encoding();
|
||||
|
||||
|
@ -1748,10 +1933,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 (check_fixup_flag(command, flag) && !seen_squash(opts))
|
||||
update_squash_message_for_fixup(&buf);
|
||||
} else {
|
||||
struct object_id head;
|
||||
struct commit *head_commit;
|
||||
|
@ -1765,19 +1952,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, check_fixup_flag(command, flag) ?
|
||||
_(skip_first_commit_msg_str) :
|
||||
_(first_commit_msg_str));
|
||||
strbuf_addstr(&buf, "\n\n");
|
||||
strbuf_addstr(&buf, body);
|
||||
if (check_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 +1977,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 || check_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 +1989,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 +2048,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 +2061,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 +2192,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 +2358,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 +2444,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 +4321,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 +4784,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,
|
||||
|
@ -5262,6 +5462,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 +5670,12 @@ static int subject2item_cmp(const void *fndata,
|
|||
|
||||
define_commit_slab(commit_todo_item, struct todo_item *);
|
||||
|
||||
static inline int skip_fixup_amend_squash(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 +5734,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_fixup_amend_squash(subject, &p)) {
|
||||
struct commit *commit2;
|
||||
|
||||
for (;;) {
|
||||
while (isspace(*p))
|
||||
p++;
|
||||
if (!skip_prefix(p, "fixup! ", &p) &&
|
||||
!skip_prefix(p, "squash! ", &p))
|
||||
if (!skip_fixup_amend_squash(p, &p))
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -5558,9 +5770,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;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#
|
||||
# - override the commit message with $FAKE_COMMIT_MESSAGE
|
||||
# - amend the commit message with $FAKE_COMMIT_AMEND
|
||||
# - copy the original commit message to a file with $FAKE_MESSAGE_COPY
|
||||
# - check that non-commit messages have a certain line count with $EXPECT_COUNT
|
||||
# - check the commit count in the commit message header with $EXPECT_HEADER_COUNT
|
||||
# - rewrite a rebase -i script as directed by $FAKE_LINES.
|
||||
|
@ -32,6 +33,7 @@ set_fake_editor () {
|
|||
exit
|
||||
test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
|
||||
test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
|
||||
test -z "$FAKE_MESSAGE_COPY" || cat "$1" >"$FAKE_MESSAGE_COPY"
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
|
@ -50,6 +52,8 @@ set_fake_editor () {
|
|||
action="$line";;
|
||||
exec_*|x_*|break|b)
|
||||
echo "$line" | sed 's/_/ /g' >> "$1";;
|
||||
merge_*|fixup_*)
|
||||
action=$(echo "$line" | sed 's/_/ /g');;
|
||||
"#")
|
||||
echo '# comment' >> "$1";;
|
||||
">")
|
||||
|
|
|
@ -84,8 +84,7 @@ test_auto_squash () {
|
|||
echo 1 >file1 &&
|
||||
git add -u &&
|
||||
test_tick &&
|
||||
git commit -m "squash! first" &&
|
||||
|
||||
git commit -m "squash! first" -m "extra para for first" &&
|
||||
git tag $1 &&
|
||||
test_tick &&
|
||||
git rebase $2 -i HEAD^^^ &&
|
||||
|
@ -142,7 +141,7 @@ test_expect_success 'auto squash that matches 2 commits' '
|
|||
echo 1 >file1 &&
|
||||
git add -u &&
|
||||
test_tick &&
|
||||
git commit -m "squash! first" &&
|
||||
git commit -m "squash! first" -m "extra para for first" &&
|
||||
git tag final-multisquash &&
|
||||
test_tick &&
|
||||
git rebase --autosquash -i HEAD~4 &&
|
||||
|
@ -195,7 +194,7 @@ test_expect_success 'auto squash that matches a sha1' '
|
|||
git add -u &&
|
||||
test_tick &&
|
||||
oid=$(git rev-parse --short HEAD^) &&
|
||||
git commit -m "squash! $oid" &&
|
||||
git commit -m "squash! $oid" -m "extra para" &&
|
||||
git tag final-shasquash &&
|
||||
test_tick &&
|
||||
git rebase --autosquash -i HEAD^^^ &&
|
||||
|
@ -206,7 +205,8 @@ test_expect_success 'auto squash that matches a sha1' '
|
|||
git cat-file blob HEAD^:file1 >actual &&
|
||||
test_cmp expect actual &&
|
||||
git cat-file commit HEAD^ >commit &&
|
||||
grep squash commit >actual &&
|
||||
! grep "squash" commit &&
|
||||
grep "^extra para" commit >actual &&
|
||||
test_line_count = 1 actual
|
||||
'
|
||||
|
||||
|
@ -216,7 +216,7 @@ test_expect_success 'auto squash that matches longer sha1' '
|
|||
git add -u &&
|
||||
test_tick &&
|
||||
oid=$(git rev-parse --short=11 HEAD^) &&
|
||||
git commit -m "squash! $oid" &&
|
||||
git commit -m "squash! $oid" -m "extra para" &&
|
||||
git tag final-longshasquash &&
|
||||
test_tick &&
|
||||
git rebase --autosquash -i HEAD^^^ &&
|
||||
|
@ -227,7 +227,8 @@ test_expect_success 'auto squash that matches longer sha1' '
|
|||
git cat-file blob HEAD^:file1 >actual &&
|
||||
test_cmp expect actual &&
|
||||
git cat-file commit HEAD^ >commit &&
|
||||
grep squash commit >actual &&
|
||||
! grep "squash" commit &&
|
||||
grep "^extra para" commit >actual &&
|
||||
test_line_count = 1 actual
|
||||
'
|
||||
|
||||
|
@ -236,7 +237,7 @@ test_auto_commit_flags () {
|
|||
echo 1 >file1 &&
|
||||
git add -u &&
|
||||
test_tick &&
|
||||
git commit --$1 first-commit &&
|
||||
git commit --$1 first-commit -m "extra para for first" &&
|
||||
git tag final-commit-$1 &&
|
||||
test_tick &&
|
||||
git rebase --autosquash -i HEAD^^^ &&
|
||||
|
@ -264,11 +265,11 @@ test_auto_fixup_fixup () {
|
|||
echo 1 >file1 &&
|
||||
git add -u &&
|
||||
test_tick &&
|
||||
git commit -m "$1! first" &&
|
||||
git commit -m "$1! first" -m "extra para for first" &&
|
||||
echo 2 >file1 &&
|
||||
git add -u &&
|
||||
test_tick &&
|
||||
git commit -m "$1! $2! first" &&
|
||||
git commit -m "$1! $2! first" -m "second extra para for first" &&
|
||||
git tag "final-$1-$2" &&
|
||||
test_tick &&
|
||||
(
|
||||
|
@ -329,12 +330,12 @@ test_expect_success 'autosquash with custom inst format' '
|
|||
git add -u &&
|
||||
test_tick &&
|
||||
oid=$(git rev-parse --short HEAD^) &&
|
||||
git commit -m "squash! $oid" &&
|
||||
git commit -m "squash! $oid" -m "extra para for first" &&
|
||||
echo 1 >file1 &&
|
||||
git add -u &&
|
||||
test_tick &&
|
||||
subject=$(git log -n 1 --format=%s HEAD~2) &&
|
||||
git commit -m "squash! $subject" &&
|
||||
git commit -m "squash! $subject" -m "second extra para for first" &&
|
||||
git tag final-squash-instFmt &&
|
||||
test_tick &&
|
||||
git rebase --autosquash -i HEAD~4 &&
|
||||
|
@ -345,8 +346,9 @@ test_expect_success 'autosquash with custom inst format' '
|
|||
git cat-file blob HEAD^:file1 >actual &&
|
||||
test_cmp expect actual &&
|
||||
git cat-file commit HEAD^ >commit &&
|
||||
grep squash commit >actual &&
|
||||
test_line_count = 2 actual
|
||||
! grep "squash" commit &&
|
||||
grep first commit >actual &&
|
||||
test_line_count = 3 actual
|
||||
'
|
||||
|
||||
test_expect_success 'autosquash with empty custom instructionFormat' '
|
||||
|
|
|
@ -0,0 +1,225 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2018 Phillip Wood
|
||||
#
|
||||
|
||||
test_description='git rebase interactive fixup options
|
||||
|
||||
This test checks the "fixup [-C|-c]" command of rebase interactive.
|
||||
In addition to amending the contents of the commit, "fixup -C"
|
||||
replaces the original commit message with the message of the fixup
|
||||
commit. "fixup -c" also replaces the original message, but opens the
|
||||
editor to allow the user to edit the message before committing.
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
. "$TEST_DIRECTORY"/lib-rebase.sh
|
||||
|
||||
EMPTY=""
|
||||
|
||||
test_commit_message () {
|
||||
rev="$1" && # commit or tag we want to test
|
||||
file="$2" && # test against the content of a file
|
||||
git show --no-patch --pretty=format:%B "$rev" >actual-message &&
|
||||
if test "$2" = -m
|
||||
then
|
||||
str="$3" && # test against a string
|
||||
printf "%s\n" "$str" >tmp-expected-message &&
|
||||
file="tmp-expected-message"
|
||||
fi
|
||||
test_cmp "$file" actual-message
|
||||
}
|
||||
|
||||
get_author () {
|
||||
rev="$1" &&
|
||||
git log -1 --pretty=format:"%an %ae" "$rev"
|
||||
}
|
||||
|
||||
test_expect_success 'setup' '
|
||||
cat >message <<-EOF &&
|
||||
amend! B
|
||||
${EMPTY}
|
||||
new subject
|
||||
${EMPTY}
|
||||
new
|
||||
body
|
||||
EOF
|
||||
|
||||
sed "1,2d" message >expected-message &&
|
||||
|
||||
test_commit A A &&
|
||||
test_commit B B &&
|
||||
get_author HEAD >expected-author &&
|
||||
ORIG_AUTHOR_NAME="$GIT_AUTHOR_NAME" &&
|
||||
ORIG_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" &&
|
||||
GIT_AUTHOR_NAME="Amend Author" &&
|
||||
GIT_AUTHOR_EMAIL="amend@example.com" &&
|
||||
test_commit "$(cat message)" A A1 A1 &&
|
||||
test_commit A2 A &&
|
||||
test_commit A3 A &&
|
||||
GIT_AUTHOR_NAME="$ORIG_AUTHOR_NAME" &&
|
||||
GIT_AUTHOR_EMAIL="$ORIG_AUTHOR_EMAIL" &&
|
||||
git checkout -b conflicts-branch A &&
|
||||
test_commit conflicts A &&
|
||||
|
||||
set_fake_editor &&
|
||||
git checkout -b branch B &&
|
||||
echo B1 >B &&
|
||||
test_tick &&
|
||||
git commit --fixup=HEAD -a &&
|
||||
test_tick &&
|
||||
git commit --allow-empty -F - <<-EOF &&
|
||||
amend! B
|
||||
${EMPTY}
|
||||
B
|
||||
${EMPTY}
|
||||
edited 1
|
||||
EOF
|
||||
test_tick &&
|
||||
git commit --allow-empty -F - <<-EOF &&
|
||||
amend! amend! B
|
||||
${EMPTY}
|
||||
B
|
||||
${EMPTY}
|
||||
edited 1
|
||||
${EMPTY}
|
||||
edited 2
|
||||
EOF
|
||||
echo B2 >B &&
|
||||
test_tick &&
|
||||
FAKE_COMMIT_AMEND="edited squash" git commit --squash=HEAD -a &&
|
||||
echo B3 >B &&
|
||||
test_tick &&
|
||||
git commit -a -F - <<-EOF &&
|
||||
amend! amend! amend! B
|
||||
${EMPTY}
|
||||
B
|
||||
${EMPTY}
|
||||
edited 1
|
||||
${EMPTY}
|
||||
edited 2
|
||||
${EMPTY}
|
||||
edited 3
|
||||
EOF
|
||||
|
||||
GIT_AUTHOR_NAME="Rebase Author" &&
|
||||
GIT_AUTHOR_EMAIL="rebase.author@example.com" &&
|
||||
GIT_COMMITTER_NAME="Rebase Committer" &&
|
||||
GIT_COMMITTER_EMAIL="rebase.committer@example.com"
|
||||
'
|
||||
|
||||
test_expect_success 'simple fixup -C works' '
|
||||
test_when_finished "test_might_fail git rebase --abort" &&
|
||||
git checkout --detach A2 &&
|
||||
FAKE_LINES="1 fixup_-C 2" git rebase -i B &&
|
||||
test_cmp_rev HEAD^ B &&
|
||||
test_cmp_rev HEAD^{tree} A2^{tree} &&
|
||||
test_commit_message HEAD -m "A2"
|
||||
'
|
||||
|
||||
test_expect_success 'simple fixup -c works' '
|
||||
test_when_finished "test_might_fail git rebase --abort" &&
|
||||
git checkout --detach A2 &&
|
||||
git log -1 --pretty=format:%B >expected-fixup-message &&
|
||||
test_write_lines "" "Modified A2" >>expected-fixup-message &&
|
||||
FAKE_LINES="1 fixup_-c 2" \
|
||||
FAKE_COMMIT_AMEND="Modified A2" \
|
||||
git rebase -i B &&
|
||||
test_cmp_rev HEAD^ B &&
|
||||
test_cmp_rev HEAD^{tree} A2^{tree} &&
|
||||
test_commit_message HEAD expected-fixup-message
|
||||
'
|
||||
|
||||
test_expect_success 'fixup -C removes amend! from message' '
|
||||
test_when_finished "test_might_fail git rebase --abort" &&
|
||||
git checkout --detach A1 &&
|
||||
FAKE_LINES="1 fixup_-C 2" git rebase -i A &&
|
||||
test_cmp_rev HEAD^ A &&
|
||||
test_cmp_rev HEAD^{tree} A1^{tree} &&
|
||||
test_commit_message HEAD expected-message &&
|
||||
get_author HEAD >actual-author &&
|
||||
test_cmp expected-author actual-author
|
||||
'
|
||||
|
||||
test_expect_success 'fixup -C with conflicts gives correct message' '
|
||||
test_when_finished "test_might_fail git rebase --abort" &&
|
||||
git checkout --detach A1 &&
|
||||
test_must_fail env FAKE_LINES="1 fixup_-C 2" git rebase -i conflicts &&
|
||||
git checkout --theirs -- A &&
|
||||
git add A &&
|
||||
FAKE_COMMIT_AMEND=edited git rebase --continue &&
|
||||
test_cmp_rev HEAD^ conflicts &&
|
||||
test_cmp_rev HEAD^{tree} A1^{tree} &&
|
||||
test_write_lines "" edited >>expected-message &&
|
||||
test_commit_message HEAD expected-message &&
|
||||
get_author HEAD >actual-author &&
|
||||
test_cmp expected-author actual-author
|
||||
'
|
||||
|
||||
test_expect_success 'skipping fixup -C after fixup gives correct message' '
|
||||
test_when_finished "test_might_fail git rebase --abort" &&
|
||||
git checkout --detach A3 &&
|
||||
test_must_fail env FAKE_LINES="1 fixup 2 fixup_-C 4" git rebase -i A &&
|
||||
git reset --hard &&
|
||||
FAKE_COMMIT_AMEND=edited git rebase --continue &&
|
||||
test_commit_message HEAD -m "B"
|
||||
'
|
||||
|
||||
test_expect_success 'sequence of fixup, fixup -C & squash --signoff works' '
|
||||
git checkout --detach branch &&
|
||||
FAKE_LINES="1 fixup 2 fixup_-C 3 fixup_-C 4 squash 5 fixup_-C 6" \
|
||||
FAKE_COMMIT_AMEND=squashed \
|
||||
FAKE_MESSAGE_COPY=actual-squash-message \
|
||||
git -c commit.status=false rebase -ik --signoff A &&
|
||||
git diff-tree --exit-code --patch HEAD branch -- &&
|
||||
test_cmp_rev HEAD^ A &&
|
||||
test_i18ncmp "$TEST_DIRECTORY/t3437/expected-squash-message" \
|
||||
actual-squash-message
|
||||
'
|
||||
|
||||
test_expect_success 'first fixup -C commented out in sequence fixup fixup -C fixup -C' '
|
||||
test_when_finished "test_might_fail git rebase --abort" &&
|
||||
git checkout branch && git checkout --detach branch~2 &&
|
||||
git log -1 --pretty=format:%b >expected-message &&
|
||||
FAKE_LINES="1 fixup 2 fixup_-C 3 fixup_-C 4" git rebase -i A &&
|
||||
test_cmp_rev HEAD^ A &&
|
||||
test_commit_message HEAD expected-message
|
||||
'
|
||||
|
||||
test_expect_success 'multiple fixup -c opens editor once' '
|
||||
test_when_finished "test_might_fail git rebase --abort" &&
|
||||
git checkout --detach A3 &&
|
||||
base=$(git rev-parse HEAD~4) &&
|
||||
FAKE_COMMIT_MESSAGE="Modified-A3" \
|
||||
FAKE_LINES="1 fixup_-C 2 fixup_-c 3 fixup_-c 4" \
|
||||
EXPECT_HEADER_COUNT=4 \
|
||||
git rebase -i $base &&
|
||||
test_cmp_rev $base HEAD^ &&
|
||||
test 1 = $(git show | grep Modified-A3 | wc -l)
|
||||
'
|
||||
|
||||
test_expect_success 'sequence squash, fixup & fixup -c gives combined message' '
|
||||
test_when_finished "test_might_fail git rebase --abort" &&
|
||||
git checkout --detach A3 &&
|
||||
FAKE_LINES="1 squash 2 fixup 3 fixup_-c 4" \
|
||||
FAKE_MESSAGE_COPY=actual-combined-message \
|
||||
git -c commit.status=false rebase -i A &&
|
||||
test_i18ncmp "$TEST_DIRECTORY/t3437/expected-combined-message" \
|
||||
actual-combined-message &&
|
||||
test_cmp_rev HEAD^ A
|
||||
'
|
||||
|
||||
test_expect_success 'fixup -C works upon --autosquash with amend!' '
|
||||
git checkout --detach branch &&
|
||||
FAKE_COMMIT_AMEND=squashed \
|
||||
FAKE_MESSAGE_COPY=actual-squash-message \
|
||||
git -c commit.status=false rebase -ik --autosquash \
|
||||
--signoff A &&
|
||||
git diff-tree --exit-code --patch HEAD branch -- &&
|
||||
test_cmp_rev HEAD^ A &&
|
||||
test_i18ncmp "$TEST_DIRECTORY/t3437/expected-squash-message" \
|
||||
actual-squash-message
|
||||
'
|
||||
|
||||
test_done
|
|
@ -0,0 +1,21 @@
|
|||
# This is a combination of 4 commits.
|
||||
# This is the 1st commit message:
|
||||
|
||||
B
|
||||
|
||||
# This is the commit message #2:
|
||||
|
||||
# amend! B
|
||||
|
||||
new subject
|
||||
|
||||
new
|
||||
body
|
||||
|
||||
# The commit message #3 will be skipped:
|
||||
|
||||
# A2
|
||||
|
||||
# This is the commit message #4:
|
||||
|
||||
A3
|
|
@ -0,0 +1,51 @@
|
|||
# This is a combination of 6 commits.
|
||||
# The 1st commit message will be skipped:
|
||||
|
||||
# B
|
||||
#
|
||||
# Signed-off-by: Rebase Committer <rebase.committer@example.com>
|
||||
|
||||
# The commit message #2 will be skipped:
|
||||
|
||||
# fixup! B
|
||||
|
||||
# The commit message #3 will be skipped:
|
||||
|
||||
# amend! B
|
||||
#
|
||||
# B
|
||||
#
|
||||
# edited 1
|
||||
#
|
||||
# Signed-off-by: Rebase Committer <rebase.committer@example.com>
|
||||
|
||||
# This is the commit message #4:
|
||||
|
||||
# amend! amend! B
|
||||
|
||||
B
|
||||
|
||||
edited 1
|
||||
|
||||
edited 2
|
||||
|
||||
Signed-off-by: Rebase Committer <rebase.committer@example.com>
|
||||
|
||||
# This is the commit message #5:
|
||||
|
||||
# squash! amend! amend! B
|
||||
|
||||
edited squash
|
||||
|
||||
# This is the commit message #6:
|
||||
|
||||
# amend! amend! amend! B
|
||||
|
||||
B
|
||||
|
||||
edited 1
|
||||
|
||||
edited 2
|
||||
|
||||
edited 3
|
||||
squashed
|
|
@ -226,10 +226,6 @@ test_commit_autosquash_multi_encoding () {
|
|||
git rev-list HEAD >actual &&
|
||||
test_line_count = 3 actual &&
|
||||
iconv -f $old -t UTF-8 "$TEST_DIRECTORY"/t3900/$msg >expect &&
|
||||
if test $flag = squash; then
|
||||
subject="$(head -1 expect)" &&
|
||||
printf "\nsquash! %s\n" "$subject" >>expect
|
||||
fi &&
|
||||
git cat-file commit HEAD^ >raw &&
|
||||
(sed "1,/^$/d" raw | iconv -f $new -t utf-8) >actual &&
|
||||
test_cmp expect actual
|
||||
|
|
Loading…
Reference in New Issue