Squash and Fixup: Combining Related Commits
Four real "WIP" commits. One meaningful commit. A real, scripted interactive rebase makes it happen.
A real feature branch, four honest "WIP" commits deep — normal, everyday, unglamorous work. Before opening a PR, a reviewer should see one real, meaningful commit, not the messy real process that produced it.\n\ngit rebase -i is genuinely interactive by design — it opens a real text editor showing a "todo list" of commits, letting you mark each one pick, squash, fixup, edit, or reword. This sandbox has no real text editor available (core.editor is set to true, a real, permanent fix from Block 2.4). Rather than fake the interactivity, this lesson uses the real, genuine technique professional teams use for scripted rebases: GIT_SEQUENCE_EDITOR runs a real command against the todo list instead of opening an editor, and GIT_EDITOR does the same for the resulting commit message.
Real, Everyday Messy Work
git commit -m "WIP"
git commit -m "WIP"
git commit -m "WIP"
git commit -m "WIP"
Four real commits, all "WIP" — completely normal mid-feature history nobody should ever see in a PR.
A Real Interactive Rebase, Without a Real Editor
GIT_SEQUENCE_EDITOR="sed -i '2,4s/^pick/squash/'" \
GIT_EDITOR="sed -i '1s/.*/Add recipe search feature/'" \
git rebase -i HEAD~4
GIT_SEQUENCE_EDITOR runs a real sed command against the real todo list git generates — marking commits 2 through 4 as squash instead of opening an editor to do it by hand. GIT_EDITOR does the same for the combined commit message that follows.
Squash vs. Fixup: A Real, Direct Contrast
git commit -m "fix typo in search description"
GIT_SEQUENCE_EDITOR="sed -i '2s/^pick/fixup/'" git rebase -i HEAD~2
squash keeps and lets you combine both real messages; fixup silently discards the fixup commit's own message, keeping only the target's. The real, final history looks exactly as if the typo had never happened.
GIT_SEQUENCE_EDITOR
An environment variable that, when set, replaces the real editor git would normally open for an interactive rebase's todo list with a real command of your choosing. Confirmed directly in this lab: a plain sed one-liner genuinely rewrote "pick" to "squash" without any real editor session.
squash vs. fixup
Both meld a commit into the one before it — squash keeps both real messages and lets you combine them; fixup silently discards the fixup commit's own message. Confirmed directly in this lab: the fixup commit's "fix typo in search description" message never appears anywhere in the final real history.
🌱 Create the Real Mess
A new branch, four real "WIP" commits — completely normal mid-feature work.
cd ~/team-recipes && git switch -c feature/recipe-searchcd ~/team-recipes && echo 'Recipe Search' > recipe-search.mdcd ~/team-recipes && git add recipe-search.mdcd ~/team-recipes && git commit -m 'WIP'cd ~/team-recipes && printf '\nSearch by ingredient.\n' >> recipe-search.mdcd ~/team-recipes && git add recipe-search.mdcd ~/team-recipes && git commit -m 'WIP'cd ~/team-recipes && printf '\nSearch by prep time.\n' >> recipe-search.mdcd ~/team-recipes && git add recipe-search.mdcd ~/team-recipes && git commit -m 'WIP'cd ~/team-recipes && printf '\nSearch by dietary tag.\n' >> recipe-search.mdcd ~/team-recipes && git add recipe-search.mdcd ~/team-recipes && git commit -m 'WIP'cd ~/team-recipes && git log --oneline -4student@lab:~$ cd ~/team-recipes && git switch -c feature/recipe-search Switched to a new branch 'feature/recipe-search' student@lab:~$ cd ~/team-recipes && echo 'Recipe Search' > recipe-search.md student@lab:~$ cd ~/team-recipes && git add recipe-search.md student@lab:~$ cd ~/team-recipes && git commit -m 'WIP' [feature/recipe-search bfbc16d] WIP 1 file changed, 1 insertion(+) create mode 100644 recipe-search.md student@lab:~$ cd ~/team-recipes && printf '\nSearch by ingredient.\n' >> recipe-search.md student@lab:~$ cd ~/team-recipes && git add recipe-search.md student@lab:~$ cd ~/team-recipes && git commit -m 'WIP' [feature/recipe-search ac45ec4] WIP 1 file changed, 2 insertions(+) student@lab:~$ cd ~/team-recipes && printf '\nSearch by prep time.\n' >> recipe-search.md student@lab:~$ cd ~/team-recipes && git add recipe-search.md student@lab:~$ cd ~/team-recipes && git commit -m 'WIP' [feature/recipe-search 430c8c1] WIP 1 file changed, 2 insertions(+) student@lab:~$ cd ~/team-recipes && printf '\nSearch by dietary tag.\n' >> recipe-search.md student@lab:~$ cd ~/team-recipes && git add recipe-search.md student@lab:~$ cd ~/team-recipes && git commit -m 'WIP' [feature/recipe-search d17997b] WIP 1 file changed, 2 insertions(+) student@lab:~$ cd ~/team-recipes && git log --oneline -4 d17997b (HEAD -> feature/recipe-search) WIP 430c8c1 WIP ac45ec4 WIP bfbc16d WIP
🧹 Squash Them Into One Real, Meaningful Commit
A real, scripted interactive rebase — no editor needed.
cd ~/team-recipes && GIT_SEQUENCE_EDITOR="sed -i '2,4s/^pick/squash/'" GIT_EDITOR="sed -i '1s/.*/Add recipe search feature/'" git rebase -i HEAD~4cd ~/team-recipes && git log --oneline -2cd ~/team-recipes && cat recipe-search.mdstudent@lab:~$ cd ~/team-recipes && GIT_SEQUENCE_EDITOR="sed -i '2,4s/^pick/squash/'" GIT_EDITOR="sed -i '1s/.*/Add recipe search feature/'" git rebase -i HEAD~4 [detached HEAD 4f0318e] Add recipe search feature Date: Thu Aug 27 20:13:13 2026 +0200 1 file changed, 7 insertions(+) create mode 100644 recipe-search.md Successfully rebased and updated refs/heads/feature/recipe-search. student@lab:~$ cd ~/team-recipes && git log --oneline -2 4f0318e (HEAD -> feature/recipe-search) Add recipe search feature f322ac1 (origin/master, origin/HEAD, master) Merge pull request: pancake toppings student@lab:~$ cd ~/team-recipes && cat recipe-search.md Recipe Search Search by ingredient. Search by prep time. Search by dietary tag.
🩹 Squash vs. Fixup: A Real, Direct Contrast
A real typo, fixed and folded in — message discarded, not combined.
cd ~/team-recipes && sed -i 's/Search by ingredient\./Search by ingredient name./' recipe-search.mdcd ~/team-recipes && git add recipe-search.mdcd ~/team-recipes && git commit -m 'fix typo in search description'cd ~/team-recipes && GIT_SEQUENCE_EDITOR="sed -i '2s/^pick/fixup/'" git rebase -i HEAD~2cd ~/team-recipes && git log --oneline -2cd ~/team-recipes && cat recipe-search.mdstudent@lab:~$ cd ~/team-recipes && sed -i 's/Search by ingredient\./Search by ingredient name./' recipe-search.md student@lab:~$ cd ~/team-recipes && git add recipe-search.md student@lab:~$ cd ~/team-recipes && git commit -m 'fix typo in search description' [feature/recipe-search 616a94d] fix typo in search description 1 file changed, 1 insertion(+), 1 deletion(-) student@lab:~$ cd ~/team-recipes && GIT_SEQUENCE_EDITOR="sed -i '2s/^pick/fixup/'" git rebase -i HEAD~2 Successfully rebased and updated refs/heads/feature/recipe-search. student@lab:~$ cd ~/team-recipes && git log --oneline -2 9b0d26a (HEAD -> feature/recipe-search) Add recipe search feature f322ac1 (origin/master, origin/HEAD, master) Merge pull request: pancake toppings student@lab:~$ cd ~/team-recipes && cat recipe-search.md Recipe Search Search by ingredient name. Search by prep time. Search by dietary tag.
Lab 3.1.1 complete. Four real WIP commits, squashed into one — then squash contrasted directly against fixup:\n\n\n Real messy branch created : ✅ 4x WIP\n Squashed into one commit : ✅ scripted rebase -i\n Fixup contrasted directly : ✅ message discarded\n
Enable JavaScript to run the live terminal and track your progress.