Undo changes in Git - Cheat sheet for git checkout, stash, reset, clean, revert, rebase -i, amend
Tags: git, github, bash, cheatsheet
Published: 2019-04-28 23:00:20

> Originally posted at [michaelzanggl.com](https://michaelzanggl.com/articles/undo-changes-in-git). Subscribe to [my newsletter](https://michaelzanggl.com/) to never miss out on new content.


Git is one of these things that you learn progressively. You start with `git add .` to stage files, `git commit -m “message”` to commit them locally and finally `git push` to push them to the remote repository. 
But over time you make mistakes and if you always just google and paste in random commands, you might easily get confused by the sheer amount of commands like `git reset`, `git revert`, `git clean`, `git checkout .`, `git rebase -i`, `git commit --amend` and more. Let’s go through them one by one!

# Table of contents

* [Understanding key terms](#understanding-key-terms)
* [Don't undo: save changes for later use](#dont-undo-save-changes-for-later-use)
* [Don't undo: Add something to the latest commit or change the message](#dont-undo-add-something-to-the-latest-commit-or-change-the-message)
* [Unstage files (precommit)](#unstage-files-precommit)
* [Revert changes (precommit)](#revert-changes-precommit)
* [Remove new/untracked files and directories](#remove-newuntracked-files-and-directories)
* [Revert a commit in a new commit](#revert-a-commit-in-a-new-commit)
* [Remove latest commit(s) without a new commit](#remove-latest-commits-without-a-new-commit)
* [Change history](#change-history)
* [Remove branch locally](#remove-branch-locally)
* [Remove remote branch](#remove-remote-branch)
* [Conclusion](#conclusion)

## Understanding key terms

You can always find more help [here](https://git-scm.com/docs/), but it is important to understand some key terms.

- Index: When you stage files using `git add` it adds the files to an index file. So Staging is the same as index.

- Untracked: If you create a new file, it is untracked until you stage it.

- HEAD: HEAD points to the latest commit of your current branch. That's why you sometimes see the funnily chosen `detached HEAD`, when you check out an older commit for example.

- The dot in `git add .` refers to the current directory. So all files in the current directory and in all sub directories will get staged. We will learn some more commands that make use of the dot.



## Don't undo: save changes for later use

> `git stash -u`

This will save every untracked files (-u flag), staged and unstaged modifications.

To retrieve the latest stash again, run

```bash
git stash apply
```

To keep your stash list clean you can also execute `git stash pop` instead. It will do the same as apply, but also remove the applied stash from the stash list.

Use `git stash list` to retrieve a list of all your stashes. 

**Use case**: You are working on something but suddenly need to change branches (e.g. to create a hotfix for a sudden urgent bug)

**Bonus**: To know what stash belongs to what, you can give your stash a note by running: `git stash push -u -m "your message"`



## Don't undo: Add something to the latest commit or change the message

> `git commit --amend -m "added file and changed message to this"`

`amend` allows you to add more files to the latest commit.

**Use case**: You forgot to stage a certain file that should have been part of the commit.

**Bonus**: Even if no file has been added you can still commit with the "amend" option to simply change the message.

**If other people are working on your branch: Be careful to not amend when the latest commit has already been published (pushed). It would require `git push --force`**




## Unstage files (precommit)

> `git reset .`

also often seen as just `git reset`

Example:

```bash
echo "code code code" >> index.js
git add .
git reset .
```

It will simply unstage `index.js` and put the changes back into your working tree. You can apply the `--hard` flag to completely get rid of your changes.

**Use case**: Out of habit, you staged all modifications using `git add .`, but want to unstage certain files to commit them separetely.

**Bonus**: To unstage only specific files you can do it the same way as with `git add`

```
git add User.js UserController.js UserService.js
git reset UserService.js User.js
```

This keeps `UserController.js` staged.





## Revert changes (precommit)

> `git checkout .`

`git checkout` is used to change branches, but if you check out a filepath instead, it has a different purpose.

If you have changed any files locally, this will revert your changes with either what is in the index or in the commit. More on that in a moment.

Example:

```bash
echo -n "1" >> newfile
git add .
git commit -m "added newfile"

echo -n "2" >> newfile
```

So we created a new file, staged and commited it and then appended the text "2" to the file.

If you now run `git checkout newfile` it will remove any local changes, in this case "2". Your working tree will be clean again.

Let's look at what happens when you start staging in between. This will give you a new perspective into `git add`, making the command more powerful than ever.

```bash
echo -n "1" >> newfile
git add .
git commit -m "added newfile"

echo -n "2" >> newfile
git add .

echo -n "3" >> newfile
```

This is the content of the file in the different states
- HEAD: "1"
- Index: "12"
- Working Tree: "123"

If you now run `git checkout newfile` it will only remove the content `3` from your local changes. In other words, if it finds the file in the index, it will revert the changes by what is in the index, not in the HEAD. It will still have `newfile` staged with the content "12". 

To also remove "2" you have to first unstage the file as we learned and then check it out again.

```bash
git reset
git checkout .
```

> Since it only replaces the files with those from the index / HEAD, `git checkout` won't do anything with untracked files.

**Use case**: You made modifications to file A and when modifying file B you realized the changes in file A were actually not necessary and it's better to just check it out again.

**Bonus**: If a file you want to checkout is unfortunate enough to have the same name as a branch, you have to checkout like this `git checkout -- master` to avoid checking out the master branch for example.





## Remove new/untracked files and directories

> `git clean -f`

Similar to `git checkout .` with the difference that it only works for untracked files. You can run `git clean --dry-run` or `git clean -n` to see which files would be **permanently** deleted. Add the `-d` flag to include directories.

```bash
touch newfile
git clean -d -n
```

The output will be `Would remove newfile`

**Bonus**: Use `git clean -i` to start interactive cleaning, giving you more options over what to do with each file individually.






## Revert a commit in a new commit

> git revert `commit-id`

Reverts the changes of the commit ID and creates a new commit for it.

**Use case**: A commit that has been pushed causes a bug and has to be reverted.

**Bonus**: Apply the `-e` or `--edit` flag to modify the commit message. For example, you can add the reason why this commit has to be reverted.






## Remove latest commit(s) without a new commit

> git reset HEAD^

Imagine you commit something by accident and you want to undo the commit. `git reset HEAD^` will revert the latest commit like it never happened and puts the modifications back to your local working tree. It will not create a new commit and the latest commit will disappear from the history.

We saw `git reset` before already to unstage files. When you pass a commit however, you can actually reset HEAD to whatever commit you want. Imagine you have the commits A, B and C. With `git reset A` A will become the latest commit and if you `git log`, you will no longer find B and C.

We said that `git reset HEAD^` keeps the changes in the working tree. So in other words, `git reset HEAD^` resets the HEAD and the index.

Use the `--soft` flag to only reset the HEAD, which means that the changes will remain `staged`.
Use the `--hard` flag to reset HEAD, index and the working tree, which means the changes will be completely deleted.

Instead of `git reset HEAD^` you can also write `git reset HEAD^1`, `git reset HEAD~1` or `git reset HEAD~`.

**If other people are working on your branch: Be careful to not reset HEAD to a previous commit when the commits you reset have already been published. It would require `git push --force`**

**Use case**: You committed modifications **locally**, but realized you were comitting to the wrong branch.

**Bonus**: There are a total of four ways to reset more than just the latest commit.

All of these four lines reset the last two commits
```bash
git reset HEAD^2
git reset HEAD^^

git reset HEAD~2
git reset HEAD~~
```

**Bonus2**: `git reset` does not actually remove the latest commit, it simply "rolls back" HEAD to the commit you want. B and C are still saved for 30 days.






## Change history

Let's take a quick look into interactive rebasing. We learned `git reset HEAD~1 --hard` as a way to remove the latest commit. We can achieve the same thing with interactive rebasing, just that it is more powerful. `rebase` this time actually changes the commit object and doesn't just point HEAD to a specific commit.

```bash
git rebase -i HEAD~4
```

We see the same `HEAD~4` as with `git reset` before. 4 refers to the number of commits you want to rebase.

This will now open an editor (vim?) with the following content

```
pick 123a44b0 your latest commit
pick C23a44b0 commit 3
pick B23a44b0 commit 2
pick A23a44b0 commit 1

# Rebase ...
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
```

Let's replace 

```
pick 123a44b0 your latest commit
pick C23a44b0 commit 3
pick B23a44b0 commit 2
pick A23a44b0 commit 1
```

by

```
pick 123a44b0 your latest commit
fixup C23a44b0 commit 3
f B23a44b0 commit 2
drop A23a44b0 commit 1
```

As you can see, you can either write out the option like `fixup` or use the abbreviation `f` in this case.

This will squash (merge) `C23a44b0` and `B23a44b0` into `123a44b0`, making one commit out of it. Regarding `A23a44b0`, this commit will get completely removed. So once the rebase is complete you will end up with only one commit `123a44b0`.

**If other people are working on your branch: Be careful to not rebase when the commits you want to rebase have already been published. It would require `git push --force`**

**Use case**: You are working **all alone on your own branch** and want to have a clean list of commits for the PR/MR.

**Bonus**: You can even move commits around by just changing the order they appear in the list.






## Remove branch locally

> `git checkout master`

> `git branch -D branch-to-delete`

**Use case**: Instead of reverting a bunch of commits, sometimes it is just faster to delete your local branch and check it out again.

## Remove remote branch

`git push origin branch-to-delete --delete`

**Use case:** After pushing you realize the name you have chosen doesn't make sense and you want to change it (**Bonus**: Do so with `git branch -m "new-name"`)

---

## Conclusion

Git is quite a beast to master and I am sure there are other ways to achieve some of these tasks. Please leave a comment if you know any and I can add them to the list.

If this article helped you, I have a lot more tips on simplifying writing software [here](https://michaelzanggl.gumroad.com/l/intent-driven-development).