]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'pw/rebase-i-internal'
authorJunio C Hamano <gitster@pobox.com>
Mon, 13 May 2019 14:50:34 +0000 (23:50 +0900)
committerJunio C Hamano <gitster@pobox.com>
Mon, 13 May 2019 14:50:34 +0000 (23:50 +0900)
The internal implementation of "git rebase -i" has been updated to
avoid forking a separate "rebase--interactive" process.

* pw/rebase-i-internal:
  rebase -i: run without forking rebase--interactive
  rebase: use a common action enum
  rebase -i: use struct rebase_options in do_interactive_rebase()
  rebase -i: use struct rebase_options to parse args
  rebase -i: use struct object_id for squash_onto
  rebase -i: use struct commit when parsing options
  rebase -i: remove duplication
  rebase -i: combine rebase--interactive.c with rebase.c
  rebase: use OPT_RERERE_AUTOUPDATE()
  rebase: rename write_basic_state()
  rebase: don't translate trace strings
  sequencer: always discard index after checkout

1  2 
Makefile
builtin/rebase.c
parse-options-cb.c
parse-options.h
sequencer.c
sequencer.h

diff --cc Makefile
Simple merge
index 966b953e86cc90c3683e4de01ef4b18a21229c74,cd233c61188ebdb8cfcde503c21a5fdbf5e474aa..ba3a574e4022c3dbd0e037782f520b1a199da42e
@@@ -83,9 -87,462 +87,440 @@@ struct rebase_options 
        char *strategy, *strategy_opts;
        struct strbuf git_format_patch_opt;
        int reschedule_failed_exec;
 +      int use_legacy_rebase;
  };
  
 -static int use_builtin_rebase(void)
 -{
 -      struct child_process cp = CHILD_PROCESS_INIT;
 -      struct strbuf out = STRBUF_INIT;
 -      int ret, env = git_env_bool("GIT_TEST_REBASE_USE_BUILTIN", -1);
 -
 -      if (env != -1)
 -              return env;
 -
 -      argv_array_pushl(&cp.args,
 -                       "config", "--bool", "rebase.usebuiltin", NULL);
 -      cp.git_cmd = 1;
 -      if (capture_command(&cp, &out, 6)) {
 -              strbuf_release(&out);
 -              return 1;
 -      }
 -
 -      strbuf_trim(&out);
 -      ret = !strcmp("true", out.buf);
 -      strbuf_release(&out);
 -      return ret;
 -}
 -
+ #define REBASE_OPTIONS_INIT {                         \
+               .type = REBASE_UNSPECIFIED,             \
+               .flags = REBASE_NO_QUIET,               \
+               .git_am_opts = ARGV_ARRAY_INIT,         \
+               .git_format_patch_opt = STRBUF_INIT     \
+       }
+ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
+ {
+       struct replay_opts replay = REPLAY_OPTS_INIT;
+       replay.action = REPLAY_INTERACTIVE_REBASE;
+       sequencer_init_config(&replay);
+       replay.signoff = opts->signoff;
+       replay.allow_ff = !(opts->flags & REBASE_FORCE);
+       if (opts->allow_rerere_autoupdate)
+               replay.allow_rerere_auto = opts->allow_rerere_autoupdate;
+       replay.allow_empty = 1;
+       replay.allow_empty_message = opts->allow_empty_message;
+       replay.verbose = opts->flags & REBASE_VERBOSE;
+       replay.reschedule_failed_exec = opts->reschedule_failed_exec;
+       replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
+       replay.strategy = opts->strategy;
+       if (opts->strategy_opts)
+               parse_strategy_opts(&replay, opts->strategy_opts);
+       return replay;
+ }
+ enum action {
+       ACTION_NONE = 0,
+       ACTION_CONTINUE,
+       ACTION_SKIP,
+       ACTION_ABORT,
+       ACTION_QUIT,
+       ACTION_EDIT_TODO,
+       ACTION_SHOW_CURRENT_PATCH,
+       ACTION_SHORTEN_OIDS,
+       ACTION_EXPAND_OIDS,
+       ACTION_CHECK_TODO_LIST,
+       ACTION_REARRANGE_SQUASH,
+       ACTION_ADD_EXEC
+ };
+ static const char *action_names[] = { "undefined",
+                                     "continue",
+                                     "skip",
+                                     "abort",
+                                     "quit",
+                                     "edit_todo",
+                                     "show_current_patch" };
+ static int add_exec_commands(struct string_list *commands)
+ {
+       const char *todo_file = rebase_path_todo();
+       struct todo_list todo_list = TODO_LIST_INIT;
+       int res;
+       if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
+               return error_errno(_("could not read '%s'."), todo_file);
+       if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf,
+                                       &todo_list)) {
+               todo_list_release(&todo_list);
+               return error(_("unusable todo list: '%s'"), todo_file);
+       }
+       todo_list_add_exec_commands(&todo_list, commands);
+       res = todo_list_write_to_file(the_repository, &todo_list,
+                                     todo_file, NULL, NULL, -1, 0);
+       todo_list_release(&todo_list);
+       if (res)
+               return error_errno(_("could not write '%s'."), todo_file);
+       return 0;
+ }
+ static int rearrange_squash_in_todo_file(void)
+ {
+       const char *todo_file = rebase_path_todo();
+       struct todo_list todo_list = TODO_LIST_INIT;
+       int res = 0;
+       if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
+               return error_errno(_("could not read '%s'."), todo_file);
+       if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf,
+                                       &todo_list)) {
+               todo_list_release(&todo_list);
+               return error(_("unusable todo list: '%s'"), todo_file);
+       }
+       res = todo_list_rearrange_squash(&todo_list);
+       if (!res)
+               res = todo_list_write_to_file(the_repository, &todo_list,
+                                             todo_file, NULL, NULL, -1, 0);
+       todo_list_release(&todo_list);
+       if (res)
+               return error_errno(_("could not write '%s'."), todo_file);
+       return 0;
+ }
+ static int transform_todo_file(unsigned flags)
+ {
+       const char *todo_file = rebase_path_todo();
+       struct todo_list todo_list = TODO_LIST_INIT;
+       int res;
+       if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
+               return error_errno(_("could not read '%s'."), todo_file);
+       if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf,
+                                       &todo_list)) {
+               todo_list_release(&todo_list);
+               return error(_("unusable todo list: '%s'"), todo_file);
+       }
+       res = todo_list_write_to_file(the_repository, &todo_list, todo_file,
+                                     NULL, NULL, -1, flags);
+       todo_list_release(&todo_list);
+       if (res)
+               return error_errno(_("could not write '%s'."), todo_file);
+       return 0;
+ }
+ static int edit_todo_file(unsigned flags)
+ {
+       const char *todo_file = rebase_path_todo();
+       struct todo_list todo_list = TODO_LIST_INIT,
+               new_todo = TODO_LIST_INIT;
+       int res = 0;
+       if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
+               return error_errno(_("could not read '%s'."), todo_file);
+       strbuf_stripspace(&todo_list.buf, 1);
+       res = edit_todo_list(the_repository, &todo_list, &new_todo, NULL, NULL, flags);
+       if (!res && todo_list_write_to_file(the_repository, &new_todo, todo_file,
+                                           NULL, NULL, -1, flags & ~(TODO_LIST_SHORTEN_IDS)))
+               res = error_errno(_("could not write '%s'"), todo_file);
+       todo_list_release(&todo_list);
+       todo_list_release(&new_todo);
+       return res;
+ }
+ static int get_revision_ranges(struct commit *upstream, struct commit *onto,
+                              const char **head_hash,
+                              char **revisions, char **shortrevisions)
+ {
+       struct commit *base_rev = upstream ? upstream : onto;
+       const char *shorthead;
+       struct object_id orig_head;
+       if (get_oid("HEAD", &orig_head))
+               return error(_("no HEAD?"));
+       *head_hash = find_unique_abbrev(&orig_head, GIT_MAX_HEXSZ);
+       *revisions = xstrfmt("%s...%s", oid_to_hex(&base_rev->object.oid),
+                                                  *head_hash);
+       shorthead = find_unique_abbrev(&orig_head, DEFAULT_ABBREV);
+       if (upstream) {
+               const char *shortrev;
+               shortrev = find_unique_abbrev(&base_rev->object.oid,
+                                             DEFAULT_ABBREV);
+               *shortrevisions = xstrfmt("%s..%s", shortrev, shorthead);
+       } else
+               *shortrevisions = xstrdup(shorthead);
+       return 0;
+ }
+ static int init_basic_state(struct replay_opts *opts, const char *head_name,
+                           struct commit *onto, const char *orig_head)
+ {
+       FILE *interactive;
+       if (!is_directory(merge_dir()) && mkdir_in_gitdir(merge_dir()))
+               return error_errno(_("could not create temporary %s"), merge_dir());
+       delete_reflog("REBASE_HEAD");
+       interactive = fopen(path_interactive(), "w");
+       if (!interactive)
+               return error_errno(_("could not mark as interactive"));
+       fclose(interactive);
+       return write_basic_state(opts, head_name, onto, orig_head);
+ }
+ static void split_exec_commands(const char *cmd, struct string_list *commands)
+ {
+       if (cmd && *cmd) {
+               string_list_split(commands, cmd, '\n', -1);
+               /* rebase.c adds a new line to cmd after every command,
+                * so here the last command is always empty */
+               string_list_remove_empty_items(commands, 0);
+       }
+ }
+ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
+ {
+       int ret;
+       const char *head_hash = NULL;
+       char *revisions = NULL, *shortrevisions = NULL;
+       struct argv_array make_script_args = ARGV_ARRAY_INIT;
+       struct todo_list todo_list = TODO_LIST_INIT;
+       struct replay_opts replay = get_replay_opts(opts);
+       struct string_list commands = STRING_LIST_INIT_DUP;
+       if (prepare_branch_to_be_rebased(the_repository, &replay,
+                                        opts->switch_to))
+               return -1;
+       if (get_revision_ranges(opts->upstream, opts->onto, &head_hash,
+                               &revisions, &shortrevisions))
+               return -1;
+       if (init_basic_state(&replay,
+                            opts->head_name ? opts->head_name : "detached HEAD",
+                            opts->onto, head_hash)) {
+               free(revisions);
+               free(shortrevisions);
+               return -1;
+       }
+       if (!opts->upstream && opts->squash_onto)
+               write_file(path_squash_onto(), "%s\n",
+                          oid_to_hex(opts->squash_onto));
+       argv_array_pushl(&make_script_args, "", revisions, NULL);
+       if (opts->restrict_revision)
+               argv_array_push(&make_script_args,
+                               oid_to_hex(&opts->restrict_revision->object.oid));
+       ret = sequencer_make_script(the_repository, &todo_list.buf,
+                                   make_script_args.argc, make_script_args.argv,
+                                   flags);
+       if (ret)
+               error(_("could not generate todo list"));
+       else {
+               discard_cache();
+               if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf,
+                                               &todo_list))
+                       BUG("unusable todo list");
+               split_exec_commands(opts->cmd, &commands);
+               ret = complete_action(the_repository, &replay, flags,
+                       shortrevisions, opts->onto_name, opts->onto, head_hash,
+                       &commands, opts->autosquash, &todo_list);
+       }
+       string_list_clear(&commands, 0);
+       free(revisions);
+       free(shortrevisions);
+       todo_list_release(&todo_list);
+       argv_array_clear(&make_script_args);
+       return ret;
+ }
+ static int run_rebase_interactive(struct rebase_options *opts,
+                                 enum action command)
+ {
+       unsigned flags = 0;
+       int abbreviate_commands = 0, ret = 0;
+       git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
+       flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
+       flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
+       flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
+       flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
+       flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
+       switch (command) {
+       case ACTION_NONE: {
+               if (!opts->onto && !opts->upstream)
+                       die(_("a base commit must be provided with --upstream or --onto"));
+               ret = do_interactive_rebase(opts, flags);
+               break;
+       }
+       case ACTION_SKIP: {
+               struct string_list merge_rr = STRING_LIST_INIT_DUP;
+               rerere_clear(the_repository, &merge_rr);
+       }
+               /* fallthrough */
+       case ACTION_CONTINUE: {
+               struct replay_opts replay_opts = get_replay_opts(opts);
+               ret = sequencer_continue(the_repository, &replay_opts);
+               break;
+       }
+       case ACTION_EDIT_TODO:
+               ret = edit_todo_file(flags);
+               break;
+       case ACTION_SHOW_CURRENT_PATCH: {
+               struct child_process cmd = CHILD_PROCESS_INIT;
+               cmd.git_cmd = 1;
+               argv_array_pushl(&cmd.args, "show", "REBASE_HEAD", "--", NULL);
+               ret = run_command(&cmd);
+               break;
+       }
+       case ACTION_SHORTEN_OIDS:
+       case ACTION_EXPAND_OIDS:
+               ret = transform_todo_file(flags);
+               break;
+       case ACTION_CHECK_TODO_LIST:
+               ret = check_todo_list_from_file(the_repository);
+               break;
+       case ACTION_REARRANGE_SQUASH:
+               ret = rearrange_squash_in_todo_file();
+               break;
+       case ACTION_ADD_EXEC: {
+               struct string_list commands = STRING_LIST_INIT_DUP;
+               split_exec_commands(opts->cmd, &commands);
+               ret = add_exec_commands(&commands);
+               string_list_clear(&commands, 0);
+               break;
+       }
+       default:
+               BUG("invalid command '%d'", command);
+       }
+       return ret;
+ }
+ static const char * const builtin_rebase_interactive_usage[] = {
+       N_("git rebase--interactive [<options>]"),
+       NULL
+ };
+ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
+ {
+       struct rebase_options opts = REBASE_OPTIONS_INIT;
+       struct object_id squash_onto = null_oid;
+       enum action command = ACTION_NONE;
+       struct option options[] = {
+               OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
+                          REBASE_FORCE),
+               OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
+               OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message,
+                        N_("allow commits with empty messages")),
+               OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")),
+               OPT_BOOL(0, "rebase-cousins", &opts.rebase_cousins,
+                        N_("keep original branch points of cousins")),
+               OPT_BOOL(0, "autosquash", &opts.autosquash,
+                        N_("move commits that begin with squash!/fixup!")),
+               OPT_BOOL(0, "signoff", &opts.signoff, N_("sign commits")),
+               OPT_BIT('v', "verbose", &opts.flags,
+                       N_("display a diffstat of what changed upstream"),
+                       REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
+               OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
+                           ACTION_CONTINUE),
+               OPT_CMDMODE(0, "skip", &command, N_("skip commit"), ACTION_SKIP),
+               OPT_CMDMODE(0, "edit-todo", &command, N_("edit the todo list"),
+                           ACTION_EDIT_TODO),
+               OPT_CMDMODE(0, "show-current-patch", &command, N_("show the current patch"),
+                           ACTION_SHOW_CURRENT_PATCH),
+               OPT_CMDMODE(0, "shorten-ids", &command,
+                       N_("shorten commit ids in the todo list"), ACTION_SHORTEN_OIDS),
+               OPT_CMDMODE(0, "expand-ids", &command,
+                       N_("expand commit ids in the todo list"), ACTION_EXPAND_OIDS),
+               OPT_CMDMODE(0, "check-todo-list", &command,
+                       N_("check the todo list"), ACTION_CHECK_TODO_LIST),
+               OPT_CMDMODE(0, "rearrange-squash", &command,
+                       N_("rearrange fixup/squash lines"), ACTION_REARRANGE_SQUASH),
+               OPT_CMDMODE(0, "add-exec-commands", &command,
+                       N_("insert exec commands in todo list"), ACTION_ADD_EXEC),
+               { OPTION_CALLBACK, 0, "onto", &opts.onto, N_("onto"), N_("onto"),
+                 PARSE_OPT_NONEG, parse_opt_commit, 0 },
+               { OPTION_CALLBACK, 0, "restrict-revision", &opts.restrict_revision,
+                 N_("restrict-revision"), N_("restrict revision"),
+                 PARSE_OPT_NONEG, parse_opt_commit, 0 },
+               { OPTION_CALLBACK, 0, "squash-onto", &squash_onto, N_("squash-onto"),
+                 N_("squash onto"), PARSE_OPT_NONEG, parse_opt_object_id, 0 },
+               { OPTION_CALLBACK, 0, "upstream", &opts.upstream, N_("upstream"),
+                 N_("the upstream commit"), PARSE_OPT_NONEG, parse_opt_commit,
+                 0 },
+               OPT_STRING(0, "head-name", &opts.head_name, N_("head-name"), N_("head name")),
+               { OPTION_STRING, 'S', "gpg-sign", &opts.gpg_sign_opt, N_("key-id"),
+                       N_("GPG-sign commits"),
+                       PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+               OPT_STRING(0, "strategy", &opts.strategy, N_("strategy"),
+                          N_("rebase strategy")),
+               OPT_STRING(0, "strategy-opts", &opts.strategy_opts, N_("strategy-opts"),
+                          N_("strategy options")),
+               OPT_STRING(0, "switch-to", &opts.switch_to, N_("switch-to"),
+                          N_("the branch or commit to checkout")),
+               OPT_STRING(0, "onto-name", &opts.onto_name, N_("onto-name"), N_("onto name")),
+               OPT_STRING(0, "cmd", &opts.cmd, N_("cmd"), N_("the command to run")),
+               OPT_RERERE_AUTOUPDATE(&opts.allow_rerere_autoupdate),
+               OPT_BOOL(0, "reschedule-failed-exec", &opts.reschedule_failed_exec,
+                        N_("automatically re-schedule any `exec` that fails")),
+               OPT_END()
+       };
+       opts.rebase_cousins = -1;
+       if (argc == 1)
+               usage_with_options(builtin_rebase_interactive_usage, options);
+       argc = parse_options(argc, argv, NULL, options,
+                       builtin_rebase_interactive_usage, PARSE_OPT_KEEP_ARGV0);
+       if (!is_null_oid(&squash_onto))
+               opts.squash_onto = &squash_onto;
+       if (opts.rebase_cousins >= 0 && !opts.rebase_merges)
+               warning(_("--[no-]rebase-cousins has no effect without "
+                         "--rebase-merges"));
+       return !!run_rebase_interactive(&opts, command);
+ }
  static int is_interactive(struct rebase_options *opts)
  {
        return opts->type == REBASE_INTERACTIVE ||
@@@ -1088,12 -1454,9 +1442,9 @@@ int cmd_rebase(int argc, const char **a
                        PARSE_OPT_NOARG | PARSE_OPT_NONEG,
                        parse_opt_interactive },
                OPT_SET_INT('p', "preserve-merges", &options.type,
 -                          N_("try to recreate merges instead of ignoring "
 -                             "them"), REBASE_PRESERVE_MERGES),
 +                          N_("(DEPRECATED) try to recreate merges instead of "
 +                             "ignoring them"), REBASE_PRESERVE_MERGES),
-               OPT_BOOL(0, "rerere-autoupdate",
-                        &options.allow_rerere_autoupdate,
-                        N_("allow rerere to update index with resolved "
-                           "conflict")),
+               OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
                OPT_BOOL('k', "keep-empty", &options.keep_empty,
                         N_("preserve empty commits during rebase")),
                OPT_BOOL(0, "autosquash", &options.autosquash,
        trace_repo_setup(prefix);
        setup_work_tree();
  
+       options.allow_empty_message = 1;
        git_config(rebase_config, &options);
  
 +      if (options.use_legacy_rebase ||
 +          !git_env_bool("GIT_TEST_REBASE_USE_BUILTIN", -1))
 +              warning(_("the rebase.useBuiltin support has been removed!\n"
 +                        "See its entry in 'git help config' for details."));
 +
        strbuf_reset(&buf);
        strbuf_addf(&buf, "%s/applying", apply_dir());
        if(file_exists(buf.buf))
                usage_with_options(builtin_rebase_usage,
                                   builtin_rebase_options);
  
-       if (action != NO_ACTION && !in_progress)
 +      if (options.type == REBASE_PRESERVE_MERGES)
 +              warning(_("git rebase --preserve-merges is deprecated. "
 +                        "Use --rebase-merges instead."));
 +
+       if (action != ACTION_NONE && !in_progress)
                die(_("No rebase in progress?"));
        setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0);
  
Simple merge
diff --cc parse-options.h
Simple merge
diff --cc sequencer.c
Simple merge
diff --cc sequencer.h
Simple merge