Skip to content

Commit be9c724

Browse files
committed
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]>
1 parent 4c39918 commit be9c724

File tree

4 files changed

+76
-22
lines changed

4 files changed

+76
-22
lines changed

builtin/rebase--helper.c

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

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

git-rebase--interactive.sh

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,7 @@ collapse_todo_ids() {
775775
# each log message will be re-retrieved in order to normalize the
776776
# autosquash arrangement
777777
rearrange_squash () {
778+
format=$(git config --get rebase.instructionFormat)
778779
# extract fixup!/squash! lines and resolve any referenced sha1's
779780
while read -r pick sha1 message
780781
do
@@ -1203,26 +1204,27 @@ else
12031204
revisions=$onto...$orig_head
12041205
shortrevisions=$shorthead
12051206
fi
1206-
format=$(git config --get rebase.instructionFormat)
1207-
# the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse
1208-
git rev-list $merges_option --format="%m%H ${format:-%s}" \
1209-
--reverse --left-right --topo-order \
1210-
$revisions ${restrict_revision+^$restrict_revision} | \
1211-
sed -n "s/^>//p" |
1212-
while read -r sha1 rest
1213-
do
1214-
1215-
if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1
1216-
then
1217-
comment_out="$comment_char "
1218-
else
1219-
comment_out=
1220-
fi
1207+
if test t != "$preserve_merges"
1208+
then
1209+
git rebase--helper --make-script ${keep_empty:+--keep-empty} \
1210+
$revisions ${restrict_revision+^$restrict_revision} >"$todo"
1211+
else
1212+
format=$(git config --get rebase.instructionFormat)
1213+
# the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse
1214+
git rev-list $merges_option --format="%m%H ${format:-%s}" \
1215+
--reverse --left-right --topo-order \
1216+
$revisions ${restrict_revision+^$restrict_revision} | \
1217+
sed -n "s/^>//p" |
1218+
while read -r sha1 rest
1219+
do
1220+
1221+
if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1
1222+
then
1223+
comment_out="$comment_char "
1224+
else
1225+
comment_out=
1226+
fi
12211227

1222-
if test t != "$preserve_merges"
1223-
then
1224-
printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo"
1225-
else
12261228
if test -z "$rebase_root"
12271229
then
12281230
preserve=t
@@ -1241,8 +1243,8 @@ do
12411243
touch "$rewritten"/$sha1
12421244
printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo"
12431245
fi
1244-
fi
1245-
done
1246+
done
1247+
fi
12461248

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

sequencer.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2347,3 +2347,47 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
23472347

23482348
strbuf_release(&sob);
23492349
}
2350+
2351+
int sequencer_make_script(int keep_empty, FILE *out,
2352+
int argc, const char **argv)
2353+
{
2354+
char *format = "%s";
2355+
struct pretty_print_context pp = {0};
2356+
struct strbuf buf = STRBUF_INIT;
2357+
struct rev_info revs;
2358+
struct commit *commit;
2359+
2360+
init_revisions(&revs, NULL);
2361+
revs.verbose_header = 1;
2362+
revs.max_parents = 1;
2363+
revs.cherry_pick = 1;
2364+
revs.limited = 1;
2365+
revs.reverse = 1;
2366+
revs.right_only = 1;
2367+
revs.sort_order = REV_SORT_IN_GRAPH_ORDER;
2368+
revs.topo_order = 1;
2369+
2370+
revs.pretty_given = 1;
2371+
git_config_get_string("rebase.instructionFormat", &format);
2372+
get_commit_format(format, &revs);
2373+
pp.fmt = revs.commit_format;
2374+
pp.output_encoding = get_log_output_encoding();
2375+
2376+
if (setup_revisions(argc, argv, &revs, NULL) > 1)
2377+
return error("make_script: unhandled options");
2378+
2379+
if (prepare_revision_walk(&revs) < 0)
2380+
return error("make_script: error preparing revisions");
2381+
2382+
while ((commit = get_revision(&revs))) {
2383+
strbuf_reset(&buf);
2384+
if (!keep_empty && is_original_commit_empty(commit))
2385+
strbuf_addf(&buf, "%c ", comment_line_char);
2386+
strbuf_addf(&buf, "pick %s ", oid_to_hex(&commit->object.oid));
2387+
pretty_print_commit(&pp, commit, &buf);
2388+
strbuf_addch(&buf, '\n');
2389+
fputs(buf.buf, out);
2390+
}
2391+
strbuf_release(&buf);
2392+
return 0;
2393+
}

sequencer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ int sequencer_remove_state(struct replay_opts *opts);
5858
int sequencer_commit(const char *defmsg, struct replay_opts *opts,
5959
int allow_empty, int edit, int amend,
6060
int cleanup_commit_message);
61+
int sequencer_make_script(int keep_empty, FILE *out,
62+
int argc, const char **argv);
6163

6264
extern const char sign_off_header[];
6365

0 commit comments

Comments
 (0)