Merge branch 'ag/rebase-i-in-c' into js/rebase-in-c-5.5-work-with-rebase-i-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:
commit
5ab7e0fb67
1
.gitignore
vendored
1
.gitignore
vendored
@ -118,7 +118,6 @@
|
||||
/git-rebase
|
||||
/git-rebase--am
|
||||
/git-rebase--common
|
||||
/git-rebase--helper
|
||||
/git-rebase--interactive
|
||||
/git-rebase--merge
|
||||
/git-rebase--preserve-merges
|
||||
|
5
Makefile
5
Makefile
@ -620,7 +620,6 @@ SCRIPT_LIB += git-mergetool--lib
|
||||
SCRIPT_LIB += git-parse-remote
|
||||
SCRIPT_LIB += git-rebase--am
|
||||
SCRIPT_LIB += git-rebase--common
|
||||
SCRIPT_LIB += git-rebase--interactive
|
||||
SCRIPT_LIB += git-rebase--preserve-merges
|
||||
SCRIPT_LIB += git-rebase--merge
|
||||
SCRIPT_LIB += git-sh-setup
|
||||
@ -927,6 +926,7 @@ LIB_OBJS += protocol.o
|
||||
LIB_OBJS += quote.o
|
||||
LIB_OBJS += reachable.o
|
||||
LIB_OBJS += read-cache.o
|
||||
LIB_OBJS += rebase-interactive.o
|
||||
LIB_OBJS += reflog-walk.o
|
||||
LIB_OBJS += refs.o
|
||||
LIB_OBJS += refs/files-backend.o
|
||||
@ -1065,7 +1065,7 @@ BUILTIN_OBJS += builtin/pull.o
|
||||
BUILTIN_OBJS += builtin/push.o
|
||||
BUILTIN_OBJS += builtin/read-tree.o
|
||||
BUILTIN_OBJS += builtin/rebase.o
|
||||
BUILTIN_OBJS += builtin/rebase--helper.o
|
||||
BUILTIN_OBJS += builtin/rebase--interactive.o
|
||||
BUILTIN_OBJS += builtin/receive-pack.o
|
||||
BUILTIN_OBJS += builtin/reflog.o
|
||||
BUILTIN_OBJS += builtin/remote.o
|
||||
@ -2404,7 +2404,6 @@ XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --language=Perl \
|
||||
LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H)
|
||||
LOCALIZED_SH = $(SCRIPT_SH)
|
||||
LOCALIZED_SH += git-parse-remote.sh
|
||||
LOCALIZED_SH += git-rebase--interactive.sh
|
||||
LOCALIZED_SH += git-rebase--preserve-merges.sh
|
||||
LOCALIZED_SH += git-sh-setup.sh
|
||||
LOCALIZED_PERL = $(SCRIPT_PERL)
|
||||
|
@ -203,7 +203,7 @@ extern int cmd_pull(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_push(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_rebase(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_rebase__interactive(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_reflog(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_remote(int argc, const char **argv, const char *prefix);
|
||||
|
@ -1,88 +0,0 @@
|
||||
#include "builtin.h"
|
||||
#include "cache.h"
|
||||
#include "config.h"
|
||||
#include "parse-options.h"
|
||||
#include "sequencer.h"
|
||||
|
||||
static const char * const builtin_rebase_helper_usage[] = {
|
||||
N_("git rebase--helper [<options>]"),
|
||||
NULL
|
||||
};
|
||||
|
||||
int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
struct replay_opts opts = REPLAY_OPTS_INIT;
|
||||
unsigned flags = 0, keep_empty = 0, rebase_merges = 0;
|
||||
int abbreviate_commands = 0, rebase_cousins = -1;
|
||||
enum {
|
||||
CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS,
|
||||
CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH,
|
||||
ADD_EXEC
|
||||
} command = 0;
|
||||
struct option options[] = {
|
||||
OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
|
||||
OPT_BOOL(0, "keep-empty", &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", &rebase_merges, N_("rebase merge commits")),
|
||||
OPT_BOOL(0, "rebase-cousins", &rebase_cousins,
|
||||
N_("keep original branch points of cousins")),
|
||||
OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
|
||||
CONTINUE),
|
||||
OPT_CMDMODE(0, "abort", &command, N_("abort rebase"),
|
||||
ABORT),
|
||||
OPT_CMDMODE(0, "make-script", &command,
|
||||
N_("make rebase script"), MAKE_SCRIPT),
|
||||
OPT_CMDMODE(0, "shorten-ids", &command,
|
||||
N_("shorten commit ids in the todo list"), SHORTEN_OIDS),
|
||||
OPT_CMDMODE(0, "expand-ids", &command,
|
||||
N_("expand commit ids in the todo list"), EXPAND_OIDS),
|
||||
OPT_CMDMODE(0, "check-todo-list", &command,
|
||||
N_("check the todo list"), CHECK_TODO_LIST),
|
||||
OPT_CMDMODE(0, "skip-unnecessary-picks", &command,
|
||||
N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS),
|
||||
OPT_CMDMODE(0, "rearrange-squash", &command,
|
||||
N_("rearrange fixup/squash lines"), REARRANGE_SQUASH),
|
||||
OPT_CMDMODE(0, "add-exec-commands", &command,
|
||||
N_("insert exec commands in todo list"), ADD_EXEC),
|
||||
OPT_END()
|
||||
};
|
||||
|
||||
sequencer_init_config(&opts);
|
||||
git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
|
||||
|
||||
opts.action = REPLAY_INTERACTIVE_REBASE;
|
||||
opts.allow_ff = 1;
|
||||
opts.allow_empty = 1;
|
||||
|
||||
argc = parse_options(argc, argv, NULL, options,
|
||||
builtin_rebase_helper_usage, PARSE_OPT_KEEP_ARGV0);
|
||||
|
||||
flags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
|
||||
flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
|
||||
flags |= rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
|
||||
flags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
|
||||
flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
|
||||
|
||||
if (rebase_cousins >= 0 && !rebase_merges)
|
||||
warning(_("--[no-]rebase-cousins has no effect without "
|
||||
"--rebase-merges"));
|
||||
|
||||
if (command == CONTINUE && argc == 1)
|
||||
return !!sequencer_continue(&opts);
|
||||
if (command == ABORT && argc == 1)
|
||||
return !!sequencer_remove_state(&opts);
|
||||
if (command == MAKE_SCRIPT && argc > 1)
|
||||
return !!sequencer_make_script(stdout, argc, argv, flags);
|
||||
if ((command == SHORTEN_OIDS || command == EXPAND_OIDS) && argc == 1)
|
||||
return !!transform_todos(flags);
|
||||
if (command == CHECK_TODO_LIST && argc == 1)
|
||||
return !!check_todo_list();
|
||||
if (command == SKIP_UNNECESSARY_PICKS && argc == 1)
|
||||
return !!skip_unnecessary_picks();
|
||||
if (command == REARRANGE_SQUASH && argc == 1)
|
||||
return !!rearrange_squash();
|
||||
if (command == ADD_EXEC && argc == 2)
|
||||
return !!sequencer_add_exec_commands(argv[1]);
|
||||
usage_with_options(builtin_rebase_helper_usage, options);
|
||||
}
|
271
builtin/rebase--interactive.c
Normal file
271
builtin/rebase--interactive.c
Normal file
@ -0,0 +1,271 @@
|
||||
#include "builtin.h"
|
||||
#include "cache.h"
|
||||
#include "config.h"
|
||||
#include "parse-options.h"
|
||||
#include "sequencer.h"
|
||||
#include "rebase-interactive.h"
|
||||
#include "argv-array.h"
|
||||
#include "refs.h"
|
||||
#include "rerere.h"
|
||||
#include "run-command.h"
|
||||
|
||||
static GIT_PATH_FUNC(path_state_dir, "rebase-merge/")
|
||||
static GIT_PATH_FUNC(path_squash_onto, "rebase-merge/squash-onto")
|
||||
static GIT_PATH_FUNC(path_interactive, "rebase-merge/interactive")
|
||||
|
||||
static int get_revision_ranges(const char *upstream, const char *onto,
|
||||
const char **head_hash,
|
||||
char **revisions, char **shortrevisions)
|
||||
{
|
||||
const char *base_rev = upstream ? upstream : onto, *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", base_rev, *head_hash);
|
||||
|
||||
shorthead = find_unique_abbrev(&orig_head, DEFAULT_ABBREV);
|
||||
|
||||
if (upstream) {
|
||||
const char *shortrev;
|
||||
struct object_id rev_oid;
|
||||
|
||||
get_oid(base_rev, &rev_oid);
|
||||
shortrev = find_unique_abbrev(&rev_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,
|
||||
const char *onto, const char *orig_head)
|
||||
{
|
||||
FILE *interactive;
|
||||
|
||||
if (!is_directory(path_state_dir()) && mkdir_in_gitdir(path_state_dir()))
|
||||
return error_errno(_("could not create temporary %s"), path_state_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 int do_interactive_rebase(struct replay_opts *opts, unsigned flags,
|
||||
const char *switch_to, const char *upstream,
|
||||
const char *onto, const char *onto_name,
|
||||
const char *squash_onto, const char *head_name,
|
||||
const char *restrict_revision, char *raw_strategies,
|
||||
const char *cmd, unsigned autosquash)
|
||||
{
|
||||
int ret;
|
||||
const char *head_hash = NULL;
|
||||
char *revisions = NULL, *shortrevisions = NULL;
|
||||
struct argv_array make_script_args = ARGV_ARRAY_INIT;
|
||||
FILE *todo_list;
|
||||
|
||||
if (prepare_branch_to_be_rebased(opts, switch_to))
|
||||
return -1;
|
||||
|
||||
if (get_revision_ranges(upstream, onto, &head_hash,
|
||||
&revisions, &shortrevisions))
|
||||
return -1;
|
||||
|
||||
if (raw_strategies)
|
||||
parse_strategy_opts(opts, raw_strategies);
|
||||
|
||||
if (init_basic_state(opts, head_name, onto, head_hash)) {
|
||||
free(revisions);
|
||||
free(shortrevisions);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!upstream && squash_onto)
|
||||
write_file(path_squash_onto(), "%s\n", squash_onto);
|
||||
|
||||
todo_list = fopen(rebase_path_todo(), "w");
|
||||
if (!todo_list) {
|
||||
free(revisions);
|
||||
free(shortrevisions);
|
||||
|
||||
return error_errno(_("could not open %s"), rebase_path_todo());
|
||||
}
|
||||
|
||||
argv_array_pushl(&make_script_args, "", revisions, NULL);
|
||||
if (restrict_revision)
|
||||
argv_array_push(&make_script_args, restrict_revision);
|
||||
|
||||
ret = sequencer_make_script(todo_list,
|
||||
make_script_args.argc, make_script_args.argv,
|
||||
flags);
|
||||
fclose(todo_list);
|
||||
|
||||
if (ret)
|
||||
error(_("could not generate todo list"));
|
||||
else {
|
||||
discard_cache();
|
||||
ret = complete_action(opts, flags, shortrevisions, onto_name, onto,
|
||||
head_hash, cmd, autosquash);
|
||||
}
|
||||
|
||||
free(revisions);
|
||||
free(shortrevisions);
|
||||
argv_array_clear(&make_script_args);
|
||||
|
||||
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 replay_opts opts = REPLAY_OPTS_INIT;
|
||||
unsigned flags = 0, keep_empty = 0, rebase_merges = 0, autosquash = 0;
|
||||
int abbreviate_commands = 0, rebase_cousins = -1, ret = 0;
|
||||
const char *onto = NULL, *onto_name = NULL, *restrict_revision = NULL,
|
||||
*squash_onto = NULL, *upstream = NULL, *head_name = NULL,
|
||||
*switch_to = NULL, *cmd = NULL;
|
||||
char *raw_strategies = NULL;
|
||||
enum {
|
||||
NONE = 0, CONTINUE, SKIP, EDIT_TODO, SHOW_CURRENT_PATCH,
|
||||
SHORTEN_OIDS, EXPAND_OIDS, CHECK_TODO_LIST, REARRANGE_SQUASH, ADD_EXEC
|
||||
} command = 0;
|
||||
struct option options[] = {
|
||||
OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
|
||||
OPT_BOOL(0, "keep-empty", &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", &rebase_merges, N_("rebase merge commits")),
|
||||
OPT_BOOL(0, "rebase-cousins", &rebase_cousins,
|
||||
N_("keep original branch points of cousins")),
|
||||
OPT_BOOL(0, "autosquash", &autosquash,
|
||||
N_("move commits that begin with squash!/fixup!")),
|
||||
OPT_BOOL(0, "signoff", &opts.signoff, N_("sign commits")),
|
||||
OPT__VERBOSE(&opts.verbose, N_("be verbose")),
|
||||
OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
|
||||
CONTINUE),
|
||||
OPT_CMDMODE(0, "skip", &command, N_("skip commit"), SKIP),
|
||||
OPT_CMDMODE(0, "edit-todo", &command, N_("edit the todo list"),
|
||||
EDIT_TODO),
|
||||
OPT_CMDMODE(0, "show-current-patch", &command, N_("show the current patch"),
|
||||
SHOW_CURRENT_PATCH),
|
||||
OPT_CMDMODE(0, "shorten-ids", &command,
|
||||
N_("shorten commit ids in the todo list"), SHORTEN_OIDS),
|
||||
OPT_CMDMODE(0, "expand-ids", &command,
|
||||
N_("expand commit ids in the todo list"), EXPAND_OIDS),
|
||||
OPT_CMDMODE(0, "check-todo-list", &command,
|
||||
N_("check the todo list"), CHECK_TODO_LIST),
|
||||
OPT_CMDMODE(0, "rearrange-squash", &command,
|
||||
N_("rearrange fixup/squash lines"), REARRANGE_SQUASH),
|
||||
OPT_CMDMODE(0, "add-exec-commands", &command,
|
||||
N_("insert exec commands in todo list"), ADD_EXEC),
|
||||
OPT_STRING(0, "onto", &onto, N_("onto"), N_("onto")),
|
||||
OPT_STRING(0, "restrict-revision", &restrict_revision,
|
||||
N_("restrict-revision"), N_("restrict revision")),
|
||||
OPT_STRING(0, "squash-onto", &squash_onto, N_("squash-onto"),
|
||||
N_("squash onto")),
|
||||
OPT_STRING(0, "upstream", &upstream, N_("upstream"),
|
||||
N_("the upstream commit")),
|
||||
OPT_STRING(0, "head-name", &head_name, N_("head-name"), N_("head name")),
|
||||
{ OPTION_STRING, 'S', "gpg-sign", &opts.gpg_sign, 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", &raw_strategies, N_("strategy-opts"),
|
||||
N_("strategy options")),
|
||||
OPT_STRING(0, "switch-to", &switch_to, N_("switch-to"),
|
||||
N_("the branch or commit to checkout")),
|
||||
OPT_STRING(0, "onto-name", &onto_name, N_("onto-name"), N_("onto name")),
|
||||
OPT_STRING(0, "cmd", &cmd, N_("cmd"), N_("the command to run")),
|
||||
OPT_RERERE_AUTOUPDATE(&opts.allow_rerere_auto),
|
||||
OPT_END()
|
||||
};
|
||||
|
||||
sequencer_init_config(&opts);
|
||||
git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
|
||||
|
||||
opts.action = REPLAY_INTERACTIVE_REBASE;
|
||||
opts.allow_ff = 1;
|
||||
opts.allow_empty = 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);
|
||||
|
||||
opts.gpg_sign = xstrdup_or_null(opts.gpg_sign);
|
||||
|
||||
flags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
|
||||
flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
|
||||
flags |= rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
|
||||
flags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
|
||||
flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
|
||||
|
||||
if (rebase_cousins >= 0 && !rebase_merges)
|
||||
warning(_("--[no-]rebase-cousins has no effect without "
|
||||
"--rebase-merges"));
|
||||
|
||||
switch (command) {
|
||||
case NONE:
|
||||
if (!onto && !upstream)
|
||||
die(_("a base commit must be provided with --upstream or --onto"));
|
||||
|
||||
ret = do_interactive_rebase(&opts, flags, switch_to, upstream, onto,
|
||||
onto_name, squash_onto, head_name, restrict_revision,
|
||||
raw_strategies, cmd, autosquash);
|
||||
break;
|
||||
case SKIP: {
|
||||
struct string_list merge_rr = STRING_LIST_INIT_DUP;
|
||||
|
||||
rerere_clear(&merge_rr);
|
||||
/* fallthrough */
|
||||
case CONTINUE:
|
||||
ret = sequencer_continue(&opts);
|
||||
break;
|
||||
}
|
||||
case EDIT_TODO:
|
||||
ret = edit_todo_list(flags);
|
||||
break;
|
||||
case 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 SHORTEN_OIDS:
|
||||
case EXPAND_OIDS:
|
||||
ret = transform_todos(flags);
|
||||
break;
|
||||
case CHECK_TODO_LIST:
|
||||
ret = check_todo_list();
|
||||
break;
|
||||
case REARRANGE_SQUASH:
|
||||
ret = rearrange_squash();
|
||||
break;
|
||||
case ADD_EXEC:
|
||||
ret = sequencer_add_exec_commands(cmd);
|
||||
break;
|
||||
default:
|
||||
BUG("invalid command '%d'", command);
|
||||
}
|
||||
|
||||
return !!ret;
|
||||
}
|
1
cache.h
1
cache.h
@ -1472,6 +1472,7 @@ extern const char *fmt_name(const char *name, const char *email);
|
||||
extern const char *ident_default_name(void);
|
||||
extern const char *ident_default_email(void);
|
||||
extern const char *git_editor(void);
|
||||
extern const char *git_sequence_editor(void);
|
||||
extern const char *git_pager(int stdout_is_tty);
|
||||
extern int is_terminal_dumb(void);
|
||||
extern int git_ident_config(const char *, const char *, void *);
|
||||
|
27
editor.c
27
editor.c
@ -1,4 +1,5 @@
|
||||
#include "cache.h"
|
||||
#include "config.h"
|
||||
#include "strbuf.h"
|
||||
#include "run-command.h"
|
||||
#include "sigchain.h"
|
||||
@ -34,10 +35,21 @@ const char *git_editor(void)
|
||||
return editor;
|
||||
}
|
||||
|
||||
int launch_editor(const char *path, struct strbuf *buffer, const char *const *env)
|
||||
const char *git_sequence_editor(void)
|
||||
{
|
||||
const char *editor = git_editor();
|
||||
const char *editor = getenv("GIT_SEQUENCE_EDITOR");
|
||||
|
||||
if (!editor)
|
||||
git_config_get_string_const("sequence.editor", &editor);
|
||||
if (!editor)
|
||||
editor = git_editor();
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
static int launch_specified_editor(const char *editor, const char *path,
|
||||
struct strbuf *buffer, const char *const *env)
|
||||
{
|
||||
if (!editor)
|
||||
return error("Terminal is dumb, but EDITOR unset");
|
||||
|
||||
@ -95,3 +107,14 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en
|
||||
return error_errno("could not read file '%s'", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int launch_editor(const char *path, struct strbuf *buffer, const char *const *env)
|
||||
{
|
||||
return launch_specified_editor(git_editor(), path, buffer, env);
|
||||
}
|
||||
|
||||
int launch_sequence_editor(const char *path, struct strbuf *buffer,
|
||||
const char *const *env)
|
||||
{
|
||||
return launch_specified_editor(git_sequence_editor(), path, buffer, env);
|
||||
}
|
||||
|
@ -135,26 +135,63 @@ finish_rebase () {
|
||||
rm -rf "$state_dir"
|
||||
}
|
||||
|
||||
run_interactive () {
|
||||
GIT_CHERRY_PICK_HELP="$resolvemsg"
|
||||
export GIT_CHERRY_PICK_HELP
|
||||
|
||||
test -n "$keep_empty" && keep_empty="--keep-empty"
|
||||
test -n "$rebase_merges" && rebase_merges="--rebase-merges"
|
||||
test -n "$rebase_cousins" && rebase_cousins="--rebase-cousins"
|
||||
test -n "$autosquash" && autosquash="--autosquash"
|
||||
test -n "$verbose" && verbose="--verbose"
|
||||
test -n "$force_rebase" && force_rebase="--no-ff"
|
||||
test -n "$restrict_revision" && \
|
||||
restrict_revision="--restrict-revision=^$restrict_revision"
|
||||
test -n "$upstream" && upstream="--upstream=$upstream"
|
||||
test -n "$onto" && onto="--onto=$onto"
|
||||
test -n "$squash_onto" && squash_onto="--squash-onto=$squash_onto"
|
||||
test -n "$onto_name" && onto_name="--onto-name=$onto_name"
|
||||
test -n "$head_name" && head_name="--head-name=$head_name"
|
||||
test -n "$strategy" && strategy="--strategy=$strategy"
|
||||
test -n "$strategy_opts" && strategy_opts="--strategy-opts=$strategy_opts"
|
||||
test -n "$switch_to" && switch_to="--switch-to=$switch_to"
|
||||
test -n "$cmd" && cmd="--cmd=$cmd"
|
||||
test -n "$action" && action="--$action"
|
||||
|
||||
exec git rebase--interactive "$action" "$keep_empty" "$rebase_merges" "$rebase_cousins" \
|
||||
"$upstream" "$onto" "$squash_onto" "$restrict_revision" \
|
||||
"$allow_empty_message" "$autosquash" "$verbose" \
|
||||
"$force_rebase" "$onto_name" "$head_name" "$strategy" \
|
||||
"$strategy_opts" "$cmd" "$switch_to" \
|
||||
"$allow_rerere_autoupdate" "$gpg_sign_opt" "$signoff"
|
||||
}
|
||||
|
||||
run_specific_rebase () {
|
||||
if [ "$interactive_rebase" = implied ]; then
|
||||
GIT_EDITOR=:
|
||||
export GIT_EDITOR
|
||||
autosquash=
|
||||
fi
|
||||
. git-rebase--$type
|
||||
|
||||
if test -z "$preserve_merges"
|
||||
if test -n "$interactive_rebase" -a -z "$preserve_merges"
|
||||
then
|
||||
git_rebase__$type
|
||||
run_interactive
|
||||
else
|
||||
git_rebase__preserve_merges
|
||||
. git-rebase--$type
|
||||
|
||||
if test -z "$preserve_merges"
|
||||
then
|
||||
git_rebase__$type
|
||||
else
|
||||
git_rebase__preserve_merges
|
||||
fi
|
||||
fi
|
||||
|
||||
ret=$?
|
||||
if test $ret -eq 0
|
||||
then
|
||||
finish_rebase
|
||||
elif test $ret -eq 2 # special exit status for rebase -i
|
||||
elif test $ret -eq 2 # special exit status for rebase -p
|
||||
then
|
||||
apply_autostash &&
|
||||
rm -rf "$state_dir" &&
|
||||
|
@ -1,283 +0,0 @@
|
||||
# This shell script fragment is sourced by git-rebase to implement
|
||||
# its interactive mode. "git rebase --interactive" makes it easy
|
||||
# to fix up commits in the middle of a series and rearrange commits.
|
||||
#
|
||||
# Copyright (c) 2006 Johannes E. Schindelin
|
||||
#
|
||||
# The original idea comes from Eric W. Biederman, in
|
||||
# https://public-inbox.org/git/m1odwkyuf5.fsf_-_@ebiederm.dsl.xmission.com/
|
||||
#
|
||||
# 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.
|
||||
todo="$state_dir"/git-rebase-todo
|
||||
|
||||
GIT_CHERRY_PICK_HELP="$resolvemsg"
|
||||
export GIT_CHERRY_PICK_HELP
|
||||
|
||||
comment_char=$(git config --get core.commentchar 2>/dev/null)
|
||||
case "$comment_char" in
|
||||
'' | auto)
|
||||
comment_char="#"
|
||||
;;
|
||||
?)
|
||||
;;
|
||||
*)
|
||||
comment_char=$(echo "$comment_char" | cut -c1)
|
||||
;;
|
||||
esac
|
||||
|
||||
orig_reflog_action="$GIT_REFLOG_ACTION"
|
||||
|
||||
comment_for_reflog () {
|
||||
case "$orig_reflog_action" in
|
||||
''|rebase*)
|
||||
GIT_REFLOG_ACTION="rebase -i ($1)"
|
||||
export GIT_REFLOG_ACTION
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
append_todo_help () {
|
||||
gettext "
|
||||
Commands:
|
||||
p, pick <commit> = use commit
|
||||
r, reword <commit> = use commit, but edit the commit message
|
||||
e, edit <commit> = use commit, but stop for amending
|
||||
s, squash <commit> = use commit, but meld into previous commit
|
||||
f, fixup <commit> = like \"squash\", but discard this commit's log message
|
||||
x, exec <command> = run command (the rest of the line) using shell
|
||||
d, drop <commit> = remove commit
|
||||
l, label <label> = label current HEAD with a name
|
||||
t, reset <label> = reset HEAD to a label
|
||||
m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
|
||||
. create a merge commit using the original merge commit's
|
||||
. message (or the oneline, if no original merge commit was
|
||||
. specified). Use -c <commit> to reword the commit message.
|
||||
|
||||
These lines can be re-ordered; they are executed from top to bottom.
|
||||
" | git stripspace --comment-lines >>"$todo"
|
||||
|
||||
if test $(get_missing_commit_check_level) = error
|
||||
then
|
||||
gettext "
|
||||
Do not remove any line. Use 'drop' explicitly to remove a commit.
|
||||
" | git stripspace --comment-lines >>"$todo"
|
||||
else
|
||||
gettext "
|
||||
If you remove a line here THAT COMMIT WILL BE LOST.
|
||||
" | git stripspace --comment-lines >>"$todo"
|
||||
fi
|
||||
}
|
||||
|
||||
die_abort () {
|
||||
apply_autostash
|
||||
rm -rf "$state_dir"
|
||||
die "$1"
|
||||
}
|
||||
|
||||
has_action () {
|
||||
test -n "$(git stripspace --strip-comments <"$1")"
|
||||
}
|
||||
|
||||
git_sequence_editor () {
|
||||
if test -z "$GIT_SEQUENCE_EDITOR"
|
||||
then
|
||||
GIT_SEQUENCE_EDITOR="$(git config sequence.editor)"
|
||||
if [ -z "$GIT_SEQUENCE_EDITOR" ]
|
||||
then
|
||||
GIT_SEQUENCE_EDITOR="$(git var GIT_EDITOR)" || return $?
|
||||
fi
|
||||
fi
|
||||
|
||||
eval "$GIT_SEQUENCE_EDITOR" '"$@"'
|
||||
}
|
||||
|
||||
expand_todo_ids() {
|
||||
git rebase--helper --expand-ids
|
||||
}
|
||||
|
||||
collapse_todo_ids() {
|
||||
git rebase--helper --shorten-ids
|
||||
}
|
||||
|
||||
# Switch to the branch in $into and notify it in the reflog
|
||||
checkout_onto () {
|
||||
GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
|
||||
output git checkout $onto || die_abort "$(gettext "could not detach HEAD")"
|
||||
git update-ref ORIG_HEAD $orig_head
|
||||
}
|
||||
|
||||
get_missing_commit_check_level () {
|
||||
check_level=$(git config --get rebase.missingCommitsCheck)
|
||||
check_level=${check_level:-ignore}
|
||||
# Don't be case sensitive
|
||||
printf '%s' "$check_level" | tr 'A-Z' 'a-z'
|
||||
}
|
||||
|
||||
# Initiate an action. If the cannot be any
|
||||
# further action it may exec a command
|
||||
# or exit and not return.
|
||||
#
|
||||
# TODO: Consider a cleaner return model so it
|
||||
# never exits and always return 0 if process
|
||||
# is complete.
|
||||
#
|
||||
# Parameter 1 is the action to initiate.
|
||||
#
|
||||
# Returns 0 if the action was able to complete
|
||||
# and if 1 if further processing is required.
|
||||
initiate_action () {
|
||||
case "$1" in
|
||||
continue)
|
||||
exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \
|
||||
--continue
|
||||
;;
|
||||
skip)
|
||||
git rerere clear
|
||||
exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \
|
||||
--continue
|
||||
;;
|
||||
edit-todo)
|
||||
git stripspace --strip-comments <"$todo" >"$todo".new
|
||||
mv -f "$todo".new "$todo"
|
||||
collapse_todo_ids
|
||||
append_todo_help
|
||||
gettext "
|
||||
You are editing the todo file of an ongoing interactive rebase.
|
||||
To continue rebase after editing, run:
|
||||
git rebase --continue
|
||||
|
||||
" | git stripspace --comment-lines >>"$todo"
|
||||
|
||||
git_sequence_editor "$todo" ||
|
||||
die "$(gettext "Could not execute editor")"
|
||||
expand_todo_ids
|
||||
|
||||
exit
|
||||
;;
|
||||
show-current-patch)
|
||||
exec git show REBASE_HEAD --
|
||||
;;
|
||||
*)
|
||||
return 1 # continue
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
setup_reflog_action () {
|
||||
comment_for_reflog start
|
||||
|
||||
if test ! -z "$switch_to"
|
||||
then
|
||||
GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to"
|
||||
output git checkout "$switch_to" -- ||
|
||||
die "$(eval_gettext "Could not checkout \$switch_to")"
|
||||
|
||||
comment_for_reflog start
|
||||
fi
|
||||
}
|
||||
|
||||
init_basic_state () {
|
||||
orig_head=$(git rev-parse --verify HEAD) || die "$(gettext "No HEAD?")"
|
||||
mkdir -p "$state_dir" || die "$(eval_gettext "Could not create temporary \$state_dir")"
|
||||
rm -f "$(git rev-parse --git-path REBASE_HEAD)"
|
||||
|
||||
: > "$state_dir"/interactive || die "$(gettext "Could not mark as interactive")"
|
||||
write_basic_state
|
||||
}
|
||||
|
||||
init_revisions_and_shortrevisions () {
|
||||
shorthead=$(git rev-parse --short $orig_head)
|
||||
shortonto=$(git rev-parse --short $onto)
|
||||
if test -z "$rebase_root"
|
||||
# this is now equivalent to ! -z "$upstream"
|
||||
then
|
||||
shortupstream=$(git rev-parse --short $upstream)
|
||||
revisions=$upstream...$orig_head
|
||||
shortrevisions=$shortupstream..$shorthead
|
||||
else
|
||||
revisions=$onto...$orig_head
|
||||
shortrevisions=$shorthead
|
||||
test -z "$squash_onto" ||
|
||||
echo "$squash_onto" >"$state_dir"/squash-onto
|
||||
fi
|
||||
}
|
||||
|
||||
complete_action() {
|
||||
test -s "$todo" || echo noop >> "$todo"
|
||||
test -z "$autosquash" || git rebase--helper --rearrange-squash || exit
|
||||
test -n "$cmd" && git rebase--helper --add-exec-commands "$cmd"
|
||||
|
||||
todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
|
||||
todocount=${todocount##* }
|
||||
|
||||
cat >>"$todo" <<EOF
|
||||
|
||||
$comment_char $(eval_ngettext \
|
||||
"Rebase \$shortrevisions onto \$shortonto (\$todocount command)" \
|
||||
"Rebase \$shortrevisions onto \$shortonto (\$todocount commands)" \
|
||||
"$todocount")
|
||||
EOF
|
||||
append_todo_help
|
||||
gettext "
|
||||
However, if you remove everything, the rebase will be aborted.
|
||||
|
||||
" | git stripspace --comment-lines >>"$todo"
|
||||
|
||||
if test -z "$keep_empty"
|
||||
then
|
||||
printf '%s\n' "$comment_char $(gettext "Note that empty commits are commented out")" >>"$todo"
|
||||
fi
|
||||
|
||||
|
||||
has_action "$todo" ||
|
||||
return 2
|
||||
|
||||
cp "$todo" "$todo".backup
|
||||
collapse_todo_ids
|
||||
git_sequence_editor "$todo" ||
|
||||
die_abort "$(gettext "Could not execute editor")"
|
||||
|
||||
has_action "$todo" ||
|
||||
return 2
|
||||
|
||||
git rebase--helper --check-todo-list || {
|
||||
ret=$?
|
||||
checkout_onto
|
||||
exit $ret
|
||||
}
|
||||
|
||||
expand_todo_ids
|
||||
|
||||
test -n "$force_rebase" ||
|
||||
onto="$(git rebase--helper --skip-unnecessary-picks)" ||
|
||||
die "Could not skip unnecessary pick commands"
|
||||
|
||||
checkout_onto
|
||||
require_clean_work_tree "rebase"
|
||||
exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \
|
||||
--continue
|
||||
}
|
||||
|
||||
git_rebase__interactive () {
|
||||
initiate_action "$action"
|
||||
ret=$?
|
||||
if test $ret = 0; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
setup_reflog_action
|
||||
init_basic_state
|
||||
|
||||
init_revisions_and_shortrevisions
|
||||
|
||||
git rebase--helper --make-script ${keep_empty:+--keep-empty} \
|
||||
${rebase_merges:+--rebase-merges} \
|
||||
${rebase_cousins:+--rebase-cousins} \
|
||||
$revisions ${restrict_revision+^$restrict_revision} >"$todo" ||
|
||||
die "$(gettext "Could not generate todo list")"
|
||||
|
||||
complete_action
|
||||
}
|
@ -711,11 +711,11 @@ do_rest () {
|
||||
}
|
||||
|
||||
expand_todo_ids() {
|
||||
git rebase--helper --expand-ids
|
||||
git rebase--interactive --expand-ids
|
||||
}
|
||||
|
||||
collapse_todo_ids() {
|
||||
git rebase--helper --shorten-ids
|
||||
git rebase--interactive --shorten-ids
|
||||
}
|
||||
|
||||
# Switch to the branch in $into and notify it in the reflog
|
||||
@ -876,8 +876,8 @@ init_revisions_and_shortrevisions () {
|
||||
|
||||
complete_action() {
|
||||
test -s "$todo" || echo noop >> "$todo"
|
||||
test -z "$autosquash" || git rebase--helper --rearrange-squash || exit
|
||||
test -n "$cmd" && git rebase--helper --add-exec-commands "$cmd"
|
||||
test -z "$autosquash" || git rebase--interactive --rearrange-squash || exit
|
||||
test -n "$cmd" && git rebase--interactive --add-exec-commands --cmd "$cmd"
|
||||
|
||||
todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
|
||||
todocount=${todocount##* }
|
||||
@ -912,7 +912,7 @@ However, if you remove everything, the rebase will be aborted.
|
||||
has_action "$todo" ||
|
||||
return 2
|
||||
|
||||
git rebase--helper --check-todo-list || {
|
||||
git rebase--interactive --check-todo-list || {
|
||||
ret=$?
|
||||
checkout_onto
|
||||
exit $ret
|
||||
|
2
git.c
2
git.c
@ -527,7 +527,7 @@ static struct cmd_struct commands[] = {
|
||||
* RUN_SETUP | NEED_WORK_TREE
|
||||
*/
|
||||
{ "rebase", cmd_rebase },
|
||||
{ "rebase--helper", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE },
|
||||
{ "rebase--interactive", cmd_rebase__interactive, RUN_SETUP | NEED_WORK_TREE },
|
||||
{ "receive-pack", cmd_receive_pack },
|
||||
{ "reflog", cmd_reflog, RUN_SETUP },
|
||||
{ "remote", cmd_remote, RUN_SETUP },
|
||||
|
90
rebase-interactive.c
Normal file
90
rebase-interactive.c
Normal file
@ -0,0 +1,90 @@
|
||||
#include "cache.h"
|
||||
#include "commit.h"
|
||||
#include "rebase-interactive.h"
|
||||
#include "sequencer.h"
|
||||
#include "strbuf.h"
|
||||
|
||||
void append_todo_help(unsigned edit_todo, unsigned keep_empty,
|
||||
struct strbuf *buf)
|
||||
{
|
||||
const char *msg = _("\nCommands:\n"
|
||||
"p, pick <commit> = use commit\n"
|
||||
"r, reword <commit> = use commit, but edit the commit message\n"
|
||||
"e, edit <commit> = use commit, but stop for amending\n"
|
||||
"s, squash <commit> = use commit, but meld into previous commit\n"
|
||||
"f, fixup <commit> = like \"squash\", but discard this commit's log message\n"
|
||||
"x, exec <command> = run command (the rest of the line) using shell\n"
|
||||
"d, drop <commit> = remove commit\n"
|
||||
"l, label <label> = label current HEAD with a name\n"
|
||||
"t, reset <label> = reset HEAD to a label\n"
|
||||
"m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n"
|
||||
". create a merge commit using the original merge commit's\n"
|
||||
". message (or the oneline, if no original merge commit was\n"
|
||||
". specified). Use -c <commit> to reword the commit message.\n"
|
||||
"\n"
|
||||
"These lines can be re-ordered; they are executed from top to bottom.\n");
|
||||
|
||||
strbuf_add_commented_lines(buf, msg, strlen(msg));
|
||||
|
||||
if (get_missing_commit_check_level() == MISSING_COMMIT_CHECK_ERROR)
|
||||
msg = _("\nDo not remove any line. Use 'drop' "
|
||||
"explicitly to remove a commit.\n");
|
||||
else
|
||||
msg = _("\nIf you remove a line here "
|
||||
"THAT COMMIT WILL BE LOST.\n");
|
||||
|
||||
strbuf_add_commented_lines(buf, msg, strlen(msg));
|
||||
|
||||
if (edit_todo)
|
||||
msg = _("\nYou are editing the todo file "
|
||||
"of an ongoing interactive rebase.\n"
|
||||
"To continue rebase after editing, run:\n"
|
||||
" git rebase --continue\n\n");
|
||||
else
|
||||
msg = _("\nHowever, if you remove everything, "
|
||||
"the rebase will be aborted.\n\n");
|
||||
|
||||
strbuf_add_commented_lines(buf, msg, strlen(msg));
|
||||
|
||||
if (!keep_empty) {
|
||||
msg = _("Note that empty commits are commented out");
|
||||
strbuf_add_commented_lines(buf, msg, strlen(msg));
|
||||
}
|
||||
}
|
||||
|
||||
int edit_todo_list(unsigned flags)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
const char *todo_file = rebase_path_todo();
|
||||
|
||||
if (strbuf_read_file(&buf, todo_file, 0) < 0)
|
||||
return error_errno(_("could not read '%s'."), todo_file);
|
||||
|
||||
strbuf_stripspace(&buf, 1);
|
||||
if (write_message(buf.buf, buf.len, todo_file, 0)) {
|
||||
strbuf_release(&buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
strbuf_release(&buf);
|
||||
|
||||
transform_todos(flags | TODO_LIST_SHORTEN_IDS);
|
||||
|
||||
if (strbuf_read_file(&buf, todo_file, 0) < 0)
|
||||
return error_errno(_("could not read '%s'."), todo_file);
|
||||
|
||||
append_todo_help(1, 0, &buf);
|
||||
if (write_message(buf.buf, buf.len, todo_file, 0)) {
|
||||
strbuf_release(&buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
strbuf_release(&buf);
|
||||
|
||||
if (launch_sequence_editor(todo_file, NULL, NULL))
|
||||
return -1;
|
||||
|
||||
transform_todos(flags & ~(TODO_LIST_SHORTEN_IDS));
|
||||
|
||||
return 0;
|
||||
}
|
8
rebase-interactive.h
Normal file
8
rebase-interactive.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef REBASE_INTERACTIVE_H
|
||||
#define REBASE_INTERACTIVE_H
|
||||
|
||||
void append_todo_help(unsigned edit_todo, unsigned keep_empty,
|
||||
struct strbuf *buf);
|
||||
int edit_todo_list(unsigned flags);
|
||||
|
||||
#endif
|
320
sequencer.c
320
sequencer.c
@ -30,6 +30,7 @@
|
||||
#include "oidset.h"
|
||||
#include "commit-slab.h"
|
||||
#include "alias.h"
|
||||
#include "rebase-interactive.h"
|
||||
|
||||
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
|
||||
|
||||
@ -52,7 +53,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
|
||||
@ -140,7 +144,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")
|
||||
@ -152,6 +156,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)
|
||||
{
|
||||
@ -373,8 +378,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;
|
||||
|
||||
@ -769,6 +774,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
|
||||
@ -823,18 +845,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");
|
||||
@ -864,21 +879,10 @@ 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);
|
||||
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)
|
||||
@ -2202,21 +2206,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++) {
|
||||
@ -2227,6 +2224,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)) {
|
||||
@ -2294,6 +2303,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)
|
||||
{
|
||||
@ -3228,6 +3286,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"
|
||||
@ -4333,24 +4440,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);
|
||||
@ -4362,7 +4465,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;
|
||||
@ -4379,7 +4482,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 */
|
||||
@ -4414,7 +4517,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,
|
||||
@ -4460,17 +4563,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"));
|
||||
}
|
||||
@ -4500,9 +4603,9 @@ int skip_unnecessary_picks(void)
|
||||
if (item->commit->parents->next)
|
||||
break; /* merge commit */
|
||||
parent_oid = &item->commit->parents->item->object.oid;
|
||||
if (hashcmp(parent_oid->hash, oid->hash))
|
||||
if (hashcmp(parent_oid->hash, output_oid->hash))
|
||||
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);
|
||||
@ -4531,15 +4634,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;
|
||||
|
22
sequencer.h
22
sequencer.h
@ -3,6 +3,7 @@
|
||||
|
||||
const char *git_path_commit_editmsg(void);
|
||||
const char *git_path_seq_dir(void);
|
||||
const char *rebase_path_todo(void);
|
||||
|
||||
#define APPEND_SIGNOFF_DEDUP (1u << 0)
|
||||
|
||||
@ -57,6 +58,15 @@ struct replay_opts {
|
||||
};
|
||||
#define REPLAY_OPTS_INIT { .action = -1, .current_fixups = STRBUF_INIT }
|
||||
|
||||
enum missing_commit_check_level {
|
||||
MISSING_COMMIT_CHECK_IGNORE = 0,
|
||||
MISSING_COMMIT_CHECK_WARN,
|
||||
MISSING_COMMIT_CHECK_ERROR
|
||||
};
|
||||
|
||||
int write_message(const void *buf, size_t len, const char *filename,
|
||||
int append_eol);
|
||||
|
||||
/* Call this to setup defaults before parsing command line options */
|
||||
void sequencer_init_config(struct replay_opts *opts);
|
||||
int sequencer_pick_revisions(struct replay_opts *opts);
|
||||
@ -79,8 +89,12 @@ int sequencer_make_script(FILE *out, int argc, const char **argv,
|
||||
|
||||
int sequencer_add_exec_commands(const char *command);
|
||||
int transform_todos(unsigned flags);
|
||||
enum missing_commit_check_level get_missing_commit_check_level(void);
|
||||
int check_todo_list(void);
|
||||
int skip_unnecessary_picks(void);
|
||||
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);
|
||||
int rearrange_squash(void);
|
||||
|
||||
extern const char sign_off_header[];
|
||||
@ -98,8 +112,14 @@ int update_head_with_reflog(const struct commit *old_head,
|
||||
void commit_post_rewrite(const struct commit *current_head,
|
||||
const struct object_id *new_head);
|
||||
|
||||
int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit);
|
||||
|
||||
#define SUMMARY_INITIAL_COMMIT (1 << 0)
|
||||
#define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)
|
||||
void print_commit_summary(const char *prefix, const struct object_id *oid,
|
||||
unsigned int flags);
|
||||
#endif
|
||||
|
||||
void parse_strategy_opts(struct replay_opts *opts, char *raw_opts);
|
||||
int write_basic_state(struct replay_opts *opts, const char *head_name,
|
||||
const char *onto, const char *orig_head);
|
||||
|
2
strbuf.h
2
strbuf.h
@ -578,6 +578,8 @@ extern void strbuf_add_unique_abbrev(struct strbuf *sb,
|
||||
* file's contents are not read into the buffer upon completion.
|
||||
*/
|
||||
extern int launch_editor(const char *path, struct strbuf *buffer, const char *const *env);
|
||||
extern int launch_sequence_editor(const char *path, struct strbuf *buffer,
|
||||
const char *const *env);
|
||||
|
||||
extern void strbuf_add_lines(struct strbuf *sb, const char *prefix, const char *buf, size_t size);
|
||||
|
||||
|
@ -75,6 +75,16 @@ test_expect_success 'rebase --keep-empty' '
|
||||
test_line_count = 6 actual
|
||||
'
|
||||
|
||||
cat > expect <<EOF
|
||||
error: nothing to do
|
||||
EOF
|
||||
|
||||
test_expect_success 'rebase -i with empty HEAD' '
|
||||
set_fake_editor &&
|
||||
test_must_fail env FAKE_LINES="1 exec_true" git rebase -i HEAD^ >actual 2>&1 &&
|
||||
test_i18ncmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'rebase -i with the exec command' '
|
||||
git checkout master &&
|
||||
(
|
||||
|
Loading…
Reference in New Issue
Block a user