Git — everything you need to know
Today, Git is the de-facto version-control tool for most software engineering projects. Of course, there are alternatives such as Mercurial and Apache Subversion. However, Git has emerged as the true winner in this version-control ecosystem.
Check out this graph from Stack Overflow Trends to understand how the version control systems fared over time.
Peter Bell has written an excellent introduction to Git with all the common commands you need here. Give it a read. In this article, the aim is to summarise and help you master Git, all in 5 minutes! Let’s get started!
Installing Git
First, let’s get Git installed through a download here. Sometimes, you may already have it installed. To check if Git is installed, open up your terminal and type
git version
This will give something like git version 2.32.0
.
On macOS, you can also use Homebrew to install Git using brew install git
.
Git Config
There are 3 things we will need to configure now: name, email and a default branch name. To do this,
git config --global user.name "Your Name"
git config --global user.email "name@email.com"
git config --global init.defaultbranch <branch_name>
In 2020, the Git community decided to change the default branch name of Git repositories from “master” to “main” to avoid using terms like “master” and “slave” and the ambiguity of the terminology. I would recommend the same, to use main
as your default branch name.
Note: For the default branch name config, you need Git version 2.28.0
or above.
Once done, list the global Git config with this command:
git config --global --list
You can edit the global config file by editing ~/.gitconfig
.
Initialising a New Repository
Now, to initialise a new Git repository, we can use the git init
command. This has to be done within a directory where you are going to keep the source files/folders to commit.
mkdir my-app
cd my-app
git init
This will create a .git
hidden folder which is used to track all the changes within the directory.
Committing Changes
Let’s create a new file called index.js
in our repository and make changes to it.
echo "console.log('hello world')" >> index.js
git status
When you create a new file, Git will automatically notice this. Typing git status
after creating the new file, will give us:
~/Desktop/my-app(main*) » git status
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
index.js
nothing added to commit but untracked files present (use "git add" to track)
Let’s stage our newly added file:
git add index.js
Running git status
again will show:
~/Desktop/my-app(main*) » git status
On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: index.js
We can see that Git recognises our new file and it is waiting for us to “commit” the file. Commit helps us to log and record our changes in Git, along with a message where we can describe what changes were made.
git commit -m "Adding index.js"
The -m
flag specifies that we want to add a commit message. This will give us:
~/Desktop/my-app(main*) » git commit -m "Adding index.js"
[main (root-commit) e25ad54] Adding index.js
1 file changed, 1 insertion(+)
create mode 100644 index.js
To edit the last committed message, we can use the --amend
flag:
git commit --amend
This will open up a editor. Edit the description and once done, you can type :wq
to exit.
Listing Our Commits
Git provides us an easy way to list our commits using git log
.
git log
This gives us:
commit e25ad54a136de851abe83a557de9d983e39c21b9 (HEAD -> main)
Author: Your Name <name@email.com>
Date: Sat Jul 17 16:24:05 2021 +0800
Adding index.js
You can press q
to exit the Git log interface.
Branches in Git
Oftentimes, we need to work simultaneously with multiple developers on the same code base. To work on your feature or changes without introducing any conflicts for others, we use branches.
To create a new branch and work within a branch, we can run
git checkout -b new_branch_name
For example, the above command will show us:
~/Desktop/my-app(main) » git checkout -b new_branch_name
Switched to a new branch 'new_branch_name'
Let’s make some new changes in the branch on our index.js
file:
echo "console.log('making a change)" >> index.js
Let’s commit our changes:
git commit -am "Making changes to index.js"
We can switch branches using the checkout
command too. Let go back to our main
branch.
git checkout main
Now we can compare the changes we have introduced using the diff
command:
git diff new_branch_name
This will show:
------------------------------------------------------------
diff --git a/index.js b/index.js
index cea4f10..f236b0f 100644
--- a/index.js
+++ b/index.js
@@ -1 +1,2 @@
console.log('hello world')
+console.log('making a change)
Git clearly lists our current file contents and the modified contents on the new branch.
Managing Conflicts
Now, what if there are changes on both the main
and our new branch? This will lead to a conflict when we try to merge them in the future. Merging is the action of combining the changes from another branch into the current branch.
On our main
branch, let’s modify index.js
.
echo "console.log('modified')" >> index.js
Let’s commit the changes using:
git commit -am "Changes to index.js"
Let’s try to merge our changes from our new branch:
git merge new_branch_name
This will give us a conflict:
~/Desktop/my-app(main) » git merge new_branch_name
Auto-merging index.js
CONFLICT (content): Merge conflict in index.js
Automatic merge failed; fix conflicts and then commit the result.
To resolve the conflicts, we have a couple of ways:
We see our changes using
git diff --ours
We see their (the incoming branch) changes using
git diff --theirs
We can entirely abort the merge using
git merge --abort
In this case, let’s go ahead and accept the incoming changes. If we open up index.js
file using Vim or your code editor, we will see:
1 console.log('hello world')
2 console.log('making a change)
3 <<<<<<< HEAD
4 console.log('modified')
5 =======
6 >>>>>>> new_branch_name
This shows that the incoming change has added a line and let’s assume we want to accept it. Let’s abort the merge operation first:
git merge --abort
We can redo the merge operation with a “strategy” flag which specifies which changes to accept.
git merge -X theirs new_branch_name
This opens a new window to describe the merge operation changes. We can press :wq
on Vim to accept the default.
Now on the main
branch, we can view the contents of our index.js
file and we will see that the changes we made on the new branch is reflected and the changes on main
branch is omitted.
Our git log
now looks like:
commit 9d913d0326722f3111596db937e64362c302fbfc (HEAD -> main)
Merge: df6200a c93871e
Author: Your Name <name@email.com>
Date: Sat Jul 17 16:43:48 2021 +0800
Merge branch 'new_branch_name'
commit c93871eac5f5dbe8b8d6a0543158b02c601a0814 (new_branch_name)
Author: Your Name <name@email.com>
Date: Sat Jul 17 16:36:51 2021 +0800
Making changes to index.js
commit df6200a2eb3116d5fe2134fc6ed78f539e815468
Author: Your Name <name@email.com>
Date: Sat Jul 17 16:35:12 2021 +0800
Changes to index.js
commit e25ad54a136de851abe83a557de9d983e39c21b9
Author: Your Name <name@email.com>
Date: Sat Jul 17 16:24:05 2021 +0800
Adding index.js
Deleting Branches
Once we have merged our code, we can do a cleanup of our branches by deleting ones we no longer need.
This can be done using the -d
flag:
git branch -d new_branch_name
And this gives us:
~/Desktop/my-app(main) » git branch -d new_branch_name
Deleted branch new_branch_name (was c93871e).
Rebasing
Rebase allows us to replay changes made in another branch to our current branch.
To try a rebase, let’s create a new branch and make some changes there.
git checkout -b new_branch
echo "new file" >> file.txt
git add file.txt
git commit -m "Adding a new file"
Let’s make some changes on our main
branch too.
git checkout main
echo "main file" >> main.txt
git add main.txt
git commit -m "Adding main file"
Now our aim is to replay the changes made in our main
branch into our new_branch
. Let’s do that:
git checkout new_branch
git rebase main
Now if we check the directory, we can see that a new file main.txt
is now added! This is because we have replayed the changes made on the main
branch into our current branch.
A word of caution: Never rebase commits that have been pushed and shared with others.
To understand more about rebase, especially to understand when and when not to use rebase, read this article by Harish Yadav!
Stashing Changes
Git also offers us the support to “save” our changes and apply them some time later in future. This is using the stash
command.
For example, let’s make some changes:
echo "I am making some changes for future" >> main.txt
At this point, we have modified our existing main.txt
file. To save this state, we can use:
git stash
To view the stash, we can use:
git stash list
This shows:
stash@{0}: WIP on new_branch: 9714ecf Adding a new file
The format of the stash is stash@{stash_number}
.
To apply the stash, we can use:
git stash pop <stash_number>
The stash_number
is an optional field through which you can specify the number of the stash you want to apply. This can be obtained using git stash list
as seen above.
To drop the changes, we can use:
git stash drop <stash_number>
Again, the stash_number
is an optional field.
To get rid of applied changes, we can use the restore
command:
git restore .
Working With Remote Repositories
While we can use Git locally, often times you will be “pushing” changes to the cloud, for example, to GitHub.
To add a remote repository link, we need to first create a new repository on a platform like GitHub. Follow these instructions to do so.
Once done, you will receive your GitHub repository link, for example: https://github.com/harishv7/example_repo.git
On our local Git repo, we use:
git remote add origin <your_github_repo_link>
git push -u origin main
This will add an “origin” to our GitHub repository and we can “push” changes in our local Git repository to the cloud.
If you applied the above commands, you will be able to see the same files and changes in your GitHub too!
To see our remote repositories, we can use:
git remote -v
Conclusion
That’s all the Git commands for this crash course! This will be more than enough to get you kickstarted on your Git journey. Git is an incredibly powerful version control system and to master it, it just takes enough practice. So, be sure to practice the commands with sample projects locally. When working with a team, make sure to exercise caution when applying irreversible changes! When in doubt, try it locally and understand the commands before executing them to minimise impact to your team.
Happy coding! 💻
More content at plainenglish.io