]> git.ipfire.org Git - thirdparty/git.git/commitdiff
rebase -i: add fixup [-C | -c] command
authorCharvi Mendiratta <charvi077@gmail.com>
Fri, 29 Jan 2021 18:20:47 +0000 (23:50 +0530)
committerJunio C Hamano <gitster@pobox.com>
Fri, 29 Jan 2021 23:21:56 +0000 (15:21 -0800)
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>
rebase-interactive.c
sequencer.c

index 762853bc7e074cba1ae904790cbc1eb07520f613..c3bd02adee2232c9e82f785607381e6611b59da6 100644 (file)
@@ -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"
index 6d9a10afcfcc59e09333bad30667bdaafd392cac..46e11d20e8cfe381c7e76ab9d6df88cade984acf 100644 (file)
@@ -1718,6 +1718,12 @@ 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;
@@ -1734,32 +1740,176 @@ static size_t subject_length(const char *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 void append_squash_message(struct strbuf *buf, const char *body,
-                                 struct replay_opts *opts)
+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)
 {
-       size_t commented_len = 0;
+       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');
+}
 
-       unlink(rebase_path_fixup_msg());
-       if (starts_with(body, "squash!") || starts_with(body, "fixup!"))
+/*
+ * 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();
 
@@ -1779,6 +1929,8 @@ static int update_squash_messages(struct repository *r,
                            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;
@@ -1792,18 +1944,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 (command == TODO_FIXUP && write_message(body, strlen(body),
+               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());
                }
-
                strbuf_addf(&buf, "%c ", comment_line_char);
                strbuf_addf(&buf, _(combined_commit_msg_fmt), 2);
                strbuf_addf(&buf, "\n%c ", comment_line_char);
-               strbuf_addstr(&buf, _(first_commit_msg_str));
+               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);
        }
@@ -1813,8 +1969,8 @@ static int update_squash_messages(struct repository *r,
                             oid_to_hex(&commit->object.oid));
        find_commit_subject(message, &body);
 
-       if (command == TODO_SQUASH) {
-               append_squash_message(&buf, body, opts);
+       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, _(skip_nth_commit_msg_fmt),
@@ -1825,7 +1981,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) {
@@ -2026,7 +2184,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)
@@ -2191,10 +2350,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);
@@ -2281,6 +2436,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");
@@ -5287,6 +5454,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");