rebase -i: generate the script via rebase--helper

The first step of an interactive rebase is to generate the so-called "todo
script", to be stored in the state directory as "git-rebase-todo" and to
be edited by the user.

Originally, we adjusted the output of `git log <options>` using a simple
sed script. Over the course of the years, the code became more
complicated. We now use shell scripting to edit the output of `git log`
conditionally, depending whether to keep "empty" commits (i.e. commits
that do not change any files).

On platforms where shell scripting is not native, this can be a serious
drag. And it opens the door for incompatibilities between platforms when
it comes to shell scripting or to Unix-y commands.

Let's just re-implement the todo script generation in plain C, using the
revision machinery directly.

This is substantially faster, improving the speed relative to the
shell script version of the interactive rebase from 2x to 3x on Windows.

Note that the rearrange_squash() function in git-rebase--interactive
relied on the fact that we set the "format" variable to the config setting
rebase.instructionFormat. Relying on a side effect like this is no good,
hence we explicitly perform that assignment (possibly again) in
rearrange_squash().

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Johannes Schindelin 2017-07-14 16:44:58 +02:00 коммит произвёл Junio C Hamano
Родитель 4e7524e012
Коммит 62db524779
4 изменённых файлов: 81 добавлений и 21 удалений

Просмотреть файл

@ -12,15 +12,19 @@ static const char * const builtin_rebase_helper_usage[] = {
int cmd_rebase__helper(int argc, const char **argv, const char *prefix) int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
{ {
struct replay_opts opts = REPLAY_OPTS_INIT; struct replay_opts opts = REPLAY_OPTS_INIT;
int keep_empty = 0;
enum { enum {
CONTINUE = 1, ABORT CONTINUE = 1, ABORT, MAKE_SCRIPT
} command = 0; } command = 0;
struct option options[] = { struct option options[] = {
OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")), OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
OPT_BOOL(0, "keep-empty", &keep_empty, N_("keep empty commits")),
OPT_CMDMODE(0, "continue", &command, N_("continue rebase"), OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
CONTINUE), CONTINUE),
OPT_CMDMODE(0, "abort", &command, N_("abort rebase"), OPT_CMDMODE(0, "abort", &command, N_("abort rebase"),
ABORT), ABORT),
OPT_CMDMODE(0, "make-script", &command,
N_("make rebase script"), MAKE_SCRIPT),
OPT_END() OPT_END()
}; };
@ -37,5 +41,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
return !!sequencer_continue(&opts); return !!sequencer_continue(&opts);
if (command == ABORT && argc == 1) if (command == ABORT && argc == 1)
return !!sequencer_remove_state(&opts); return !!sequencer_remove_state(&opts);
if (command == MAKE_SCRIPT && argc > 1)
return !!sequencer_make_script(keep_empty, stdout, argc, argv);
usage_with_options(builtin_rebase_helper_usage, options); usage_with_options(builtin_rebase_helper_usage, options);
} }

Просмотреть файл

@ -785,6 +785,7 @@ collapse_todo_ids() {
# each log message will be re-retrieved in order to normalize the # each log message will be re-retrieved in order to normalize the
# autosquash arrangement # autosquash arrangement
rearrange_squash () { rearrange_squash () {
format=$(git config --get rebase.instructionFormat)
# extract fixup!/squash! lines and resolve any referenced sha1's # extract fixup!/squash! lines and resolve any referenced sha1's
while read -r pick sha1 message while read -r pick sha1 message
do do
@ -1210,26 +1211,27 @@ else
revisions=$onto...$orig_head revisions=$onto...$orig_head
shortrevisions=$shorthead shortrevisions=$shorthead
fi fi
format=$(git config --get rebase.instructionFormat) if test t != "$preserve_merges"
# the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse then
git rev-list $merges_option --format="%m%H ${format:-%s}" \ git rebase--helper --make-script ${keep_empty:+--keep-empty} \
--reverse --left-right --topo-order \ $revisions ${restrict_revision+^$restrict_revision} >"$todo"
$revisions ${restrict_revision+^$restrict_revision} | \ else
sed -n "s/^>//p" | format=$(git config --get rebase.instructionFormat)
while read -r sha1 rest # the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse
do git rev-list $merges_option --format="%m%H ${format:-%s}" \
--reverse --left-right --topo-order \
$revisions ${restrict_revision+^$restrict_revision} | \
sed -n "s/^>//p" |
while read -r sha1 rest
do
if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1 if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1
then then
comment_out="$comment_char " comment_out="$comment_char "
else else
comment_out= comment_out=
fi fi
if test t != "$preserve_merges"
then
printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo"
else
if test -z "$rebase_root" if test -z "$rebase_root"
then then
preserve=t preserve=t
@ -1248,8 +1250,8 @@ do
touch "$rewritten"/$sha1 touch "$rewritten"/$sha1
printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo" printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo"
fi fi
fi done
done fi
# Watch for commits that been dropped by --cherry-pick # Watch for commits that been dropped by --cherry-pick
if test t = "$preserve_merges" if test t = "$preserve_merges"

Просмотреть файл

@ -2413,3 +2413,52 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
strbuf_release(&sob); strbuf_release(&sob);
} }
int sequencer_make_script(int keep_empty, FILE *out,
int argc, const char **argv)
{
char *format = NULL;
struct pretty_print_context pp = {0};
struct strbuf buf = STRBUF_INIT;
struct rev_info revs;
struct commit *commit;
init_revisions(&revs, NULL);
revs.verbose_header = 1;
revs.max_parents = 1;
revs.cherry_pick = 1;
revs.limited = 1;
revs.reverse = 1;
revs.right_only = 1;
revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
revs.topo_order = 1;
revs.pretty_given = 1;
git_config_get_string("rebase.instructionFormat", &format);
if (!format || !*format) {
free(format);
format = xstrdup("%s");
}
get_commit_format(format, &revs);
free(format);
pp.fmt = revs.commit_format;
pp.output_encoding = get_log_output_encoding();
if (setup_revisions(argc, argv, &revs, NULL) > 1)
return error(_("make_script: unhandled options"));
if (prepare_revision_walk(&revs) < 0)
return error(_("make_script: error preparing revisions"));
while ((commit = get_revision(&revs))) {
strbuf_reset(&buf);
if (!keep_empty && is_original_commit_empty(commit))
strbuf_addf(&buf, "%c ", comment_line_char);
strbuf_addf(&buf, "pick %s ", oid_to_hex(&commit->object.oid));
pretty_print_commit(&pp, commit, &buf);
strbuf_addch(&buf, '\n');
fputs(buf.buf, out);
}
strbuf_release(&buf);
return 0;
}

Просмотреть файл

@ -45,6 +45,9 @@ int sequencer_continue(struct replay_opts *opts);
int sequencer_rollback(struct replay_opts *opts); int sequencer_rollback(struct replay_opts *opts);
int sequencer_remove_state(struct replay_opts *opts); int sequencer_remove_state(struct replay_opts *opts);
int sequencer_make_script(int keep_empty, FILE *out,
int argc, const char **argv);
extern const char sign_off_header[]; extern const char sign_off_header[];
void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag); void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag);