Lesson 2 of 5 in Rewriting History
Rebase conflicts
Story: Two agents changed the same moment in history. When you replay yours, you'll have to decide what really happened.
A rebase replays your commits one at a time, and each replay is a small merge: git applies that commit's change to the new history. If a commit changes the same lines as the branch you're rebasing onto, git can't choose between them. The rebase stops at that commit with a conflict:
$ git rebase main
Auto-merging style.css
CONFLICT (content): Merge conflict in style.css
error: could not apply aa5ec9a... Make the heading purple
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
hint: Disable this message with "git config advice.mergeConflict false"
Could not apply aa5ec9a... Make the heading purple
In the middle of a rebase
The rebase is paused, not finished. git status tells you where you are:
$ git status
interactive rebase in progress; onto 862276a
Last command done (1 command done):
pick aa5ec9a Make the heading purple
Next command to do (1 remaining command):
pick f3ef3d2 Add an about page
(use "git rebase --edit-todo" to view and edit)
You are currently rebasing branch 'theme' on '862276a'.
(fix conflicts and then run "git rebase --continue")
(use "git rebase --skip" to skip this patch)
(use "git rebase --abort" to check out the original branch)
Unmerged paths:
(use "git restore --staged <file>..." to unstage)
(use "git add <file>..." to mark resolution)
both modified: style.css
no changes added to commit (use "git add" and/or "git commit -a")
git calls it an interactive rebase even though you didn't ask for one: a plain rebase runs on the same machinery. Interactive rebase is a later Lesson, so ignore --edit-todo for now. Here's where you are:
pick aa5ec9ais the commit being replayed, and the next command is the one still to come.HEADis detached, on the new history being built. So far, that'smain's commits plus any of yours already replayed.git branchshows it as(no branch, rebasing theme).- Your branch hasn't moved yet.
themestill points at its old commit. It only moves when the rebase finishes.
The conflicted file holds both versions between conflict markers:
<<<<<<< HEAD
h1 { color: teal; font-family: serif; }
=======
h1 { color: purple; }
>>>>>>> aa5ec9a (Make the heading purple)
In a rebase, the sides read the other way round from a merge into your branch. HEAD is the new history being built, so here it's main's version. The other side is your commit being replayed.
Three ways out
Continue. Fix the file as you would for a merge conflict: open it with edit, keep what should stay, and delete the markers. Then stage it and carry on:
git add style.css
git rebase --continue
git opens the commit's message in the edit pane first, so you can change it. Save it as it is to keep the original message. git commits the fixed copy and replays the rest of your commits. If another one conflicts, it stops again, and you do the same again. When the last commit is in, it moves your branch:
Successfully rebased and updated refs/heads/theme.
You can also commit the fix yourself, with git commit -am "<message>", and then run git rebase --continue. git sees the commit is made and goes on to the next one.
If you forget to stage the file, the rebase won't go on:
$ git rebase --continue
style.css: needs merge
You must edit all merge conflicts and then
mark them as resolved using git add
Skip. git rebase --skip leaves the current commit out of the new history and goes on with the next one. Use it when your commit isn't needed any more, for example because main already has its own version of the same fix. Skipping drops that commit's change, so be sure.
If you resolve a conflict by keeping only main's side, your commit has nothing left to add. Then git rebase --continue leaves it out too, just as --skip would.
Abort. git rebase --abort stops the whole rebase. Your branch, HEAD and your files go back to exactly how they were before you ran git rebase. It drops the commits already replayed in this rebase too: an abort undoes all of it, not just the current commit. It's the safe way out when a rebase gets confusing. You can always start it again, or merge instead.
One commit at a time
A merge shows you every conflict at once. A rebase replays one commit at a time, so you meet each conflict in the commit that caused it. If several of your commits touch the same lines, you can resolve the same spot more than once. On a long branch with many conflicting commits, that's a real trade-off, and merging can be the easier choice.
New commands
git rebase --continuegit rebase --abortgit rebase --skip
Explainer: A rebase stops at a conflict
Graph as text
project: your repo, you are here
- HEAD → theme at f3ef3d2 "Add an about page"
Branches and tags
- main at 862276a "Use a serif heading"
- theme at f3ef3d2 "Add an about page"
Commits, newest first
- 862276a "Use a serif heading"; parent a4b8d97; refs: main
- f3ef3d2 "Add an about page"; parent aa5ec9a; refs: HEAD → theme
- aa5ec9a "Make the heading purple"; parent a4b8d97
- a4b8d97 "Style the heading"; parent ab89288
- ab89288 "Add the home page"; parent 37d7cf7
- 37d7cf7 "Add a README"; no parent
Challenge 1 of 3
Story: Purple, said one agent. Serif, said another. Both were right.
You're on theme. Rebase it onto main. One of its commits changes the same line of style.css as main's newest commit, so the rebase will stop with a conflict.
Keep both changes: the heading should end up purple and serif. Then finish the rebase.
Unlocks after Remotes & Collaboration and Undoing Things.
Terminal
Unlocks after Remotes & Collaboration and Undoing Things.
This Lesson can be played once Remotes & Collaboration and Undoing Things are complete. Its text and Explainer are open to everyone.