Skip to content

Commit 9e3cebd

Browse files
charvi-077gitster
authored andcommitted
rebase -i: add fixup [-C | -c] command
Add options to `fixup` command to fixup both the commit contents and message. `fixup -C` command is used to replace the original commit message and `fixup -c`, additionally allows to edit the commit message. Original-patch-by: Phillip Wood <[email protected]> Mentored-by: Christian Couder <[email protected]> Mentored-by: Phillip Wood <[email protected]> Signed-off-by: Charvi Mendiratta <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 71ee81c commit 9e3cebd

File tree

2 files changed

+197
-20
lines changed

2 files changed

+197
-20
lines changed

rebase-interactive.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ void append_todo_help(int command_count,
4444
"r, reword <commit> = use commit, but edit the commit message\n"
4545
"e, edit <commit> = use commit, but stop for amending\n"
4646
"s, squash <commit> = use commit, but meld into previous commit\n"
47-
"f, fixup <commit> = like \"squash\", but discard this commit's log message\n"
47+
"f, fixup [-C | -c] <commit> = like \"squash\", but discard this\n"
48+
" commit's log message. Use -C to replace with this\n"
49+
" commit message or -c to edit the commit message\n"
4850
"x, exec <command> = run command (the rest of the line) using shell\n"
4951
"b, break = stop here (continue rebase later with 'git rebase --continue')\n"
5052
"d, drop <commit> = remove commit\n"

sequencer.c

Lines changed: 194 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1718,6 +1718,12 @@ static int is_pick_or_similar(enum todo_command command)
17181718
}
17191719
}
17201720

1721+
enum todo_item_flags {
1722+
TODO_EDIT_MERGE_MSG = (1 << 0),
1723+
TODO_REPLACE_FIXUP_MSG = (1 << 1),
1724+
TODO_EDIT_FIXUP_MSG = (1 << 2),
1725+
};
1726+
17211727
static size_t subject_length(const char *body)
17221728
{
17231729
const char *p = body;
@@ -1734,32 +1740,176 @@ static size_t subject_length(const char *body)
17341740

17351741
static const char first_commit_msg_str[] = N_("This is the 1st commit message:");
17361742
static const char nth_commit_msg_fmt[] = N_("This is the commit message #%d:");
1743+
static const char skip_first_commit_msg_str[] = N_("The 1st commit message will be skipped:");
17371744
static const char skip_nth_commit_msg_fmt[] = N_("The commit message #%d will be skipped:");
17381745
static const char combined_commit_msg_fmt[] = N_("This is a combination of %d commits.");
17391746

1740-
static void append_squash_message(struct strbuf *buf, const char *body,
1741-
struct replay_opts *opts)
1747+
static int check_fixup_flag(enum todo_command command,
1748+
enum todo_item_flags flag)
1749+
{
1750+
return command == TODO_FIXUP && ((flag & TODO_REPLACE_FIXUP_MSG) ||
1751+
(flag & TODO_EDIT_FIXUP_MSG));
1752+
}
1753+
1754+
/*
1755+
* Wrapper around strbuf_add_commented_lines() which avoids double
1756+
* commenting commit subjects.
1757+
*/
1758+
static void add_commented_lines(struct strbuf *buf, const void *str, size_t len)
1759+
{
1760+
const char *s = str;
1761+
while (len > 0 && s[0] == comment_line_char) {
1762+
size_t count;
1763+
const char *n = memchr(s, '\n', len);
1764+
if (!n)
1765+
count = len;
1766+
else
1767+
count = n - s + 1;
1768+
strbuf_add(buf, s, count);
1769+
s += count;
1770+
len -= count;
1771+
}
1772+
strbuf_add_commented_lines(buf, s, len);
1773+
}
1774+
1775+
/* Does the current fixup chain contain a squash command? */
1776+
static int seen_squash(struct replay_opts *opts)
1777+
{
1778+
return starts_with(opts->current_fixups.buf, "squash") ||
1779+
strstr(opts->current_fixups.buf, "\nsquash");
1780+
}
1781+
1782+
static void update_comment_bufs(struct strbuf *buf1, struct strbuf *buf2, int n)
17421783
{
1743-
size_t commented_len = 0;
1784+
strbuf_setlen(buf1, 2);
1785+
strbuf_addf(buf1, _(nth_commit_msg_fmt), n);
1786+
strbuf_addch(buf1, '\n');
1787+
strbuf_setlen(buf2, 2);
1788+
strbuf_addf(buf2, _(skip_nth_commit_msg_fmt), n);
1789+
strbuf_addch(buf2, '\n');
1790+
}
17441791

1745-
unlink(rebase_path_fixup_msg());
1746-
if (starts_with(body, "squash!") || starts_with(body, "fixup!"))
1792+
/*
1793+
* Comment out any un-commented commit messages, updating the message comments
1794+
* to say they will be skipped but do not comment out the empty lines that
1795+
* surround commit messages and their comments.
1796+
*/
1797+
static void update_squash_message_for_fixup(struct strbuf *msg)
1798+
{
1799+
void (*copy_lines)(struct strbuf *, const void *, size_t) = strbuf_add;
1800+
struct strbuf buf1 = STRBUF_INIT, buf2 = STRBUF_INIT;
1801+
const char *s, *start;
1802+
char *orig_msg;
1803+
size_t orig_msg_len;
1804+
int i = 1;
1805+
1806+
strbuf_addf(&buf1, "# %s\n", _(first_commit_msg_str));
1807+
strbuf_addf(&buf2, "# %s\n", _(skip_first_commit_msg_str));
1808+
s = start = orig_msg = strbuf_detach(msg, &orig_msg_len);
1809+
while (s) {
1810+
const char *next;
1811+
size_t off;
1812+
if (skip_prefix(s, buf1.buf, &next)) {
1813+
/*
1814+
* Copy the last message, preserving the blank line
1815+
* preceding the current line
1816+
*/
1817+
off = (s > start + 1 && s[-2] == '\n') ? 1 : 0;
1818+
copy_lines(msg, start, s - start - off);
1819+
if (off)
1820+
strbuf_addch(msg, '\n');
1821+
/*
1822+
* The next message needs to be commented out but the
1823+
* message header is already commented out so just copy
1824+
* it and the blank line that follows it.
1825+
*/
1826+
strbuf_addbuf(msg, &buf2);
1827+
if (*next == '\n')
1828+
strbuf_addch(msg, *next++);
1829+
start = s = next;
1830+
copy_lines = add_commented_lines;
1831+
update_comment_bufs(&buf1, &buf2, ++i);
1832+
} else if (skip_prefix(s, buf2.buf, &next)) {
1833+
off = (s > start + 1 && s[-2] == '\n') ? 1 : 0;
1834+
copy_lines(msg, start, s - start - off);
1835+
start = s - off;
1836+
s = next;
1837+
copy_lines = strbuf_add;
1838+
update_comment_bufs(&buf1, &buf2, ++i);
1839+
} else {
1840+
s = strchr(s, '\n');
1841+
if (s)
1842+
s++;
1843+
}
1844+
}
1845+
copy_lines(msg, start, orig_msg_len - (start - orig_msg));
1846+
free(orig_msg);
1847+
strbuf_release(&buf1);
1848+
strbuf_release(&buf2);
1849+
}
1850+
1851+
static int append_squash_message(struct strbuf *buf, const char *body,
1852+
enum todo_command command, struct replay_opts *opts,
1853+
enum todo_item_flags flag)
1854+
{
1855+
const char *fixup_msg;
1856+
size_t commented_len = 0, fixup_off;
1857+
/*
1858+
* amend is non-interactive and not normally used with fixup!
1859+
* or squash! commits, so only comment out those subjects when
1860+
* squashing commit messages.
1861+
*/
1862+
if (starts_with(body, "amend!") ||
1863+
((command == TODO_SQUASH || seen_squash(opts)) &&
1864+
(starts_with(body, "squash!") || starts_with(body, "fixup!"))))
17471865
commented_len = subject_length(body);
1866+
17481867
strbuf_addf(buf, "\n%c ", comment_line_char);
17491868
strbuf_addf(buf, _(nth_commit_msg_fmt),
17501869
++opts->current_fixup_count + 1);
17511870
strbuf_addstr(buf, "\n\n");
17521871
strbuf_add_commented_lines(buf, body, commented_len);
1872+
/* buf->buf may be reallocated so store an offset into the buffer */
1873+
fixup_off = buf->len;
17531874
strbuf_addstr(buf, body + commented_len);
1875+
1876+
/* fixup -C after squash behaves like squash */
1877+
if (check_fixup_flag(command, flag) && !seen_squash(opts)) {
1878+
/*
1879+
* We're replacing the commit message so we need to
1880+
* append the Signed-off-by: trailer if the user
1881+
* requested '--signoff'.
1882+
*/
1883+
if (opts->signoff)
1884+
append_signoff(buf, 0, 0);
1885+
1886+
if ((command == TODO_FIXUP) &&
1887+
(flag & TODO_REPLACE_FIXUP_MSG) &&
1888+
(file_exists(rebase_path_fixup_msg()) ||
1889+
!file_exists(rebase_path_squash_msg()))) {
1890+
fixup_msg = skip_blank_lines(buf->buf + fixup_off);
1891+
if (write_message(fixup_msg, strlen(fixup_msg),
1892+
rebase_path_fixup_msg(), 0) < 0)
1893+
return error(_("cannot write '%s'"),
1894+
rebase_path_fixup_msg());
1895+
} else {
1896+
unlink(rebase_path_fixup_msg());
1897+
}
1898+
} else {
1899+
unlink(rebase_path_fixup_msg());
1900+
}
1901+
1902+
return 0;
17541903
}
17551904

17561905
static int update_squash_messages(struct repository *r,
17571906
enum todo_command command,
17581907
struct commit *commit,
1759-
struct replay_opts *opts)
1908+
struct replay_opts *opts,
1909+
enum todo_item_flags flag)
17601910
{
17611911
struct strbuf buf = STRBUF_INIT;
1762-
int res;
1912+
int res = 0;
17631913
const char *message, *body;
17641914
const char *encoding = get_commit_output_encoding();
17651915

@@ -1779,6 +1929,8 @@ static int update_squash_messages(struct repository *r,
17791929
opts->current_fixup_count + 2);
17801930
strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
17811931
strbuf_release(&header);
1932+
if (check_fixup_flag(command, flag) && !seen_squash(opts))
1933+
update_squash_message_for_fixup(&buf);
17821934
} else {
17831935
struct object_id head;
17841936
struct commit *head_commit;
@@ -1792,18 +1944,22 @@ static int update_squash_messages(struct repository *r,
17921944
return error(_("could not read HEAD's commit message"));
17931945

17941946
find_commit_subject(head_message, &body);
1795-
if (command == TODO_FIXUP && write_message(body, strlen(body),
1947+
if (command == TODO_FIXUP && !flag && write_message(body, strlen(body),
17961948
rebase_path_fixup_msg(), 0) < 0) {
17971949
unuse_commit_buffer(head_commit, head_message);
17981950
return error(_("cannot write '%s'"), rebase_path_fixup_msg());
17991951
}
1800-
18011952
strbuf_addf(&buf, "%c ", comment_line_char);
18021953
strbuf_addf(&buf, _(combined_commit_msg_fmt), 2);
18031954
strbuf_addf(&buf, "\n%c ", comment_line_char);
1804-
strbuf_addstr(&buf, _(first_commit_msg_str));
1955+
strbuf_addstr(&buf, check_fixup_flag(command, flag) ?
1956+
_(skip_first_commit_msg_str) :
1957+
_(first_commit_msg_str));
18051958
strbuf_addstr(&buf, "\n\n");
1806-
strbuf_addstr(&buf, body);
1959+
if (check_fixup_flag(command, flag))
1960+
strbuf_add_commented_lines(&buf, body, strlen(body));
1961+
else
1962+
strbuf_addstr(&buf, body);
18071963

18081964
unuse_commit_buffer(head_commit, head_message);
18091965
}
@@ -1813,8 +1969,8 @@ static int update_squash_messages(struct repository *r,
18131969
oid_to_hex(&commit->object.oid));
18141970
find_commit_subject(message, &body);
18151971

1816-
if (command == TODO_SQUASH) {
1817-
append_squash_message(&buf, body, opts);
1972+
if (command == TODO_SQUASH || check_fixup_flag(command, flag)) {
1973+
res = append_squash_message(&buf, body, command, opts, flag);
18181974
} else if (command == TODO_FIXUP) {
18191975
strbuf_addf(&buf, "\n%c ", comment_line_char);
18201976
strbuf_addf(&buf, _(skip_nth_commit_msg_fmt),
@@ -1825,7 +1981,9 @@ static int update_squash_messages(struct repository *r,
18251981
return error(_("unknown command: %d"), command);
18261982
unuse_commit_buffer(commit, message);
18271983

1828-
res = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0);
1984+
if (!res)
1985+
res = write_message(buf.buf, buf.len, rebase_path_squash_msg(),
1986+
0);
18291987
strbuf_release(&buf);
18301988

18311989
if (!res) {
@@ -2026,7 +2184,8 @@ static int do_pick_commit(struct repository *r,
20262184
if (command == TODO_REWORD)
20272185
reword = 1;
20282186
else if (is_fixup(command)) {
2029-
if (update_squash_messages(r, command, commit, opts))
2187+
if (update_squash_messages(r, command, commit,
2188+
opts, item->flags))
20302189
return -1;
20312190
flags |= AMEND_MSG;
20322191
if (!final_fixup)
@@ -2191,10 +2350,6 @@ static int read_and_refresh_cache(struct repository *r,
21912350
return 0;
21922351
}
21932352

2194-
enum todo_item_flags {
2195-
TODO_EDIT_MERGE_MSG = 1
2196-
};
2197-
21982353
void todo_list_release(struct todo_list *todo_list)
21992354
{
22002355
strbuf_release(&todo_list->buf);
@@ -2281,6 +2436,18 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,
22812436
return 0;
22822437
}
22832438

2439+
if (item->command == TODO_FIXUP) {
2440+
if (skip_prefix(bol, "-C", &bol) &&
2441+
(*bol == ' ' || *bol == '\t')) {
2442+
bol += strspn(bol, " \t");
2443+
item->flags |= TODO_REPLACE_FIXUP_MSG;
2444+
} else if (skip_prefix(bol, "-c", &bol) &&
2445+
(*bol == ' ' || *bol == '\t')) {
2446+
bol += strspn(bol, " \t");
2447+
item->flags |= TODO_EDIT_FIXUP_MSG;
2448+
}
2449+
}
2450+
22842451
if (item->command == TODO_MERGE) {
22852452
if (skip_prefix(bol, "-C", &bol))
22862453
bol += strspn(bol, " \t");
@@ -5287,6 +5454,14 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis
52875454
short_commit_name(item->commit) :
52885455
oid_to_hex(&item->commit->object.oid);
52895456

5457+
if (item->command == TODO_FIXUP) {
5458+
if (item->flags & TODO_EDIT_FIXUP_MSG)
5459+
strbuf_addstr(buf, " -c");
5460+
else if (item->flags & TODO_REPLACE_FIXUP_MSG) {
5461+
strbuf_addstr(buf, " -C");
5462+
}
5463+
}
5464+
52905465
if (item->command == TODO_MERGE) {
52915466
if (item->flags & TODO_EDIT_MERGE_MSG)
52925467
strbuf_addstr(buf, " -c");

0 commit comments

Comments
 (0)