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:
Junio C Hamano
2018-11-02 11:04:53 +09:00
17 changed files with 742 additions and 451 deletions

View File

@ -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;