Skip to content

Commit b079fee

Browse files
trastgitster
authored andcommitted
rebase -i: invoke post-rewrite hook
Aside from the same issue that rebase also has (remembering the original commit across a conflict resolution), rebase -i brings an extra twist: We need to defer writing the rewritten list in the case of {squash,fixup} because their rewritten result should be the last commit in the squashed group. Signed-off-by: Thomas Rast <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 96e1948 commit b079fee

File tree

2 files changed

+146
-1
lines changed

2 files changed

+146
-1
lines changed

git-rebase--interactive.sh

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ AUTHOR_SCRIPT="$DOTEST"/author-script
9696
# command is processed, this file is deleted.
9797
AMEND="$DOTEST"/amend
9898

99+
# For the post-rewrite hook, we make a list of rewritten commits and
100+
# their new sha1s. The rewritten-pending list keeps the sha1s of
101+
# commits that have been processed, but not committed yet,
102+
# e.g. because they are waiting for a 'squash' command.
103+
REWRITTEN_LIST="$DOTEST"/rewritten-list
104+
REWRITTEN_PENDING="$DOTEST"/rewritten-pending
105+
99106
PRESERVE_MERGES=
100107
STRATEGY=
101108
ONTO=
@@ -198,6 +205,7 @@ make_patch () {
198205
}
199206

200207
die_with_patch () {
208+
echo "$1" > "$DOTEST"/stopped-sha
201209
make_patch "$1"
202210
git rerere
203211
die "$2"
@@ -348,6 +356,7 @@ pick_one_preserving_merges () {
348356
printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
349357
die_with_patch $sha1 "Error redoing merge $sha1"
350358
fi
359+
echo "$sha1 $(git rev-parse HEAD^0)" >> "$REWRITTEN_LIST"
351360
;;
352361
*)
353362
output git cherry-pick "$@" ||
@@ -425,6 +434,26 @@ die_failed_squash() {
425434
die_with_patch $1 ""
426435
}
427436

437+
flush_rewritten_pending() {
438+
test -s "$REWRITTEN_PENDING" || return
439+
newsha1="$(git rev-parse HEAD^0)"
440+
sed "s/$/ $newsha1/" < "$REWRITTEN_PENDING" >> "$REWRITTEN_LIST"
441+
rm -f "$REWRITTEN_PENDING"
442+
}
443+
444+
record_in_rewritten() {
445+
oldsha1="$(git rev-parse $1)"
446+
echo "$oldsha1" >> "$REWRITTEN_PENDING"
447+
448+
case "$(peek_next_command)" in
449+
squash|s|fixup|f)
450+
;;
451+
*)
452+
flush_rewritten_pending
453+
;;
454+
esac
455+
}
456+
428457
do_next () {
429458
rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit
430459
read command sha1 rest < "$TODO"
@@ -438,6 +467,7 @@ do_next () {
438467
mark_action_done
439468
pick_one $sha1 ||
440469
die_with_patch $sha1 "Could not apply $sha1... $rest"
470+
record_in_rewritten $sha1
441471
;;
442472
reword|r)
443473
comment_for_reflog reword
@@ -446,13 +476,15 @@ do_next () {
446476
pick_one $sha1 ||
447477
die_with_patch $sha1 "Could not apply $sha1... $rest"
448478
git commit --amend --no-post-rewrite
479+
record_in_rewritten $sha1
449480
;;
450481
edit|e)
451482
comment_for_reflog edit
452483

453484
mark_action_done
454485
pick_one $sha1 ||
455486
die_with_patch $sha1 "Could not apply $sha1... $rest"
487+
echo "$1" > "$DOTEST"/stopped-sha
456488
make_patch $sha1
457489
git rev-parse --verify HEAD > "$AMEND"
458490
warn "Stopped at $sha1... $rest"
@@ -509,6 +541,7 @@ do_next () {
509541
rm -f "$SQUASH_MSG" "$FIXUP_MSG"
510542
;;
511543
esac
544+
record_in_rewritten $sha1
512545
;;
513546
*)
514547
warn "Unknown command: $command $sha1 $rest"
@@ -537,6 +570,11 @@ do_next () {
537570
test ! -f "$DOTEST"/verbose ||
538571
git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
539572
} &&
573+
if test -x "$GIT_DIR"/hooks/post-rewrite &&
574+
test -s "$REWRITTEN_LIST"; then
575+
"$GIT_DIR"/hooks/post-rewrite rebase < "$REWRITTEN_LIST"
576+
true # we don't care if this hook failed
577+
fi &&
540578
rm -rf "$DOTEST" &&
541579
git gc --auto &&
542580
warn "Successfully rebased and updated $HEADNAME."
@@ -571,7 +609,12 @@ skip_unnecessary_picks () {
571609
esac
572610
echo "$command${sha1:+ }$sha1${rest:+ }$rest" >&$fd
573611
done <"$TODO" >"$TODO.new" 3>>"$DONE" &&
574-
mv -f "$TODO".new "$TODO" ||
612+
mv -f "$TODO".new "$TODO" &&
613+
case "$(peek_next_command)" in
614+
squash|s|fixup|f)
615+
record_in_rewritten "$ONTO"
616+
;;
617+
esac ||
575618
die "Could not skip unnecessary pick commands"
576619
}
577620

@@ -685,6 +728,7 @@ first and then run 'git rebase --continue' again."
685728
test -n "$amend" && git reset --soft $amend
686729
die "Could not commit staged changes."
687730
}
731+
record_in_rewritten "$(cat "$DOTEST"/stopped-sha)"
688732
fi
689733

690734
require_clean_work_tree

t/t5407-post-rewrite-hook.sh

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,105 @@ EOF
7979
verify_hook_input
8080
'
8181

82+
test_expect_success 'git rebase -m' '
83+
git reset --hard D &&
84+
clear_hook_input &&
85+
test_must_fail git rebase -m --onto A B &&
86+
echo C > foo &&
87+
git add foo &&
88+
git rebase --continue &&
89+
echo rebase >expected.args &&
90+
cat >expected.data <<EOF &&
91+
$(git rev-parse C) $(git rev-parse HEAD^)
92+
$(git rev-parse D) $(git rev-parse HEAD)
93+
EOF
94+
verify_hook_input
95+
'
96+
97+
test_expect_success 'git rebase -m --skip' '
98+
git reset --hard D &&
99+
clear_hook_input &&
100+
test_must_fail git rebase --onto A B &&
101+
test_must_fail git rebase --skip &&
102+
echo D > foo &&
103+
git add foo &&
104+
git rebase --continue &&
105+
echo rebase >expected.args &&
106+
cat >expected.data <<EOF &&
107+
$(git rev-parse D) $(git rev-parse HEAD)
108+
EOF
109+
verify_hook_input
110+
'
111+
112+
. "$TEST_DIRECTORY"/lib-rebase.sh
113+
114+
set_fake_editor
115+
116+
# Helper to work around the lack of one-shot exporting for
117+
# test_must_fail (as it is a shell function)
118+
test_fail_interactive_rebase () {
119+
(
120+
FAKE_LINES="$1" &&
121+
shift &&
122+
export FAKE_LINES &&
123+
test_must_fail git rebase -i "$@"
124+
)
125+
}
126+
127+
test_expect_success 'git rebase -i (unchanged)' '
128+
git reset --hard D &&
129+
clear_hook_input &&
130+
test_fail_interactive_rebase "1 2" --onto A B &&
131+
echo C > foo &&
132+
git add foo &&
133+
git rebase --continue &&
134+
echo rebase >expected.args &&
135+
cat >expected.data <<EOF &&
136+
$(git rev-parse C) $(git rev-parse HEAD^)
137+
$(git rev-parse D) $(git rev-parse HEAD)
138+
EOF
139+
verify_hook_input
140+
'
141+
142+
test_expect_success 'git rebase -i (skip)' '
143+
git reset --hard D &&
144+
clear_hook_input &&
145+
test_fail_interactive_rebase "2" --onto A B &&
146+
echo D > foo &&
147+
git add foo &&
148+
git rebase --continue &&
149+
echo rebase >expected.args &&
150+
cat >expected.data <<EOF &&
151+
$(git rev-parse D) $(git rev-parse HEAD)
152+
EOF
153+
verify_hook_input
154+
'
155+
156+
test_expect_success 'git rebase -i (squash)' '
157+
git reset --hard D &&
158+
clear_hook_input &&
159+
test_fail_interactive_rebase "1 squash 2" --onto A B &&
160+
echo C > foo &&
161+
git add foo &&
162+
git rebase --continue &&
163+
echo rebase >expected.args &&
164+
cat >expected.data <<EOF &&
165+
$(git rev-parse C) $(git rev-parse HEAD)
166+
$(git rev-parse D) $(git rev-parse HEAD)
167+
EOF
168+
verify_hook_input
169+
'
170+
171+
test_expect_success 'git rebase -i (fixup without conflict)' '
172+
git reset --hard D &&
173+
clear_hook_input &&
174+
FAKE_LINES="1 fixup 2" git rebase -i B &&
175+
echo rebase >expected.args &&
176+
cat >expected.data <<EOF &&
177+
$(git rev-parse C) $(git rev-parse HEAD)
178+
$(git rev-parse D) $(git rev-parse HEAD)
179+
EOF
180+
verify_hook_input
181+
'
182+
82183
test_done

0 commit comments

Comments
 (0)