Originally posted at michaelzanggl.com. Subscribe to my newsletter 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
- Don't undo: save changes for later use
- Don't undo: Add something to the latest commit or change the message
- Unstage files (precommit)
- Revert changes (precommit)
- Remove new/untracked files and directories
- Revert a commit in a new commit
- Remove latest commit(s) without a new commit
- Change history
- Remove branch locally
- Remove remote branch
- Conclusion
Understanding key terms
You can always find more help here, 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
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:
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:
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.
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.
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.
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
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.
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.
Top comments (4)
In case you didn't know this one already: How to undo (almost) anything with git :)
I've got it bookmarked so it shows up as soon as I type "undo" in my browser address bar. Probably the most-used URL I frequent.
Such a great tutorial! Easy to digest and apply, thanks heaps :)
Use all of them regularly, but this list is good with use-case explanations.
Great article. Really clean explanations