Skip to content

Beginner · Git in depth · topic 12 of 53 · 35 min

Rebase, undo and rescue: rebase, reset, revert, reflog and stash

Explanation

Rebase replays your commits on top of another branch to give a straight-line history, and interactive rebase lets you squash, reword or drop commits before sharing them. Never rebase commits others already have. To undo, use revert on shared history and reset only on local history. Lost a commit? git reflog remembers where HEAD has been. git stash parks unfinished work.

What rebase does

Merging keeps both lines of history and joins them with a merge commit. Rebasing does something different: it takes the commits that are only on your branch, temporarily removes them, moves your branch to the tip of the other branch, and then replays your commits one by one on top. The result looks as if you had started your work from the latest main.

bash
# before:  main: A - B - C        feature: A - B - X - Y
git switch feature
git rebase main
# after:   feature: A - B - C - X' - Y'   (X' and Y' are NEW commits)
X' and Y' have the same changes as X and Y, but new parents, so they get new IDs.
Think of it like this: Rebasing is like rewriting your diary entries as if you had read the latest news before writing them. The content is the same, but the entries are freshly written, so they are not the same pages any more. Anyone who photocopied your old pages now has a different version.

Interactive rebase: tidy up before you share

While working, you often make commits like "wip", "fix typo" and "really fix it". Before opening a pull request, interactive rebase lets you clean them up. git rebase -i HEAD~4 opens an editor listing the last four commits, oldest first, each with an action word.

output
pick   1a2b3c4 Add coupon field to checkout
fixup  5d6e7f8 fix typo
squash 9a8b7c6 Validate coupon expiry date
drop   0f1e2d3 Temporary debug logging
reword 4c5d6e7 add tests
Save and close the editor, and Git rewrites the commits in that order.
ActionWhat it does
pickKeep the commit as it is
rewordKeep the changes, edit the message
squashMelt into the previous commit and combine both messages
fixupMelt into the previous commit and throw this message away
dropRemove the commit completely
editStop at this commit so you can change it

If a replayed commit conflicts, Git stops. Fix the files, git add them, and run git rebase --continue. git rebase --abort returns everything to how it was.

The golden rule

Careful: Never rebase commits that other people already have, such as anything on a shared main branch. Rebase creates new commits with new IDs; your teammates still have the old ones, and the histories split in confusing ways. Rebase only your own local or personal branches. After rebasing a branch you had already pushed for your own pull request, push with git push --force-with-lease, which refuses to overwrite the server if someone else pushed in the meantime. Plain --force does not check.

Merge or rebase?

MergeRebase
HistoryTrue record, with merge commitsStraight line, easy to read
Safe on shared branchesYesNo (rewrites commits)
ConflictsSolved once, in the mergeMay be solved commit by commit
Typical useBringing a finished branch into mainUpdating your own branch, cleaning up before a PR

Cherry-pick: copy one commit

git cherry-pick <id> applies the changes of one commit onto your current branch as a new commit. It is useful to bring a single bug fix from main into a release branch, without merging everything else.

Undoing: reset versus revert

git reset moves the current branch pointer back to an earlier commit. The three modes differ in what happens to the changes from the commits you step back over.

bash
git reset --soft HEAD~1    # undo the last commit, keep its changes STAGED
git reset --mixed HEAD~1   # (default) undo the commit, keep changes in your files, unstaged
git reset --hard HEAD~1    # undo the commit AND throw away its changes (dangerous)

git revert 5e2c8d1         # make a NEW commit that undoes 5e2c8d1; history is kept
HEAD~1 means "one commit before HEAD".

reset rewrites history, so it follows the golden rule: use it only on commits nobody else has. On a shared branch use git revert, which adds a new commit with the opposite changes. Everyone simply pulls it.

reflog: the safety net

Git writes down every place HEAD has pointed to in the reflog (reference log). Even after a reset --hard or a bad rebase, the old commits usually still exist for a while (by default, unreachable entries are kept about 30 days). Find the ID and bring it back.

bash
git reflog
# 7d41c2e HEAD@{0}: reset: moving to HEAD~3
# a19f0b3 HEAD@{1}: commit: Add UPI QR payment option
git branch rescue a19f0b3     # a new branch pointing at the "lost" commit
The reflog is only on your machine; it is not pushed or shared.

stash: park unfinished work

bash
git stash push -m "half-done coupon form"   # save changes and clean the working directory
git stash list                               # stash@{0}: On feature: half-done coupon form
git stash apply stash@{0}                    # bring it back (the stash stays in the list)
git stash pop                                # bring back the latest and remove it from the list
git stash push -u -m "with new files"        # -u also stashes untracked files
Stashes are easy to forget. For anything you will keep for more than a few hours, a work-in-progress commit on a branch is safer.

Common mistakes and how to fix them

  • Ran reset --hard and lost work: check git reflog straight away and create a branch at the old commit. Uncommitted changes that were never staged or committed cannot be recovered this way.
  • Rebased main and force-pushed: tell the team at once. Restore the old tip from someone's clone or the reflog, and protect main so force pushes are blocked.
  • Squash in the first line of the todo list fails, because there is no earlier commit to melt into. The first line must be pick or reword.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Rebase, undo and rescue: rebase, reset, revert, reflog and stash · 1/9

Rebase and undo

This lesson covers rewriting and undoing history safely: rebase, reset, revert, reflog and stash.

Prefer a video by a teacher? Find free videos on “git rebase interactive reset revert reflog explained” on YouTube ↗

In real life: A hotfix at a Bengaluru fintech

A wrong interest rate went live in a loan app. The on-call engineer does not reset main, because twenty developers have already pulled it. She runs git revert on the bad commit, which adds one new commit putting the old rate back, and the pipeline deploys it in minutes. The same fix is needed in the release/2.4 branch used by the older Android app, so she runs git cherry-pick with the revert commit's ID on that branch. History stays honest, and nobody's clone breaks.

bash
git switch main
git revert 8c1d2e3          # new commit: Revert "Update loan interest rate"
git push
git switch release/2.4
git cherry-pick 4f5a6b7     # copy that revert into the release branch

Practice (0/4 done)

Task 1 · Question

You committed too early and want to undo the last commit but keep all its changes staged. Which command?

Task 2 · Question

A bad commit is already on the shared main branch that the whole team pulls. What is the safe way to undo it?

Task 3 · Code · Python

Simulate an interactive rebase todo list. Write apply_todo(lines) where each line is "action id message", e.g. "pick 1a2b3c4 Add coupon field". Actions: pick keeps the commit; drop removes it; squash melts it into the previous resulting commit and joins the messages with " / " (real Git opens an editor here); fixup melts it into the previous commit and discards its own message. Return the list of resulting commit messages in order. If squash or fixup has no previous commit, raise ValueError, just like Git refuses.

Task 4 · Practice

In a practice repository, make four commits on a branch called feature/coupon: "Add coupon field", "wip", "fix typo" and "Add coupon tests". Use interactive rebase to turn them into two clean commits. Then simulate a disaster: run git reset --hard HEAD~2 and use the reflog to get the commits back. Finally, stash a small change, switch branches, and bring it back.

Quick quiz

Question 1

1.Why does rebasing change commit IDs?

Question 2

2.In interactive rebase, which action melts a commit into the previous one and discards its message?

Question 3

3.What does git reset --mixed HEAD~1 (the default mode) do?

Question 4

4.You need one bug-fix commit from main in the release/3.1 branch, and nothing else. Which command fits?

Question 5

5.After git reset --hard, which command helps you find the commit you lost?

Question 6

6.Why prefer git push --force-with-lease over git push --force after rebasing your own PR branch?

Read more (free)

Before this, learn

This topic builds on these. If something here feels new, open them first.

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Why does rebasing change commit IDs?

Answer: The replayed commits have new parents, and the parent is part of the hash

A commit's ID is a hash that includes its parent. New parent, new ID.

In interactive rebase, which action melts a commit into the previous one and discards its message?

Answer: fixup

fixup combines the changes but keeps only the previous commit's message; squash keeps both messages.

What does git reset --mixed HEAD~1 (the default mode) do?

Answer: Undoes the last commit and leaves its changes in your files, unstaged

Mixed moves the branch back and resets the staging area, but your working files keep the changes.

You need one bug-fix commit from main in the release/3.1 branch, and nothing else. Which command fits?

Answer: git cherry-pick <fix-commit-id>

cherry-pick applies just that commit's changes as a new commit on the current branch.

After git reset --hard, which command helps you find the commit you lost?

Answer: git reflog

The reflog records every position HEAD has had, including before the reset.

Why prefer git push --force-with-lease over git push --force after rebasing your own PR branch?

Answer: It refuses to overwrite the remote if someone else pushed new commits

force-with-lease checks that the remote is still where you last saw it, so you do not wipe out a teammate's commits.

main has no new commits since feature was created. What does git merge feature (run on main) do by default?Related

Answer: Moves main forward to the tip of feature (fast-forward)

When main is an ancestor of feature, Git just moves the pointer. Use --no-ff if you want a merge commit anyway.

From Branching, merging and conflicts

Which commit message is best?Related

Answer: Fix PIN code check for codes starting with 0

A good subject says what the commit does, as a short command, so the history explains itself.

From Git and GitHub

You ran git add on a file. Which command shows the changes that will go into the next commit?Related

Answer: git diff --staged

git diff --staged compares the staging area with the last commit. Plain git diff shows only unstaged changes.

From Git basics: repositories, staging and commits

In trunk-based development, how do teams merge unfinished features safely?Related

Answer: They hide unfinished work behind feature flags

Feature flags let half-built code be merged and deployed while switched off, so branches stay short-lived.

From Collaboration: remotes, pull requests and branching strategies

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 2 more topics in “Git in depth” before its exam: Collaboration: remotes, pull requests and branching strategies, Semantic versioning, tags and releases.
  2. Module 3 exam: Git in depth · 13 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Docker. 53 topics left in the course before the final exam.

← Previous: Branching, merging and conflicts