Git fork

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>

authored by

Charvi Mendiratta and committed by
Junio C Hamano
9e3cebd9 71ee81cd

+197 -20
+3 -1
rebase-interactive.c
··· 44 44 "r, reword <commit> = use commit, but edit the commit message\n" 45 45 "e, edit <commit> = use commit, but stop for amending\n" 46 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" 48 50 "x, exec <command> = run command (the rest of the line) using shell\n" 49 51 "b, break = stop here (continue rebase later with 'git rebase --continue')\n" 50 52 "d, drop <commit> = remove commit\n"
+194 -19
sequencer.c
··· 1718 1718 } 1719 1719 } 1720 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 + 1721 1727 static size_t subject_length(const char *body) 1722 1728 { 1723 1729 const char *p = body; ··· 1734 1740 1735 1741 static const char first_commit_msg_str[] = N_("This is the 1st commit message:"); 1736 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:"); 1737 1744 static const char skip_nth_commit_msg_fmt[] = N_("The commit message #%d will be skipped:"); 1738 1745 static const char combined_commit_msg_fmt[] = N_("This is a combination of %d commits."); 1739 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 + { 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 + 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) 1742 1798 { 1743 - size_t commented_len = 0; 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 + } 1744 1850 1745 - unlink(rebase_path_fixup_msg()); 1746 - if (starts_with(body, "squash!") || starts_with(body, "fixup!")) 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!")))) 1747 1865 commented_len = subject_length(body); 1866 + 1748 1867 strbuf_addf(buf, "\n%c ", comment_line_char); 1749 1868 strbuf_addf(buf, _(nth_commit_msg_fmt), 1750 1869 ++opts->current_fixup_count + 1); 1751 1870 strbuf_addstr(buf, "\n\n"); 1752 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; 1753 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; 1754 1903 } 1755 1904 1756 1905 static int update_squash_messages(struct repository *r, 1757 1906 enum todo_command command, 1758 1907 struct commit *commit, 1759 - struct replay_opts *opts) 1908 + struct replay_opts *opts, 1909 + enum todo_item_flags flag) 1760 1910 { 1761 1911 struct strbuf buf = STRBUF_INIT; 1762 - int res; 1912 + int res = 0; 1763 1913 const char *message, *body; 1764 1914 const char *encoding = get_commit_output_encoding(); 1765 1915 ··· 1779 1929 opts->current_fixup_count + 2); 1780 1930 strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len); 1781 1931 strbuf_release(&header); 1932 + if (check_fixup_flag(command, flag) && !seen_squash(opts)) 1933 + update_squash_message_for_fixup(&buf); 1782 1934 } else { 1783 1935 struct object_id head; 1784 1936 struct commit *head_commit; ··· 1792 1944 return error(_("could not read HEAD's commit message")); 1793 1945 1794 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), 1796 1948 rebase_path_fixup_msg(), 0) < 0) { 1797 1949 unuse_commit_buffer(head_commit, head_message); 1798 1950 return error(_("cannot write '%s'"), rebase_path_fixup_msg()); 1799 1951 } 1800 - 1801 1952 strbuf_addf(&buf, "%c ", comment_line_char); 1802 1953 strbuf_addf(&buf, _(combined_commit_msg_fmt), 2); 1803 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)); 1805 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); 1807 1963 1808 1964 unuse_commit_buffer(head_commit, head_message); 1809 1965 } ··· 1813 1969 oid_to_hex(&commit->object.oid)); 1814 1970 find_commit_subject(message, &body); 1815 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); 1818 1974 } else if (command == TODO_FIXUP) { 1819 1975 strbuf_addf(&buf, "\n%c ", comment_line_char); 1820 1976 strbuf_addf(&buf, _(skip_nth_commit_msg_fmt), ··· 1825 1981 return error(_("unknown command: %d"), command); 1826 1982 unuse_commit_buffer(commit, message); 1827 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); 1829 1987 strbuf_release(&buf); 1830 1988 1831 1989 if (!res) { ··· 2026 2184 if (command == TODO_REWORD) 2027 2185 reword = 1; 2028 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)) 2030 2189 return -1; 2031 2190 flags |= AMEND_MSG; 2032 2191 if (!final_fixup) ··· 2190 2349 } 2191 2350 return 0; 2192 2351 } 2193 - 2194 - enum todo_item_flags { 2195 - TODO_EDIT_MERGE_MSG = 1 2196 - }; 2197 2352 2198 2353 void todo_list_release(struct todo_list *todo_list) 2199 2354 { ··· 2279 2434 item->arg_offset = bol - buf; 2280 2435 item->arg_len = (int)(eol - bol); 2281 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 + } 2282 2449 } 2283 2450 2284 2451 if (item->command == TODO_MERGE) { ··· 5286 5453 const char *oid = flags & TODO_LIST_SHORTEN_IDS ? 5287 5454 short_commit_name(item->commit) : 5288 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 + } 5289 5464 5290 5465 if (item->command == TODO_MERGE) { 5291 5466 if (item->flags & TODO_EDIT_MERGE_MSG)