Git can also reset the revision history to a specific point so that you can start over from that point.
Suppose you realise your last few commits have gone in the wrong direction, and you want to go back to an earlier commit and continue from there — as if the “bad” commits never happened. Git’s reset feature can help you do that.
Git reset moves the tip of the current branch to a specific commit, optionally adjusting your staged and unstaged changes to match. This effectively rewrites the branch's history by discarding any commits that came after that point.
Resetting is different from the checkout feature:
- Reset: Lets you start over from a past state. It rewrites history by moving the branch ref to a new location.
- Checkout: Lets you explore a past state without rewriting history. It just moves the
HEADref.
→
[reset to C2...]
main branch!There are three types of resets: soft, mixed, hard. All three move the branch pointer to a new commit, but they vary based on what happens to the staging area and the working directory.
- soft reset: Moves the cumulative changes from the discarded commits into the staging area, waiting to be committed again. Any staged and unstaged changes that existed before the reset will remain untouched.
- mixed reset: Cumulative changes from the discarded commits, and any existing staged changes, are moved into the working directory.
- hard reset: All staged and unstaged changes are discarded. Both the working directory and the staging area are aligned with the target commit (as if no changes were done after that commit).
Scenario Imagine the following scenario. After working with the things repo for a while, you realised that you made the following mistakes.
i) First, you added four 'bad' commits (i.e., commits that shouldn't have been created) -- shown as B1 to B4 in the revision graph given below.
iii) Last, you did a 'bad' change in `shapes.txt`, but didn't stage it yet.
Target To rewrite the history of the repo in way that gets rid of the 'bad' commits/changes listed above.
Preparation
Now we have some 'bad' commits and some 'bad' changes in both the staging area and the working directory. Let's use the reset feature to get rid of all of them, but do it in three steps so that you can learn all three types of resets.
1 Do a soft reset to B2 (i.e., discard last two commits). Verify,
- the
mainbranch is now pointing atB2, and, - the changes that were in the discarded commits (i.e.,
B3andB4) are now in the staging area.
Use the git reset --soft <commit> command to do a soft reset.
git reset --soft HEAD~2
You can run the following commands to verify the current status of the repo is as expected.
git status # check overall status
git log --oneline --decorate # check the branch tip
git diff # check unstaged changes
git diff --staged # check staged changes
Right-click on the commit that you want to reset to, and choose Reset <branch-name> to this commit option.
In the next dialog, choose Soft - keep all local changes.

2 Do a mixed reset to commit B1. Verify,
- the
mainbranch is now pointing atB1. - the staging area is empty.
- the accumulated changes from all three discarded commits (including those from the previous soft reset) are now appearing as unstaged changes in the working directory.
Note howincorrect.txtappears as an 'untracked' file -- this is because unstaging a change of type 'add file' results in an untracked file.
Use the git reset --mixed <commit> command to do a mixed reset. The --mixed flag is the default, and can be omitted.
git reset HEAD~1
Verify the repo status, as before.
Similar to the previous reset, but choose the Mixed - keep working copy but reset index option in the reset dialog.

3 Do a hard reset to commit C4. Verify,
- the
mainbranch is now pointing atC4i.e., all 'bad' commits are gone. - the staging area is empty.
- there are no unstaged changes (except for the untracked files
incorrect.txt-- Git leaves untracked files alone, as untracked files are not meant to be under Git's control).
Use the git reset --hard <commit> command.
git reset --hard HEAD~1
Verify the repo status, as before.
Similar to the previous reset, but choose the Hard - discard all working copy changes option.
done!
Rewriting history can cause your local repo to diverge from its remote counterpart. For example, if you discard earlier commits and create new ones in their place, and you’ve already pushed the original commits to a remote repository, your local branch history will no longer match the corresponding remote branch. Git refers to this as a diverged history.
To protect the integrity of the remote, Git will reject attempts to push a diverged branch using a normal push. If you want to overwrite the remote history with your local version, you must perform a force push.
Scenario You have a local repo that is linked to a remote repo on the GitHub. You have pushed all local commits to the remote repo (i.e., the two are in sync).
Target You wish to rewrite the last commit in the local repo and update the remote repo to match the local repo.
Preparation
1 Rewrite the last commit as follows: Reset the current branch back by one commit, and add a new commit.
For example, you can use the following commands.
git reset --hard HEAD~1
echo "water" >> drinks.txt
git add .
git commit -m "Add drinks.txt"
2 Observe how the local branch is diverged.
git log --oneline --graph --all
* 9ff4d04 (HEAD -> main) Add drinks.txt
| * d086f15 (origin/main) shapes.txt: Add some shapes
|/
* 01472c4 colours.txt: Add some colours
* 4d6714c Update fruits list
* 55be601 Add colours.txt, shapes.txt
* fb8d75d Add elderberries and figs into fruits.txt
* 16cad65 Add fruits.txt
3 Attempt to push to the remote. Observe Git rejects the push.
git push origin main
To https://github.com/.../things.git
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to 'https://github.com/.../gitmastery-samplerepo-things.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: ...
4 Do a force-push.
You can use the --force (or -f) flag to force push.
git push -f origin main
A safer alternative to --force is --force-with-lease which overwrites the remote branch only if it hasn’t changed since you last fetched it (i.e., only if remote doesn't have recent changes that you are unaware of):
git push --force-with-lease origin main
done!
