git set go 🐱👤
So before starting, can you name one important skill that every developer must possess? You’re right, that’s git. It doesn't matter which tech stack you follow or which domain you follow, git is one of those skills which you must have on your tech stack.
Today we’ll learn some of the most useful Git/GitHub concepts that will heavily boost up your productivity 😎
Before moving further, I expect that you know the basics of Git/GitHub:
- The three-stage architecture of Git
- Staging files and committing changes
- pushing and pulling changes from a remote repository
If you already know these concepts, that’s great! But if you are unfamiliar with any one of these, I would recommend you learn these concepts first.
So Let’s start 🚗
SELECTIVE STAGING
git add
and git commit
are the first thing that we do while learning git. But these commands also contain many variations. There are quite a lot of things you can do with just these two commands. We’ll be looking at one of the most useful flags which can be used with git add
.
Here we have added two functions to our code in a file index.js. Now the first function is thoroughly tested and is ready to be committed, but the second one is yet to be tested. Now to only commit the first function, we need to only stage that part of the file. To achieve this we’ll use
git add -p <file_name>
What it does is give you some options using which you can selectively stage hunks of code in a particular file. It will present you with the hunk and some options:
y → stage the hunk
n → do not stage the hunk
.
.
e → edit the hunk
Now if the hunk contains the desired code, you can type y
and this will stage the hunk, but if it doesn't contain the required code, you can also type n
to de-select the hunk. But since in our case the hunk contains both the good and bad functions, we need to edit the hunk to only contain the good one i.e. the first function. So we’ll type e
and enter into the editing mode:
How to use the editor 👀
The editor you see here is the vim editor. By default, git opens vim editor unless you configure it to use another. I would recommend you leave it at default and use vim since its terminal-based ⚙
But how do you edit using this editor 🤔
The editor is quite simple. There are mainly two modes:
1. Command mode → Using this mode, you can navigate the file.
2. Insert mode → Using this mode, you can insert text and make changes in the file.
By default, you are in command mode. You can simply use the
h
,j
,k
,l
keys ( or even the arrow keys) to navigate to a different location in the file. On coming to your desired location, pressi
to go to the insert mode. Now you can change some text, insert new text, delete lines, and do everything you would do with a normal editor. After making the changes, go back to the command mode by pressing theESC
key. Now, in command mode, type:wq
to save the file and exit the editor.Seems complicated 😥? Not at all, just practice 2–3 times and you’ll get used to it 😉.
Now, using this editor we can edit the hunks. We just need to simply delete the lines we don’t need. Here, we’ll delete the second function and then we’ll save the file and exit the editor.
After saving, when you use git status, you will see that the file index.js
is both staged and not staged, which means that some parts are staged and some are not. Now we can safely commit the changes, and then the commit only contains the first function 😀.
UPDATING THE MOST RECENT COMMIT
Hmm, we thought that the second function was a sus, and may fail some tests. Now, since we have tested it thoroughly, it is safe to be committed. But, if we commit the change, we’ll have two different commits for these two functions, which we do not want 🙂.
But no need to regret it, we can now use git commit --amend
to update the previous commit with the new changes.
First, add the file to the staging area and then use
git commit --amend -m <your_commit_message>
As you can see there is no extra commit for adding the second function 😀
Quick Tip 🖤
if you want your commit message to be the same as the previous commit’s message you can use the--no-edit
flag with the commit statement
git commit --amend --no-edit
CAUTION WHILE USING git commit --amend
while using --amend
you must ensure that you have not yet pushed the previous commit to your GitHub repository (or any other remote repository). If you have already pushed the changes to your remote repository, you’ll not be able to update your repository by simply using:
git push <remote_name> <branch_name>
So I recommend you to not use amend if you have already pushed your code to GitHub, unless, you like to live dangerously 💀
So, does that mean you cannot further push your changes if you have already pushed the recent commit and also used --amend
? Well, NO 😏, we can still push the changes. But this time, it won’t be a normal push, but a force push. To understand force push we must know how git organizes the commits.
HOW HISTORY WORKS IN GIT
This topic is quite important for understanding further concepts, so
PAY ATTENTION !
Every commit in git depends on the previous commit. Also, every commit in git is an object containing a hash. This hash tells git about the new changes that were introduced in a particular file with respect to the previous commit. This is how git organizes our project.
You can see these hashes by opening the hidden .git
folder.
All of your commits, branches, tags, and all other objects and events are stored here. For instance, if you navigate to .git/refs/heads/
, you can see all of your branches.
Here, we only have one i.e. master. The file contains the current HEAD position in that particular branch.
Here, you can see that the file contains a hash of commit, to which the HEAD currently points to. This is the same hash you see on the git log
output.
Now, let us consider the history of our project
The string highlighted in red is the hash of commit before using --amend
. Now, when we use git commit --amend
The commit’s hash is changed 😶. This happened because there were some more changes added to the previous commit, and to tell git about those changes, the commit’s hash is updated.
Now, let us imagine that we have already pushed the previous commit to GitHub. Now, in Github, there are only two commits and the hash of the top-most commit is:
2b35b887fefb8686094d9b2882e6429118267c41
After amending the commit, our local repository still has only 2 commits. But now the commit’s hash of top-most commit is:
5c7131b20436546e0fec394f44e1a6404cdff825
Now when we try to push these changes, GitHub will reject these changes, since the history in your GitHub repo will get modified.
To overcome this problem, we’ll use force push.
While trying to push the changes, we’ll use -f
or --force
flag:
git push <remote_name> <branch_name> -f
This command will force GitHub to make the state of the GitHub repository match the state of our local repository, or in simple words, change the history.
NOTE:
Modifying history of remote repositories (especially public repositories) is not recommended. It severly affect the state of the repository, and will cause problems for other contributors working on that particular repositories. It can also lead to loss of important commits which might not get reverted back.
RESETTING
Let's assume that the two functions which we committed earlier have some bugs. Now, even after staring at the code for an hour, you cannot find the bug, so you decide to remove the two functions and go back to the previous state (or previous commit). This is what git reset
is used for. It takes us to a particular commit in our history. There are three ways you can use git reset
:
- Soft reset
- Mixed reset
- Hard reset
Soft Reset →
Soft reset takes you to the commit specified but it won’t change the working tree and the index.
As the working tree and the index are the same but different from HEAD, git status
will now show changes in green ready to be committed. Even if we had some un-committed changes, git will preserve them while doing a soft reset.
Let’s now use git reset
with a soft flag to see things in action.
Currently, we have 2 commits. Now, use the following command:
git reset --soft <commit_hash>
Since we want to go the initial commit state, we’ll use:
git reset --soft 03cd347301bddacf0b3e52fb30705bbf80514c77
After using this command we’ll see, that we now have only one commit i.e. initial commit. Also, our working tree is the same and the changes that were there in the second commit are now staged.
Mixed Reset →
Git, by default, does mixed reset. Running git reset
with --mixed
option updates the index with the contents of whatever commit HEAD points to currently, leaving Working Directory intact. In simple words, It will undo the last commit and will un-stage all the changes. Upon using it, our repository will look like we have made some changes, but have staged them yet.
As the working tree is the same but the index is different, git status
will show that the changes are not staged for commit in red. Here also, even if we had some un-committed changes, git will preserve them while doing the mixed reset.
Let’s use the git reset
command, but now with the mixed flag:
git reset --mixed 03cd347301bddacf0b3e52fb30705bbf80514c77
After using this command we’ll see, that we now have only one commit i.e. initial commit. Also, our working tree is the same and the changes that were there in the second commit are unstaged.
Hard Reset ⚡ →
This is one of the most dangerous commands you can use in git because it will undo the commit and change both the index and the working tree. In short, your repository’s state will become exactly similar to the commit specified. Despite being dangerous, it is the most frequent command you’ll use 🙂
Here, you can see, HEAD, the index, and the working tree, all are changed. That means if you had some un-committed changes before using this command, they will be gone forever. That’s why it is recommended to avoid a hard reset as much as possible.
Now let’s use the git reset
command with the hard flag:
git reset --hard 03cd347301bddacf0b3e52fb30705bbf80514c77
After using this command we’ll see, that we now have only one commit i.e. initial commit. Also, our working tree and the index are now changed, and the state of our repository is the same as what was in the specified commit.
Quick Tips 🖤
1. While doing a mixed reset, you won’t need to explicitly use the
--mixed
flag, because, by default, git does a mixed reset only.2. You can also use a reference’s hash or reference’s name instead of a commit hash while performing a reset. For example, to make the state of a
dev
branch similar to themain
branch, we can use the following command in the dev branch:
git reset --hard main
REVERTING
Resetting may cause you to lose some valuable information, especially the hard reset. One better alternative is a revert.
git revert
takes a commit (specified by us), removes it changes, and then makes a new commit. The newly made commit will not have the changes that were introduced in the specified commit.
Let’s use revert to understand it better.
git revert <commit_hash>
since we want to revert the changes of the second commit, we’ll use the commit hash of the second commit:
git revert 5c7131b20436546e0fec394f44e1a6404cdff825
Upon entering this command, git will open an editor where you can change the commit message of the revert commit.
You can enter your message or leave it at default. After that save the file and exit the editor. After exiting the editor, git will revert the changes and will make a revert commit.
You can also verify that by looking at your working tree. The two functions are removed from index.js
:
Quick Tip 🖤
You can use the
--no-edit
flag to keep the default commit message while doing revert. Using this, git won’t prompt us to enter a commit message using an editor.You can use the
--no-commit
flag to tell git not to commit the changes while reverting, but place the inverted changes to the staging area and the working tree.
STASHING
Suppose we have written some code that might not be useful now, but we’ll need it while implementing some features in the future. So, now we want to save these changes for future use, but we also need to ensure that these changes should not be present in our codebase as of now 🙂.
There are two ways to accomplish this task:
I guess no one would ever want to use the first method 😅. So, let's look at what exactly is git stash
.
On using git stash
command, git takes all the tracked changes, saves them for later use, and then removes them from the working tree.
Let's see git stash
in action. Here we add another function stashDemo()
to our index.js
file:
As of now, we won’t need this function but may need it in the future. So to save this change, we’ll stage all the changes and then create a stash
$ git add index.js
$ git stash
After using these commands the staged changes will be removed from the current working tree and will be saved as a stash.
Now we can view the list of all the stashes using:
git stash list
Every stash contains an index. Here, we only have on stash and is represented by the index 0
. This index helps us in applying the correct stash to our working tree.
To apply a stash, we’ll use the following command:
git stash apply <index_of_the_stash>
Since the index in our case is 0
, we’ll write:
git stash apply 0
After applying the stash, all our changes that were saved in the stash earlier will again be written in the respective files:
Now we can do whatever we want. we can edit them if some changes are needed, or can directly stage them and make a commit.
But, if we use the git stash list
command again, we can still see our stash present there. To delete the stash, we can use the following command:
git stash drop <index_of_the_stash>
Quick tip 🖤
1. If you have not staged all the changes, but want all of them in the stash, you can save a step and use:
git stash -u
This will stage all the files and then create a stash
2. If you have more than one stash, it may become difficult to identify which one you need. To solve this problem, you can specify a stash message using:
git stash -m <your_message>
3. You can directly apply and delete the top-most stash using:
git stash pop
RE-WRITING HISTORY: INTERACTIVE REBASE
Interactive rebasing is one of the most useful features in git. Using rebase you can:
- reword a commit
- edit a commit
- squash multiple commits into one
- remove a commit
- reset to a particular commit
And much more.
For this example, we’ll squash two commits into one commit. To squash the commits, we’ll first use the following command with the commit hash of the commit up till which we want to perform rebase:
git rebase -i <commit_hash>
We want to perform rebase on the first two commits, therefore in the place of commit hash we’ll write the hash of the third commit:
git rebase -i 03cd347
Upon entering this command, git will open up an interactive rebase session with the list of operations we can perform:
By default pick
is written in front of the commits. To perform rebase, we need to change these options. Here, we are going to squash the two commits or more precisely meld the bottom-most commit into the previous commit. To do so we’ll replace the pick
operation in the bottom-most commit with s
or squash
. This way, we’ll tell git to take the bottom-most commit and merge it into the previous commit.
After making the change, save the file and exit the editor. Now, git will ask us to name the squashed commit. To name the commit, you can select either of the previous commit messages or write a custom message. After doing so, comment out or delete all the other lines. Now save the file and exit the editor.
Now, we can see that the two commits are now squashed into one 😀
Here we only discussed how to squash commits, but I highly recommend exploring all the other options present 😊
And there you have it! Today we looked at some of the most important concepts git you’ll need as a developer. But, git is vast and everything cannot be covered in one blog, so I highly recommend you to explore more such interesting concepts 😄.
GIT SET GO 🚀
ABOUT ME 👦
Hello everyone, I’m Varun Tiwari, a Web-Development enthusiast, currently pursuing an Integrated B.Tech and M.Tech in Information Technology from ABV-IIITM, Gwalior. I’m exploring new technologies in the field of Web-Development.
Want to Know more about me? visit my website