Practice: Rebase, deeper
Self-check questions
Section titled “Self-check questions”Answer each in your own words first, then open the answer to check.
Q1. What does the -i flag add to git rebase?
Show answer
It makes the rebase interactive: git opens a text editor with a todo list of all the commits being replayed, and you can edit each commit’s action (pick, reword, squash, fixup, drop, etc.) before the replay starts.
Q2. List five of the action keywords in an interactive rebase todo list and what each does.
Show answer
pick: replay as-is (default)reword: replay but stop to edit messagesquash: combine into previous, keep both messagesfixup: combine into previous, discard this messagedrop: delete the commit entirely
(Other answers also valid: edit, exec, break.)
Q3. What’s the difference between squash and fixup?
Show answer
Both combine a commit into the previous one. Squash keeps BOTH commit messages and opens the editor for you to write a combined message. Fixup DISCARDS this commit’s message and uses only the previous commit’s message. Use fixup when this commit is just a tweak (typo fix, missed comma) whose message adds no value.
Q4. Why does interactive rebase show commits OLDEST FIRST instead of newest-first like git log?
Show answer
Because rebase replays commits in chronological order (oldest first). The todo list mirrors the replay order. This is opposite to git log (which is newest-first because you usually want to see recent activity). It catches new users off-guard; the rule of thumb is “the order you see in the rebase editor is the order they’ll replay.”
Q5. What does --autosquash do, and how do you use it with --fixup?
Show answer
--autosquash tells git rebase -i to automatically rearrange any commits whose messages start with fixup! or squash! so they sit immediately AFTER the commit they target, and to pre-fill the action keyword (fixup or squash).
You use it with --fixup like this: during development, when you want to amend a previous commit, run git commit --fixup=<sha> instead of making a new “fix” commit. This creates a commit with message fixup! <original message>. Later, when you cleanup with git rebase -i --autosquash <base>, the editor opens with everything already arranged. You just save.
You can make autosquash the default with git config --global rebase.autosquash true.
Q6. What’s the rule about rebasing published commits, and why?
Show answer
Don’t rebase commits you’ve pushed to a shared branch. Reason: rebase rewrites history, giving commits new SHAs. If teammates have pulled the original commits, their local copies diverge from the rewritten remote. The next time they pull, git tries to reconcile two histories, and the result is either a confusing merge or, if you force-push, lost work.
The corollary: branch protection on main (a platform setting that forbids force-push) is the most important guardrail.
Q7. What’s the difference between git push --force and git push --force-with-lease?
Show answer
--force overwrites the remote with your local history, no matter what. If someone else pushed to the branch since your last fetch, their work is gone.
--force-with-lease only overwrites IF the remote is still at the commit you last fetched. If someone else has pushed since, the force-push is REJECTED and you have a chance to investigate. It’s the safer default for force-pushing your own feature branches.
The discipline: always use --force-with-lease unless you have a specific reason not to.
Q8. You finished a rebase and realize it was wrong. How do you get back to the pre-rebase state?
Show answer
Run git reflog to see the recent HEAD-modifying actions. Find the entry just before the “rebase (start)” line. Run git reset --hard HEAD@{N} to that entry. You’re back at the pre-rebase state with all your original commits.
Caveat: git reset --hard discards uncommitted working-tree changes. Stash first if you have any.
Q9. When would you choose rebase over merge for integrating your feature branch with main?
Show answer
For cleaning up your local feature branch before opening a PR (the most common case). For teams that prefer linear history. For integrating your feature on top of an advancing main without adding merge commits. For branches that haven’t been pushed (or that only you pull).
Q10. When would you choose merge over rebase?
Show answer
When the branch you’d be rebasing has been pushed and teammates have pulled it (the published-history rule). When you want to preserve the honest parallel-development history. For long-lived release branches where the merge points matter for audit. When your team’s workflow (GitFlow, etc.) calls for merge commits at integration points.
Interactive rebase cleanup drill
Section titled “Interactive rebase cleanup drill”- Create a test repo (
git init test-rebase && cd test-rebase). - Create a file
notes.txtwith one line, commit with message “initial.” Add a tagstart. - Make 5 commits, each adding a line:
- “WIP first attempt”
- “fix typo”
- “actually fix”
- “add another line”
- “WIP more”
- Run
git log --onelineto see your 6 commits. - Run
git rebase -i start. - In the todo list, change the actions to: reword first WIP to “add first line,” fixup the two typo fixes into it, reword “add another” to “add second line,” fixup the final “WIP more” into it.
- Save. Edit commit messages as prompted.
- After the rebase, run
git log --onelineagain. You should have 2 clean commits plus the initial.
Fixup-and-autosquash drill
Section titled “Fixup-and-autosquash drill”- In the same or a fresh test repo, make a commit “add foo function” (just create a file
foo.pywith a placeholder). - Note its SHA.
- Make another commit “add bar function” (
bar.py). - Realize foo.py needs a fix. Edit it. Run
git commit --fixup=<sha-of-foo-commit>. - Make another commit “add baz function.”
- Run
git rebase -i --autosquash <starting-sha>(the SHA before “add foo function”). - The editor should pre-fill the fixup action and rearrange. Just save.
- Verify the fixup got folded into the foo commit.
Conflict-during-rebase drill
Section titled “Conflict-during-rebase drill”- Create a fresh repo. Make commit A: file
port.confwith content “port=8080.” - Branch
feature/port-9090. On it, change to “port=9090,” commit. - Switch back to main. Change to “port=8443,” commit.
- Switch to
feature/port-9090. Trygit rebase main. - You should hit a conflict.
- Resolve by keeping main’s
port=8443. Mark resolved withgit add port.conf. - Run
git rebase --continue. - Verify the branch is now based on main with port=8443. The original port=9090 change is effectively dropped (the rebase replayed but the resolution chose main’s value).
Recovery from rebase drill
Section titled “Recovery from rebase drill”- Create a fresh repo. Make 4 commits with distinct messages.
- Run
git rebase -i HEAD~3and arbitrarily squash some. - Save and complete.
- Run
git log --onelineto see the rewritten history. - Run
git reflog. Find the entry just before “rebase (start).” - Run
git reset --hard HEAD@{<N>}to that entry. - Run
git log --onelineagain. The original 4 commits should be back.
Scenario reflections
Section titled “Scenario reflections”Scenario A: You’ve been working on a feature branch for a week. Your manager asks you to open a PR today. Your branch has 18 commits, most of them “WIP” or single-word messages. Walk through your approach to preparing the PR.
Show answer
Run git rebase -i main. Plan the cleanup before opening the editor: which 3-5 logical commits do you want the final history to show? Typically: data model, API endpoint, UI, tests. Maybe more for a large feature. Edit the todo list: pick the right starting commits, fixup or squash the WIP and typo commits into them, reword to clean messages. Resolve any conflicts. Push with --force-with-lease. Open PR.
If 18 commits feels too many to manage in interactive rebase: consider doing a single git reset --soft main to put all your changes in the staging area, then making 3-5 fresh commits from scratch. Heavier-handed but sometimes cleaner.
Scenario B: You accidentally ran git push --force on main after rebasing it. Three teammates have pulled main today. Walk through the recovery process you’d lead.
Show answer
Apologize first (this is real damage, not just an inconvenience). Triage:
- Find someone whose local main hasn’t been pulled-since-the-force-push. Their local has the OLD history.
- That person checks out main, then
git push --force-with-lease origin mainto restore the old history. (Make sure platform protection isn’t blocking; you may need a brief admin override.) - Everyone else resets their local main to the restored remote:
git fetch && git reset --hard origin/main. - If anyone made commits on broken main during the window, those commits need to be recovered (find their SHAs from reflog, cherry-pick onto restored main).
Post-mortem: enable branch protection forbidding force-push to main. Never again.
Scenario C: You’re rebasing a feature branch onto main and hitting conflict after conflict: every commit you’ve made conflicts with main’s recent changes. After ten conflicts, you’re exhausted. What are your options?
Show answer
Options:
git rebase --abortand approach differently. Either merge main into your feature (instead of rebase), accepting a merge commit; or do a partial rebase (squash your feature branch’s commits to one or two locally first, then rebase the smaller version with fewer conflict surfaces).- Continue the rebase if individual conflicts are tractable, but pace yourself (take breaks; a tired conflict resolution often introduces bugs).
- Ask the teammate whose commits are causing the conflicts to pair on the resolution: they understand the recent changes better.
The lesson: a conflict storm usually signals that your feature branch has been alive too long. Future fix: rebase more often (weekly, daily) so each individual rebase has few conflicts.
Scenario D: You’re on a team that uses GitFlow. Someone proposes switching to a “rebase before merge” policy. Discuss the pros and cons.
Show answer
Pros: linear history is easier to read with git log. git blame traces cleanly without merge-commit detours. Bisect (binary search through history) is simpler. PRs are typically cleaner because the cleanup happens before merge.
Cons: rewriting history is more error-prone for less experienced engineers. The honest “this branch was developed in parallel” story is lost. The release-branch audit trail GitFlow provides is diluted. Force-pushing feature branches becomes routine, slightly raising the risk of “force-push to wrong branch” mistakes.
Decision should be team-wide and documented. The choice is style, not correctness.
Flashcards
Section titled “Flashcards”Q. git rebase -i HEAD~5
Interactive rebase of the last 5 commits: opens editor with todo list.
Q. Action keyword: pick
Replay this commit as-is (default).
Q. Action keyword: reword
Replay but stop to edit the commit message.
Q. Action keyword: squash
Combine this commit into the previous one, keep both messages for editing.
Q. Action keyword: fixup
Combine this commit into the previous one, discard this commit’s message.
Q. Action keyword: drop
Delete this commit entirely (the changes disappear).
Q. Action keyword: edit
Pause after replaying so you can amend the commit’s content.
Q. git commit --fixup=(sha)
Create a commit marked fixup! <original-message> for later autosquash.
Q. git rebase -i --autosquash main
Interactive rebase that auto-arranges fixup commits next to their targets.
Q. git rebase --abort
Give up the in-progress rebase, restore to pre-rebase state.
Q. git rebase --continue
Resume rebase after resolving a conflict.
Q. The published-history rule
Don’t rebase commits you’ve pushed to a shared branch: rewriting breaks teammates’ local history.
Q. git push --force-with-lease
Force-push only if remote is still at the commit you last fetched: safer than git push --force.
Q. Reflog use case
Recover from any HEAD-modifying mistake (rebase, reset, hard-deleted branch): records last 90 days of HEAD moves.
Q. git reset --hard HEAD@(N)
Return to the Nth-back HEAD state from the reflog (git reset --hard HEAD@{N}).
Q. Rebase vs merge: rebase when...
Cleaning up local history before PR, team prefers linear log, integrating into a branch nobody else pulls from.
Q. Rebase vs merge: merge when...
Branch is published and others pulled, want to preserve parallel-development story, long-lived release branches.
Q. Branch protection on main
Repository setting that forbids force-push to main: most important guardrail against accidental history rewrite.