From 1d621fea180e2612facb190b758bf03dc79e4423 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:40 +0100 Subject: [PATCH 01/18] rebase -i: Make the condition for an "if" more transparent Test $no_ff separately rather than testing it indirectly by gluing it onto a comparison of two SHA1s. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 55c451ade4..db10dd7b29 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -164,7 +164,8 @@ pick_one () { parent_sha1=$(git rev-parse --verify $sha1^) || die "Could not get the parent of $sha1" current_sha1=$(git rev-parse --verify HEAD) - if test "$no_ff$current_sha1" = "$parent_sha1"; then + if test -z "$no_ff" -a "$current_sha1" = "$parent_sha1" + then output git reset --hard $sha1 test "a$1" = a-n && output git reset --soft $current_sha1 sha1=$(git rev-parse --short $sha1) From 50438340bcadd790956b5081b8a16cba28156e22 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:41 +0100 Subject: [PATCH 02/18] rebase -i: Remove dead code This branch of the "if" is only executed if $no_ff is empty, which only happens if $1 was not '-n'. (This code has been dead since 1d25c8cf82eead72e11287d574ef72d3ebec0db1.) Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index db10dd7b29..aae94d2f6f 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -167,7 +167,6 @@ pick_one () { if test -z "$no_ff" -a "$current_sha1" = "$parent_sha1" then output git reset --hard $sha1 - test "a$1" = a-n && output git reset --soft $current_sha1 sha1=$(git rev-parse --short $sha1) output warn Fast-forward to $sha1 else From aa7eaff8b1514ef1241c2f5867670b495b33c455 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:42 +0100 Subject: [PATCH 03/18] rebase -i: Inline expression Inline expression when generating output rather than overwriting the "sha1" local variable with a short SHA1. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index aae94d2f6f..6fd3105b54 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -167,8 +167,7 @@ pick_one () { if test -z "$no_ff" -a "$current_sha1" = "$parent_sha1" then output git reset --hard $sha1 - sha1=$(git rev-parse --short $sha1) - output warn Fast-forward to $sha1 + output warn Fast-forward to $(git rev-parse --short $sha1) else output git cherry-pick "$@" fi From 699f13ca9af6b045826b8d44c2f3870affd7823d Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:43 +0100 Subject: [PATCH 04/18] rebase -i: Use "test -n" instead of "test ! -z" It is a tiny bit simpler. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 6fd3105b54..2e1b2fa3cc 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -156,7 +156,7 @@ pick_one () { output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1" test -d "$REWRITTEN" && pick_one_preserving_merges "$@" && return - if test ! -z "$REBASE_ROOT" + if test -n "$REBASE_ROOT" then output git cherry-pick "$@" return From bdb011ade4db4a50cde13b5ec870c3dccff7e093 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:44 +0100 Subject: [PATCH 05/18] rebase -i: Use symbolic constant $MSG consistently The filename constant $MSG was previously used in some places and written out literally in others. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 2e1b2fa3cc..efd5749973 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -129,8 +129,8 @@ make_patch () { echo "Root commit" ;; esac > "$DOTEST"/patch - test -f "$DOTEST"/message || - git cat-file commit "$1" | sed "1,/^$/d" > "$DOTEST"/message + test -f "$MSG" || + git cat-file commit "$1" | sed "1,/^$/d" > "$MSG" test -f "$DOTEST"/author-script || get_author_ident_from_commit "$1" > "$DOTEST"/author-script } @@ -341,7 +341,7 @@ peek_next_command () { } do_next () { - rm -f "$DOTEST"/message "$DOTEST"/author-script \ + rm -f "$MSG" "$DOTEST"/author-script \ "$DOTEST"/amend || exit read command sha1 rest < "$TODO" case "$command" in @@ -559,7 +559,7 @@ first and then run 'git rebase --continue' again." die "Cannot rewind the HEAD" fi export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE && - git commit --no-verify -F "$DOTEST"/message -e || { + git commit --no-verify -F "$MSG" -e || { test -n "$amend" && git reset --soft $amend die "Could not commit staged changes." } From 80883bb30a9c0c41f3b4975874f8f5e527396543 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:45 +0100 Subject: [PATCH 06/18] rebase -i: Document how temporary files are used Add documentation, inferred by reverse-engineering, about how git-rebase--interactive.sh uses many of its temporary files. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 41 +++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index efd5749973..acc92c439d 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -34,11 +34,45 @@ root rebase all reachable commmits up to the root(s) require_work_tree DOTEST="$GIT_DIR/rebase-merge" + +# The file containing rebase commands, comments, and empty lines. +# This file is created by "git rebase -i" then edited by the user. As +# the lines are processed, they are removed from the front of this +# file and written to the tail of $DONE. TODO="$DOTEST"/git-rebase-todo + +# The rebase command lines that have already been processed. A line +# is moved here when it is first handled, before any associated user +# actions. DONE="$DOTEST"/done + +# The commit message that is planned to be used for any changes that +# need to be committed following a user interaction. MSG="$DOTEST"/message + +# The file into which is accumulated the suggested commit message for +# squash/fixup commands. When the first of a series of squash/fixups +# is seen, the file is created and the commit message from the +# previous commit and from the first squash/fixup commit are written +# to it. The commit message for each subsequent squash/fixup commit +# is appended to the file as it is processed. +# +# The first line of the file is of the form +# # This is a combination of $COUNT commits. +# where $COUNT is the number of commits whose messages have been +# written to the file so far (including the initial "pick" commit). +# Each time that a commit message is processed, this line is read and +# updated. It is deleted just before the combined commit is made. SQUASH_MSG="$DOTEST"/message-squash + +# $REWRITTEN is the name of a directory containing files for each +# commit that is reachable by at least one merge base of $HEAD and +# $UPSTREAM. They are not necessarily rewritten, but their children +# might be. This ensures that commits on merged, but otherwise +# unrelated side branches are left alone. (Think "X" in the man page's +# example.) REWRITTEN="$DOTEST"/rewritten + DROPPED="$DOTEST"/dropped PRESERVE_MERGES= STRATEGY= @@ -683,13 +717,6 @@ first and then run 'git rebase --continue' again." test t = "$VERBOSE" && : > "$DOTEST"/verbose if test t = "$PRESERVE_MERGES" then - # $REWRITTEN contains files for each commit that is - # reachable by at least one merge base of $HEAD and - # $UPSTREAM. They are not necessarily rewritten, but - # their children might be. - # This ensures that commits on merged, but otherwise - # unrelated side branches are left alone. (Think "X" - # in the man page's example.) if test -z "$REBASE_ROOT" then mkdir "$REWRITTEN" && From 0aac0de4feb569ed17d501f04f17ecb8801dbad9 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:46 +0100 Subject: [PATCH 07/18] rebase -i: Introduce a constant AUTHOR_SCRIPT Add a constant AUTHOR_SCRIPT, holding the filename of the $DOTEST/author_script file, and document how this temporary file is used. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index acc92c439d..17641d444e 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -74,6 +74,12 @@ SQUASH_MSG="$DOTEST"/message-squash REWRITTEN="$DOTEST"/rewritten DROPPED="$DOTEST"/dropped + +# A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and +# GIT_AUTHOR_DATE that will be used for the commit that is currently +# being rebased. +AUTHOR_SCRIPT="$DOTEST"/author-script + PRESERVE_MERGES= STRATEGY= ONTO= @@ -165,8 +171,8 @@ make_patch () { esac > "$DOTEST"/patch test -f "$MSG" || git cat-file commit "$1" | sed "1,/^$/d" > "$MSG" - test -f "$DOTEST"/author-script || - get_author_ident_from_commit "$1" > "$DOTEST"/author-script + test -f "$AUTHOR_SCRIPT" || + get_author_ident_from_commit "$1" > "$AUTHOR_SCRIPT" } die_with_patch () { @@ -375,8 +381,7 @@ peek_next_command () { } do_next () { - rm -f "$MSG" "$DOTEST"/author-script \ - "$DOTEST"/amend || exit + rm -f "$MSG" "$AUTHOR_SCRIPT" "$DOTEST"/amend || exit read command sha1 rest < "$TODO" case "$command" in '#'*|''|noop) @@ -452,7 +457,7 @@ do_next () { rm -f "$GIT_DIR"/MERGE_MSG || exit ;; esac - echo "$author_script" > "$DOTEST"/author-script + echo "$author_script" > "$AUTHOR_SCRIPT" if test $failed = f then # This is like --amend, but with a different message @@ -579,7 +584,7 @@ do then : Nothing to commit -- skip this else - . "$DOTEST"/author-script || + . "$AUTHOR_SCRIPT" || die "Cannot find the author identity" amend= if test -f "$DOTEST"/amend From a4049ae7ac97cb8887e9cd499c4dd0fed0eb968d Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:47 +0100 Subject: [PATCH 08/18] rebase -i: Introduce a constant AMEND Add a constant AMEND holding the filename of the $DOTEST/amend file, and document how this temporary file is used. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 17641d444e..31f9b3b038 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -80,6 +80,14 @@ DROPPED="$DOTEST"/dropped # being rebased. AUTHOR_SCRIPT="$DOTEST"/author-script +# When an "edit" rebase command is being processed, the SHA1 of the +# commit to be edited is recorded in this file. When "git rebase +# --continue" is executed, if there are any staged changes then they +# will be amended to the HEAD commit, but only provided the HEAD +# commit is still the commit to be edited. When any other rebase +# command is processed, this file is deleted. +AMEND="$DOTEST"/amend + PRESERVE_MERGES= STRATEGY= ONTO= @@ -381,7 +389,7 @@ peek_next_command () { } do_next () { - rm -f "$MSG" "$AUTHOR_SCRIPT" "$DOTEST"/amend || exit + rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit read command sha1 rest < "$TODO" case "$command" in '#'*|''|noop) @@ -409,7 +417,7 @@ do_next () { pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" make_patch $sha1 - git rev-parse --verify HEAD > "$DOTEST"/amend + git rev-parse --verify HEAD > "$AMEND" warn "Stopped at $sha1... $rest" warn "You can amend the commit now, with" warn @@ -587,10 +595,10 @@ do . "$AUTHOR_SCRIPT" || die "Cannot find the author identity" amend= - if test -f "$DOTEST"/amend + if test -f "$AMEND" then amend=$(git rev-parse --verify HEAD) - test "$amend" = $(cat "$DOTEST"/amend) || + test "$amend" = $(cat "$AMEND") || die "\ You have uncommitted changes in your working tree. Please, commit them first and then run 'git rebase --continue' again." From 959c0d06eafd7723517c953e80ee1a60881c373b Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:48 +0100 Subject: [PATCH 09/18] t3404: Test the commit count in commit messages generated by "rebase -i" The first line of commit messages generated for "rebase -i" squash/fixup commits includes a count of the number of commits that are being combined. Add machinery to check that this count is correct, and add such a check to some test cases. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- t/lib-rebase.sh | 6 +++++- t/t3404-rebase-interactive.sh | 9 +++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh index 0db8250c58..2d922ae43c 100644 --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@ -2,9 +2,10 @@ # After setting the fake editor with this function, you can # -# - override the commit message with $FAKE_COMMIT_MESSAGE, +# - override the commit message with $FAKE_COMMIT_MESSAGE # - amend the commit message with $FAKE_COMMIT_AMEND # - check that non-commit messages have a certain line count with $EXPECT_COUNT +# - check the commit count in the commit message header with $EXPECT_HEADER_COUNT # - rewrite a rebase -i script as directed by $FAKE_LINES. # $FAKE_LINES consists of a sequence of words separated by spaces. # The following word combinations are possible: @@ -25,6 +26,9 @@ set_fake_editor () { cat >> fake-editor.sh <<\EOF case "$1" in */COMMIT_EDITMSG) + test -z "$EXPECT_HEADER_COUNT" || + test "$EXPECT_HEADER_COUNT" = $(sed -n '1s/^# This is a combination of \(.*\) commits\./\1/p' < "$1") || + exit test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1" test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1" exit diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index d9382e41d3..0335b781a0 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -135,7 +135,8 @@ test_expect_success 'squash' ' test_tick && GIT_AUTHOR_NAME="Nitfol" git commit -m "nitfol" file7 && echo "******************************" && - FAKE_LINES="1 squash 2" git rebase -i --onto master HEAD~2 && + FAKE_LINES="1 squash 2" EXPECT_HEADER_COUNT=two \ + git rebase -i --onto master HEAD~2 && test B = $(cat file7) && test $(git rev-parse HEAD^) = $(git rev-parse master) ' @@ -230,6 +231,7 @@ test_expect_success 'verbose flag is heeded, even after --continue' ' test_expect_success 'multi-squash only fires up editor once' ' base=$(git rev-parse HEAD~4) && FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 squash 2 squash 3 squash 4" \ + EXPECT_HEADER_COUNT=4 \ git rebase -i $base && test $base = $(git rev-parse HEAD^) && test 1 = $(git show | grep ONCE | wc -l) @@ -239,6 +241,7 @@ test_expect_success 'multi-fixup only fires up editor once' ' git checkout -b multi-fixup E && base=$(git rev-parse HEAD~4) && FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 fixup 2 fixup 3 fixup 4" \ + EXPECT_HEADER_COUNT=4 \ git rebase -i $base && test $base = $(git rev-parse HEAD^) && test 1 = $(git show | grep ONCE | wc -l) && @@ -258,6 +261,7 @@ test_expect_success 'squash and fixup generate correct log messages' ' git checkout -b squash-fixup E && base=$(git rev-parse HEAD~4) && FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 fixup 2 squash 3 fixup 4" \ + EXPECT_HEADER_COUNT=4 \ git rebase -i $base && git cat-file commit HEAD | sed -e 1,/^\$/d > actual-squash-fixup && test_cmp expect-squash-fixup actual-squash-fixup && @@ -297,7 +301,8 @@ test_expect_success 'squash works as expected' ' git commit -m $n done && one=$(git rev-parse HEAD~3) && - FAKE_LINES="1 squash 3 2" git rebase -i HEAD~3 && + FAKE_LINES="1 squash 3 2" EXPECT_HEADER_COUNT=two \ + git rebase -i HEAD~3 && test $one = $(git rev-parse HEAD~2) ' From 5065ed296abc1c0b66ad7c5e963e048cb90b6ee6 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:49 +0100 Subject: [PATCH 10/18] rebase -i: Improve consistency of commit count in generated commit messages Use the numeral "2" instead of the word "two" when two commits are being interactively squashed. This makes the treatment consistent with that for higher numbers of commits. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 2 +- t/t3404-rebase-interactive.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 31f9b3b038..702c979414 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -360,7 +360,7 @@ make_squash_message () { }' <"$SQUASH_MSG" else COUNT=2 - echo "# This is a combination of two commits." + echo "# This is a combination of 2 commits." echo "# The first commit's message is:" echo git cat-file commit HEAD | sed -e '1,/^$/d' diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 0335b781a0..05117091eb 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -135,7 +135,7 @@ test_expect_success 'squash' ' test_tick && GIT_AUTHOR_NAME="Nitfol" git commit -m "nitfol" file7 && echo "******************************" && - FAKE_LINES="1 squash 2" EXPECT_HEADER_COUNT=two \ + FAKE_LINES="1 squash 2" EXPECT_HEADER_COUNT=2 \ git rebase -i --onto master HEAD~2 && test B = $(cat file7) && test $(git rev-parse HEAD^) = $(git rev-parse master) @@ -301,7 +301,7 @@ test_expect_success 'squash works as expected' ' git commit -m $n done && one=$(git rev-parse HEAD~3) && - FAKE_LINES="1 squash 3 2" EXPECT_HEADER_COUNT=two \ + FAKE_LINES="1 squash 3 2" EXPECT_HEADER_COUNT=2 \ git rebase -i HEAD~3 && test $one = $(git rev-parse HEAD~2) ' From f99e269c44a7f07699a92c7e31d31ed93dafb409 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:50 +0100 Subject: [PATCH 11/18] rebase -i: Simplify commit counting for generated commit messages Read the old count from the first line of the old commit message rather than counting the number of commit message blocks in the file. This is simpler, faster, and more robust (e.g., it cannot be confused by strange commit message contents). Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 702c979414..d60e059838 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -349,11 +349,9 @@ nth_string () { make_squash_message () { if test -f "$SQUASH_MSG"; then - # We want to be careful about matching only the commit - # message comment lines generated by this function. - # "[snrt][tdh]" matches the nth_string endings. - COUNT=$(($(sed -n "s/^# Th[^0-9]*\([1-9][0-9]*\)[snrt][tdh] commit message.*:/\1/p" \ - < "$SQUASH_MSG" | sed -ne '$p')+1)) + COUNT=$(($(sed -n \ + -e "1s/^# This is a combination of \(.*\) commits\./\1/p" \ + -e "q" < "$SQUASH_MSG")+1)) echo "# This is a combination of $COUNT commits." sed -e 1d -e '2,/^./{ /^$/d @@ -376,9 +374,6 @@ make_squash_message () { echo echo "# The $(nth_string $COUNT) commit message will be skipped:" echo - # Comment the lines of the commit message out using - # "# " rather than "# " to make them less likely to - # confuse the sed regexp above. git cat-file commit $2 | sed -e '1,/^$/d' -e 's/^/# /' ;; esac From ee0a4afbe2baeec2bfdf3d8e7abc77f24294298d Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:51 +0100 Subject: [PATCH 12/18] rebase -i: Extract a function "commit_message" ...instead of repeating the same short but slightly obscure blob of code in several places. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index d60e059838..1499a67bba 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -118,6 +118,11 @@ output () { esac } +# Output the commit message for the specified commit. +commit_message () { + git cat-file commit "$1" | sed "1,/^$/d" +} + run_pre_rebase_hook () { if test -z "$OK_TO_SKIP_PRE_REBASE" && test -x "$GIT_DIR/hooks/pre-rebase" @@ -178,7 +183,7 @@ make_patch () { ;; esac > "$DOTEST"/patch test -f "$MSG" || - git cat-file commit "$1" | sed "1,/^$/d" > "$MSG" + commit_message "$1" > "$MSG" test -f "$AUTHOR_SCRIPT" || get_author_ident_from_commit "$1" > "$AUTHOR_SCRIPT" } @@ -316,7 +321,7 @@ pick_one_preserving_merges () { # redo merge author_script=$(get_author_ident_from_commit $sha1) eval "$author_script" - msg="$(git cat-file commit $sha1 | sed -e '1,/^$/d')" + msg="$(commit_message $sha1)" # No point in merging the first parent, that's HEAD new_parents=${new_parents# $first_parent} if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ @@ -361,20 +366,20 @@ make_squash_message () { echo "# This is a combination of 2 commits." echo "# The first commit's message is:" echo - git cat-file commit HEAD | sed -e '1,/^$/d' + commit_message HEAD fi case $1 in squash) echo echo "# This is the $(nth_string $COUNT) commit message:" echo - git cat-file commit $2 | sed -e '1,/^$/d' + commit_message $2 ;; fixup) echo echo "# The $(nth_string $COUNT) commit message will be skipped:" echo - git cat-file commit $2 | sed -e '1,/^$/d' -e 's/^/# /' + commit_message $2 | sed -e 's/^/# /' ;; esac } From 5c5d059a0d8d2b0341077f7468dd480352775e1c Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:52 +0100 Subject: [PATCH 13/18] rebase -i: Handle the author script all in one place in do_next This change has no practical effect but makes the code easier to follow. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 1499a67bba..122ba314d6 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -447,6 +447,8 @@ do_next () { make_squash_message $squash_style $sha1 > "$MSG" failed=f author_script=$(get_author_ident_from_commit HEAD) + echo "$author_script" > "$AUTHOR_SCRIPT" + eval "$author_script" output git reset --soft HEAD^ pick_one -n $sha1 || failed=t case "$(peek_next_command)" in @@ -465,11 +467,9 @@ do_next () { rm -f "$GIT_DIR"/MERGE_MSG || exit ;; esac - echo "$author_script" > "$AUTHOR_SCRIPT" if test $failed = f then # This is like --amend, but with a different message - eval "$author_script" GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ From 7756ecffe7d914fdbb42629a408d6da78b9794c1 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:53 +0100 Subject: [PATCH 14/18] rebase -i: Extract function do_with_author Call it instead of repeating similar code blocks in several places. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 122ba314d6..7d30829506 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -203,6 +203,15 @@ has_action () { sane_grep '^[^#]' "$1" >/dev/null } +# Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and +# GIT_AUTHOR_DATE exported from the current environment. +do_with_author () { + GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ + GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ + GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ + "$@" +} + pick_one () { no_ff= case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac @@ -324,11 +333,8 @@ pick_one_preserving_merges () { msg="$(commit_message $sha1)" # No point in merging the first parent, that's HEAD new_parents=${new_parents# $first_parent} - if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ - GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ - GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ - output git merge $STRATEGY -m "$msg" \ - $new_parents + if ! do_with_author output \ + git merge $STRATEGY -m "$msg" $new_parents then printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG die_with_patch $sha1 "Error redoing merge $sha1" @@ -470,11 +476,9 @@ do_next () { if test $failed = f then # This is like --amend, but with a different message - GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ - GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ - GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ - $USE_OUTPUT git commit --no-verify \ - $MSG_OPT "$EDIT_OR_FILE" || failed=t + do_with_author $USE_OUTPUT git commit --no-verify \ + $MSG_OPT "$EDIT_OR_FILE" || + failed=t fi if test $failed = t then @@ -605,8 +609,7 @@ first and then run 'git rebase --continue' again." git reset --soft HEAD^ || die "Cannot rewind the HEAD" fi - export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE && - git commit --no-verify -F "$MSG" -e || { + do_with_author git commit --no-verify -F "$MSG" -e || { test -n "$amend" && git reset --soft $amend die "Could not commit staged changes." } From bde1a68624c59d891ec02fda473c3aef98ec9358 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:54 +0100 Subject: [PATCH 15/18] rebase -i: Change function make_squash_message into update_squash_message Alter the file $SQUASH_MSG in place rather than outputting the new message then juggling it around. Change the function name accordingly. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 7d30829506..d8c3c9aac9 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -358,21 +358,26 @@ nth_string () { esac } -make_squash_message () { +update_squash_message () { if test -f "$SQUASH_MSG"; then + mv "$SQUASH_MSG" "$SQUASH_MSG".bak || exit COUNT=$(($(sed -n \ -e "1s/^# This is a combination of \(.*\) commits\./\1/p" \ - -e "q" < "$SQUASH_MSG")+1)) - echo "# This is a combination of $COUNT commits." - sed -e 1d -e '2,/^./{ - /^$/d - }' <"$SQUASH_MSG" + -e "q" < "$SQUASH_MSG".bak)+1)) + { + echo "# This is a combination of $COUNT commits." + sed -e 1d -e '2,/^./{ + /^$/d + }' <"$SQUASH_MSG".bak + } >$SQUASH_MSG else COUNT=2 - echo "# This is a combination of 2 commits." - echo "# The first commit's message is:" - echo - commit_message HEAD + { + echo "# This is a combination of 2 commits." + echo "# The first commit's message is:" + echo + commit_message HEAD + } >$SQUASH_MSG fi case $1 in squash) @@ -387,7 +392,7 @@ make_squash_message () { echo commit_message $2 | sed -e 's/^/# /' ;; - esac + esac >>$SQUASH_MSG } peek_next_command () { @@ -450,7 +455,7 @@ do_next () { die "Cannot '$squash_style' without a previous commit" mark_action_done - make_squash_message $squash_style $sha1 > "$MSG" + update_squash_message $squash_style $sha1 failed=f author_script=$(get_author_ident_from_commit HEAD) echo "$author_script" > "$AUTHOR_SCRIPT" @@ -460,16 +465,16 @@ do_next () { case "$(peek_next_command)" in squash|s|fixup|f) USE_OUTPUT=output + cp "$SQUASH_MSG" "$MSG" || exit MSG_OPT=-F EDIT_OR_FILE="$MSG" - cp "$MSG" "$SQUASH_MSG" ;; *) USE_OUTPUT= MSG_OPT= EDIT_OR_FILE=-e - rm -f "$SQUASH_MSG" || exit - cp "$MSG" "$GIT_DIR"/SQUASH_MSG + cp "$SQUASH_MSG" "$MSG" || exit + mv "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit rm -f "$GIT_DIR"/MERGE_MSG || exit ;; esac From a25eb13909088f04f87acec26c522cc555f6b4a9 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:55 +0100 Subject: [PATCH 16/18] rebase -i: For fixup commands without squashes, do not start editor If the "rebase -i" commands include a series of fixup commands without any squash commands, then commit the combined commit using the commit message of the corresponding "pick" without starting up the commit-message editor. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 81 +++++++++++++++++++++++------------ t/t3404-rebase-interactive.sh | 7 ++- 2 files changed, 57 insertions(+), 31 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index d8c3c9aac9..1569a7ad91 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -65,6 +65,13 @@ MSG="$DOTEST"/message # updated. It is deleted just before the combined commit is made. SQUASH_MSG="$DOTEST"/message-squash +# If the current series of squash/fixups has not yet included a squash +# command, then this file exists and holds the commit message of the +# original "pick" commit. (If the series ends without a "squash" +# command, then this can be used as the commit message of the combined +# commit without opening the editor.) +FIXUP_MSG="$DOTEST"/message-fixup + # $REWRITTEN is the name of a directory containing files for each # commit that is reachable by at least one merge base of $HEAD and # $UPSTREAM. They are not necessarily rewritten, but their children @@ -358,7 +365,7 @@ nth_string () { esac } -update_squash_message () { +update_squash_messages () { if test -f "$SQUASH_MSG"; then mv "$SQUASH_MSG" "$SQUASH_MSG".bak || exit COUNT=$(($(sed -n \ @@ -371,16 +378,18 @@ update_squash_message () { }' <"$SQUASH_MSG".bak } >$SQUASH_MSG else + commit_message HEAD > "$FIXUP_MSG" || die "Cannot write $FIXUP_MSG" COUNT=2 { echo "# This is a combination of 2 commits." echo "# The first commit's message is:" echo - commit_message HEAD + cat "$FIXUP_MSG" } >$SQUASH_MSG fi case $1 in squash) + rm -f "$FIXUP_MSG" echo echo "# This is the $(nth_string $COUNT) commit message:" echo @@ -455,7 +464,7 @@ do_next () { die "Cannot '$squash_style' without a previous commit" mark_action_done - update_squash_message $squash_style $sha1 + update_squash_messages $squash_style $sha1 failed=f author_script=$(get_author_ident_from_commit HEAD) echo "$author_script" > "$AUTHOR_SCRIPT" @@ -464,34 +473,52 @@ do_next () { pick_one -n $sha1 || failed=t case "$(peek_next_command)" in squash|s|fixup|f) - USE_OUTPUT=output - cp "$SQUASH_MSG" "$MSG" || exit - MSG_OPT=-F - EDIT_OR_FILE="$MSG" + # This is an intermediate commit; its message will only be + # used in case of trouble. So use the long version: + if test $failed = f + then + do_with_author output git commit --no-verify -F "$SQUASH_MSG" || + failed=t + fi + if test $failed = t + then + cp "$SQUASH_MSG" "$MSG" || exit + # After any kind of hiccup, prevent committing without + # opening the commit message editor: + rm -f "$FIXUP_MSG" + cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit + warn + warn "Could not apply $sha1... $rest" + die_with_patch $sha1 "" + fi ;; *) - USE_OUTPUT= - MSG_OPT= - EDIT_OR_FILE=-e - cp "$SQUASH_MSG" "$MSG" || exit - mv "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit - rm -f "$GIT_DIR"/MERGE_MSG || exit + # This is the final command of this squash/fixup group + if test $failed = f + then + if test -f "$FIXUP_MSG" + then + do_with_author git commit --no-verify -F "$FIXUP_MSG" || + failed=t + else + cp "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit + rm -f "$GIT_DIR"/MERGE_MSG + do_with_author git commit --no-verify -e || + failed=t + fi + fi + rm -f "$FIXUP_MSG" + if test $failed = t + then + mv "$SQUASH_MSG" "$MSG" || exit + cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit + warn + warn "Could not apply $sha1... $rest" + die_with_patch $sha1 "" + fi + rm -f "$SQUASH_MSG" ;; esac - if test $failed = f - then - # This is like --amend, but with a different message - do_with_author $USE_OUTPUT git commit --no-verify \ - $MSG_OPT "$EDIT_OR_FILE" || - failed=t - fi - if test $failed = t - then - cp "$MSG" "$GIT_DIR"/MERGE_MSG - warn - warn "Could not apply $sha1... $rest" - die_with_patch $sha1 "" - fi ;; *) warn "Unknown command: $command $sha1 $rest" diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 05117091eb..175a86c2cb 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -237,14 +237,13 @@ test_expect_success 'multi-squash only fires up editor once' ' test 1 = $(git show | grep ONCE | wc -l) ' -test_expect_success 'multi-fixup only fires up editor once' ' +test_expect_success 'multi-fixup does not fire up editor' ' git checkout -b multi-fixup E && base=$(git rev-parse HEAD~4) && - FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 fixup 2 fixup 3 fixup 4" \ - EXPECT_HEADER_COUNT=4 \ + FAKE_COMMIT_AMEND="NEVER" FAKE_LINES="1 fixup 2 fixup 3 fixup 4" \ git rebase -i $base && test $base = $(git rev-parse HEAD^) && - test 1 = $(git show | grep ONCE | wc -l) && + test 0 = $(git show | grep NEVER | wc -l) && git checkout to-be-rebased && git branch -D multi-fixup ' From 6c4c44c45881260877d7d9fd113fd75b74d7777c Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:56 +0100 Subject: [PATCH 17/18] t3404: Set up more of the test repo in the "setup" step ...and reuse these pre-created branches in tests rather than creating duplicates. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- t/t3404-rebase-interactive.sh | 51 ++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 175a86c2cb..a119ce0d37 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -14,15 +14,20 @@ that the result still makes sense. set_fake_editor -# set up two branches like this: +# Set up the repository like this: # -# A - B - C - D - E (master) +# one - two - three - four (conflict-branch) +# / +# A - B - C - D - E (master) +# | \ +# | F - G - H (branch1) +# | \ +# \ I (branch2) # \ -# F - G - H (branch1) -# \ -# I (branch2) +# J - K - L - M (no-conflict-branch) # -# where A, B, D and G touch the same file. +# where A, B, D and G all touch file1, and one, two, three, four all +# touch file "conflict". test_expect_success 'setup' ' test_commit A file1 && @@ -36,9 +41,20 @@ test_expect_success 'setup' ' test_commit H file5 && git checkout -b branch2 F && test_commit I file6 + git checkout -b conflict-branch A && + for n in one two three four + do + test_commit $n conflict + done && + git checkout -b no-conflict-branch A && + for n in J K L M + do + test_commit $n file$n + done ' test_expect_success 'no changes are a nop' ' + git checkout branch2 && git rebase -i F && test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" && test $(git rev-parse I) = $(git rev-parse HEAD) @@ -97,7 +113,7 @@ cat > expect2 << EOF D ======= G ->>>>>>> 91201e5... G +>>>>>>> 51047de... G EOF test_expect_success 'stop on conflicting pick' ' @@ -293,12 +309,7 @@ test_expect_success 'squash ignores blank lines' ' ' test_expect_success 'squash works as expected' ' - for n in one two three four - do - echo $n >> file$n && - git add file$n && - git commit -m $n - done && + git checkout -b squash-works no-conflict-branch && one=$(git rev-parse HEAD~3) && FAKE_LINES="1 squash 3 2" EXPECT_HEADER_COUNT=2 \ git rebase -i HEAD~3 && @@ -306,12 +317,7 @@ test_expect_success 'squash works as expected' ' ' test_expect_success 'interrupted squash works as expected' ' - for n in one two three four - do - echo $n >> conflict && - git add conflict && - git commit -m $n - done && + git checkout -b interrupted-squash conflict-branch && one=$(git rev-parse HEAD~3) && ( FAKE_LINES="1 squash 3 2" && @@ -328,12 +334,7 @@ test_expect_success 'interrupted squash works as expected' ' ' test_expect_success 'interrupted squash works as expected (case 2)' ' - for n in one two three four - do - echo $n >> conflict && - git add conflict && - git commit -m $n - done && + git checkout -b interrupted-squash2 conflict-branch && one=$(git rev-parse HEAD~3) && ( FAKE_LINES="3 squash 1 2" && From 6bdcd0d2fcca7c3985a078c8d343a863136fb675 Mon Sep 17 00:00:00 2001 From: Michael Haggerty Date: Thu, 14 Jan 2010 06:54:57 +0100 Subject: [PATCH 18/18] rebase -i: Retain user-edited commit messages after squash/fixup conflicts When a squash/fixup fails due to a conflict, the user is required to edit the commit message. Previously, if further squash/fixup commands followed the conflicting squash/fixup, this user-edited message was discarded and a new automatically-generated commit message was suggested. Change the handling of conflicts within squash/fixup command series: Whenever the user is required to intervene, consider the resulting commit to be a new basis for the following squash/fixups and use its commit message in later suggested combined commit messages. Signed-off-by: Michael Haggerty Signed-off-by: Junio C Hamano --- git-rebase--interactive.sh | 66 ++++++++++++++--------------------- t/t3404-rebase-interactive.sh | 36 +++++++++++++++++++ 2 files changed, 63 insertions(+), 39 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 1569a7ad91..b835a29759 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -408,6 +408,21 @@ peek_next_command () { sed -n -e "/^#/d" -e "/^$/d" -e "s/ .*//p" -e "q" < "$TODO" } +# A squash/fixup has failed. Prepare the long version of the squash +# commit message, then die_with_patch. This code path requires the +# user to edit the combined commit message for all commits that have +# been squashed/fixedup so far. So also erase the old squash +# messages, effectively causing the combined commit to be used as the +# new basis for any further squash/fixups. Args: sha1 rest +die_failed_squash() { + mv "$SQUASH_MSG" "$MSG" || exit + rm -f "$FIXUP_MSG" + cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit + warn + warn "Could not apply $1... $2" + die_with_patch $1 "" +} + do_next () { rm -f "$MSG" "$AUTHOR_SCRIPT" "$AMEND" || exit read command sha1 rest < "$TODO" @@ -465,58 +480,31 @@ do_next () { mark_action_done update_squash_messages $squash_style $sha1 - failed=f author_script=$(get_author_ident_from_commit HEAD) echo "$author_script" > "$AUTHOR_SCRIPT" eval "$author_script" output git reset --soft HEAD^ - pick_one -n $sha1 || failed=t + pick_one -n $sha1 || die_failed_squash $sha1 "$rest" case "$(peek_next_command)" in squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: - if test $failed = f - then - do_with_author output git commit --no-verify -F "$SQUASH_MSG" || - failed=t - fi - if test $failed = t - then - cp "$SQUASH_MSG" "$MSG" || exit - # After any kind of hiccup, prevent committing without - # opening the commit message editor: - rm -f "$FIXUP_MSG" - cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit - warn - warn "Could not apply $sha1... $rest" - die_with_patch $sha1 "" - fi + do_with_author output git commit --no-verify -F "$SQUASH_MSG" || + die_failed_squash $sha1 "$rest" ;; *) # This is the final command of this squash/fixup group - if test $failed = f + if test -f "$FIXUP_MSG" then - if test -f "$FIXUP_MSG" - then - do_with_author git commit --no-verify -F "$FIXUP_MSG" || - failed=t - else - cp "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit - rm -f "$GIT_DIR"/MERGE_MSG - do_with_author git commit --no-verify -e || - failed=t - fi + do_with_author git commit --no-verify -F "$FIXUP_MSG" || + die_failed_squash $sha1 "$rest" + else + cp "$SQUASH_MSG" "$GIT_DIR"/SQUASH_MSG || exit + rm -f "$GIT_DIR"/MERGE_MSG + do_with_author git commit --no-verify -e || + die_failed_squash $sha1 "$rest" fi - rm -f "$FIXUP_MSG" - if test $failed = t - then - mv "$SQUASH_MSG" "$MSG" || exit - cp "$MSG" "$GIT_DIR"/MERGE_MSG || exit - warn - warn "Could not apply $sha1... $rest" - die_with_patch $sha1 "" - fi - rm -f "$SQUASH_MSG" + rm -f "$SQUASH_MSG" "$FIXUP_MSG" ;; esac ;; diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index a119ce0d37..4e3513709e 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -264,6 +264,42 @@ test_expect_success 'multi-fixup does not fire up editor' ' git branch -D multi-fixup ' +test_expect_success 'commit message used after conflict' ' + git checkout -b conflict-fixup conflict-branch && + base=$(git rev-parse HEAD~4) && + ( + FAKE_LINES="1 fixup 3 fixup 4" && + export FAKE_LINES && + test_must_fail git rebase -i $base + ) && + echo three > conflict && + git add conflict && + FAKE_COMMIT_AMEND="ONCE" EXPECT_HEADER_COUNT=2 \ + git rebase --continue && + test $base = $(git rev-parse HEAD^) && + test 1 = $(git show | grep ONCE | wc -l) && + git checkout to-be-rebased && + git branch -D conflict-fixup +' + +test_expect_success 'commit message retained after conflict' ' + git checkout -b conflict-squash conflict-branch && + base=$(git rev-parse HEAD~4) && + ( + FAKE_LINES="1 fixup 3 squash 4" && + export FAKE_LINES && + test_must_fail git rebase -i $base + ) && + echo three > conflict && + git add conflict && + FAKE_COMMIT_AMEND="TWICE" EXPECT_HEADER_COUNT=2 \ + git rebase --continue && + test $base = $(git rev-parse HEAD^) && + test 2 = $(git show | grep TWICE | wc -l) && + git checkout to-be-rebased && + git branch -D conflict-squash +' + cat > expect-squash-fixup << EOF B