Scenario
Section titled “Scenario”You are squashing three messy commits before opening a pull request. The rebase stops on a conflict. You resolve it, continue, and it stops again on the next commit.
Then you lose track of where you are, and the branch looks wrong.
This is the situation where people force-push something destructive out of confusion. It is entirely recoverable, and this lab is about making that recovery familiar.
Objective
Section titled “Objective”Drive an interactive rebase through multiple conflicts, use --continue, --skip and --abort
deliberately, and recover the original branch from the reflog after a rebase you decide was a
mistake.
Prerequisites
Section titled “Prerequisites”- Comfortable resolving a merge conflict — do Lab 02 first
- Interactive Rebase explains the verbs used here
Starting state
Section titled “Starting state”mkdir -p /tmp/lab-rebase && cd /tmp/lab-rebasegit init -q .git config user.email "lab@example.com"git config user.name "Lab User"
echo "line 1" > app.txtgit add . && git commit -q -m "Initial commit"
git switch -qc feature/cleanupecho "line 2 draft" >> app.txt && git commit -qam "WIP: first attempt"sed -i 's/line 2 draft/line 2 revised/' app.txt && git commit -qam "WIP: fix attempt"sed -i 's/line 2 revised/line 2 final/' app.txt && git commit -qam "WIP: actually final"
git switch -q mainecho "line 2 from main" >> app.txt && git commit -qam "Main edits the same line"
git switch -q feature/cleanupgit log --oneline --all --graphThree throwaway commits on the branch, all touching the same line, and main has changed that
line too. Rebasing onto main will conflict repeatedly.
Record where you started — you will need it:
git rev-parse HEAD-
Start the rebase. To keep it scriptable, set a non-interactive editor that squashes the second and third commits into the first:
Terminal window GIT_SEQUENCE_EDITOR="sed -i '2,3s/^pick/squash/'" git rebase -i mainIt will stop on a conflict almost immediately.
-
Read where you are.
git statustells you which commit is being applied and what to do next. Run it — the message is more useful than people expect. -
Resolve the conflict. Edit
app.txtto a sensible final state with no markers, then:Terminal window git add app.txtgit rebase --continue -
Handle the next stop. It may conflict again, or open an editor for the squashed message. If an editor opens, save and close it.
-
Deliberately break it. Once the rebase completes, decide it was wrong. Look at the log, then recover the pre-rebase branch:
Terminal window git refloggit reset --hard <the-sha-you-recorded>git log --onelineConfirm all three original commits are back.
-
Do it again, then abort mid-flight:
Terminal window GIT_SEQUENCE_EDITOR="sed -i '2,3s/^pick/squash/'" git rebase -i main# stop at the first conflict, then:git rebase --abortgit log --onelineConfirm you are exactly where you began.
“I don’t know if a rebase is in progress.”
git statusls .git/rebase-merge .git/rebase-apply 2>/dev/nullEither directory existing means a rebase is unfinished.
--continue says there are no changes. Your resolution matched what was already there, so
the commit would be empty. Either git rebase --skip to drop it, or check you actually saved.
Which side is which during a rebase? Rebase replays your commits onto the base, so the
sides are reversed relative to a merge: HEAD is the base you are replaying onto, and the
incoming side is your commit. This trips up almost everyone.
Solution
Section titled “Solution”ORIG=$(git rev-parse HEAD)
GIT_SEQUENCE_EDITOR="sed -i '2,3s/^pick/squash/'" git rebase -i main# CONFLICT (content): Merge conflict in app.txt
printf 'line 1\nline 2 final\n' > app.txtgit add app.txtgit rebase --continue# repeat resolve/continue if it stops again
git log --oneline # squashed result
# Recover the original branch:git reset --hard "$ORIG"git log --oneline # three commits back
# Or, without having recorded it:git refloggit reset --hard HEAD@{n} # the entry before "rebase (start)"Explanation
Section titled “Explanation”A rebase is a sequence of cherry-picks. Each original commit is replayed onto the new base in turn. That is why conflicts arrive one commit at a time — each replay is its own three-way merge — and why a five-commit rebase can conflict five times on the same line.
--continue, --skip, --abort are the three exits. Continue after resolving; skip to
drop the commit being applied; abort to restore the original branch entirely.
--abort is completely safe while a rebase is running. It restores the branch to the commit
recorded in ORIG_HEAD, exactly as it was.
After a rebase finishes, the reflog is the escape. The original commits are unreferenced but
still present. git reset --hard to the pre-rebase SHA restores the branch — this is the same
mechanism as Lab 01.
Squashing rewrites SHAs. Every replayed commit is a new object. This is exactly why the rule is rebase before you share.
Clean up
Section titled “Clean up”cd /tmp && rm -rf lab-rebaseRelated lessons
Section titled “Related lessons”Next lab
Section titled “Next lab”Debug a failing GitHub Actions workflow — move from local Git to CI.