From 87e061f07c3044bea95e2b9d111f077223a8761d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 19 Dec 2016 14:50:30 +0100 Subject: [PATCH 01/38] sequencer: avoid unnecessary curly braces This was noticed while addressing Junio Hamano's concern that some "else" operators were on separate lines than the preceding closing brace. Signed-off-by: Johannes Schindelin --- sequencer.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index 0b78f3149f..b2a65547d8 100644 --- a/sequencer.c +++ b/sequencer.c @@ -679,9 +679,8 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } discard_cache(); - if (!commit->parents) { + if (!commit->parents) parent = NULL; - } else if (commit->parents->next) { /* Reverting or cherry-picking a merge commit */ int cnt; From c01da86827519798f0016186737a0a17047dc98c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 19 Dec 2016 14:52:04 +0100 Subject: [PATCH 02/38] sequencer: move "else" keyword onto the same line as preceding brace It is the current coding style of the Git project to write if (...) { ... } else { ... } instead of putting the closing brace and the "else" keyword on separate lines. Pointed out by Junio Hamano. Signed-off-by: Johannes Schindelin --- sequencer.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index b2a65547d8..654122a445 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1117,8 +1117,7 @@ static int create_seq_dir(void) error(_("a cherry-pick or revert is already in progress")); advise(_("try \"git cherry-pick (--continue | --quit | --abort)\"")); return -1; - } - else if (mkdir(git_path_seq_dir(), 0777) < 0) + } else if (mkdir(git_path_seq_dir(), 0777) < 0) return error_errno(_("could not create sequencer directory '%s'"), git_path_seq_dir()); return 0; From 067327a082cfce34a851a35b353ef51891880270 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 19 Dec 2016 17:28:27 +0100 Subject: [PATCH 03/38] sequencer: use a helper to find the commit message It is actually not safe to look for a commit message by looking for the first empty line and skipping it. The find_commit_subject() function looks more carefully, so let's use it. Since we are interested in the entire commit message, we re-compute the string length after verifying that the commit subject is not empty (in which case the entire commit message would be empty, something that should not happen but that we want to handle gracefully). Signed-off-by: Johannes Schindelin --- sequencer.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/sequencer.c b/sequencer.c index 654122a445..a5463e2c69 100644 --- a/sequencer.c +++ b/sequencer.c @@ -750,14 +750,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, next = commit; next_label = msg.label; - /* - * Append the commit log message to msgbuf; it starts - * after the tree, parent, author, committer - * information followed by "\n\n". - */ - p = strstr(msg.message, "\n\n"); - if (p) - strbuf_addstr(&msgbuf, skip_blank_lines(p + 2)); + /* Append the commit log message to msgbuf. */ + if (find_commit_subject(msg.message, &p)) + strbuf_addstr(&msgbuf, p); if (opts->record_origin) { if (!has_conforming_footer(&msgbuf, NULL, 0)) From cccbea53727972a58b18e915582edf3733b55f08 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 9 Feb 2016 14:10:43 +0100 Subject: [PATCH 04/38] sequencer: support a new action: 'interactive rebase' This patch introduces a new action for the sequencer. It really does not do a whole lot of its own right now, but lays the ground work for patches to come. The intention, of course, is to finally make the sequencer the work horse of the interactive rebase (the original idea behind the "sequencer" concept). Signed-off-by: Johannes Schindelin --- sequencer.c | 36 ++++++++++++++++++++++++++++++++---- sequencer.h | 3 ++- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/sequencer.c b/sequencer.c index a5463e2c69..ce84c2905f 100644 --- a/sequencer.c +++ b/sequencer.c @@ -29,6 +29,14 @@ static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts") static GIT_PATH_FUNC(git_path_head_file, "sequencer/head") static GIT_PATH_FUNC(git_path_abort_safety_file, "sequencer/abort-safety") +static GIT_PATH_FUNC(rebase_path, "rebase-merge") +/* + * The file containing rebase commands, comments, and empty lines. + * This file is created by "git rebase -i" then edited by the user. As + * the lines are processed, they are removed from the front of this + * file and written to the tail of 'done'. + */ +static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") /* * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and * GIT_AUTHOR_DATE that will be used for the commit that is currently @@ -41,19 +49,22 @@ static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script") */ static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") -/* We will introduce the 'interactive rebase' mode later */ static inline int is_rebase_i(const struct replay_opts *opts) { - return 0; + return opts->action == REPLAY_INTERACTIVE_REBASE; } static const char *get_dir(const struct replay_opts *opts) { + if (is_rebase_i(opts)) + return rebase_path(); return git_path_seq_dir(); } static const char *get_todo_path(const struct replay_opts *opts) { + if (is_rebase_i(opts)) + return rebase_path_todo(); return git_path_todo_file(); } @@ -169,7 +180,15 @@ int sequencer_remove_state(struct replay_opts *opts) static const char *action_name(const struct replay_opts *opts) { - return opts->action == REPLAY_REVERT ? N_("revert") : N_("cherry-pick"); + switch (opts->action) { + case REPLAY_REVERT: + return N_("revert"); + case REPLAY_PICK: + return N_("cherry-pick"); + case REPLAY_INTERACTIVE_REBASE: + return N_("rebase -i"); + } + die(_("Unknown action: %d"), opts->action); } struct commit_message { @@ -411,7 +430,9 @@ static int do_recursive_merge(struct commit *base, struct commit *next, if (active_cache_changed && write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) - /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ + /* TRANSLATORS: %s will be "revert", "cherry-pick" or + * "rebase -i". + */ return error(_("%s: Unable to write new index file"), _(action_name(opts))); rollback_lock_file(&index_lock); @@ -1245,6 +1266,13 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) const char *todo_path = get_todo_path(opts); int next = todo_list->current, offset, fd; + /* + * rebase -i writes "git-rebase-todo" without the currently executing + * command, appending it to "done" instead. + */ + if (is_rebase_i(opts)) + next++; + fd = hold_lock_file_for_update(&todo_lock, todo_path, 0); if (fd < 0) return error_errno(_("could not lock '%s'"), todo_path); diff --git a/sequencer.h b/sequencer.h index 7a513c576b..cb21cfddee 100644 --- a/sequencer.h +++ b/sequencer.h @@ -7,7 +7,8 @@ const char *git_path_seq_dir(void); enum replay_action { REPLAY_REVERT, - REPLAY_PICK + REPLAY_PICK, + REPLAY_INTERACTIVE_REBASE }; struct replay_opts { From 80c468e2690af723d679680a5bb3089f7cb7ca4b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:07:35 +0200 Subject: [PATCH 05/38] sequencer (rebase -i): implement the 'noop' command The 'noop' command is probably the most boring of all rebase -i commands to support in the sequencer. Which makes it an excellent candidate for this first stab to add support for rebase -i's commands to the sequencer. For the moment, let's also treat empty lines and commented-out lines as 'noop'; We will refine that handling later in this patch series. To make it easier to identify "classes" of todo_commands (such as: determine whether a command is pick-like, i.e. handles a single commit), let's enforce a certain order of said commands. Signed-off-by: Johannes Schindelin --- sequencer.c | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index ce84c2905f..7a5c680ac6 100644 --- a/sequencer.c +++ b/sequencer.c @@ -654,14 +654,23 @@ static int allow_empty(struct replay_opts *opts, struct commit *commit) return 1; } +/* + * Note that ordering matters in this enum. Not only must it match the mapping + * below, it is also divided into several sections that matter. When adding + * new commands, make sure you add it in the right section. + */ enum todo_command { + /* commands that handle commits */ TODO_PICK = 0, - TODO_REVERT + TODO_REVERT, + /* commands that do nothing but are counted for reporting progress */ + TODO_NOOP }; static const char *todo_command_strings[] = { "pick", - "revert" + "revert", + "noop" }; static const char *command_to_string(const enum todo_command command) @@ -671,6 +680,10 @@ static const char *command_to_string(const enum todo_command command) die("Unknown command: %d", command); } +static int is_noop(const enum todo_command command) +{ + return TODO_NOOP <= (size_t)command; +} static int do_pick_commit(enum todo_command command, struct commit *commit, struct replay_opts *opts) @@ -926,6 +939,14 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) /* left-trim */ bol += strspn(bol, " \t"); + if (bol == eol || *bol == '\r' || *bol == comment_line_char) { + item->command = TODO_NOOP; + item->commit = NULL; + item->arg = bol; + item->arg_len = eol - bol; + return 0; + } + for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++) if (skip_prefix(bol, todo_command_strings[i], &bol)) { item->command = i; @@ -934,6 +955,13 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) if (i >= ARRAY_SIZE(todo_command_strings)) return -1; + if (item->command == TODO_NOOP) { + item->commit = NULL; + item->arg = bol; + item->arg_len = eol - bol; + return 0; + } + /* Eat up extra spaces/ tabs before object name */ padding = strspn(bol, " \t"); if (!padding) @@ -1336,7 +1364,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) struct todo_item *item = todo_list->items + todo_list->current; if (save_todo(todo_list, opts)) return -1; - res = do_pick_commit(item->command, item->commit, opts); + if (item->command <= TODO_REVERT) + res = do_pick_commit(item->command, item->commit, + opts); + else if (!is_noop(item->command)) + return error(_("unknown command %d"), item->command); + todo_list->current++; if (res) return res; From 43b0dc3090d71829becb46620edd8b937c1a94a1 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:07:47 +0200 Subject: [PATCH 06/38] sequencer (rebase -i): implement the 'edit' command This patch is a straight-forward reimplementation of the `edit` operation of the interactive rebase command. Well, not *quite* straight-forward: when stopping, the `edit` command wants to write the `patch` file (which is not only the patch, but includes the commit message and author information). To that end, this patch requires the earlier work that taught the log-tree machinery to respect the `file` setting of rev_info->diffopt to write to a file stream different than stdout. Signed-off-by: Johannes Schindelin --- sequencer.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 114 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index 7a5c680ac6..9cf7d5eb34 100644 --- a/sequencer.c +++ b/sequencer.c @@ -16,6 +16,7 @@ #include "refs.h" #include "argv-array.h" #include "quote.h" +#include "log-tree.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -43,6 +44,20 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") * being rebased. */ static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script") +/* + * When an "edit" rebase command is being processed, the SHA1 of the + * commit to be edited is recorded in this file. When "git rebase + * --continue" is executed, if there are any staged changes then they + * will be amended to the HEAD commit, but only provided the HEAD + * commit is still the commit to be edited. When any other rebase + * command is processed, this file is deleted. + */ +static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend") +/* + * When we stop at a given patch via the "edit" command, this file contains + * the abbreviated commit name of the corresponding patch. + */ +static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") /* * The following files are written by git-rebase just after parsing the * command-line (and are only consumed, not modified, by the sequencer). @@ -663,6 +678,7 @@ enum todo_command { /* commands that handle commits */ TODO_PICK = 0, TODO_REVERT, + TODO_EDIT, /* commands that do nothing but are counted for reporting progress */ TODO_NOOP }; @@ -670,6 +686,7 @@ enum todo_command { static const char *todo_command_strings[] = { "pick", "revert", + "edit", "noop" }; @@ -1349,9 +1366,87 @@ static int save_opts(struct replay_opts *opts) return res; } +static int make_patch(struct commit *commit, struct replay_opts *opts) +{ + struct strbuf buf = STRBUF_INIT; + struct rev_info log_tree_opt; + const char *subject, *p; + int res = 0; + + p = short_commit_name(commit); + if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0) + return -1; + + strbuf_addf(&buf, "%s/patch", get_dir(opts)); + memset(&log_tree_opt, 0, sizeof(log_tree_opt)); + init_revisions(&log_tree_opt, NULL); + log_tree_opt.abbrev = 0; + log_tree_opt.diff = 1; + log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH; + log_tree_opt.disable_stdin = 1; + log_tree_opt.no_commit_id = 1; + log_tree_opt.diffopt.file = fopen(buf.buf, "w"); + log_tree_opt.diffopt.use_color = GIT_COLOR_NEVER; + if (!log_tree_opt.diffopt.file) + res |= error_errno(_("could not open '%s'"), buf.buf); + else { + res |= log_tree_commit(&log_tree_opt, commit); + fclose(log_tree_opt.diffopt.file); + } + strbuf_reset(&buf); + + strbuf_addf(&buf, "%s/message", get_dir(opts)); + if (!file_exists(buf.buf)) { + const char *commit_buffer = get_commit_buffer(commit, NULL); + find_commit_subject(commit_buffer, &subject); + res |= write_message(subject, strlen(subject), buf.buf, 1); + unuse_commit_buffer(commit, commit_buffer); + } + strbuf_release(&buf); + + return res; +} + +static int intend_to_amend(void) +{ + unsigned char head[20]; + char *p; + + if (get_sha1("HEAD", head)) + return error(_("cannot read HEAD")); + + p = sha1_to_hex(head); + return write_message(p, strlen(p), rebase_path_amend(), 1); +} + +static int error_with_patch(struct commit *commit, + const char *subject, int subject_len, + struct replay_opts *opts, int exit_code, int to_amend) +{ + if (make_patch(commit, opts)) + return -1; + + if (to_amend) { + if (intend_to_amend()) + return -1; + + fprintf(stderr, "You can amend the commit now, with\n" + "\n" + " git commit --amend %s\n" + "\n" + "Once you are satisfied with your changes, run\n" + "\n" + " git rebase --continue\n", gpg_sign_opt_quoted(opts)); + } else if (exit_code) + fprintf(stderr, "Could not apply %s... %.*s\n", + short_commit_name(commit), subject_len, subject); + + return exit_code; +} + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { - int res; + int res = 0; setenv(GIT_REFLOG_ACTION, action_name(opts), 0); if (opts->allow_ff) @@ -1364,10 +1459,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) struct todo_item *item = todo_list->items + todo_list->current; if (save_todo(todo_list, opts)) return -1; - if (item->command <= TODO_REVERT) + if (item->command <= TODO_EDIT) { res = do_pick_commit(item->command, item->commit, opts); - else if (!is_noop(item->command)) + if (item->command == TODO_EDIT) { + struct commit *commit = item->commit; + if (!res) + warning(_("stopped at %s... %.*s"), + short_commit_name(commit), + item->arg_len, item->arg); + return error_with_patch(commit, + item->arg, item->arg_len, opts, res, + !res); + } + } else if (!is_noop(item->command)) return error(_("unknown command %d"), item->command); todo_list->current++; @@ -1375,6 +1480,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) return res; } + if (is_rebase_i(opts)) { + /* Stopped in the middle, as planned? */ + if (todo_list->current < todo_list->nr) + return 0; + } + /* * Sequence of picks finished successfully; cleanup by * removing the .git/sequencer directory From a20b1e2308023490e47c100784f6e7db1aae20ea Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:08:01 +0200 Subject: [PATCH 07/38] sequencer (rebase -i): implement the 'exec' command The 'exec' command is a little special among rebase -i's commands, as it does *not* have a SHA-1 as first parameter. Instead, everything after the `exec` command is treated as command-line to execute. Let's reuse the arg/arg_len fields of the todo_item structure (which hold the oneline for pick/edit commands) to point to the command-line. Signed-off-by: Johannes Schindelin --- sequencer.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/sequencer.c b/sequencer.c index 9cf7d5eb34..8a4418d655 100644 --- a/sequencer.c +++ b/sequencer.c @@ -17,6 +17,7 @@ #include "argv-array.h" #include "quote.h" #include "log-tree.h" +#include "wt-status.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -679,6 +680,8 @@ enum todo_command { TODO_PICK = 0, TODO_REVERT, TODO_EDIT, + /* commands that do something else than handling a single commit */ + TODO_EXEC, /* commands that do nothing but are counted for reporting progress */ TODO_NOOP }; @@ -687,6 +690,7 @@ static const char *todo_command_strings[] = { "pick", "revert", "edit", + "exec", "noop" }; @@ -985,6 +989,12 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) return -1; bol += padding; + if (item->command == TODO_EXEC) { + item->arg = bol; + item->arg_len = (int)(eol - bol); + return 0; + } + end_of_object_name = (char *) bol + strcspn(bol, " \t\n"); saved = *end_of_object_name; *end_of_object_name = '\0'; @@ -1444,6 +1454,46 @@ static int error_with_patch(struct commit *commit, return exit_code; } +static int do_exec(const char *command_line) +{ + const char *child_argv[] = { NULL, NULL }; + int dirty, status; + + fprintf(stderr, "Executing: %s\n", command_line); + child_argv[0] = command_line; + status = run_command_v_opt(child_argv, RUN_USING_SHELL); + + /* force re-reading of the cache */ + if (discard_cache() < 0 || read_cache() < 0) + return error(_("could not read index")); + + dirty = require_clean_work_tree("rebase", NULL, 1, 1); + + if (status) { + warning(_("execution failed: %s\n%s" + "You can fix the problem, and then run\n" + "\n" + " git rebase --continue\n" + "\n"), + command_line, + dirty ? N_("and made changes to the index and/or the " + "working tree\n") : ""); + if (status == 127) + /* command not found */ + status = 1; + } else if (dirty) { + warning(_("execution succeeded: %s\nbut " + "left changes to the index and/or the working tree\n" + "Commit or stash your changes, and then run\n" + "\n" + " git rebase --continue\n" + "\n"), command_line); + status = 1; + } + + return status; +} + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { int res = 0; @@ -1472,6 +1522,13 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) item->arg, item->arg_len, opts, res, !res); } + } else if (item->command == TODO_EXEC) { + char *end_of_arg = (char *)(item->arg + item->arg_len); + int saved = *end_of_arg; + + *end_of_arg = '\0'; + res = do_exec(item->arg); + *end_of_arg = saved; } else if (!is_noop(item->command)) return error(_("unknown command %d"), item->command); From bf35a4a79f3b804bfd257843194476fea54f48d4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 12 Apr 2016 15:30:26 +0200 Subject: [PATCH 08/38] sequencer (rebase -i): learn about the 'verbose' mode When calling `git rebase -i -v`, the user wants to see some statistics after the commits were rebased. Let's show some. The strbuf we use to perform that task will be used for other things in subsequent commits, hence it is declared and initialized in a wider scope than strictly needed here. Signed-off-by: Johannes Schindelin --- sequencer.c | 28 ++++++++++++++++++++++++++++ sequencer.h | 1 + 2 files changed, 29 insertions(+) diff --git a/sequencer.c b/sequencer.c index 8a4418d655..e6895a76bb 100644 --- a/sequencer.c +++ b/sequencer.c @@ -64,6 +64,8 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") * command-line (and are only consumed, not modified, by the sequencer). */ static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") +static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head") +static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") static inline int is_rebase_i(const struct replay_opts *opts) { @@ -1135,6 +1137,9 @@ static int read_populate_opts(struct replay_opts *opts) } strbuf_release(&buf); + if (file_exists(rebase_path_verbose())) + opts->verbose = 1; + return 0; } @@ -1538,9 +1543,32 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) } if (is_rebase_i(opts)) { + struct strbuf buf = STRBUF_INIT; + /* Stopped in the middle, as planned? */ if (todo_list->current < todo_list->nr) return 0; + + if (opts->verbose) { + struct rev_info log_tree_opt; + struct object_id orig, head; + + memset(&log_tree_opt, 0, sizeof(log_tree_opt)); + init_revisions(&log_tree_opt, NULL); + log_tree_opt.diff = 1; + log_tree_opt.diffopt.output_format = + DIFF_FORMAT_DIFFSTAT; + log_tree_opt.disable_stdin = 1; + + if (read_oneliner(&buf, rebase_path_orig_head(), 0) && + !get_sha1(buf.buf, orig.hash) && + !get_sha1("HEAD", head.hash)) { + diff_tree_sha1(orig.hash, head.hash, + "", &log_tree_opt.diffopt); + log_tree_diff_flush(&log_tree_opt); + } + } + strbuf_release(&buf); } /* diff --git a/sequencer.h b/sequencer.h index cb21cfddee..f885b68395 100644 --- a/sequencer.h +++ b/sequencer.h @@ -24,6 +24,7 @@ struct replay_opts { int allow_empty; int allow_empty_message; int keep_redundant_commits; + int verbose; int mainline; From aa7fe005661a08f12ed1c72a983dca10d4cf27d8 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 4 Mar 2016 16:18:50 +0100 Subject: [PATCH 09/38] sequencer (rebase -i): write the 'done' file In the interactive rebase, commands that were successfully processed are not simply discarded, but appended to the 'done' file instead. This is used e.g. to display the current state to the user in the output of `git status` or the progress. Signed-off-by: Johannes Schindelin --- sequencer.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/sequencer.c b/sequencer.c index e6895a76bb..542dbfaa38 100644 --- a/sequencer.c +++ b/sequencer.c @@ -39,6 +39,12 @@ static GIT_PATH_FUNC(rebase_path, "rebase-merge") * file and written to the tail of 'done'. */ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") +/* + * The rebase command lines that have already been processed. A line + * is moved here when it is first handled, before any associated user + * actions. + */ +static GIT_PATH_FUNC(rebase_path_done, "rebase-merge/done") /* * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and * GIT_AUTHOR_DATE that will be used for the commit that is currently @@ -1343,6 +1349,23 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) return error_errno(_("could not write to '%s'"), todo_path); if (commit_lock_file(&todo_lock) < 0) return error(_("failed to finalize '%s'."), todo_path); + + if (is_rebase_i(opts)) { + const char *done_path = rebase_path_done(); + int fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666); + int prev_offset = !next ? 0 : + todo_list->items[next - 1].offset_in_buf; + + if (fd >= 0 && offset > prev_offset && + write_in_full(fd, todo_list->buf.buf + prev_offset, + offset - prev_offset) < 0) { + close(fd); + return error_errno(_("could not write to '%s'"), + done_path); + } + if (fd >= 0) + close(fd); + } return 0; } From b25d7b9420c6ccecee784d9d40dfce005a18a14f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 23 May 2016 16:49:51 +0200 Subject: [PATCH 10/38] sequencer (rebase -i): add support for the 'fixup' and 'squash' commands This is a huge patch, and at the same time a huge step forward to execute the performance-critical parts of the interactive rebase in a builtin command. Since 'fixup' and 'squash' are not only similar, but also need to know about each other (we want to reduce a series of fixups/squashes into a single, final commit message edit, from the user's point of view), we really have to implement them both at the same time. Most of the actual work is done by the existing code path that already handles the "pick" and the "edit" commands; We added support for other features (e.g. to amend the commit message) in the patches leading up to this one, yet there are still quite a few bits in this patch that simply would not make sense as individual patches (such as: determining whether there was anything to "fix up" in the "todo" script, etc). In theory, it would be possible to reuse the fast-forward code path also for the fixup and the squash code paths, but in practice this would make the code less readable. The end result cannot be fast-forwarded anyway, therefore let's just extend the cherry-picking code path for now. Since the sequencer parses the entire `git-rebase-todo` script in one go, fixup or squash commands without a preceding pick can be reported early (in git-rebase--interactive, we could only report such errors just before executing the fixup/squash). Signed-off-by: Johannes Schindelin --- sequencer.c | 229 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 218 insertions(+), 11 deletions(-) diff --git a/sequencer.c b/sequencer.c index 542dbfaa38..de4065b614 100644 --- a/sequencer.c +++ b/sequencer.c @@ -45,6 +45,35 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") * actions. */ static GIT_PATH_FUNC(rebase_path_done, "rebase-merge/done") +/* + * The commit message that is planned to be used for any changes that + * need to be committed following a user interaction. + */ +static GIT_PATH_FUNC(rebase_path_message, "rebase-merge/message") +/* + * The file into which is accumulated the suggested commit message for + * squash/fixup commands. When the first of a series of squash/fixups + * is seen, the file is created and the commit message from the + * previous commit and from the first squash/fixup commit are written + * to it. The commit message for each subsequent squash/fixup commit + * is appended to the file as it is processed. + * + * The first line of the file is of the form + * # This is a combination of $count commits. + * where $count is the number of commits whose messages have been + * written to the file so far (including the initial "pick" commit). + * Each time that a commit message is processed, this line is read and + * updated. It is deleted just before the combined commit is made. + */ +static GIT_PATH_FUNC(rebase_path_squash_msg, "rebase-merge/message-squash") +/* + * If the current series of squash/fixups has not yet included a squash + * command, then this file exists and holds the commit message of the + * original "pick" commit. (If the series ends without a "squash" + * command, then this can be used as the commit message of the combined + * commit without opening the editor.) + */ +static GIT_PATH_FUNC(rebase_path_fixup_msg, "rebase-merge/message-fixup") /* * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and * GIT_AUTHOR_DATE that will be used for the commit that is currently @@ -688,6 +717,8 @@ enum todo_command { TODO_PICK = 0, TODO_REVERT, TODO_EDIT, + TODO_FIXUP, + TODO_SQUASH, /* commands that do something else than handling a single commit */ TODO_EXEC, /* commands that do nothing but are counted for reporting progress */ @@ -698,6 +729,8 @@ static const char *todo_command_strings[] = { "pick", "revert", "edit", + "fixup", + "squash", "exec", "noop" }; @@ -714,15 +747,114 @@ static int is_noop(const enum todo_command command) return TODO_NOOP <= (size_t)command; } -static int do_pick_commit(enum todo_command command, struct commit *commit, - struct replay_opts *opts) +static int is_fixup(enum todo_command command) { + return command == TODO_FIXUP || command == TODO_SQUASH; +} + +static int update_squash_messages(enum todo_command command, + struct commit *commit, struct replay_opts *opts) +{ + struct strbuf buf = STRBUF_INIT; + int count, res; + const char *message, *body; + + if (file_exists(rebase_path_squash_msg())) { + struct strbuf header = STRBUF_INIT; + char *eol, *p; + + if (strbuf_read_file(&buf, rebase_path_squash_msg(), 2048) <= 0) + return error(_("could not read '%s'"), + rebase_path_squash_msg()); + + p = buf.buf + 1; + eol = strchrnul(buf.buf, '\n'); + if (buf.buf[0] != comment_line_char || + (p += strcspn(p, "0123456789\n")) == eol) + return error(_("unexpected 1st line of squash message:" + "\n\n\t%.*s"), + (int)(eol - buf.buf), buf.buf); + count = strtol(p, NULL, 10); + + if (count < 1) + return error(_("invalid 1st line of squash message:\n" + "\n\t%.*s"), + (int)(eol - buf.buf), buf.buf); + + strbuf_addf(&header, "%c ", comment_line_char); + strbuf_addf(&header, + _("This is a combination of %d commits."), ++count); + strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len); + strbuf_release(&header); + } else { + unsigned char head[20]; + struct commit *head_commit; + const char *head_message, *body; + + if (get_sha1("HEAD", head)) + return error(_("need a HEAD to fixup")); + if (!(head_commit = lookup_commit_reference(head))) + return error(_("could not read HEAD")); + if (!(head_message = get_commit_buffer(head_commit, NULL))) + 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)) { + unuse_commit_buffer(head_commit, head_message); + return error(_("cannot write '%s'"), + rebase_path_fixup_msg()); + } + + count = 2; + strbuf_addf(&buf, "%c ", comment_line_char); + strbuf_addf(&buf, _("This is a combination of %d commits."), + count); + strbuf_addf(&buf, "\n%c ", comment_line_char); + strbuf_addstr(&buf, _("This is the 1st commit message:")); + strbuf_addstr(&buf, "\n\n"); + strbuf_addstr(&buf, body); + + unuse_commit_buffer(head_commit, head_message); + } + + if (!(message = get_commit_buffer(commit, NULL))) + return error(_("could not read commit message of %s"), + 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:"), count); + strbuf_addstr(&buf, "\n\n"); + strbuf_addstr(&buf, body); + } else if (command == TODO_FIXUP) { + strbuf_addf(&buf, "\n%c ", comment_line_char); + strbuf_addf(&buf, _("The commit message #%d will be skipped:"), + count); + strbuf_addstr(&buf, "\n\n"); + strbuf_add_commented_lines(&buf, body, strlen(body)); + } else + return error(_("unknown command: %d"), command); + unuse_commit_buffer(commit, message); + + res = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0); + strbuf_release(&buf); + return res; +} + +static int do_pick_commit(enum todo_command command, struct commit *commit, + struct replay_opts *opts, int final_fixup) +{ + int edit = opts->edit, cleanup_commit_message = 0; + const char *msg_file = edit ? NULL : git_path_merge_msg(); unsigned char head[20]; struct commit *base, *next, *parent; const char *base_label, *next_label; struct commit_message msg = { NULL, NULL, NULL, NULL }; struct strbuf msgbuf = STRBUF_INIT; - int res, unborn = 0, allow; + int res, unborn = 0, amend = 0, allow; if (opts->no_commit) { /* @@ -767,7 +899,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, else parent = commit->parents->item; - if (opts->allow_ff && + if (opts->allow_ff && !is_fixup(command) && ((parent && !hashcmp(parent->object.oid.hash, head)) || (!parent && unborn))) return fast_forward_to(commit->object.oid.hash, head, unborn, opts); @@ -826,6 +958,27 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } } + if (is_fixup(command)) { + if (update_squash_messages(command, commit, opts)) + return -1; + amend = 1; + if (!final_fixup) + msg_file = rebase_path_squash_msg(); + else if (file_exists(rebase_path_fixup_msg())) { + cleanup_commit_message = 1; + msg_file = rebase_path_fixup_msg(); + } else { + const char *dest = git_path("SQUASH_MSG"); + unlink(dest); + if (copy_file(dest, rebase_path_squash_msg(), 0666)) + return error(_("could not rename '%s' to '%s'"), + rebase_path_squash_msg(), dest); + unlink(git_path("MERGE_MSG")); + msg_file = dest; + edit = 1; + } + } + if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); @@ -881,8 +1034,13 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, goto leave; } if (!opts->no_commit) - res = run_git_commit(opts->edit ? NULL : git_path_merge_msg(), - opts, allow, opts->edit, 0, 0); + res = run_git_commit(msg_file, opts, allow, edit, amend, + cleanup_commit_message); + + if (!res && final_fixup) { + unlink(rebase_path_fixup_msg()); + unlink(rebase_path_squash_msg()); + } leave: free_message(commit, &msg); @@ -1023,7 +1181,7 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) { struct todo_item *item; char *p = buf, *next_p; - int i, res = 0; + int i, res = 0, fixup_okay = file_exists(rebase_path_done()); for (i = 1; *p; i++, p = next_p) { char *eol = strchrnul(p, '\n'); @@ -1038,8 +1196,16 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) if (parse_insn_line(item, p, eol)) { res = error(_("invalid line %d: %.*s"), i, (int)(eol - p), p); - item->command = -1; + item->command = TODO_NOOP; } + + if (fixup_okay) + ; /* do nothing */ + else if (is_fixup(item->command)) + return error(_("cannot '%s' without a previous commit"), + command_to_string(item->command)); + else if (!is_noop(item->command)) + fixup_okay = 1; } if (!todo_list->nr) return error(_("no commits parsed.")); @@ -1482,6 +1648,20 @@ static int error_with_patch(struct commit *commit, return exit_code; } +static int error_failed_squash(struct commit *commit, + struct replay_opts *opts, int subject_len, const char *subject) +{ + if (rename(rebase_path_squash_msg(), rebase_path_message())) + return error(_("could not rename '%s' to '%s'"), + rebase_path_squash_msg(), rebase_path_message()); + unlink(rebase_path_fixup_msg()); + unlink(git_path("MERGE_MSG")); + if (copy_file(git_path("MERGE_MSG"), rebase_path_message(), 0666)) + return error(_("could not copy '%s' to '%s'"), + rebase_path_message(), git_path("MERGE_MSG")); + return error_with_patch(commit, subject, subject_len, opts, 1, 0); +} + static int do_exec(const char *command_line) { const char *child_argv[] = { NULL, NULL }; @@ -1522,6 +1702,21 @@ static int do_exec(const char *command_line) return status; } +static int is_final_fixup(struct todo_list *todo_list) +{ + int i = todo_list->current; + + if (!is_fixup(todo_list->items[i].command)) + return 0; + + while (++i < todo_list->nr) + if (is_fixup(todo_list->items[i].command)) + return 0; + else if (!is_noop(todo_list->items[i].command)) + break; + return 1; +} + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { int res = 0; @@ -1537,9 +1732,15 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) struct todo_item *item = todo_list->items + todo_list->current; if (save_todo(todo_list, opts)) return -1; - if (item->command <= TODO_EDIT) { + if (is_rebase_i(opts)) { + unlink(rebase_path_message()); + unlink(rebase_path_author_script()); + unlink(rebase_path_stopped_sha()); + unlink(rebase_path_amend()); + } + if (item->command <= TODO_SQUASH) { res = do_pick_commit(item->command, item->commit, - opts); + opts, is_final_fixup(todo_list)); if (item->command == TODO_EDIT) { struct commit *commit = item->commit; if (!res) @@ -1550,6 +1751,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) item->arg, item->arg_len, opts, res, !res); } + if (res && is_fixup(item->command)) { + if (res == 1) + intend_to_amend(); + return error_failed_squash(item->commit, opts, + item->arg_len, item->arg); + } } else if (item->command == TODO_EXEC) { char *end_of_arg = (char *)(item->arg + item->arg_len); int saved = *end_of_arg; @@ -1648,7 +1855,7 @@ static int single_pick(struct commit *cmit, struct replay_opts *opts) { setenv(GIT_REFLOG_ACTION, action_name(opts), 0); return do_pick_commit(opts->action == REPLAY_PICK ? - TODO_PICK : TODO_REVERT, cmit, opts); + TODO_PICK : TODO_REVERT, cmit, opts, 0); } int sequencer_pick_revisions(struct replay_opts *opts) From 65c7aed39164c7b4ec9a4e4352fd4bc4bc6db97a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:08:09 +0200 Subject: [PATCH 11/38] sequencer (rebase -i): implement the short commands For users' convenience, most rebase commands can be abbreviated, e.g. 'p' instead of 'pick' and 'x' instead of 'exec'. Let's teach the sequencer to handle those abbreviated commands just fine. Signed-off-by: Johannes Schindelin --- sequencer.c | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/sequencer.c b/sequencer.c index de4065b614..83a58da822 100644 --- a/sequencer.c +++ b/sequencer.c @@ -725,20 +725,23 @@ enum todo_command { TODO_NOOP }; -static const char *todo_command_strings[] = { - "pick", - "revert", - "edit", - "fixup", - "squash", - "exec", - "noop" +static struct { + char c; + const char *str; +} todo_command_info[] = { + { 'p', "pick" }, + { 0, "revert" }, + { 'e', "edit" }, + { 'f', "fixup" }, + { 's', "squash" }, + { 'x', "exec" }, + { 0, "noop" } }; static const char *command_to_string(const enum todo_command command) { - if ((size_t)command < ARRAY_SIZE(todo_command_strings)) - return todo_command_strings[command]; + if ((size_t)command < ARRAY_SIZE(todo_command_info)) + return todo_command_info[command].str; die("Unknown command: %d", command); } @@ -1134,12 +1137,16 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) return 0; } - for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++) - if (skip_prefix(bol, todo_command_strings[i], &bol)) { + for (i = 0; i < ARRAY_SIZE(todo_command_info); i++) + if (skip_prefix(bol, todo_command_info[i].str, &bol)) { + item->command = i; + break; + } else if (bol[1] == ' ' && *bol == todo_command_info[i].c) { + bol++; item->command = i; break; } - if (i >= ARRAY_SIZE(todo_command_strings)) + if (i >= ARRAY_SIZE(todo_command_info)) return -1; if (item->command == TODO_NOOP) { @@ -1334,7 +1341,7 @@ static int walk_revs_populate_todo(struct todo_list *todo_list, { enum todo_command command = opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT; - const char *command_string = todo_command_strings[command]; + const char *command_string = todo_command_info[command].str; struct commit *commit; if (prepare_revs(opts)) From ec0170f0d173e8e43219dad44326eafcf4e4d1fd Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:11:12 +0200 Subject: [PATCH 12/38] sequencer (rebase -i): write an author-script file When the interactive rebase aborts, it writes out an author-script file to record the author information for the current commit. As we are about to teach the sequencer how to perform the actions behind an interactive rebase, it needs to write those author-script files, too. Signed-off-by: Johannes Schindelin --- sequencer.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index 83a58da822..1dda1b78cc 100644 --- a/sequencer.c +++ b/sequencer.c @@ -530,6 +530,52 @@ static int is_index_unchanged(void) return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.oid.hash); } +static int write_author_script(const char *message) +{ + struct strbuf buf = STRBUF_INIT; + const char *eol; + int res; + + for (;;) + if (!*message || starts_with(message, "\n")) { +missing_author: + /* Missing 'author' line? */ + unlink(rebase_path_author_script()); + return 0; + } else if (skip_prefix(message, "author ", &message)) + break; + else if ((eol = strchr(message, '\n'))) + message = eol + 1; + else + goto missing_author; + + strbuf_addstr(&buf, "GIT_AUTHOR_NAME='"); + while (*message && *message != '\n' && *message != '\r') + if (skip_prefix(message, " <", &message)) + break; + else if (*message != '\'') + strbuf_addch(&buf, *(message++)); + else + strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addstr(&buf, "'\nGIT_AUTHOR_EMAIL='"); + while (*message && *message != '\n' && *message != '\r') + if (skip_prefix(message, "> ", &message)) + break; + else if (*message != '\'') + strbuf_addch(&buf, *(message++)); + else + strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addstr(&buf, "'\nGIT_AUTHOR_DATE='@"); + while (*message && *message != '\n' && *message != '\r') + if (*message != '\'') + strbuf_addch(&buf, *(message++)); + else + strbuf_addf(&buf, "'\\\\%c'", *(message++)); + res = write_message(buf.buf, buf.len, rebase_path_author_script(), 1); + strbuf_release(&buf); + return res; +} + /* * Read the author-script file into an environment block, ready for use in * run_command(), that can be free()d afterwards. @@ -982,7 +1028,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } } - if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { + if (is_rebase_i(opts) && write_author_script(msg.message) < 0) + res = -1; + else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); if (res < 0) From 2ab8a6b08003e99d06e302456466a752b55994c9 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 17:12:20 +0100 Subject: [PATCH 13/38] sequencer (rebase -i): allow continuing with staged changes When an interactive rebase is interrupted, the user may stage changes before continuing, and we need to commit those changes in that case. Please note that the nested "if" added to the sequencer_continue() is not combined into a single "if" because it will be extended with an "else" clause in a later patch in this patch series. Signed-off-by: Johannes Schindelin --- sequencer.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/sequencer.c b/sequencer.c index 1dda1b78cc..7dd7ea53e0 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1873,6 +1873,42 @@ static int continue_single_pick(void) return run_command_v_opt(argv, RUN_GIT_CMD); } +static int commit_staged_changes(struct replay_opts *opts) +{ + int amend = 0; + + if (has_unstaged_changes(1)) + return error(_("cannot rebase: You have unstaged changes.")); + if (!has_uncommitted_changes(0)) + return 0; + + if (file_exists(rebase_path_amend())) { + struct strbuf rev = STRBUF_INIT; + unsigned char head[20], to_amend[20]; + + if (get_sha1("HEAD", head)) + return error(_("cannot amend non-existing commit")); + if (!read_oneliner(&rev, rebase_path_amend(), 0)) + return error(_("invalid file: '%s'"), rebase_path_amend()); + if (get_sha1_hex(rev.buf, to_amend)) + return error(_("invalid contents: '%s'"), + rebase_path_amend()); + if (hashcmp(head, to_amend)) + return error(_("\nYou have uncommitted changes in your " + "working tree. Please, commit them\n" + "first and then run 'git rebase " + "--continue' again.")); + + strbuf_release(&rev); + amend = 1; + } + + if (run_git_commit(rebase_path_message(), opts, 1, 1, amend, 0)) + return error(_("could not commit staged changes.")); + unlink(rebase_path_amend()); + return 0; +} + int sequencer_continue(struct replay_opts *opts) { struct todo_list todo_list = TODO_LIST_INIT; @@ -1881,6 +1917,10 @@ int sequencer_continue(struct replay_opts *opts) if (read_and_refresh_cache(opts)) return -1; + if (is_rebase_i(opts)) { + if (commit_staged_changes(opts)) + return -1; + } if (!file_exists(get_todo_path(opts))) return continue_single_pick(); if (read_populate_opts(opts)) From 74b4936fa233e77cc7fa11982d676f692c4808fa Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 9 Apr 2016 16:29:21 +0200 Subject: [PATCH 14/38] sequencer (rebase -i): remove CHERRY_PICK_HEAD when no longer needed The scripted version of the interactive rebase already does that. Signed-off-by: Johannes Schindelin --- sequencer.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index 7dd7ea53e0..4ba086b210 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1879,8 +1879,13 @@ static int commit_staged_changes(struct replay_opts *opts) if (has_unstaged_changes(1)) return error(_("cannot rebase: You have unstaged changes.")); - if (!has_uncommitted_changes(0)) + if (!has_uncommitted_changes(0)) { + const char *cherry_pick_head = git_path("CHERRY_PICK_HEAD"); + + if (file_exists(cherry_pick_head) && unlink(cherry_pick_head)) + return error(_("could not remove CHERRY_PICK_HEAD")); return 0; + } if (file_exists(rebase_path_amend())) { struct strbuf rev = STRBUF_INIT; From fb54fb16ab3cc03cc552442132f57c44facab8fc Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 31 Aug 2016 08:43:51 +0200 Subject: [PATCH 15/38] sequencer (rebase -i): skip some revert/cherry-pick specific code path When a cherry-pick continues without a "todo script", the intention is simply to pick a single commit. However, when an interactive rebase is continued without a "todo script", it means that the last command has been completed and that we now need to clean up. This commit guards the revert/cherry-pick specific steps so that they are not executed in rebase -i mode. Signed-off-by: Johannes Schindelin --- sequencer.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/sequencer.c b/sequencer.c index 4ba086b210..99051ae19b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1925,26 +1925,28 @@ int sequencer_continue(struct replay_opts *opts) if (is_rebase_i(opts)) { if (commit_staged_changes(opts)) return -1; - } - if (!file_exists(get_todo_path(opts))) + } else if (!file_exists(get_todo_path(opts))) return continue_single_pick(); if (read_populate_opts(opts)) return -1; if ((res = read_populate_todo(&todo_list, opts))) goto release_todo_list; - /* Verify that the conflict has been resolved */ - if (file_exists(git_path_cherry_pick_head()) || - file_exists(git_path_revert_head())) { - res = continue_single_pick(); - if (res) + if (!is_rebase_i(opts)) { + /* Verify that the conflict has been resolved */ + if (file_exists(git_path_cherry_pick_head()) || + file_exists(git_path_revert_head())) { + res = continue_single_pick(); + if (res) + goto release_todo_list; + } + if (index_differs_from("HEAD", 0, 0)) { + res = error_dirty_index(opts); goto release_todo_list; + } + todo_list.current++; } - if (index_differs_from("HEAD", 0, 0)) { - res = error_dirty_index(opts); - goto release_todo_list; - } - todo_list.current++; + res = pick_commits(&todo_list, opts); release_todo_list: todo_list_release(&todo_list); From 70f4f94a475035bcc685424804ac0376995205e1 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:11:54 +0200 Subject: [PATCH 16/38] sequencer (rebase -i): the todo can be empty when continuing When the last command of an interactive rebase fails, the user needs to resolve the problem and then continue the interactive rebase. Naturally, the todo script is empty by then. So let's not complain about that! To that end, let's move that test out of the function that parses the todo script, and into the more high-level function read_populate_todo(). This is also necessary by now because the lower-level parse_insn_buffer() has no idea whether we are performing an interactive rebase or not. Signed-off-by: Johannes Schindelin --- sequencer.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index 99051ae19b..5b03e17221 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1262,8 +1262,7 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) else if (!is_noop(item->command)) fixup_okay = 1; } - if (!todo_list->nr) - return error(_("no commits parsed.")); + return res; } @@ -1287,6 +1286,10 @@ static int read_populate_todo(struct todo_list *todo_list, if (res) return error(_("unusable instruction sheet: '%s'"), todo_file); + if (!todo_list->nr && + (!is_rebase_i(opts) || !file_exists(rebase_path_done()))) + return error(_("no commits parsed.")); + if (!is_rebase_i(opts)) { enum todo_command valid = opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT; From e5b755779598314d08492858f4678ce7717cd577 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:12:54 +0200 Subject: [PATCH 17/38] sequencer (rebase -i): update refs after a successful rebase An interactive rebase operates on a detached HEAD (to keep the reflog of the original branch relatively clean), and updates the branch only at the end. Now that the sequencer learns to perform interactive rebases, it also needs to learn the trick to update the branch before removing the directory containing the state of the interactive rebase. We introduce a new head_ref variable in a wider scope than necessary at the moment, to allow for a later patch that prints out "Successfully rebased and updated ". Signed-off-by: Johannes Schindelin --- sequencer.c | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index 5b03e17221..6198d4b9e7 100644 --- a/sequencer.c +++ b/sequencer.c @@ -101,6 +101,8 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head") static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") +static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name") +static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto") static inline int is_rebase_i(const struct replay_opts *opts) { @@ -1831,12 +1833,53 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) } if (is_rebase_i(opts)) { - struct strbuf buf = STRBUF_INIT; + struct strbuf head_ref = STRBUF_INIT, buf = STRBUF_INIT; /* Stopped in the middle, as planned? */ if (todo_list->current < todo_list->nr) return 0; + if (read_oneliner(&head_ref, rebase_path_head_name(), 0) && + starts_with(head_ref.buf, "refs/")) { + unsigned char head[20], orig[20]; + int res; + + if (get_sha1("HEAD", head)) { + res = error(_("cannot read HEAD")); +cleanup_head_ref: + strbuf_release(&head_ref); + strbuf_release(&buf); + return res; + } + if (!read_oneliner(&buf, rebase_path_orig_head(), 0) || + get_sha1_hex(buf.buf, orig)) { + res = error(_("could not read orig-head")); + goto cleanup_head_ref; + } + strbuf_addf(&buf, "rebase -i (finish): %s onto ", + head_ref.buf); + if (!read_oneliner(&buf, rebase_path_onto(), 0)) { + res = error(_("could not read 'onto'")); + goto cleanup_head_ref; + } + if (update_ref(buf.buf, head_ref.buf, head, orig, + REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) { + res = error(_("could not update %s"), + head_ref.buf); + goto cleanup_head_ref; + } + strbuf_reset(&buf); + strbuf_addf(&buf, + "rebase -i (finish): returning to %s", + head_ref.buf); + if (create_symref("HEAD", head_ref.buf, buf.buf)) { + res = error(_("could not update HEAD to %s"), + head_ref.buf); + goto cleanup_head_ref; + } + strbuf_reset(&buf); + } + if (opts->verbose) { struct rev_info log_tree_opt; struct object_id orig, head; @@ -1857,6 +1900,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) } } strbuf_release(&buf); + strbuf_release(&head_ref); } /* From 9a42bdbe091f5bcd21485fa48634361333e5502a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 10 Mar 2016 21:47:32 +0100 Subject: [PATCH 18/38] sequencer (rebase -i): leave a patch upon error When doing an interactive rebase, we want to leave a 'patch' file for further inspection by the user (even if we never tried to actually apply that patch, since we're cherry-picking instead). Signed-off-by: Johannes Schindelin --- sequencer.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index 6198d4b9e7..b6c288fa4d 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1816,7 +1816,9 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) intend_to_amend(); return error_failed_squash(item->commit, opts, item->arg_len, item->arg); - } + } else if (res && is_rebase_i(opts)) + return res | error_with_patch(item->commit, + item->arg, item->arg_len, opts, res, 0); } else if (item->command == TODO_EXEC) { char *end_of_arg = (char *)(item->arg + item->arg_len); int saved = *end_of_arg; From fd49b01ab7be0236efb8c61624c0d132a6a1bd74 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:13:31 +0200 Subject: [PATCH 19/38] sequencer (rebase -i): implement the 'reword' command This is now trivial, as all the building blocks are in place: all we need to do is to flip the "edit" switch when committing. Signed-off-by: Johannes Schindelin --- sequencer.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index b6c288fa4d..20fe6e9816 100644 --- a/sequencer.c +++ b/sequencer.c @@ -765,6 +765,7 @@ enum todo_command { TODO_PICK = 0, TODO_REVERT, TODO_EDIT, + TODO_REWORD, TODO_FIXUP, TODO_SQUASH, /* commands that do something else than handling a single commit */ @@ -780,6 +781,7 @@ static struct { { 'p', "pick" }, { 0, "revert" }, { 'e', "edit" }, + { 'r', "reword" }, { 'f', "fixup" }, { 's', "squash" }, { 'x', "exec" }, @@ -1009,7 +1011,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } } - if (is_fixup(command)) { + if (command == TODO_REWORD) + edit = 1; + else if (is_fixup(command)) { if (update_squash_messages(command, commit, opts)) return -1; amend = 1; @@ -1818,7 +1822,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) item->arg_len, item->arg); } else if (res && is_rebase_i(opts)) return res | error_with_patch(item->commit, - item->arg, item->arg_len, opts, res, 0); + item->arg, item->arg_len, opts, res, + item->command == TODO_REWORD); } else if (item->command == TODO_EXEC) { char *end_of_arg = (char *)(item->arg + item->arg_len); int saved = *end_of_arg; From 22ffad0f52a4e28e9120b93a6d0d0012858436a4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 23 May 2016 16:56:21 +0200 Subject: [PATCH 20/38] sequencer (rebase -i): allow fast-forwarding for edit/reword The sequencer already knew how to fast-forward instead of cherry-picking, if possible. We want to continue to do this, of course, but in case of the 'reword' command, we will need to call `git commit` after fast-forwarding. Signed-off-by: Johannes Schindelin --- sequencer.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/sequencer.c b/sequencer.c index 20fe6e9816..daa07fa1d9 100644 --- a/sequencer.c +++ b/sequencer.c @@ -907,7 +907,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, const char *base_label, *next_label; struct commit_message msg = { NULL, NULL, NULL, NULL }; struct strbuf msgbuf = STRBUF_INIT; - int res, unborn = 0, amend = 0, allow; + int res, unborn = 0, amend = 0, allow = 0; if (opts->no_commit) { /* @@ -952,11 +952,23 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, else parent = commit->parents->item; + if (get_message(commit, &msg) != 0) + return error(_("cannot get commit message for %s"), + oid_to_hex(&commit->object.oid)); + if (opts->allow_ff && !is_fixup(command) && ((parent && !hashcmp(parent->object.oid.hash, head)) || - (!parent && unborn))) - return fast_forward_to(commit->object.oid.hash, head, unborn, opts); - + (!parent && unborn))) { + if (is_rebase_i(opts)) + write_author_script(msg.message); + res = fast_forward_to(commit->object.oid.hash, head, unborn, + opts); + if (res || command != TODO_REWORD) + goto leave; + edit = amend = 1; + msg_file = NULL; + goto fast_forward_edit; + } if (parent && parse_commit(parent) < 0) /* TRANSLATORS: The first %s will be a "todo" command like "revert" or "pick", the second %s a SHA1. */ @@ -964,10 +976,6 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, command_to_string(command), oid_to_hex(&parent->object.oid)); - if (get_message(commit, &msg) != 0) - return error(_("cannot get commit message for %s"), - oid_to_hex(&commit->object.oid)); - /* * "commit" is an existing commit. We would want to apply * the difference it introduces since its first parent "prev" @@ -1091,6 +1099,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, goto leave; } if (!opts->no_commit) +fast_forward_edit: res = run_git_commit(msg_file, opts, allow, edit, amend, cleanup_commit_message); From fed9f8d98e874e55c0757ca286d27b6e43a841d5 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 9 Apr 2016 16:27:51 +0200 Subject: [PATCH 21/38] sequencer (rebase -i): refactor setting the reflog message This makes the code DRYer, with the obvious benefit that we can enhance the code further in a single place. We can also reuse the functionality elsewhere by calling this new function. Signed-off-by: Johannes Schindelin --- sequencer.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/sequencer.c b/sequencer.c index daa07fa1d9..6f33652302 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1790,6 +1790,26 @@ static int is_final_fixup(struct todo_list *todo_list) return 1; } +static const char *reflog_message(struct replay_opts *opts, + const char *sub_action, const char *fmt, ...) +{ + va_list ap; + static struct strbuf buf = STRBUF_INIT; + + va_start(ap, fmt); + strbuf_reset(&buf); + strbuf_addstr(&buf, action_name(opts)); + if (sub_action) + strbuf_addf(&buf, " (%s)", sub_action); + if (fmt) { + strbuf_addstr(&buf, ": "); + strbuf_vaddf(&buf, fmt, ap); + } + va_end(ap); + + return buf.buf; +} + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { int res = 0; @@ -1857,6 +1877,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (read_oneliner(&head_ref, rebase_path_head_name(), 0) && starts_with(head_ref.buf, "refs/")) { + const char *msg; unsigned char head[20], orig[20]; int res; @@ -1872,23 +1893,21 @@ cleanup_head_ref: res = error(_("could not read orig-head")); goto cleanup_head_ref; } - strbuf_addf(&buf, "rebase -i (finish): %s onto ", - head_ref.buf); if (!read_oneliner(&buf, rebase_path_onto(), 0)) { res = error(_("could not read 'onto'")); goto cleanup_head_ref; } - if (update_ref(buf.buf, head_ref.buf, head, orig, + msg = reflog_message(opts, "finish", "%s onto %s", + head_ref.buf, buf.buf); + if (update_ref(msg, head_ref.buf, head, orig, REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) { res = error(_("could not update %s"), head_ref.buf); goto cleanup_head_ref; } - strbuf_reset(&buf); - strbuf_addf(&buf, - "rebase -i (finish): returning to %s", + msg = reflog_message(opts, "finish", "returning to %s", head_ref.buf); - if (create_symref("HEAD", head_ref.buf, buf.buf)) { + if (create_symref("HEAD", head_ref.buf, msg)) { res = error(_("could not update HEAD to %s"), head_ref.buf); goto cleanup_head_ref; From fa48acc1f49d132b34c7cb0ea9d346a6937faec7 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 9 Apr 2016 16:28:07 +0200 Subject: [PATCH 22/38] sequencer (rebase -i): set the reflog message consistently We already used the same reflog message as the scripted version of rebase -i when finishing. With this commit, we do that also for all the commands before that. Signed-off-by: Johannes Schindelin --- sequencer.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sequencer.c b/sequencer.c index 6f33652302..e6319bb9eb 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1832,6 +1832,10 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) unlink(rebase_path_amend()); } if (item->command <= TODO_SQUASH) { + if (is_rebase_i(opts)) + setenv("GIT_REFLOG_ACTION", reflog_message(opts, + command_to_string(item->command), NULL), + 1); res = do_pick_commit(item->command, item->commit, opts, is_final_fixup(todo_list)); if (item->command == TODO_EDIT) { From 2993afa641a79da0d5952e797d37eeea4aaa4cf5 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 12 Apr 2016 17:26:31 +0200 Subject: [PATCH 23/38] sequencer (rebase -i): copy commit notes at end When rebasing commits that have commit notes attached, the interactive rebase rewrites those notes faithfully at the end. The sequencer must do this, too, if it wishes to do interactive rebase's job. Signed-off-by: Johannes Schindelin --- sequencer.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/sequencer.c b/sequencer.c index e6319bb9eb..a3c2a31195 100644 --- a/sequencer.c +++ b/sequencer.c @@ -94,6 +94,15 @@ static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend") * the abbreviated commit name of the corresponding patch. */ static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") +/* + * For the post-rewrite hook, we make a list of rewritten commits and + * their new sha1s. The rewritten-pending list keeps the sha1s of + * commits that have been processed, but not committed yet, + * e.g. because they are waiting for a 'squash' command. + */ +static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list") +static GIT_PATH_FUNC(rebase_path_rewritten_pending, + "rebase-merge/rewritten-pending") /* * The following files are written by git-rebase just after parsing the * command-line (and are only consumed, not modified, by the sequencer). @@ -897,6 +906,44 @@ static int update_squash_messages(enum todo_command command, return res; } +static void flush_rewritten_pending(void) { + struct strbuf buf = STRBUF_INIT; + unsigned char newsha1[20]; + FILE *out; + + if (strbuf_read_file(&buf, rebase_path_rewritten_pending(), 82) > 0 && + !get_sha1("HEAD", newsha1) && + (out = fopen(rebase_path_rewritten_list(), "a"))) { + char *bol = buf.buf, *eol; + + while (*bol) { + eol = strchrnul(bol, '\n'); + fprintf(out, "%.*s %s\n", (int)(eol - bol), + bol, sha1_to_hex(newsha1)); + if (!*eol) + break; + bol = eol + 1; + } + fclose(out); + unlink(rebase_path_rewritten_pending()); + } + strbuf_release(&buf); +} + +static void record_in_rewritten(struct object_id *oid, + enum todo_command next_command) { + FILE *out = fopen(rebase_path_rewritten_pending(), "a"); + + if (!out) + return; + + fprintf(out, "%s\n", oid_to_hex(oid)); + fclose(out); + + if (!is_fixup(next_command)) + flush_rewritten_pending(); +} + static int do_pick_commit(enum todo_command command, struct commit *commit, struct replay_opts *opts, int final_fixup) { @@ -1790,6 +1837,17 @@ static int is_final_fixup(struct todo_list *todo_list) return 1; } +static enum todo_command peek_command(struct todo_list *todo_list, int offset) +{ + int i; + + for (i = todo_list->current + offset; i < todo_list->nr; i++) + if (!is_noop(todo_list->items[i].command)) + return todo_list->items[i].command; + + return -1; +} + static const char *reflog_message(struct replay_opts *opts, const char *sub_action, const char *fmt, ...) { @@ -1848,6 +1906,9 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) item->arg, item->arg_len, opts, res, !res); } + if (is_rebase_i(opts) && !res) + record_in_rewritten(&item->commit->object.oid, + peek_command(todo_list, 1)); if (res && is_fixup(item->command)) { if (res == 1) intend_to_amend(); @@ -1874,6 +1935,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (is_rebase_i(opts)) { struct strbuf head_ref = STRBUF_INIT, buf = STRBUF_INIT; + struct stat st; /* Stopped in the middle, as planned? */ if (todo_list->current < todo_list->nr) @@ -1938,6 +2000,20 @@ cleanup_head_ref: log_tree_diff_flush(&log_tree_opt); } } + flush_rewritten_pending(); + if (!stat(rebase_path_rewritten_list(), &st) && + st.st_size > 0) { + struct child_process child = CHILD_PROCESS_INIT; + + child.in = open(rebase_path_rewritten_list(), O_RDONLY); + child.git_cmd = 1; + argv_array_push(&child.args, "notes"); + argv_array_push(&child.args, "copy"); + argv_array_push(&child.args, "--for-rewrite=rebase"); + /* we don't care if this copying failed */ + run_command(&child); + } + strbuf_release(&buf); strbuf_release(&head_ref); } From e2f04b04ebf6b2376cc641e197da4d19175a163d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 17:30:25 +0200 Subject: [PATCH 24/38] sequencer (rebase -i): record interrupted commits in rewritten, too When continuing after a `pick` command failed, we want that commit to show up in the rewritten-list (and its notes to be rewritten), too. Signed-off-by: Johannes Schindelin --- sequencer.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sequencer.c b/sequencer.c index a3c2a31195..b6ce2a19fe 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2107,6 +2107,14 @@ int sequencer_continue(struct replay_opts *opts) goto release_todo_list; } todo_list.current++; + } else if (file_exists(rebase_path_stopped_sha())) { + struct strbuf buf = STRBUF_INIT; + struct object_id oid; + + if (read_oneliner(&buf, rebase_path_stopped_sha(), 1) && + !get_sha1_committish(buf.buf, oid.hash)) + record_in_rewritten(&oid, peek_command(&todo_list, 0)); + strbuf_release(&buf); } res = pick_commits(&todo_list, opts); From e401524255787c09afb37a97bcb169a0b9a57430 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 17:27:03 +0200 Subject: [PATCH 25/38] sequencer (rebase -i): run the post-rewrite hook, if needed Signed-off-by: Johannes Schindelin --- sequencer.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sequencer.c b/sequencer.c index b6ce2a19fe..5bc8a86adb 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2004,6 +2004,8 @@ cleanup_head_ref: if (!stat(rebase_path_rewritten_list(), &st) && st.st_size > 0) { struct child_process child = CHILD_PROCESS_INIT; + const char *post_rewrite_hook = + find_hook("post-rewrite"); child.in = open(rebase_path_rewritten_list(), O_RDONLY); child.git_cmd = 1; @@ -2012,6 +2014,18 @@ cleanup_head_ref: argv_array_push(&child.args, "--for-rewrite=rebase"); /* we don't care if this copying failed */ run_command(&child); + + if (post_rewrite_hook) { + struct child_process hook = CHILD_PROCESS_INIT; + + hook.in = open(rebase_path_rewritten_list(), + O_RDONLY); + hook.stdout_to_stderr = 1; + argv_array_push(&hook.args, post_rewrite_hook); + argv_array_push(&hook.args, "rebase"); + /* we don't care if this hook failed */ + run_command(&hook); + } } strbuf_release(&buf); From 3788efacde33a2aa38be1fb1592439c22a959ea6 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 16:05:06 +0200 Subject: [PATCH 26/38] sequencer (rebase -i): respect the rebase.autostash setting Git's `rebase` command inspects the `rebase.autostash` config setting to determine whether it should stash any uncommitted changes before rebasing and re-apply them afterwards. As we introduce more bits and pieces to let the sequencer act as interactive rebase's backend, here is the part that adds support for the autostash feature. Signed-off-by: Johannes Schindelin --- sequencer.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/sequencer.c b/sequencer.c index 5bc8a86adb..a454ed8960 100644 --- a/sequencer.c +++ b/sequencer.c @@ -112,6 +112,7 @@ static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head") static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name") static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto") +static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash") static inline int is_rebase_i(const struct replay_opts *opts) { @@ -1848,6 +1849,47 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset) return -1; } +static int apply_autostash(struct replay_opts *opts) +{ + struct strbuf stash_sha1 = STRBUF_INIT; + struct child_process child = CHILD_PROCESS_INIT; + int ret = 0; + + if (!read_oneliner(&stash_sha1, rebase_path_autostash(), 1)) { + strbuf_release(&stash_sha1); + return 0; + } + strbuf_trim(&stash_sha1); + + child.git_cmd = 1; + argv_array_push(&child.args, "stash"); + argv_array_push(&child.args, "apply"); + argv_array_push(&child.args, stash_sha1.buf); + if (!run_command(&child)) + printf(_("Applied autostash.")); + else { + struct child_process store = CHILD_PROCESS_INIT; + + store.git_cmd = 1; + argv_array_push(&store.args, "stash"); + argv_array_push(&store.args, "store"); + argv_array_push(&store.args, "-m"); + argv_array_push(&store.args, "autostash"); + argv_array_push(&store.args, "-q"); + argv_array_push(&store.args, stash_sha1.buf); + if (run_command(&store)) + ret = error(_("cannot store %s"), stash_sha1.buf); + else + printf(_("Applying autostash resulted in conflicts.\n" + "Your changes are safe in the stash.\n" + "You can run \"git stash pop\" or" + " \"git stash drop\" at any time.\n")); + } + + strbuf_release(&stash_sha1); + return ret; +} + static const char *reflog_message(struct replay_opts *opts, const char *sub_action, const char *fmt, ...) { @@ -2027,6 +2069,7 @@ cleanup_head_ref: run_command(&hook); } } + apply_autostash(opts); strbuf_release(&buf); strbuf_release(&head_ref); From ebff10548ad49e0af275ad78bd8344a8f1c17703 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 30 Mar 2016 17:58:55 +0200 Subject: [PATCH 27/38] sequencer (rebase -i): respect strategy/strategy_opts settings The sequencer already has an idea about using different merge strategies. We just piggy-back on top of that, using rebase -i's own settings, when running the sequencer in interactive rebase mode. Signed-off-by: Johannes Schindelin --- sequencer.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index a454ed8960..3d695ba6ba 100644 --- a/sequencer.c +++ b/sequencer.c @@ -113,6 +113,8 @@ static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name") static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto") static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash") +static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy") +static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts") static inline int is_rebase_i(const struct replay_opts *opts) { @@ -1415,6 +1417,26 @@ static int populate_opts_cb(const char *key, const char *value, void *data) return 0; } +static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf) +{ + int i; + + strbuf_reset(buf); + if (!read_oneliner(buf, rebase_path_strategy(), 0)) + return; + opts->strategy = strbuf_detach(buf, NULL); + if (!read_oneliner(buf, rebase_path_strategy_opts(), 0)) + return; + + opts->xopts_nr = split_cmdline(buf->buf, (const char ***)&opts->xopts); + for (i = 0; i < opts->xopts_nr; i++) { + const char *arg = opts->xopts[i]; + + skip_prefix(arg, "--", &arg); + opts->xopts[i] = xstrdup(arg); + } +} + static int read_populate_opts(struct replay_opts *opts) { if (is_rebase_i(opts)) { @@ -1428,11 +1450,13 @@ static int read_populate_opts(struct replay_opts *opts) opts->gpg_sign = xstrdup(buf.buf + 2); } } - strbuf_release(&buf); if (file_exists(rebase_path_verbose())) opts->verbose = 1; + read_strategy_opts(opts, &buf); + strbuf_release(&buf); + return 0; } From aa6e92ef6f5983d14e5c947cc90cd80616ac0239 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 7 Apr 2016 16:31:59 +0200 Subject: [PATCH 28/38] sequencer (rebase -i): allow rescheduling commands The interactive rebase has the very special magic that a cherry-pick that exits with a status different from 0 and 1 signifies a failure to even record that a cherry-pick was started. This can happen e.g. when a fast-forward fails because it would overwrite untracked files. In that case, we must reschedule the command that we thought we already had at least started successfully. Signed-off-by: Johannes Schindelin --- sequencer.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sequencer.c b/sequencer.c index 3d695ba6ba..3ec7f9a3bc 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1962,6 +1962,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) 1); res = do_pick_commit(item->command, item->commit, opts, is_final_fixup(todo_list)); + if (is_rebase_i(opts) && res < 0) { + /* Reschedule */ + todo_list->current--; + if (save_todo(todo_list, opts)) + return -1; + } if (item->command == TODO_EDIT) { struct commit *commit = item->commit; if (!res) From 74d7ecdd1afb36e022f91674b63ecc7e32c11167 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 15:18:29 +0200 Subject: [PATCH 29/38] sequencer (rebase -i): implement the 'drop' command The parsing part of a 'drop' command is almost identical to parsing a 'pick', while the operation is the same as that of a 'noop'. Signed-off-by: Johannes Schindelin --- sequencer.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index 3ec7f9a3bc..0bad984dfc 100644 --- a/sequencer.c +++ b/sequencer.c @@ -783,7 +783,8 @@ enum todo_command { /* commands that do something else than handling a single commit */ TODO_EXEC, /* commands that do nothing but are counted for reporting progress */ - TODO_NOOP + TODO_NOOP, + TODO_DROP }; static struct { @@ -797,7 +798,8 @@ static struct { { 'f', "fixup" }, { 's', "squash" }, { 'x', "exec" }, - { 0, "noop" } + { 0, "noop" }, + { 'd', "drop" } }; static const char *command_to_string(const enum todo_command command) @@ -809,7 +811,7 @@ static const char *command_to_string(const enum todo_command command) static int is_noop(const enum todo_command command) { - return TODO_NOOP <= (size_t)command; + return TODO_NOOP <= command; } static int is_fixup(enum todo_command command) From 82f1238ab94b4c6a623864c9bc669c968edc015f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 08:45:17 +0200 Subject: [PATCH 30/38] sequencer (rebase -i): differentiate between comments and 'noop' In the upcoming patch, we will support rebase -i's progress reporting. The progress skips comments but counts 'noop's. Signed-off-by: Johannes Schindelin --- sequencer.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/sequencer.c b/sequencer.c index 0bad984dfc..18d7e26d90 100644 --- a/sequencer.c +++ b/sequencer.c @@ -784,7 +784,9 @@ enum todo_command { TODO_EXEC, /* commands that do nothing but are counted for reporting progress */ TODO_NOOP, - TODO_DROP + TODO_DROP, + /* comments (not counted for reporting progress) */ + TODO_COMMENT }; static struct { @@ -799,12 +801,13 @@ static struct { { 's', "squash" }, { 'x', "exec" }, { 0, "noop" }, - { 'd', "drop" } + { 'd', "drop" }, + { 0, NULL } }; static const char *command_to_string(const enum todo_command command) { - if ((size_t)command < ARRAY_SIZE(todo_command_info)) + if (command < TODO_COMMENT) return todo_command_info[command].str; die("Unknown command: %d", command); } @@ -1245,14 +1248,14 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) bol += strspn(bol, " \t"); if (bol == eol || *bol == '\r' || *bol == comment_line_char) { - item->command = TODO_NOOP; + item->command = TODO_COMMENT; item->commit = NULL; item->arg = bol; item->arg_len = eol - bol; return 0; } - for (i = 0; i < ARRAY_SIZE(todo_command_info); i++) + for (i = 0; i < TODO_COMMENT; i++) if (skip_prefix(bol, todo_command_info[i].str, &bol)) { item->command = i; break; @@ -1261,7 +1264,7 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) item->command = i; break; } - if (i >= ARRAY_SIZE(todo_command_info)) + if (i >= TODO_COMMENT) return -1; if (item->command == TODO_NOOP) { From 99023f00af2cd90848489bf0d1c7d700e9b525ed Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 14:55:10 +0100 Subject: [PATCH 31/38] sequencer: make reading author-script more elegant Rather than abusing a strbuf to come up with an environment block, let's just use the argv_array structure which serves the same purpose much better. While at it, rename the function to reflect the fact that it does not really care exactly what environment variables are defined in said file. Suggested-by: Jeff King Signed-off-by: Johannes Schindelin --- sequencer.c | 34 ++++++++++++---------------------- t/t3404-rebase-interactive.sh | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/sequencer.c b/sequencer.c index 18d7e26d90..b8efd33f01 100644 --- a/sequencer.c +++ b/sequencer.c @@ -591,18 +591,17 @@ missing_author: } /* - * Read the author-script file into an environment block, ready for use in - * run_command(), that can be free()d afterwards. + * Read a list of environment variable assignments (such as the author-script + * file) into an environment block. Returns -1 on error, 0 otherwise. */ -static char **read_author_script(void) +static int read_env_script(struct argv_array *env) { struct strbuf script = STRBUF_INIT; int i, count = 0; - char *p, *p2, **env; - size_t env_size; + char *p, *p2; if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0) - return NULL; + return -1; for (p = script.buf; *p; p++) if (skip_prefix(p, "'\\\\''", (const char **)&p2)) @@ -614,19 +613,12 @@ static char **read_author_script(void) count++; } - env_size = (count + 1) * sizeof(*env); - strbuf_grow(&script, env_size); - memmove(script.buf + env_size, script.buf, script.len); - p = script.buf + env_size; - env = (char **)strbuf_detach(&script, NULL); - - for (i = 0; i < count; i++) { - env[i] = p; + for (i = 0, p = script.buf; i < count; i++) { + argv_array_push(env, p); p += strlen(p) + 1; } - env[count] = NULL; - return env; + return 0; } static const char staged_changes_advice[] = @@ -659,14 +651,12 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, int allow_empty, int edit, int amend, int cleanup_commit_message) { - char **env = NULL; - struct argv_array array; + struct argv_array env = ARGV_ARRAY_INIT, array; int rc; const char *value; if (is_rebase_i(opts)) { - env = read_author_script(); - if (!env) { + if (!read_env_script(&env)) { const char *gpg_opt = gpg_sign_opt_quoted(opts); return error(_(staged_changes_advice), @@ -702,9 +692,9 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, argv_array_push(&array, "--allow-empty-message"); rc = run_command_v_opt_cd_env(array.argv, RUN_GIT_CMD, NULL, - (const char *const *)env); + (const char *const *)env.argv); argv_array_clear(&array); - free(env); + argv_array_clear(&env); return rc; } diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index c896a4c106..e2f18d11f6 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -237,6 +237,22 @@ test_expect_success 'retain authorship' ' git show HEAD | grep "^Author: Twerp Snog" ' +test_expect_success 'retain authorship w/ conflicts' ' + git reset --hard twerp && + test_commit a conflict a conflict-a && + git reset --hard twerp && + GIT_AUTHOR_NAME=AttributeMe \ + test_commit b conflict b conflict-b && + set_fake_editor && + test_must_fail git rebase -i conflict-a && + echo resolved >conflict && + git add conflict && + git rebase --continue && + test $(git rev-parse conflict-a^0) = $(git rev-parse HEAD^) && + git show >out && + grep AttributeMe out +' + test_expect_success 'squash' ' git reset --hard twerp && echo B > file7 && From 1d92e012bc6b97e3c4419b1c1a9540c0540e1fd7 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 2 Jan 2017 15:23:42 +0100 Subject: [PATCH 32/38] sequencer: use run_command() directly Instead of using the convenience function run_command_v_opt_cd_env(), we now use the run_command() function. The former function is simply a wrapper of the latter, trying to make it more convenient to use. However, we already have to construct the argv and the env parameters, and we will need even finer control e.g. over the output of the command, so let's just stop using the convenience function. Based on patches and suggestions by Johannes Sixt and Jeff King. Signed-off-by: Johannes Schindelin --- sequencer.c | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/sequencer.c b/sequencer.c index b8efd33f01..4008134333 100644 --- a/sequencer.c +++ b/sequencer.c @@ -651,12 +651,13 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, int allow_empty, int edit, int amend, int cleanup_commit_message) { - struct argv_array env = ARGV_ARRAY_INIT, array; - int rc; + struct child_process cmd = CHILD_PROCESS_INIT; const char *value; + cmd.git_cmd = 1; + if (is_rebase_i(opts)) { - if (!read_env_script(&env)) { + if (read_env_script(&cmd.env_array)) { const char *gpg_opt = gpg_sign_opt_quoted(opts); return error(_(staged_changes_advice), @@ -664,39 +665,34 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, } } - argv_array_init(&array); - argv_array_push(&array, "commit"); - argv_array_push(&array, "-n"); + argv_array_push(&cmd.args, "commit"); + argv_array_push(&cmd.args, "-n"); if (amend) - argv_array_push(&array, "--amend"); + argv_array_push(&cmd.args, "--amend"); if (opts->gpg_sign) - argv_array_pushf(&array, "-S%s", opts->gpg_sign); + argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign); if (opts->signoff) - argv_array_push(&array, "-s"); + argv_array_push(&cmd.args, "-s"); if (defmsg) - argv_array_pushl(&array, "-F", defmsg, NULL); + argv_array_pushl(&cmd.args, "-F", defmsg, NULL); if (cleanup_commit_message) - argv_array_push(&array, "--cleanup=strip"); + argv_array_push(&cmd.args, "--cleanup=strip"); if (edit) - argv_array_push(&array, "-e"); + argv_array_push(&cmd.args, "-e"); else if (!cleanup_commit_message && !opts->signoff && !opts->record_origin && git_config_get_value("commit.cleanup", &value)) - argv_array_push(&array, "--cleanup=verbatim"); + argv_array_push(&cmd.args, "--cleanup=verbatim"); if (allow_empty) - argv_array_push(&array, "--allow-empty"); + argv_array_push(&cmd.args, "--allow-empty"); if (opts->allow_empty_message) - argv_array_push(&array, "--allow-empty-message"); + argv_array_push(&cmd.args, "--allow-empty-message"); - rc = run_command_v_opt_cd_env(array.argv, RUN_GIT_CMD, NULL, - (const char *const *)env.argv); - argv_array_clear(&array); - argv_array_clear(&env); - return rc; + return run_command(&cmd); } static int is_original_commit_empty(struct commit *commit) From bf6af8b553fac19ab09d92e84bf819e99a372be5 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 12 Apr 2016 14:56:52 +0200 Subject: [PATCH 33/38] sequencer (rebase -i): show only failed `git commit`'s output This is the behavior of the shell script version of the interactive rebase, by using the `output` function defined in `git-rebase.sh`. Signed-off-by: Johannes Schindelin --- sequencer.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/sequencer.c b/sequencer.c index 4008134333..6f9b6cc398 100644 --- a/sequencer.c +++ b/sequencer.c @@ -657,6 +657,11 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, cmd.git_cmd = 1; if (is_rebase_i(opts)) { + if (!edit) { + cmd.stdout_to_stderr = 1; + cmd.err = -1; + } + if (read_env_script(&cmd.env_array)) { const char *gpg_opt = gpg_sign_opt_quoted(opts); @@ -691,6 +696,19 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, if (opts->allow_empty_message) argv_array_push(&cmd.args, "--allow-empty-message"); + if (cmd.err == -1) { + /* hide stderr on success */ + struct strbuf buf = STRBUF_INIT; + int rc = pipe_command(&cmd, + NULL, 0, + /* stdout is already redirected */ + NULL, 0, + &buf, 0); + if (rc) + fputs(buf.buf, stderr); + strbuf_release(&buf); + return rc; + } return run_command(&cmd); } From 628e35c8e2db9ad4464653eef16435bcd955e936 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 12 Apr 2016 14:56:52 +0200 Subject: [PATCH 34/38] sequencer (rebase -i): show only failed cherry-picks' output This is the behavior of the shell script version of the interactive rebase, by using the `output` function defined in `git-rebase.sh`. Signed-off-by: Johannes Schindelin --- sequencer.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sequencer.c b/sequencer.c index 6f9b6cc398..364b5a28a9 100644 --- a/sequencer.c +++ b/sequencer.c @@ -480,6 +480,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next, o.ancestor = base ? base_label : "(empty tree)"; o.branch1 = "HEAD"; o.branch2 = next ? next_label : "(empty tree)"; + if (is_rebase_i(opts)) + o.buffer_output = 2; head_tree = parse_tree_indirect(head); next_tree = next ? next->tree : empty_tree(); @@ -491,6 +493,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next, clean = merge_trees(&o, head_tree, next_tree, base_tree, &result); + if (is_rebase_i(opts) && clean <= 0) + fputs(o.obuf.buf, stdout); strbuf_release(&o.obuf); if (clean < 0) return clean; From 7ab36ddf0c67ae641ace59fc9441233d621a0741 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 21 Apr 2016 12:51:50 +0200 Subject: [PATCH 35/38] sequencer (rebase -i): suggest --edit-todo upon unknown command This is the same behavior as known from `git rebase -i`. Signed-off-by: Johannes Schindelin --- sequencer.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index 364b5a28a9..86282cee4b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1361,8 +1361,12 @@ static int read_populate_todo(struct todo_list *todo_list, close(fd); res = parse_insn_buffer(todo_list->buf.buf, todo_list); - if (res) + if (res) { + if (is_rebase_i(opts)) + return error(_("please fix this using " + "'git rebase --edit-todo'.")); return error(_("unusable instruction sheet: '%s'"), todo_file); + } if (!todo_list->nr && (!is_rebase_i(opts) || !file_exists(rebase_path_done()))) From 84de0026c96e5d663297f1c3effccfb5f9497a75 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 15:51:19 +0200 Subject: [PATCH 36/38] sequencer (rebase -i): show the progress The interactive rebase keeps the user informed about its progress. If the sequencer wants to do the grunt work of the interactive rebase, it also needs to show that progress. Signed-off-by: Johannes Schindelin --- sequencer.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/sequencer.c b/sequencer.c index 86282cee4b..3b6ea5b2fd 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1228,6 +1228,7 @@ struct todo_list { struct strbuf buf; struct todo_item *items; int nr, alloc, current; + int done_nr, total_nr; }; #define TODO_LIST_INIT { STRBUF_INIT } @@ -1344,6 +1345,17 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) return res; } +static int count_commands(struct todo_list *todo_list) +{ + int count = 0, i; + + for (i = 0; i < todo_list->nr; i++) + if (todo_list->items[i].command != TODO_COMMENT) + count++; + + return count; +} + static int read_populate_todo(struct todo_list *todo_list, struct replay_opts *opts) { @@ -1386,6 +1398,21 @@ static int read_populate_todo(struct todo_list *todo_list, return error(_("cannot revert during a cherry-pick.")); } + if (is_rebase_i(opts)) { + struct todo_list done = TODO_LIST_INIT; + + if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 && + !parse_insn_buffer(done.buf.buf, &done)) + todo_list->done_nr = count_commands(&done); + else + todo_list->done_nr = 0; + + todo_list->total_nr = todo_list->done_nr + + count_commands(todo_list); + + todo_list_release(&done); + } + return 0; } @@ -1967,6 +1994,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (save_todo(todo_list, opts)) return -1; if (is_rebase_i(opts)) { + if (item->command != TODO_COMMENT) + fprintf(stderr, "Rebasing (%d/%d)%s", + ++(todo_list->done_nr), + todo_list->total_nr, + opts->verbose ? "\n" : "\r"); unlink(rebase_path_message()); unlink(rebase_path_author_script()); unlink(rebase_path_stopped_sha()); From 516d63f33565e58b8b512b7bae0ee5c77fa33a56 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 16:40:13 +0200 Subject: [PATCH 37/38] sequencer (rebase -i): write the progress into files For the benefit of e.g. the shell prompt, the interactive rebase not only displays the progress for the user to see, but also writes it into the msgnum/end files in the state directory. Teach the sequencer this new trick. Signed-off-by: Johannes Schindelin --- sequencer.c | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index 3b6ea5b2fd..66b217a993 100644 --- a/sequencer.c +++ b/sequencer.c @@ -45,6 +45,16 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") * actions. */ static GIT_PATH_FUNC(rebase_path_done, "rebase-merge/done") +/* + * The file to keep track of how many commands were already processed (e.g. + * for the prompt). + */ +static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum"); +/* + * The file to keep track of how many commands are to be processed in total + * (e.g. for the prompt). + */ +static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end"); /* * The commit message that is planned to be used for any changes that * need to be committed following a user interaction. @@ -1400,6 +1410,7 @@ static int read_populate_todo(struct todo_list *todo_list, if (is_rebase_i(opts)) { struct todo_list done = TODO_LIST_INIT; + FILE *f = fopen(rebase_path_msgtotal(), "w"); if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 && !parse_insn_buffer(done.buf.buf, &done)) @@ -1409,8 +1420,12 @@ static int read_populate_todo(struct todo_list *todo_list, todo_list->total_nr = todo_list->done_nr + count_commands(todo_list); - todo_list_release(&done); + + if (f) { + fprintf(f, "%d\n", todo_list->total_nr); + fclose(f); + } } return 0; @@ -1994,11 +2009,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (save_todo(todo_list, opts)) return -1; if (is_rebase_i(opts)) { - if (item->command != TODO_COMMENT) + if (item->command != TODO_COMMENT) { + FILE *f = fopen(rebase_path_msgnum(), "w"); + + todo_list->done_nr++; + + if (f) { + fprintf(f, "%d\n", todo_list->done_nr); + fclose(f); + } fprintf(stderr, "Rebasing (%d/%d)%s", - ++(todo_list->done_nr), + todo_list->done_nr, todo_list->total_nr, opts->verbose ? "\n" : "\r"); + } unlink(rebase_path_message()); unlink(rebase_path_author_script()); unlink(rebase_path_stopped_sha()); From c6f666c98b73a70c663cf1ada7262d36bf3da398 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 17:28:04 +0200 Subject: [PATCH 38/38] sequencer (rebase -i): write out the final message The shell script version of the interactive rebase has a very specific final message. Teach the sequencer to print the same. Signed-off-by: Johannes Schindelin --- sequencer.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sequencer.c b/sequencer.c index 66b217a993..7aad134b05 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2174,6 +2174,9 @@ cleanup_head_ref: } apply_autostash(opts); + fprintf(stderr, "Successfully rebased and updated %s.\n", + head_ref.buf); + strbuf_release(&buf); strbuf_release(&head_ref); }