Merge branch 'ag/rebase-i-in-c'
Rewrite of the remaining "rebase -i" machinery in C. * ag/rebase-i-in-c: rebase -i: move rebase--helper modes to rebase--interactive rebase -i: remove git-rebase--interactive.sh rebase--interactive2: rewrite the submodes of interactive rebase in C rebase -i: implement the main part of interactive rebase as a builtin rebase -i: rewrite init_basic_state() in C rebase -i: rewrite write_basic_state() in C rebase -i: rewrite the rest of init_revisions_and_shortrevisions() in C rebase -i: implement the logic to initialize $revisions in C rebase -i: remove unused modes and functions rebase -i: rewrite complete_action() in C t3404: todo list with commented-out commands only aborts sequencer: change the way skip_unnecessary_picks() returns its result sequencer: refactor append_todo_help() to write its message to a buffer rebase -i: rewrite checkout_onto() in C rebase -i: rewrite setup_reflog_action() in C sequencer: add a new function to silence a command, except if it fails rebase -i: rewrite the edit-todo functionality in C editor: add a function to launch the sequence editor rebase -i: rewrite append_todo_help() in C sequencer: make three functions and an enum from sequencer.c public
This commit is contained in:
320
sequencer.c
320
sequencer.c
@ -31,6 +31,7 @@
|
||||
#include "commit-slab.h"
|
||||
#include "alias.h"
|
||||
#include "commit-reach.h"
|
||||
#include "rebase-interactive.h"
|
||||
|
||||
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
|
||||
|
||||
@ -53,7 +54,10 @@ static GIT_PATH_FUNC(rebase_path, "rebase-merge")
|
||||
* 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")
|
||||
GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")
|
||||
static GIT_PATH_FUNC(rebase_path_todo_backup,
|
||||
"rebase-merge/git-rebase-todo.backup")
|
||||
|
||||
/*
|
||||
* The rebase command lines that have already been processed. A line
|
||||
* is moved here when it is first handled, before any associated user
|
||||
@ -141,7 +145,7 @@ static GIT_PATH_FUNC(rebase_path_refs_to_delete, "rebase-merge/refs-to-delete")
|
||||
|
||||
/*
|
||||
* The following files are written by git-rebase just after parsing the
|
||||
* command-line (and are only consumed, not modified, by the sequencer).
|
||||
* command-line.
|
||||
*/
|
||||
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")
|
||||
@ -153,6 +157,7 @@ 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 GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
|
||||
static GIT_PATH_FUNC(rebase_path_quiet, "rebase-merge/quiet")
|
||||
|
||||
static int git_sequencer_config(const char *k, const char *v, void *cb)
|
||||
{
|
||||
@ -377,8 +382,8 @@ static void print_advice(int show_hint, struct replay_opts *opts)
|
||||
}
|
||||
}
|
||||
|
||||
static int write_message(const void *buf, size_t len, const char *filename,
|
||||
int append_eol)
|
||||
int write_message(const void *buf, size_t len, const char *filename,
|
||||
int append_eol)
|
||||
{
|
||||
struct lock_file msg_file = LOCK_INIT;
|
||||
|
||||
@ -804,6 +809,23 @@ N_("you have staged changes in your working tree\n"
|
||||
#define VERIFY_MSG (1<<4)
|
||||
#define CREATE_ROOT_COMMIT (1<<5)
|
||||
|
||||
static int run_command_silent_on_success(struct child_process *cmd)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int rc;
|
||||
|
||||
cmd->stdout_to_stderr = 1;
|
||||
rc = pipe_command(cmd,
|
||||
NULL, 0,
|
||||
NULL, 0,
|
||||
&buf, 0);
|
||||
|
||||
if (rc)
|
||||
fputs(buf.buf, stderr);
|
||||
strbuf_release(&buf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we are cherry-pick, and if the merge did not result in
|
||||
* hand-editing, we will hit this commit and inherit the original
|
||||
@ -865,18 +887,11 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
|
||||
|
||||
cmd.git_cmd = 1;
|
||||
|
||||
if (is_rebase_i(opts)) {
|
||||
if (!(flags & EDIT_MSG)) {
|
||||
cmd.stdout_to_stderr = 1;
|
||||
cmd.err = -1;
|
||||
}
|
||||
if (is_rebase_i(opts) && read_env_script(&cmd.env_array)) {
|
||||
const char *gpg_opt = gpg_sign_opt_quoted(opts);
|
||||
|
||||
if (read_env_script(&cmd.env_array)) {
|
||||
const char *gpg_opt = gpg_sign_opt_quoted(opts);
|
||||
|
||||
return error(_(staged_changes_advice),
|
||||
gpg_opt, gpg_opt);
|
||||
}
|
||||
return error(_(staged_changes_advice),
|
||||
gpg_opt, gpg_opt);
|
||||
}
|
||||
|
||||
argv_array_push(&cmd.args, "commit");
|
||||
@ -906,21 +921,10 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
|
||||
if (!(flags & EDIT_MSG))
|
||||
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);
|
||||
if (is_rebase_i(opts) && !(flags & EDIT_MSG))
|
||||
return run_command_silent_on_success(&cmd);
|
||||
else
|
||||
return run_command(&cmd);
|
||||
}
|
||||
|
||||
static int rest_is_empty(const struct strbuf *sb, int start)
|
||||
@ -2244,21 +2248,14 @@ 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)
|
||||
void parse_strategy_opts(struct replay_opts *opts, char *raw_opts)
|
||||
{
|
||||
int i;
|
||||
char *strategy_opts_string;
|
||||
char *strategy_opts_string = raw_opts;
|
||||
|
||||
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;
|
||||
|
||||
strategy_opts_string = buf->buf;
|
||||
if (*strategy_opts_string == ' ')
|
||||
strategy_opts_string++;
|
||||
|
||||
opts->xopts_nr = split_cmdline(strategy_opts_string,
|
||||
(const char ***)&opts->xopts);
|
||||
for (i = 0; i < opts->xopts_nr; i++) {
|
||||
@ -2269,6 +2266,18 @@ static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf)
|
||||
}
|
||||
}
|
||||
|
||||
static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf)
|
||||
{
|
||||
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;
|
||||
|
||||
parse_strategy_opts(opts, buf->buf);
|
||||
}
|
||||
|
||||
static int read_populate_opts(struct replay_opts *opts)
|
||||
{
|
||||
if (is_rebase_i(opts)) {
|
||||
@ -2336,6 +2345,55 @@ static int read_populate_opts(struct replay_opts *opts)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void write_strategy_opts(struct replay_opts *opts)
|
||||
{
|
||||
int i;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
|
||||
for (i = 0; i < opts->xopts_nr; ++i)
|
||||
strbuf_addf(&buf, " --%s", opts->xopts[i]);
|
||||
|
||||
write_file(rebase_path_strategy_opts(), "%s\n", buf.buf);
|
||||
strbuf_release(&buf);
|
||||
}
|
||||
|
||||
int write_basic_state(struct replay_opts *opts, const char *head_name,
|
||||
const char *onto, const char *orig_head)
|
||||
{
|
||||
const char *quiet = getenv("GIT_QUIET");
|
||||
|
||||
if (head_name)
|
||||
write_file(rebase_path_head_name(), "%s\n", head_name);
|
||||
if (onto)
|
||||
write_file(rebase_path_onto(), "%s\n", onto);
|
||||
if (orig_head)
|
||||
write_file(rebase_path_orig_head(), "%s\n", orig_head);
|
||||
|
||||
if (quiet)
|
||||
write_file(rebase_path_quiet(), "%s\n", quiet);
|
||||
else
|
||||
write_file(rebase_path_quiet(), "\n");
|
||||
|
||||
if (opts->verbose)
|
||||
write_file(rebase_path_verbose(), "");
|
||||
if (opts->strategy)
|
||||
write_file(rebase_path_strategy(), "%s\n", opts->strategy);
|
||||
if (opts->xopts_nr > 0)
|
||||
write_strategy_opts(opts);
|
||||
|
||||
if (opts->allow_rerere_auto == RERERE_AUTOUPDATE)
|
||||
write_file(rebase_path_allow_rerere_autoupdate(), "--rerere-autoupdate\n");
|
||||
else if (opts->allow_rerere_auto == RERERE_NOAUTOUPDATE)
|
||||
write_file(rebase_path_allow_rerere_autoupdate(), "--no-rerere-autoupdate\n");
|
||||
|
||||
if (opts->gpg_sign)
|
||||
write_file(rebase_path_gpg_sign_opt(), "-S%s\n", opts->gpg_sign);
|
||||
if (opts->signoff)
|
||||
write_file(rebase_path_signoff(), "--signoff\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int walk_revs_populate_todo(struct todo_list *todo_list,
|
||||
struct replay_opts *opts)
|
||||
{
|
||||
@ -3286,6 +3344,55 @@ static const char *reflog_message(struct replay_opts *opts,
|
||||
return buf.buf;
|
||||
}
|
||||
|
||||
static int run_git_checkout(struct replay_opts *opts, const char *commit,
|
||||
const char *action)
|
||||
{
|
||||
struct child_process cmd = CHILD_PROCESS_INIT;
|
||||
|
||||
cmd.git_cmd = 1;
|
||||
|
||||
argv_array_push(&cmd.args, "checkout");
|
||||
argv_array_push(&cmd.args, commit);
|
||||
argv_array_pushf(&cmd.env_array, GIT_REFLOG_ACTION "=%s", action);
|
||||
|
||||
if (opts->verbose)
|
||||
return run_command(&cmd);
|
||||
else
|
||||
return run_command_silent_on_success(&cmd);
|
||||
}
|
||||
|
||||
int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit)
|
||||
{
|
||||
const char *action;
|
||||
|
||||
if (commit && *commit) {
|
||||
action = reflog_message(opts, "start", "checkout %s", commit);
|
||||
if (run_git_checkout(opts, commit, action))
|
||||
return error(_("could not checkout %s"), commit);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int checkout_onto(struct replay_opts *opts,
|
||||
const char *onto_name, const char *onto,
|
||||
const char *orig_head)
|
||||
{
|
||||
struct object_id oid;
|
||||
const char *action = reflog_message(opts, "start", "checkout %s", onto_name);
|
||||
|
||||
if (get_oid(orig_head, &oid))
|
||||
return error(_("%s: not a valid OID"), orig_head);
|
||||
|
||||
if (run_git_checkout(opts, onto, action)) {
|
||||
apply_autostash(opts);
|
||||
sequencer_remove_state(opts);
|
||||
return error(_("could not detach HEAD"));
|
||||
}
|
||||
|
||||
return update_ref(NULL, "ORIG_HEAD", &oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR);
|
||||
}
|
||||
|
||||
static const char rescheduled_advice[] =
|
||||
N_("Could not execute the todo command\n"
|
||||
"\n"
|
||||
@ -4420,24 +4527,20 @@ int transform_todos(unsigned flags)
|
||||
return i;
|
||||
}
|
||||
|
||||
enum check_level {
|
||||
CHECK_IGNORE = 0, CHECK_WARN, CHECK_ERROR
|
||||
};
|
||||
|
||||
static enum check_level get_missing_commit_check_level(void)
|
||||
enum missing_commit_check_level get_missing_commit_check_level(void)
|
||||
{
|
||||
const char *value;
|
||||
|
||||
if (git_config_get_value("rebase.missingcommitscheck", &value) ||
|
||||
!strcasecmp("ignore", value))
|
||||
return CHECK_IGNORE;
|
||||
return MISSING_COMMIT_CHECK_IGNORE;
|
||||
if (!strcasecmp("warn", value))
|
||||
return CHECK_WARN;
|
||||
return MISSING_COMMIT_CHECK_WARN;
|
||||
if (!strcasecmp("error", value))
|
||||
return CHECK_ERROR;
|
||||
return MISSING_COMMIT_CHECK_ERROR;
|
||||
warning(_("unrecognized setting %s for option "
|
||||
"rebase.missingCommitsCheck. Ignoring."), value);
|
||||
return CHECK_IGNORE;
|
||||
return MISSING_COMMIT_CHECK_IGNORE;
|
||||
}
|
||||
|
||||
define_commit_slab(commit_seen, unsigned char);
|
||||
@ -4449,7 +4552,7 @@ define_commit_slab(commit_seen, unsigned char);
|
||||
*/
|
||||
int check_todo_list(void)
|
||||
{
|
||||
enum check_level check_level = get_missing_commit_check_level();
|
||||
enum missing_commit_check_level check_level = get_missing_commit_check_level();
|
||||
struct strbuf todo_file = STRBUF_INIT;
|
||||
struct todo_list todo_list = TODO_LIST_INIT;
|
||||
struct strbuf missing = STRBUF_INIT;
|
||||
@ -4466,7 +4569,7 @@ int check_todo_list(void)
|
||||
advise_to_edit_todo = res =
|
||||
parse_insn_buffer(todo_list.buf.buf, &todo_list);
|
||||
|
||||
if (res || check_level == CHECK_IGNORE)
|
||||
if (res || check_level == MISSING_COMMIT_CHECK_IGNORE)
|
||||
goto leave_check;
|
||||
|
||||
/* Mark the commits in git-rebase-todo as seen */
|
||||
@ -4501,7 +4604,7 @@ int check_todo_list(void)
|
||||
if (!missing.len)
|
||||
goto leave_check;
|
||||
|
||||
if (check_level == CHECK_ERROR)
|
||||
if (check_level == MISSING_COMMIT_CHECK_ERROR)
|
||||
advise_to_edit_todo = res = 1;
|
||||
|
||||
fprintf(stderr,
|
||||
@ -4547,17 +4650,17 @@ static int rewrite_file(const char *path, const char *buf, size_t len)
|
||||
}
|
||||
|
||||
/* skip picking commits whose parents are unchanged */
|
||||
int skip_unnecessary_picks(void)
|
||||
static int skip_unnecessary_picks(struct object_id *output_oid)
|
||||
{
|
||||
const char *todo_file = rebase_path_todo();
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
struct todo_list todo_list = TODO_LIST_INIT;
|
||||
struct object_id onto_oid, *oid = &onto_oid, *parent_oid;
|
||||
struct object_id *parent_oid;
|
||||
int fd, i;
|
||||
|
||||
if (!read_oneliner(&buf, rebase_path_onto(), 0))
|
||||
return error(_("could not read 'onto'"));
|
||||
if (get_oid(buf.buf, &onto_oid)) {
|
||||
if (get_oid(buf.buf, output_oid)) {
|
||||
strbuf_release(&buf);
|
||||
return error(_("need a HEAD to fixup"));
|
||||
}
|
||||
@ -4587,9 +4690,9 @@ int skip_unnecessary_picks(void)
|
||||
if (item->commit->parents->next)
|
||||
break; /* merge commit */
|
||||
parent_oid = &item->commit->parents->item->object.oid;
|
||||
if (!oideq(parent_oid, oid))
|
||||
if (!oideq(parent_oid, output_oid))
|
||||
break;
|
||||
oid = &item->commit->object.oid;
|
||||
oidcpy(output_oid, &item->commit->object.oid);
|
||||
}
|
||||
if (i > 0) {
|
||||
int offset = get_item_line_offset(&todo_list, i);
|
||||
@ -4618,15 +4721,114 @@ int skip_unnecessary_picks(void)
|
||||
|
||||
todo_list.current = i;
|
||||
if (is_fixup(peek_command(&todo_list, 0)))
|
||||
record_in_rewritten(oid, peek_command(&todo_list, 0));
|
||||
record_in_rewritten(output_oid, peek_command(&todo_list, 0));
|
||||
}
|
||||
|
||||
todo_list_release(&todo_list);
|
||||
printf("%s\n", oid_to_hex(oid));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int complete_action(struct replay_opts *opts, unsigned flags,
|
||||
const char *shortrevisions, const char *onto_name,
|
||||
const char *onto, const char *orig_head, const char *cmd,
|
||||
unsigned autosquash)
|
||||
{
|
||||
const char *shortonto, *todo_file = rebase_path_todo();
|
||||
struct todo_list todo_list = TODO_LIST_INIT;
|
||||
struct strbuf *buf = &(todo_list.buf);
|
||||
struct object_id oid;
|
||||
struct stat st;
|
||||
|
||||
get_oid(onto, &oid);
|
||||
shortonto = find_unique_abbrev(&oid, DEFAULT_ABBREV);
|
||||
|
||||
if (!lstat(todo_file, &st) && st.st_size == 0 &&
|
||||
write_message("noop\n", 5, todo_file, 0))
|
||||
return -1;
|
||||
|
||||
if (autosquash && rearrange_squash())
|
||||
return -1;
|
||||
|
||||
if (cmd && *cmd)
|
||||
sequencer_add_exec_commands(cmd);
|
||||
|
||||
if (strbuf_read_file(buf, todo_file, 0) < 0)
|
||||
return error_errno(_("could not read '%s'."), todo_file);
|
||||
|
||||
if (parse_insn_buffer(buf->buf, &todo_list)) {
|
||||
todo_list_release(&todo_list);
|
||||
return error(_("unusable todo list: '%s'"), todo_file);
|
||||
}
|
||||
|
||||
if (count_commands(&todo_list) == 0) {
|
||||
apply_autostash(opts);
|
||||
sequencer_remove_state(opts);
|
||||
todo_list_release(&todo_list);
|
||||
|
||||
return error(_("nothing to do"));
|
||||
}
|
||||
|
||||
strbuf_addch(buf, '\n');
|
||||
strbuf_commented_addf(buf, Q_("Rebase %s onto %s (%d command)",
|
||||
"Rebase %s onto %s (%d commands)",
|
||||
count_commands(&todo_list)),
|
||||
shortrevisions, shortonto, count_commands(&todo_list));
|
||||
append_todo_help(0, flags & TODO_LIST_KEEP_EMPTY, buf);
|
||||
|
||||
if (write_message(buf->buf, buf->len, todo_file, 0)) {
|
||||
todo_list_release(&todo_list);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (copy_file(rebase_path_todo_backup(), todo_file, 0666))
|
||||
return error(_("could not copy '%s' to '%s'."), todo_file,
|
||||
rebase_path_todo_backup());
|
||||
|
||||
if (transform_todos(flags | TODO_LIST_SHORTEN_IDS))
|
||||
return error(_("could not transform the todo list"));
|
||||
|
||||
strbuf_reset(buf);
|
||||
|
||||
if (launch_sequence_editor(todo_file, buf, NULL)) {
|
||||
apply_autostash(opts);
|
||||
sequencer_remove_state(opts);
|
||||
todo_list_release(&todo_list);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
strbuf_stripspace(buf, 1);
|
||||
if (buf->len == 0) {
|
||||
apply_autostash(opts);
|
||||
sequencer_remove_state(opts);
|
||||
todo_list_release(&todo_list);
|
||||
|
||||
return error(_("nothing to do"));
|
||||
}
|
||||
|
||||
todo_list_release(&todo_list);
|
||||
|
||||
if (check_todo_list()) {
|
||||
checkout_onto(opts, onto_name, onto, orig_head);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (transform_todos(flags & ~(TODO_LIST_SHORTEN_IDS)))
|
||||
return error(_("could not transform the todo list"));
|
||||
|
||||
if (opts->allow_ff && skip_unnecessary_picks(&oid))
|
||||
return error(_("could not skip unnecessary pick commands"));
|
||||
|
||||
if (checkout_onto(opts, onto_name, oid_to_hex(&oid), orig_head))
|
||||
return -1;
|
||||
;
|
||||
if (require_clean_work_tree("rebase", "", 1, 1))
|
||||
return -1;
|
||||
|
||||
return sequencer_continue(opts);
|
||||
}
|
||||
|
||||
struct subject2item_entry {
|
||||
struct hashmap_entry entry;
|
||||
int i;
|
||||
|
Reference in New Issue
Block a user