We closed module 4 with a warning: from now on the history is shared. Ana, Bruno and Carla all work on the same repository at git.example.com, and every push leaves behind commits the other two can download at any moment. That is precisely the circumstance that turns git rebase into Git's most useful and most feared tool.
git rebase exists for one very specific purpose: to take a series of commits and reapply them onto a different base. It lets a branch that was born three days ago sit "on top of" main again without cluttering the history with merges; it lets you reorder and tidy up your work before showing it to anyone; and it lets you move a branch that was hanging off the wrong place. What people fear about it is its other side: it does not move commits, it replaces them with new ones. And replacing commits that have already been published is, as we shall see, a remarkably effective way of ruining your colleagues' afternoon.
This lesson explains the mechanism in enough detail that no magic is left: which objects get created, which hashes change, what is preserved, how to resolve the conflicts that turn up halfway through and — above all — when it is sensible to use rebase and when it is sensible to use merge.
Contents
- The module's golden rule
- What
git rebaseactually does - A rebase step by step in
task-manager - What is preserved and what changes in the reapplied commits
- Rebase versus merge
git rebase --onto: moving one specific stretch- Conflicts during a rebase:
--continue,--skip,--abort - The inversion of
oursandtheirs - Breaking the golden rule: what happens exactly
git pull --rebaserebase.autoStashand other conveniences- The safety net
- The module's golden rule
Before a single command, the rule that governs the whole of module 5:
Do not rewrite history you have already published and that other people may have downloaded.
Everything we shall see in lessons 05-01, 05-02 and 05-03 creates new commits that replace others. While those commits live only on your disk, rewriting them is free: nobody else has seen them. The moment you send them to git.example.com they stop being yours: they become part of a shared reality, and changing them forces everyone else to redo their copy.
The rule has one very narrow and very well-known exception: your own working branch, published only to have a backup or for review, that nobody else is using as a base. It is a legitimate and common case, but it demands that you warn people and it demands --force-with-lease (lesson 04-05). We shall come back to the rule in every section that rewrites commits, until it becomes tiresome. That is the point.
- What
git rebase actually does
git rebase actually doesThe sentence usually trotted out is "rebase moves your commits on top of another branch". It is convenient and it is false, and the difference matters.
Remember the data model from lesson 01-04: a commit is an immutable object whose SHA-1 hash is computed from all of its content, and that content includes its parent's hash. Therefore:
- If you change a commit's parent, its content changes.
- If its content changes, its hash changes.
- If its hash changes, it is no longer the same commit: it is a different object.
A commit can no more be "moved" than the past can be changed. What git rebase does is:
- Work out the list of commits that are on your branch and not on the new base.
- Save the change (the diff) each one introduces.
- Position itself on the new base.
- Apply those changes one by one, creating a new commit for each.
- Move the branch reference to the tip of the new chain.
The old commits still exist in the object database — Git's objects are not deleted the instant they go unused — but they are left with no branch reaching them. For all practical purposes, they are dead history.
gitGraph commit id: "c5d9b1e" commit id: "b3f7c21" branch feature/colour-labels commit id: "7c2e5f9" commit id: "a4b1d83" checkout main commit id: "e91d4a8"
That is the starting point: Carla created her branch when main was at b3f7c21, and while she was working Ana published e91d4a8. After the rebase:
gitGraph commit id: "c5d9b1e" commit id: "b3f7c21" commit id: "e91d4a8" branch feature/colour-labels commit id: "d5e8f31" commit id: "92c7a06"
Look carefully at the identifiers: 7c2e5f9 and a4b1d83 have disappeared from the drawing, and in their place are d5e8f31 and 92c7a06. The content of the changes is the same, the messages are the same, the author is the same. The commits are not.
- A rebase step by step in
task-manager
task-managerCarla has spent two days on feature/colour-labels: she wants every task to be able to carry a colour. Meanwhile, Ana has published a change on main. Carla wants to catch up without creating a merge commit.
# 1. Starting point: fetch the latest from the server
git switch feature/colour-labels
git fetch origin
git log --oneline --graph --all -6* e91d4a8 (origin/main, main) Extract the task element creation into its own function | * a4b1d83 (HEAD -> feature/colour-labels) Show the colour label in the list | * 7c2e5f9 Add the colour field to the task model |/ * b3f7c21 Store the tasks in localStorage * c5d9b1e Document installation in the README
The fork is clear: two commits on one side, one on the other, and b3f7c21 as the common ancestor. Let us check which commits are going to be reapplied before touching anything:
# 2. Which commits the rebase will reapply (those on my branch and not on main)
git log --oneline main..HEADThat is exactly the list we were talking about: the main..HEAD range you learned in lesson 02-06. Now for the real thing:
* 92c7a06 (HEAD -> feature/colour-labels) Show the colour label in the list * d5e8f31 Add the colour field to the task model * e91d4a8 (origin/main, main) Extract the task element creation into its own function * b3f7c21 Store the tasks in localStorage * c5d9b1e Document installation in the README
There is no fork any more: a single straight line. And the hashes of Carla's two commits have changed. It is worth verifying with git show that the change is identical:
# 5. The old commit still exists, even though no branch reaches it
git show --stat 7c2e5f9 | head -6
git show --stat d5e8f31 | head -6commit 7c2e5f9... Author: Carla Vidal <[email protected]> Date: Mon Jul 27 09:14:22 2026 +0200 Add the colour field to the task model
commit d5e8f31... Author: Carla Vidal <[email protected]> Date: Mon Jul 27 09:14:22 2026 +0200 Add the colour field to the task model
Same author, same author date, same message, same diff. A different object.
One important detail to close on: since Carla's branch was not published, there is nothing to negotiate here. If it had been, the next git push would have been rejected as non-fast-forward (lesson 04-05) and she would have had to use --force-with-lease.
- What is preserved and what changes in the reapplied commits
This table settles most beginners' doubts:
| Part of the commit | Preserved after the rebase? |
|---|---|
| Message | Yes (unless you change it with -i) |
| Author and email (author) | Yes |
| Author date | Yes |
| Committer | No: you become the committer |
| Committer date | No: it is set to now |
| Content of the change (the diff) | Yes, if there is no conflict |
Resulting tree (tree) |
Depends on the base: it almost always changes |
| Parent | No: that is the whole point |
| Hash | No |
Two practical consequences:
git logshows the author date by default, so after a rebase the history still displays the original dates. If you want to see the committer date:git log --pretty=fuller.- If somebody on your team looks at "who committed what", the rebase makes you the committer of other people's commits. It is not a problem, but it is worth knowing.
- Rebase versus merge
Both integrate one branch's work with another's. The difference is not technical but narrative: what story do you want the repository to tell?
| Aspect | git merge |
git rebase |
|---|---|---|
| What it does | Creates one new commit with two parents | Creates new commits, one per original |
| Resulting history | Forked, with merge knots | Linear |
| Original commits | Preserved intact | Replaced (new hashes) |
| Traceability | Reflects what really happened: who worked in parallel and when it came together | Reflects an idealised version: it looks as though everything was done in order |
| When conflicts happen | Just once, at the merge; resolved once | Once per clashing commit; may repeat |
Readability of git log |
Worse with many short branches | Better: it reads top to bottom |
git bisect (lesson 06-02) |
Works, but with knots | Cleaner |
| Safety on published work | Total: it rewrites nothing | Dangerous: it rewrites |
| Reversibility | git revert -m 1 (lesson 05-06) |
Not applicable: you have to revert commit by commit |
And the criterion, boiled down to three rules you actually can memorise:
- Rebase for what is still yours. Your local branch, before publishing it or before asking for it to be integrated: bring it up to date on
mainwithrebase. - Merge to join histories that are already public. Integrating a finished feature branch into
mainis an event that deserves to be recorded; there a merge (often--no-ff, lesson 03-03) is the honest option. - When in doubt, merge. It is the option that never destroys anything.
Some teams take this to the extreme in one direction ("always a linear history") and others in the opposite one ("never rewrite anything"). Both positions are defensible and we shall meet them by name in module 7 (workflows) and in lesson 08-02, when we discuss clean-history policy. Here we are concerned with the mechanism.
git rebase --onto: moving one specific stretch
git rebase --onto: moving one specific stretchgit rebase <base> covers 90% of cases, but it carries an implicit assumption: that you want to reapply everything between the common ancestor and your branch. Sometimes that is not what you want.
It happened to Carla. She started feature/sort-by-date without noticing that she was standing on feature/colour-labels rather than on main. Now her new branch drags along the two colour-label commits, which have nothing to do with it.
gitGraph commit id: "e91d4a8" branch feature/colour-labels commit id: "d5e8f31" commit id: "92c7a06" branch feature/sort-by-date commit id: "3f9a2c4" commit id: "6b1e7d5"
She wants feature/sort-by-date to hang directly off main (which is at e91d4a8) and to contain only 3f9a2c4 and 6b1e7d5. That is what the three-argument form is for:
The way to read it aloud is this:
<to>— the branch I want to move (if you omit it, the current branch).<from>— the point after which the commits I want to take with me begin. This commit is not included; it works as the exclusive boundary of afrom..torange.<new-base>— where I want them to end up attached.
Applied to Carla's case:
# 1. See which commits I am taking (same range semantics)
git log --oneline feature/colour-labels..feature/sort-by-dategitGraph commit id: "e91d4a8" branch feature/colour-labels commit id: "d5e8f31" commit id: "92c7a06" checkout main branch feature/sort-by-date commit id: "0c4f8a3" commit id: "5d2b9e7"
The two date commits have been reapplied on top of main with new hashes, and the colour-label branch has stayed exactly where it was, untouched.
Other common uses of --onto, so that you recognise the pattern:
# Drop the first 3 commits of a branch (start counting 3 further along)
git rebase --onto main main~3 my-branch
# Take a branch's commits over to a different branch
git rebase --onto fix/urgent main feature/something
# Drop ONE commit from the middle: everything after it, on top of its parent
git rebase --onto 4e7f2a9 8b6d3c2 mainThat last one is a good mental exercise: 8b6d3c2 is the commit we want to remove, 4e7f2a9 is its parent, and we are saying "take everything that comes after 8b6d3c2 and stick it straight onto its parent". Interactive rebase (lesson 05-02) has a far more comfortable way of doing the same thing, but it is worth understanding that underneath it is this.
- Conflicts during a rebase
A rebase applies commits one by one, so it can stop once per commit. The mechanics of resolution — the <<<<<<< markers, git status, editing, git add — are exactly the ones you learned in lesson 03-05; we shall not repeat them. What changes is how you get out of the jam.
Suppose Carla rebases and the second commit clashes:
Auto-merging app.js CONFLICT (content): Merge conflict in app.js error: could not apply 92c7a06... Show the colour label in the list 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 original branch: run "git rebase --abort". Could not apply 92c7a06... Show the colour label in the list
interactive rebase in progress; onto e91d4a8 Last command done (2 commands done): pick d5e8f31 Add the colour field to the task model pick 92c7a06 Show the colour label in the list No commands remaining. You are currently rebasing branch 'feature/colour-labels' on 'e91d4a8'. (fix conflicts and then run "git rebase --continue") (use "git rebase --skip" to skip this patch) (use "git rebase --abort" to checkout the original branch) Unmerged paths: (use "git restore --staged <file>..." to unstage) (use "git add <file>..." to mark resolution) both modified: app.js
That git status block is your control panel: it tells you which commit in the list you are on and what is left. The three ways out:
| Command | What it does | When to use it |
|---|---|---|
git rebase --continue |
Commits with your resolution and carries on with the next one | The normal case, after resolving and running git add |
git rebase --skip |
Discards the conflicting commit entirely and carries on | When that change is already in the base and reapplying it adds nothing |
git rebase --abort |
Undoes the whole rebase and returns the branch to its original state | When you have tied yourself in knots or the conflict is worse than expected |
Three important warnings:
--skipdoes not mean "get this conflict out of my way": it means "throw this commit in the bin". If the commit contained changes that exist nowhere else, you lose them from the resulting branch. Use it only when you are certain its content has already been applied.--abortis completely safe. It puts the branch back exactly where it was, with the original commits and their original hashes. When in doubt, abort and think with the repository in a calm state.- Do not run
git commitby hand during a rebase. After resolving and runninggit add, it isgit rebase --continuethat creates the commit. If you rungit commit,--continuewill tell you there is nothing left to commit (and in older versions it can leave the list out of step).
A useful detail: if, after resolving the conflict, the result is identical to the base (that is, your commit no longer contributes anything), git rebase --continue detects it and offers to skip it. That is normal and does not indicate any problem.
- The inversion of
ours and theirs
ours and theirsHere is the trap that confuses everyone, veterans included.
In a normal merge (lesson 03-05), ours is your branch — where you are — and theirs is the branch you are bringing in. Intuitive.
In a rebase it is inverted. And this is not a whim: it is the logical consequence of the mechanism. Remember that a rebase first positions itself on the new base and then applies your commits on top, one by one, as though they were somebody else's patches. So at each step:
| Side | In a merge |
In a rebase |
|---|---|---|
ours / --ours / stage :2: |
Your current branch | The new base (main, what was already there) |
theirs / --theirs / stage :3: |
The branch you are merging | Your commit, the one being reapplied |
HEAD during the conflict |
Your branch | The base plus the commits already reapplied |
Put memorably: during a rebase, "theirs" is you.
The practical consequence is immediate. If during a rebase you want to keep your version of the file:
# WRONG: this keeps main's version, not yours
git checkout --ours app.js
# RIGHT: during a rebase, your commit is "theirs"
git checkout --theirs app.js
git add app.js
git rebase --continueThe same applies to -X ours / -X theirs when you pass them to the rebase, and to the names of the index stages (:2: and :3:) you saw in 03-05. If you are ever unsure, do not guess: look at the content before deciding.
git show :2:app.js | head -20 # "ours" = the base
git show :3:app.js | head -20 # "theirs" = your commitOne piece of advice that saves a lot of grief: set the zdiff3 style (lesson 03-05) here as well. Seeing the common ancestor inside the marker makes orientation far less dependent on remembering who is who:
And if the same conflict repeats commit after commit within the same rebase, there is git rerere (reuse recorded resolution), which memorises how you resolved a conflict and applies it on its own the next time an identical one appears. It was mentioned in 03-05 and configuring it is beyond this lesson.
- Breaking the golden rule: what happens exactly
Let us see it with names attached, because the mechanism of the disaster explains the rule better than any warning.
- Bruno publishes
feature/csv-exportwith three commits:2a7f4c1,9e3b8d6,5c1a9f2. - Carla runs
git fetchand creates her branch from that work. She now has those three commits on her disk. - Bruno rebases his branch onto
mainbecausemainhas moved on. His three commits becomed8b2e50,71f6c34,ae95d18. - Bruno runs
git push --force-with-lease. The server now points atae95d18. - Carla runs
git pull.
What Carla sees:
* branch feature/csv-export -> FETCH_HEAD + ae95d18...5c1a9f2 feature/csv-export -> origin/feature/csv-export (forced update)
And from here on, depending on her pull configuration:
- With
pull.ff only(the one we set in 01-06): the pull fails with a divergence warning. That is the best case: Carla finds out and can ask. - With merge: Git merges the old chain with the new one and the three commits appear duplicated, each of them twice with different hashes, plus a merge commit, plus a handful of absurd conflicts in which every change clashes with itself.
- With rebase: Carla reapplies her commits (which include Bruno's three old ones) on top of the new chain, and gets the same festival of duplicates.
And if Carla resolves it badly and publishes, the old commits go back to the server and Bruno's rebase is undone. It is the classic cycle: somebody rebases, somebody else reverts it by accident, and the history ends up with everything twice over.
How to avoid it, in order of effectiveness:
- Do not rewrite shared branches. If more than one person works on it, it gets integrated with merge and that is that.
- If the branch is yours and published only as a backup, rebase as often as you like, but warn people before forcing and always use
--force-with-lease(never--force), which refuses if somebody has published something you do not have. - If you have been the victim, the way out is
git reset --hard origin/<branch>to discard your old copy and reapply your own work on top. The full range of cases, with the steps for not losing work, is in lesson 09-03.
git pull --rebase
git pull --rebaseIn lesson 04-04 we left this option pending. We can now understand it fully.
git pull is fetch + integration. With --rebase, the integration is a rebase of your local commits on top of what you have just fetched:
A comparison of the three modes, now complete:
| Mode | If you have not worked locally | If you have worked and there is divergence | History |
|---|---|---|---|
--ff-only |
Advances the branch | Fails and warns you | Untouched |
--no-rebase (merge) |
Advances the branch | Creates a merge commit | With knots |
--rebase |
Advances the branch | Reapplies your commits on top | Linear |
Why it is so popular: when two people touch different parts of the same project and synchronise often, pull with merge generates a trail of "Merge branch 'main' of git.example.com..." commits that say absolutely nothing. pull --rebase gets rid of them.
And why it has to be used with your head switched on: it rewrites your local commits. That is harmless if you have not published them (which is the typical case: you have just made them and there has been no push yet), and it is exactly the scenario of section 9 if you have.
To leave it configured:
# Make 'git pull' always rebase, in every repository
git config --global pull.rebase true
# Only for one specific branch
git config branch.main.rebase true
# Advisable if you enable the above: do not flatten the merges YOU made deliberately
git config --global pull.rebase mergespull.rebase merges (equivalent to --rebase-merges) deserves a note: by default, a rebase discards the merge commits of the chain it reapplies and flattens everything into one line. If you had deliberate merges in your local work, merges preserves them by rebuilding them.
If you leave pull.ff only as your global configuration (our recommendation from 01-06 and the safest one while learning), you can always ask for a rebase on the spot with git pull --rebase. Explicit wins.
rebase.autoStash and other conveniences
rebase.autoStash and other conveniencesgit rebase demands a clean working tree: if you have uncommitted changes, it refuses to start.
The manual solution is git stash, rebase and git stash pop (lesson 05-04). The automatic one:
With that, Git sets the changes aside on its own, rebases and brings them back when it finishes. If there is a conflict when bringing them back, it warns you and the stash stays safely on the stack. It is an excellent convenience, and its equivalent for pull is rebase.autoStash combined with pull.rebase, or git pull --rebase --autostash.
Other options worth knowing:
| Option / setting | What it does |
|---|---|
git rebase -i |
Interactive rebase: the whole subject of lesson 05-02 |
--rebase-merges |
Preserves merge commits instead of flattening them |
--keep-empty |
Preserves commits that end up empty once reapplied |
--no-verify |
Does not run the hooks (module 6) on each reapplied commit |
-X ours / -X theirs |
Automatic conflict resolution in favour of one side (remember the inversion!) |
--exec "<command>" |
Runs a command after each reapplied commit (lesson 05-02) |
git config rebase.updateRefs true |
Also updates the other branches that pointed at commits in the reapplied stretch |
git rebase --show-current-patch |
Shows the commit that is failing right now |
rebase.updateRefs solves an annoying problem: if you have stacked branches (b comes off a, c comes off b) and you rebase c, the intermediate references used to be left pointing at the old commits. With this option enabled, Git drags them along.
- The safety net
A rebase can go wrong: you can --skip a commit you should not have, resolve a conflict the wrong way round, or realise ten minutes later that the base should have been another one.
Good news: the original commits are still there. As we saw in section 2, a rebase does not delete objects, it merely stops referencing them, and Git keeps a record of where each reference has been. That record is the reflog, and with it the state before a rebase can nearly always be recovered.
This course devotes lesson 09-04: Recovering Lost Commits to that subject, including the exact procedure for undoing a disastrous rebase. Here it is enough to know two things:
- That the net exists: a botched rebase is recoverable for weeks.
- That it is no excuse for skipping the golden rule, because the reflog is local. It recovers your repository; it does not fix those of the colleagues who already downloaded the commits you rewrote.
And the cheap reflex you can adopt today: before a rebase that gives you pause, leave a marker.
git branch backup-before-rebase
git rebase main
# If it goes wrong: git reset --hard backup-before-rebase
# If it goes well: git branch -d backup-before-rebaseA branch costs 41 bytes. Peace of mind, rather more.
Common Mistakes and Tips
Mistake 1: believing that a rebase "moves" commits. It replaces them with new ones with different hashes. All of the danger of rebasing follows from that fact, and whoever takes it on board stops being surprised.
Mistake 2: rebasing a shared branch. If Bruno and Carla are both working on feature/csv-export, neither of them rebases it. It gets integrated with merge. The convenience of a linear history does not make up for an afternoon of duplicates.
Mistake 3: using --ours during a rebase thinking it is your version. It is the base's. During a rebase, you are theirs. When in doubt, git show :2:file and git show :3:file.
Mistake 4: using git rebase --skip to "get the conflict off my back". It discards the entire commit. If it contained real work, that work disappears from the resulting branch.
Mistake 5: running git commit in the middle of a rebase. The one that commits is git rebase --continue. You only resolve and run git add.
Mistake 6: git push --force instead of --force-with-lease. The first stamps on whatever is on the server without looking; the second refuses if somebody has published something you do not have. The difference was explained in 04-05 and here is where it really matters.
Mistake 7: rebasing without fetching first. git rebase main uses your local main, which may be two days out of date. The right order is git fetch origin, update main, and then rebase (or rebase directly onto origin/main).
Tip 1: rebase early and often. A branch that catches up every day has small conflicts; one that does so after a fortnight has one conflict per commit and all of them large.
Tip 2: always look at git log --oneline main..HEAD before rebasing. That list is exactly what is about to be rewritten. If its length or its content surprises you, the base you were about to use was not the one you thought.
Tip 3: enable rebase.autoStash and merge.conflictStyle zdiff3. Two lines of configuration that remove two of the most common sources of friction.
Tip 4: if the rebase turns ugly, abort. git rebase --abort is free and always works. Trying again with a clear head, with smaller commits or with a well-chosen --onto is usually far quicker than pressing on.
Exercises
Exercise 1: a basic rebase and a proof of immutability
Create a practice repository with a main branch and a feature/something branch coming off it. Add two commits to each. Then:
- Note down the hashes of the two commits on the feature branch.
- Rebase it onto
main. - Demonstrate with commands that the hashes have changed, that the author date has been preserved and that the committer date has not.
- Demonstrate that the original commits still exist in the object database.
Exercise 2: --onto to untangle a branch
Reproduce Carla's problem: create branch-a on top of main with two commits, and branch-b on top of branch-a with two more. Then, using git rebase --onto, get branch-b to hang off main containing only its own two commits. Verify the result with git log --graph --all --oneline.
Exercise 3: a rebase conflict and the inversion of sides
Provoke a conflict during a rebase (both branches modifying the same line of a file). With the rebase stopped:
- Show the three versions of the file from the index (stages
:1:,:2:and:3:) and identify which one corresponds to your commit. - Resolve it by keeping your version using the correct
git checkoutoption. - Finish the rebase.
- Repeat the experiment from scratch but abort with
--abort, and check that the branch gets its original hashes back.
Solutions
Solution 1:
mkdir /tmp/practice-rebase && cd /tmp/practice-rebase
git init -b main
echo "base line" > f.txt && git add . && git commit -m "Base"
git switch -c feature/something
echo "a" >> f.txt && git commit -am "Commit A"
echo "b" >> f.txt && git commit -am "Commit B"
git switch main
echo "something else" > g.txt && git add . && git commit -m "Commit on main 1"
echo "more" >> g.txt && git commit -am "Commit on main 2"# 2 and 3. Rebase and date check
git log --pretty='%h | author: %ad | committed: %cd | %s' --date=iso main..HEAD
git rebase main
git log --pretty='%h | author: %ad | committed: %cd | %s' --date=iso main..HEADThe hash is different, the author date identical and the committer date is the one from the moment of the rebase.
It exists as an object even though no branch reaches it. That is what makes the recovery of lesson 09-04 possible.
Solution 2:
mkdir /tmp/practice-onto && cd /tmp/practice-onto
git init -b main
echo "base" > f.txt && git add . && git commit -m "Base"
git switch -c branch-a
echo "a1" >> f.txt && git commit -am "A1"
echo "a2" >> f.txt && git commit -am "A2"
git switch -c branch-b
echo "b1" > b.txt && git add . && git commit -m "B1"
echo "b2" >> b.txt && git commit -am "B2"* 5e2b8c4 (HEAD -> branch-b) B2 * a93f16d B1 | * 6f4c2e9 (branch-a) A2 | * 3b7d5a8 A1 |/ * 1c8e4f2 (main) Base
branch-b hangs off main with its two commits and with no trace of A1 or A2.
Solution 3:
mkdir /tmp/practice-rebase-conflict && cd /tmp/practice-rebase-conflict
git init -b main
printf 'first\nsecond\nthird\n' > f.txt && git add . && git commit -m "Base"
git switch -c my-branch
sed -i 's/second/second MINE/' f.txt && git commit -am "Change from my branch"
git switch main
sed -i 's/second/second FROM MAIN/' f.txt && git commit -am "Change from main"
git switch my-branch
git rebase main# 1. The three versions from the index
git show :1:f.txt # common base
git show :2:f.txt # "ours" = main, the new base
git show :3:f.txt # "theirs" = MY commitStage :3: is mine: during a rebase, my commit is theirs.
# 2 and 3. Keep my own and carry on
git checkout --theirs f.txt
git add f.txt
git rebase --continue
git log --oneline# 4. The same scenario, aborting
git reset --hard b7e3f19 # (rebuild the setup from scratch if you prefer)
git switch my-branch
git log --oneline -1 # note the hash
git rebase main # conflict
git rebase --abort
git log --oneline -1 # the same hash as before--abort restores the branch exactly as it was: same commits, same hashes, same working tree.
Conclusion
git rebase stops being frightening as soon as you understand what it really does. The essentials of this lesson:
- Rebase does not move commits: it creates new ones. Change the parent, and the hash changes; change the hash, and it is a different object. The originals are left orphaned but remain in the database.
git rebase <base>reapplies onto<base>everything that is on your branch and not on it;git rebase --onto <new-base> <from> <to>lets you choose precisely which stretch to move and where to.- Rebase versus merge is not a technical question but a narrative one: merge tells what happened, rebase tells a tidied-up version. Rebase for what is still yours; merge to join public histories; when in doubt, merge.
- Rebase conflicts are resolved just like merge conflicts (lesson 03-05), but you get out with
--continue,--skip(which discards the commit) or--abort(which is always safe), and theours/theirssides are inverted: the base isours, your commit istheirs. - The golden rule: do not rewrite published history that others may hold. If you do, your colleagues end up with everything duplicated and the only way out is to coordinate.
git pull --rebaseapplies this same idea to synchronising and removes the useless merge commits;rebase.autoStashremoves the friction of having uncommitted changes.- The reflog is the safety net for your repository (lesson 09-04), not for everyone else's.
What comes next
So far we have used rebase for one thing only: changing the base. But the same mechanism — breaking a branch down into a list of commits and applying them again — allows a great deal more if you are allowed to edit that list before it runs: change the order, join two commits into one, split one into two, fix a message or remove a commit altogether.
That is exactly what git rebase -i does, and it is what Bruno needs to turn his fix, wip 2 and wip 3 commits into something he can show without embarrassment. We shall see it in lesson 05-02: Interactive Rebase.
Mastering Git: From Beginner to Advanced
Module 1: Introduction to Git
- What Is Git?
- Installing Git
- Basic Git Terminology
- The Git Data Model
- Configuring Git
- Initial Configuration
Module 2: Basic Git Operations
- Creating a Repository
- Cloning a Repository
- The Basic Git Workflow
- Staging and Committing Changes
- Inspecting Changes with git diff
- Viewing Commit History
Module 3: Branching and Merging
- Understanding Branches
- Creating and Switching Branches
- Merging Branches
- Merge Strategies
- Resolving Merge Conflicts
- Branch Management
Module 4: Working with Remote Repositories
- Understanding Remote Repositories
- Adding a Remote Repository
- Authenticating with Remote Repositories
- Fetching and Pulling Changes
- Pushing Changes
- Tracking Branches
Module 5: Advanced Git Operations
Module 6: Git Tools and Techniques
- Using Git Hooks
- Git Bisect
- Git Blame
- Git Log and Aliases
- Git Submodules
- Multiple Working Copies with git worktree
Module 7: Collaboration and Workflow Strategies
- Forks and Pull Requests
- Code Reviews with Git
- The Git Flow Workflow
- GitHub Flow
- Trunk Based Development
- Continuous Integration with Git
Module 8: Git Best Practices and Tips
- Writing Good Commit Messages
- Keeping a Clean History
- Ignoring Files with .gitignore
- File Attributes with .gitattributes
- Security Best Practices
- Performance Tips
Module 9: Troubleshooting and Debugging
- Common Git Problems
- Undoing Changes
- Resolving Divergence with the Remote
- Recovering Lost Commits
- Dealing with Corrupted Repositories
- Advanced Debugging Techniques
