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 <phillip.wood@dunelm.org.uk> Mentored-by: Christian Couder <chriscool@tuxfamily.org> Mentored-by: Phillip Wood <phillip.wood@dunelm.org.uk> Signed-off-by: Charvi Mendiratta <charvi077@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Charvi Mendiratta committed
Jan 29, 2021 at 23:50 UTC
9e3cebd97cbd47909e683e617d5ffa2781f0adaa
2 files changed
+197
-20
rebase-interactive.c
+3
-1
@@ -44,7 +44,9 @@ void append_todo_help(int command_count,
44
"r, reword <commit> = use commit, but edit the commit message\n"
45
"e, edit <commit> = use commit, but stop for amending\n"
46
"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"
50
"x, exec <command> = run command (the rest of the line) using shell\n"
51
"b, break = stop here (continue rebase later with 'git rebase --continue')\n"
52
"d, drop <commit> = remove commit\n"
sequencer.c
+194
-19
@@ -1718,6 +1718,12 @@ static int is_pick_or_similar(enum todo_command command)
1718
}
1719
}
1720
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
+
1727
static size_t subject_length(const char *body)
1728
{
1729
const char *p = body;
@@ -1734,32 +1740,176 @@ static size_t subject_length(const char *body)
1740
1741
static const char first_commit_msg_str[] = N_("This is the 1st commit message:");
1742
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:");
1744
static const char skip_nth_commit_msg_fmt[] = N_("The commit message #%d will be skipped:");
1745
static const char combined_commit_msg_fmt[] = N_("This is a combination of %d commits.");
1746
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)
1783
{
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
+}
1791
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!"))))
1865
commented_len = subject_length(body);
1866
+
1867
strbuf_addf(buf, "\n%c ", comment_line_char);
1868
strbuf_addf(buf, _(nth_commit_msg_fmt),
1869
++opts->current_fixup_count + 1);
1870
strbuf_addstr(buf, "\n\n");
1871
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;
1874
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;
1903
}
1904
1905
static int update_squash_messages(struct repository *r,
1906
enum todo_command command,
1907
struct commit *commit,
1759
- struct replay_opts *opts)
1908
+ struct replay_opts *opts,
1909
+ enum todo_item_flags flag)
1910
{
1911
struct strbuf buf = STRBUF_INIT;
1762
- int res;
1912
+ int res = 0;
1913
const char *message, *body;
1914
const char *encoding = get_commit_output_encoding();
1915
@@ -1779,6 +1929,8 @@ static int update_squash_messages(struct repository *r,
1929
opts->current_fixup_count + 2);
1930
strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
1931
strbuf_release(&header);
1932
+ if (check_fixup_flag(command, flag) && !seen_squash(opts))
1933
+ update_squash_message_for_fixup(&buf);
1934
} else {
1935
struct object_id head;
1936
struct commit *head_commit;
@@ -1792,18 +1944,22 @@ static int update_squash_messages(struct repository *r,
1944
return error(_("could not read HEAD's commit message"));
1945
1946
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),
1948
rebase_path_fixup_msg(), 0) < 0) {
1949
unuse_commit_buffer(head_commit, head_message);
1950
return error(_("cannot write '%s'"), rebase_path_fixup_msg());
1951
}
1800
-
1952
strbuf_addf(&buf, "%c ", comment_line_char);
1953
strbuf_addf(&buf, _(combined_commit_msg_fmt), 2);
1954
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));
1958
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);
1963
1964
unuse_commit_buffer(head_commit, head_message);
1965
}
@@ -1813,8 +1969,8 @@ static int update_squash_messages(struct repository *r,
1969
oid_to_hex(&commit->object.oid));
1970
find_commit_subject(message, &body);
1971
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);
1974
} else if (command == TODO_FIXUP) {
1975
strbuf_addf(&buf, "\n%c ", comment_line_char);
1976
strbuf_addf(&buf, _(skip_nth_commit_msg_fmt),
@@ -1825,7 +1981,9 @@ static int update_squash_messages(struct repository *r,
1981
return error(_("unknown command: %d"), command);
1982
unuse_commit_buffer(commit, message);
1983
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);
1987
strbuf_release(&buf);
1988
1989
if (!res) {
@@ -2026,7 +2184,8 @@ static int do_pick_commit(struct repository *r,
2184
if (command == TODO_REWORD)
2185
reword = 1;
2186
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))
2189
return -1;
2190
flags |= AMEND_MSG;
2191
if (!final_fixup)
@@ -2191,10 +2350,6 @@ static int read_and_refresh_cache(struct repository *r,
2350
return 0;
2351
}
2352
2194
-enum todo_item_flags {
2195
- TODO_EDIT_MERGE_MSG = 1
2196
-};
2197
-
2353
void todo_list_release(struct todo_list *todo_list)
2354
{
2355
strbuf_release(&todo_list->buf);
@@ -2281,6 +2436,18 @@ static int parse_insn_line(struct repository *r, struct todo_item *item,
2436
return 0;
2437
}
2438
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
+
2451
if (item->command == TODO_MERGE) {
2452
if (skip_prefix(bol, "-C", &bol))
2453
bol += strspn(bol, " \t");
@@ -5287,6 +5454,14 @@ static void todo_list_to_strbuf(struct repository *r, struct todo_list *todo_lis
5454
short_commit_name(item->commit) :
5455
oid_to_hex(&item->commit->object.oid);
5456
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
+
5465
if (item->command == TODO_MERGE) {
5466
if (item->flags & TODO_EDIT_MERGE_MSG)
5467
strbuf_addstr(buf, " -c");