git set go 🐱‍👤

Varun Tiwari
tech@iiit-gwalior
Published in
14 min readNov 29, 2021

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:

  1. The three-stage architecture of Git
  2. Staging files and committing changes
  3. 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 addand 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, press i 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 the ESC 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>
Before amending the commit
Amending the commit
After amending the commit

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

commit hash of recent commit before amending

The string highlighted in red is the hash of commit before using --amend. Now, when we use git commit --amend

commit hash of recent commit after amending

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:

  1. Soft reset
  2. Mixed reset
  3. 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.

before git reset

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.

working tree unchanged
changes 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.

using mixed reset
the working tree remains the same
changes 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.

working tree and index changed
working tree changed to what was in ‘initial 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 the main 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:

new function

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.

After using git 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:

After applying the stash

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:

  1. reword a commit
  2. edit a commit
  3. squash multiple commits into one
  4. remove a commit
  5. 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

--

--