Git (2 Part Series)
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 checkout .,
git rebase -i,
git commit --amend and more. Let’s go through them one by one!
- 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
You can always find more help here, but it is important to understand some key terms.
Index: When you stage files using
git addit 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.
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.
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"
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
git reset .
also often seen as just
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 User.js UserController.js UserService.js git reset UserService.js User.js
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.
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 checkoutwon'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.
git clean -f
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
git clean -i to start interactive cleaning, giving you more options over what to do with each file individually.
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
--edit flag to modify the commit message. For example, you can add the reason why this commit has to be reverted.
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.
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.
--soft flag to only reset the HEAD, which means that the changes will remain
--hard flag to reset HEAD, index and the working tree, which means the changes will be completely deleted.
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~~
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.
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
pick 123a44b0 your latest commit pick C23a44b0 commit 3 pick B23a44b0 commit 2 pick A23a44b0 commit 1
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)
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
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.
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.
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")
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.
Want to learn more about Git or a way to remember all these small things. Why not rebuild something like VS Code's Git integration. Here is the source code for starters: https://github.com/Microsoft/vscode/tree/master/extensions/git.