Skip to content

Commit 35aa419

Browse files
committed
rebase -i: skip unnecessary picks using the rebase--helper
In particular on Windows, where shell scripts are even more expensive than on MacOSX or Linux, it makes sense to move a loop that forks Git at least once for every line in the todo list into a builtin. Note: The original code did not try to skip unnecessary picks of root commits but punts instead (probably --root was not considered common enough of a use case to bother optimizing). We do the same, for now. Signed-off-by: Johannes Schindelin <[email protected]>
1 parent fa01259 commit 35aa419

File tree

4 files changed

+96
-39
lines changed

4 files changed

+96
-39
lines changed

builtin/rebase--helper.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
1414
int keep_empty = 0;
1515
enum {
1616
CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_SHA1S, EXPAND_SHA1S,
17-
CHECK_TODO_LIST
17+
CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS
1818
} command = 0;
1919
struct option options[] = {
2020
OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
@@ -31,6 +31,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
3131
N_("expand SHA-1s in the todo list"), EXPAND_SHA1S),
3232
OPT_CMDMODE(0, "check-todo-list", &command,
3333
N_("check the todo list"), CHECK_TODO_LIST),
34+
OPT_CMDMODE(0, "skip-unnecessary-picks", &command,
35+
N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS),
3436
OPT_END()
3537
};
3638

@@ -55,5 +57,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
5557
return !!transform_todo_ids(0);
5658
if (command == CHECK_TODO_LIST && argc == 1)
5759
return !!check_todo_list();
60+
if (command == SKIP_UNNECESSARY_PICKS && argc == 1)
61+
return !!skip_unnecessary_picks();
5862
usage_with_options(builtin_rebase_helper_usage, options);
5963
}

git-rebase--interactive.sh

Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -703,43 +703,6 @@ do_rest () {
703703
done
704704
}
705705

706-
# skip picking commits whose parents are unchanged
707-
skip_unnecessary_picks () {
708-
fd=3
709-
while read -r command rest
710-
do
711-
# fd=3 means we skip the command
712-
case "$fd,$command" in
713-
3,pick|3,p)
714-
# pick a commit whose parent is current $onto -> skip
715-
sha1=${rest%% *}
716-
case "$(git rev-parse --verify --quiet "$sha1"^)" in
717-
"$onto"*)
718-
onto=$sha1
719-
;;
720-
*)
721-
fd=1
722-
;;
723-
esac
724-
;;
725-
3,"$comment_char"*|3,)
726-
# copy comments
727-
;;
728-
*)
729-
fd=1
730-
;;
731-
esac
732-
printf '%s\n' "$command${rest:+ }$rest" >&$fd
733-
done <"$todo" >"$todo.new" 3>>"$done" &&
734-
mv -f "$todo".new "$todo" &&
735-
case "$(peek_next_command)" in
736-
squash|s|fixup|f)
737-
record_in_rewritten "$onto"
738-
;;
739-
esac ||
740-
die "$(gettext "Could not skip unnecessary pick commands")"
741-
}
742-
743706
transform_todo_ids () {
744707
while read -r command rest
745708
do
@@ -1165,7 +1128,9 @@ git rebase--helper --check-todo-list || {
11651128

11661129
expand_todo_ids
11671130

1168-
test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks
1131+
test -d "$rewritten" || test -n "$force_rebase" ||
1132+
onto="$(git rebase--helper --skip-unnecessary-picks)" ||
1133+
die "Could not skip unnecessary pick commands"
11691134

11701135
checkout_onto
11711136
if test -z "$rebase_root" && test ! -d "$rewritten"

sequencer.c

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2565,3 +2565,90 @@ int check_todo_list(void)
25652565

25662566
return res;
25672567
}
2568+
2569+
/* skip picking commits whose parents are unchanged */
2570+
int skip_unnecessary_picks(void)
2571+
{
2572+
const char *todo_file = rebase_path_todo();
2573+
struct strbuf buf = STRBUF_INIT;
2574+
struct todo_list todo_list = TODO_LIST_INIT;
2575+
struct object_id onto_oid, *oid = &onto_oid, *parent_oid;
2576+
int fd, i;
2577+
2578+
if (!read_oneliner(&buf, rebase_path_onto(), 0))
2579+
return error("Could not read 'onto'");
2580+
if (get_sha1(buf.buf, onto_oid.hash)) {
2581+
strbuf_release(&buf);
2582+
return error("Need a HEAD to fixup");
2583+
}
2584+
strbuf_release(&buf);
2585+
2586+
fd = open(todo_file, O_RDONLY);
2587+
if (fd < 0) {
2588+
return error(_("Could not open %s (%s)"),
2589+
todo_file, strerror(errno));
2590+
}
2591+
if (strbuf_read(&todo_list.buf, fd, 0) < 0) {
2592+
close(fd);
2593+
return error(_("Could not read %s."), todo_file);
2594+
}
2595+
close(fd);
2596+
if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0)
2597+
return -1;
2598+
2599+
for (i = 0; i < todo_list.nr; i++) {
2600+
struct todo_item *item = todo_list.items + i;
2601+
2602+
if (item->command >= TODO_NOOP)
2603+
continue;
2604+
if (item->command != TODO_PICK)
2605+
break;
2606+
if (parse_commit(item->commit))
2607+
return error(_("Could not parse commit %s"),
2608+
oid_to_hex(&item->commit->object.oid));
2609+
if (!item->commit->parents)
2610+
break; /* root commit */
2611+
if (item->commit->parents->next)
2612+
break; /* merge commit */
2613+
parent_oid = &item->commit->parents->item->object.oid;
2614+
if (hashcmp(parent_oid->hash, oid->hash))
2615+
break;
2616+
oid = &item->commit->object.oid;
2617+
}
2618+
if (i > 0) {
2619+
int offset = i < todo_list.nr ?
2620+
todo_list.items[i].offset_in_buf : todo_list.buf.len;
2621+
const char *done_path = rebase_path_done();
2622+
2623+
fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666);
2624+
if (write_in_full(fd, todo_list.buf.buf, offset) < 0) {
2625+
todo_list_release(&todo_list);
2626+
return error_errno(_("Could not write to %s"),
2627+
done_path);
2628+
}
2629+
close(fd);
2630+
2631+
fd = open(rebase_path_todo(), O_WRONLY, 0666);
2632+
if (write_in_full(fd, todo_list.buf.buf + offset,
2633+
todo_list.buf.len - offset) < 0) {
2634+
todo_list_release(&todo_list);
2635+
return error_errno(_("Could not write to %s"),
2636+
rebase_path_todo());
2637+
}
2638+
if (ftruncate(fd, todo_list.buf.len - offset) < 0) {
2639+
todo_list_release(&todo_list);
2640+
return error_errno(_("Could not truncate %s"),
2641+
rebase_path_todo());
2642+
}
2643+
close(fd);
2644+
2645+
todo_list.current = i;
2646+
if (is_fixup(peek_command(&todo_list, 0)))
2647+
record_in_rewritten(oid, peek_command(&todo_list, 0));
2648+
}
2649+
2650+
todo_list_release(&todo_list);
2651+
printf("%s\n", oid_to_hex(oid));
2652+
2653+
return 0;
2654+
}

sequencer.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ int sequencer_make_script(int keep_empty, FILE *out,
6363

6464
int transform_todo_ids(int shorten_sha1s);
6565
int check_todo_list(void);
66+
int skip_unnecessary_picks(void);
6667

6768
extern const char sign_off_header[];
6869

0 commit comments

Comments
 (0)