Skip to content

Commit 62db524

Browse files
dschogitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 4e7524e commit 62db524

File tree

4 files changed

+82
-22
lines changed

4 files changed

+82
-22
lines changed

builtin/rebase--helper.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,19 @@ static const char * const builtin_rebase_helper_usage[] = {
1212
int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
1313
{
1414
struct replay_opts opts = REPLAY_OPTS_INIT;
15+
int keep_empty = 0;
1516
enum {
16-
CONTINUE = 1, ABORT
17+
CONTINUE = 1, ABORT, MAKE_SCRIPT
1718
} command = 0;
1819
struct option options[] = {
1920
OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
21+
OPT_BOOL(0, "keep-empty", &keep_empty, N_("keep empty commits")),
2022
OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
2123
CONTINUE),
2224
OPT_CMDMODE(0, "abort", &command, N_("abort rebase"),
2325
ABORT),
26+
OPT_CMDMODE(0, "make-script", &command,
27+
N_("make rebase script"), MAKE_SCRIPT),
2428
OPT_END()
2529
};
2630

@@ -37,5 +41,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
3741
return !!sequencer_continue(&opts);
3842
if (command == ABORT && argc == 1)
3943
return !!sequencer_remove_state(&opts);
44+
if (command == MAKE_SCRIPT && argc > 1)
45+
return !!sequencer_make_script(keep_empty, stdout, argc, argv);
4046
usage_with_options(builtin_rebase_helper_usage, options);
4147
}

git-rebase--interactive.sh

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,7 @@ collapse_todo_ids() {
785785
# each log message will be re-retrieved in order to normalize the
786786
# autosquash arrangement
787787
rearrange_squash () {
788+
format=$(git config --get rebase.instructionFormat)
788789
# extract fixup!/squash! lines and resolve any referenced sha1's
789790
while read -r pick sha1 message
790791
do
@@ -1210,26 +1211,27 @@ else
12101211
revisions=$onto...$orig_head
12111212
shortrevisions=$shorthead
12121213
fi
1213-
format=$(git config --get rebase.instructionFormat)
1214-
# the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse
1215-
git rev-list $merges_option --format="%m%H ${format:-%s}" \
1216-
--reverse --left-right --topo-order \
1217-
$revisions ${restrict_revision+^$restrict_revision} | \
1218-
sed -n "s/^>//p" |
1219-
while read -r sha1 rest
1220-
do
1221-
1222-
if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1
1223-
then
1224-
comment_out="$comment_char "
1225-
else
1226-
comment_out=
1227-
fi
1214+
if test t != "$preserve_merges"
1215+
then
1216+
git rebase--helper --make-script ${keep_empty:+--keep-empty} \
1217+
$revisions ${restrict_revision+^$restrict_revision} >"$todo"
1218+
else
1219+
format=$(git config --get rebase.instructionFormat)
1220+
# the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse
1221+
git rev-list $merges_option --format="%m%H ${format:-%s}" \
1222+
--reverse --left-right --topo-order \
1223+
$revisions ${restrict_revision+^$restrict_revision} | \
1224+
sed -n "s/^>//p" |
1225+
while read -r sha1 rest
1226+
do
1227+
1228+
if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1
1229+
then
1230+
comment_out="$comment_char "
1231+
else
1232+
comment_out=
1233+
fi
12281234

1229-
if test t != "$preserve_merges"
1230-
then
1231-
printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo"
1232-
else
12331235
if test -z "$rebase_root"
12341236
then
12351237
preserve=t
@@ -1248,8 +1250,8 @@ do
12481250
touch "$rewritten"/$sha1
12491251
printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo"
12501252
fi
1251-
fi
1252-
done
1253+
done
1254+
fi
12531255

12541256
# Watch for commits that been dropped by --cherry-pick
12551257
if test t = "$preserve_merges"

sequencer.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2413,3 +2413,52 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
24132413

24142414
strbuf_release(&sob);
24152415
}
2416+
2417+
int sequencer_make_script(int keep_empty, FILE *out,
2418+
int argc, const char **argv)
2419+
{
2420+
char *format = NULL;
2421+
struct pretty_print_context pp = {0};
2422+
struct strbuf buf = STRBUF_INIT;
2423+
struct rev_info revs;
2424+
struct commit *commit;
2425+
2426+
init_revisions(&revs, NULL);
2427+
revs.verbose_header = 1;
2428+
revs.max_parents = 1;
2429+
revs.cherry_pick = 1;
2430+
revs.limited = 1;
2431+
revs.reverse = 1;
2432+
revs.right_only = 1;
2433+
revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
2434+
revs.topo_order = 1;
2435+
2436+
revs.pretty_given = 1;
2437+
git_config_get_string("rebase.instructionFormat", &format);
2438+
if (!format || !*format) {
2439+
free(format);
2440+
format = xstrdup("%s");
2441+
}
2442+
get_commit_format(format, &revs);
2443+
free(format);
2444+
pp.fmt = revs.commit_format;
2445+
pp.output_encoding = get_log_output_encoding();
2446+
2447+
if (setup_revisions(argc, argv, &revs, NULL) > 1)
2448+
return error(_("make_script: unhandled options"));
2449+
2450+
if (prepare_revision_walk(&revs) < 0)
2451+
return error(_("make_script: error preparing revisions"));
2452+
2453+
while ((commit = get_revision(&revs))) {
2454+
strbuf_reset(&buf);
2455+
if (!keep_empty && is_original_commit_empty(commit))
2456+
strbuf_addf(&buf, "%c ", comment_line_char);
2457+
strbuf_addf(&buf, "pick %s ", oid_to_hex(&commit->object.oid));
2458+
pretty_print_commit(&pp, commit, &buf);
2459+
strbuf_addch(&buf, '\n');
2460+
fputs(buf.buf, out);
2461+
}
2462+
strbuf_release(&buf);
2463+
return 0;
2464+
}

sequencer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ int sequencer_continue(struct replay_opts *opts);
4545
int sequencer_rollback(struct replay_opts *opts);
4646
int sequencer_remove_state(struct replay_opts *opts);
4747

48+
int sequencer_make_script(int keep_empty, FILE *out,
49+
int argc, const char **argv);
50+
4851
extern const char sign_off_header[];
4952

5053
void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag);

0 commit comments

Comments
 (0)