I decided to write this post because I believe there are lots of people out there that probably struggle using Git or maybe don’t really know what it is. Speaking for myself, I didn’t use it for several years, mostly because I wasn’t really aware of how useful and powerful it can be! Keep in mind that I am just a Git newbie and this article is mostly a compilation on how to set up a local repository, use it and working a remote repository.
What is Git? Why should I use it?
Git is a distributed version control system (VCS) and a source control management (SCM) tool created by Linus Torvalds in 2005. As a distributed version and source control system, Git allows multiple users to work on a project (a repository) and concurrently contribute to it. Git also works on local repositories instead of relying on a remote server (of course you can work with a remote repository too).
Regarding why to use it, well, did you ever found yourself working on a project, trying to fix a bug or testing a new feature which ended up in a catastrophic error or problem which then took you hours, days or weeks to solve or undo? Well, Git can help you!
Git tracks alterations to any file within a project and allows you to compare files amongst different revisions (or commits in Git jargon) and even returning to project to a previous known-to-work state. It can also start new sandboxes (the branches in Git jargon) where you can test new functionalities or features and then merge them to the master project or simply discard them!
Another important reason to learn and to use Git is that most companies use it and expect current and future employees to use it too! So what you are waiting for?
Git States or Stages
When working with Git, it is important to understand the different states or stages a file can be. First of all, files can be tracked by Git or they can be untracked. Obviously only tracked files will be part of the VCS/SCM. Also, keep in mind that while you can track almost any kind of file, only text files can be really understood by Git, so that it can track the differences between them.
In Git jargon, all tracked files are your working files and they can be located in the same project directory or in any folder within it.

Working files are staged when they are added for the first time or added after a change. The staging area is a cache or buffer which stores all files until you move them to the next state (the commit stage) or get them back to the previous state.
Commit state is like a local hard copy of your files, it can work as a backup but it is much more than that because Git allows you to compare changes between committed and working files. Each commit operation also includes a description (message) of what has changed since the last commit (or, what is the reason for this commit).
Note that Git does not simply store a new version of your files, it really store the differences between current and previous version, so you can track what changed and why it changed. All these information is stored within the .git directory, which is created when you initialize a new Git project.
A local repository can also be used to update or get updates from a remote one.
Getting Started with Git
The first thing to do in order to use Git is installing it (if your computer does not have it already). I am not covering how to do that, because you can easily find information on the Internet. Just keep in mind that I am using Linux command line here, so if you are using Windows, you will probably have to use a Git command line within a cmd box instead.
Also, before creating a project with Git, it is important to set some very important internal variables: your user name and email. These are important to distinguish who changed what within a distributed version control system.
So, let’s first set your user name and email using the following commands:
git config --global user.name "your name here" git config --global user-email "your email here"
You can check your Git configuration with the following command:
git config --list
Keep in mind that Git can have global configurations, system configurations (per user) and local configurations (per project).
Now that you made the basic Git setup, let’s make Git to be aware of a project we want it to track. All you have to do is to go to your project base directory and from there issue git init command:
fabiop@XPS15 ~/Development $ mkdir gittest fabiop@XPS15 ~/Development $ cd gittest fabiop@XPS15 ~/Development $ nano test.c
Note that I just created a “gittest” directory within my “development” directory, then changed to that directory and started nano to type a very simple C program:
#include <stdio.h> void main (void) { printf("Hello world!\r\n"); }
Once you save that program, you can use gcc to compile it, and test your newly test.o binary:
fabiop@XPS15 ~/Development $ gcc test.c -o test fabiop@XPS15 ~/Development $ ./test Hello world!
Now let’s instruct Git to track our small project (git init command):
fabiop@XPS15 ~/Development/gittest $ git init Initialized empty Git repository in /home/fabiop/Development/gittest/.git/
Git replied that it successfully created a new empty repository there (a .git directory was just created within my project directory). You can do the same in any other existing project directory.
Now Git is set and will track anything that happens in our project. But as of now, no file is being tracked! We can check that by issuing a git status command:
fabiop@XPS15 ~/Development/gittest $ git status On branch master Initial commit Untracked files: (use "git add <file>..." to include in what will be committed) test.c test nothing added to commit but untracked files present (use "git add" to track)
Note that Git is reporting we are in our master branch and that there are two untracked files. It also states that we can track a file by issuing a git add command to track a file. In fact, you can track files by specifying each file name or simply instruct Git to track all files. But be careful! Our test.o file is not a text file and in fact it should not be tracked ever! As of now, we could simply issue a git add test.c command to track our C file, but what if we had a lot of files? Well, there is a way to instruct Git to ignore some files and it is by creating a .gitignore file! Let’s create a .gitignore file with just a single line:
test
fabiop@XPS15 ~/Development/gittest $ nano .gitignore fabiop@XPS15 ~/Development/gittest $ cat .gitignore test
Note that usually you are going to add all sort of object (*.o) and intermediate files (*.a, *.map, etc) to your gitignore!
Now we can issue a git add . command to add all files (except for those explicitly listed in the .gitignore file):
fabiop@XPS15 ~/Development/gittest $ git add . fabiop@XPS15 ~/Development/gittest $ git status On branch master Initial commit Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: .gitignore new file: test.c
Note that now we have two files staged: .gitignore and test.c, test.o was left out and will not be tracked!
From here, we can work and change our test.c file (which would lead to a new changed file, with the same file appearing in the staging section as well) or we can commit our changes and create our first “hard copy” of the project. Note that as our project was not committed yet, we can unstage any file by issuing a git rm –cached command on the desired file.
Commit
Now that we have some staged files, it is time to commit them, this will save the current state of our project. We can do that by issuing a git commit command (which will open a text editor for inputting the commit message) or you can use git commit -m “your commit message” instead. The -m instructs Git to use the supplied message instead of ask the user for a commit message:
fabiop@XPS15 ~/Development/gittest $ git commit -m "First commit" [master (root-commit) 7dc24ac] First commit 2 files changed, 7 insertions(+) create mode 100644 .gitignore create mode 100644 test.c
Congratulations on your first commit! We can issue a git status command to make sure everything is fine:
fabiop@XPS15 ~/Development/gittest $ git status On branch master nothing to commit, working directory clean
From now on, every time you modify your project you can commit the changes so they can be registered and stored by Git.
But what if I want to undo or change a working file to a previous state? This is when you can use git checkout command! Suppose you changed your test.c file and now you want to restore it to the way it was in our first commit. All you have to do is to issue a git checkout command:
fabiop@XPS15 ~/Development/gittest $ git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: test.c no changes added to commit (use "git add" and/or "git commit -a") fabiop@XPS15 ~/Development/gittest $ git checkout test.c
Now if you check your test.c file, its contents will be exactly what it was in the last commit! Cool isn’t it?
You can also unstage files by using the git reset HEAD command. Suppose you have staged a file named test2.c and now you don’t want to include it anymore:
fabiop@XPS15 ~/Development/gittest $ git reset HEAD test2.c fabiop@XPS15 ~/Development/gittest $ git status On branch master Untracked files: (use "git add <file>..." to include in what will be committed) test2.c nothing added to commit but untracked files present (use "git add" to track)
Git can also track file deletions. You can delete a file from Git by using git rm command (which deletes the file and stages that information) or you can delete a file using your file system and then staging that change on Git:
fabiop@XPS15 ~/Development/gittest $ git rm test2.c rm 'test2.c' fabiop@XPS15 ~/Development/gittest $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) deleted: test2.c fabiop@XPS15 ~/Development/gittest $ ls test.c test.o
Or you could delete it on your file system and then stage that deletion:
fabiop@XPS15 ~/Development/gittest $ rm test2.c fabiop@XPS15 ~/Development/gittest $ ls test.c test.o fabiop@XPS15 ~/Development/gittest $ git status On branch master Changes not staged for commit: (use "git add/rm <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) deleted: test2.c no changes added to commit (use "git add" and/or "git commit -a") fabiop@XPS15 ~/Development/gittest $ git add test2.c fabiop@XPS15 ~/Development/gittest $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) deleted: test2.c
Anyway, keep in mind that only after you commit those changes, they will be stored into your local repository!
Now as we continue working in our project, we are going to make more changes to it and commit them, suppose we added some code to our test.c.
#include <stdio.h> void main (void) { printf("Hello world!\r\n"); printf("New test\r\n"); }
In order to store the new changes we must add the file to the staging area and commit it:
fabiop@XPS15 ~/Development/gittest $ git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: test.c no changes added to commit (use "git add" and/or "git commit -a") fabiop@XPS15 ~/Development/gittest $ git add . fabiop@XPS15 ~/Development/gittest $ git commit -m "new printf added" [master 65b7522] new printf added 1 file changed, 1 insertion(+) fabiop@XPS15 ~/Development/gittest $ git log commit 65b75228d0638db2af34326f8b81b15f764b7a5b Author: Fábio Pereira <nobody@nowhere.com> Date: Sun Jun 18 14:17:54 2017 -0400 new printf added commit 7dc24ac2c3ddf55ea3ecf62a7c51d8c386623065 Author: Fábio Pereira <nobody@nowhere.com> Date: Sun Jun 18 14:15:36 2017 -0400 First commit
Can you see I used git log in order to get a list of all commits we have? Each commit includes a hash hexadecimal number, who made the change, the date and time and also the commit message. That hash code can be used with a Git checkout command in order to switch to a previous commit as we will see later.
Branches
One of the very nice things about Git is the ability to create and work with branches. A Git branch is a snapshot of your project from which you can work without disturbing your main project. This is very useful when creating or testing new features on an existing project. You can get a list of available branches at any time by using the git branch command:
fabiop@XPS15 ~/Development/gittest $ git branch * master
As of now, our project has only one branch, which is known as master branch. The star in front of it shows that we are currently in that branch. Let’s create a new branch (version2) to include a new feature:
fabiop@XPS15 ~/Development/gittest $ git branch version2 fabiop@XPS15 ~/Development/gittest $ git branch * master version2
Now we have to move our environment to that version2 branch by issuing a git checkout command:
fabiop@XPS15 ~/Development/gittest $ git checkout version2 Switched to branch 'version2' fabiop@XPS15 ~/Development/gittest $ git branch master * version2
From now on, all changes we do will affect only version2 branch, so let’s change our test.c code:
#include <stdio.h> void main (void) { printf("Hello world!\r\n"); printf("New test\r\n"); for (int temp=0; temp<10; temp++) printf("%i\r\n",temp); }
After changing our test.c file, we can type these commands:
fabiop@XPS15 ~/Development/gittest $ git status On branch version2 Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: test.c no changes added to commit (use "git add" and/or "git commit -a") fabiop@XPS15 ~/Development/gittest $ git add . fabiop@XPS15 ~/Development/gittest $ git commit -m "added counter from 0 to 9" [version2 7476c9b] added counter from 0 to 9 1 file changed, 1 insertion(+), 1 deletion(-)
Now if we look at our commit log, we will see this:
fabiop@XPS15 ~/Development/gittest $ git log commit 22c50f54195cfe1a9838d49d5fba4b0b80ae1fce Author: Fábio Pereira <fabio.jve@gmail.com> Date: Sun Jun 18 14:30:36 2017 -0400 added counter from 0 to 9 commit 65b75228d0638db2af34326f8b81b15f764b7a5b Author: Fábio Pereira <fabio.jve@gmail.com> Date: Sun Jun 18 14:17:54 2017 -0400 new printf added commit 7dc24ac2c3ddf55ea3ecf62a7c51d8c386623065 Author: Fábio Pereira <fabio.jve@gmail.com> Date: Sun Jun 18 14:15:36 2017 -0400 First commit
We can move back and forth between our version2 and master branch without loosing any piece of code:
fabiop@XPS15 ~/Development/gittest $ gcc test.c -o test.o fabiop@XPS15 ~/Development/gittest $ ./test.o Hello world! 0 1 2 3 4 5 6 7 8 9 fabiop@XPS15 ~/Development/gittest $ git checkout master Switched to branch 'master' fabiop@XPS15 ~/Development/gittest $ gcc test.c -o test.o fabiop@XPS15 ~/Development/gittest $ ./test.o Hello world! New test
Nice isn’t it?
Another usage of checkout is switching to a previous commit within a branch. Let’s suppose we wanted to switch to the first commit within version2 branch. First we can use git branch to check our current branch and then change to the desired branch with git checkout:
fabiop@XPS15 ~/Development/gittest $ git branch * master version2 fabiop@XPS15 ~/Development/gittest $ git checkout version2 Switched to branch 'version2'
Now we can use git log command to list all commits within the current branch and then use Git checkout to switch to one specific commit:
fabiop@XPS15 ~/Development/gittest $ git log commit 7476c9b7cf07b24356a6dfcb5f299c7098348470 Author: Fábio Pereira <fabio.jve@gmail.com> Date: Sun Jun 18 14:30:15 2017 -0400 added counter from 0 to 9 commit 65b75228d0638db2af34326f8b81b15f764b7a5b Author: Fábio Pereira <fabio.jve@gmail.com> Date: Sun Jun 18 14:17:54 2017 -0400 new printf added commit 7dc24ac2c3ddf55ea3ecf62a7c51d8c386623065 Author: Fábio Pereira <fabio.jve@gmail.com> Date: Sun Jun 18 14:15:36 2017 -0400 First commit fabiop@XPS15 ~/Development/gittest $ git checkout 7dc24ac2c3ddf55ea3ecf62a7c51d8c386623065 Note: checking out '7dc24ac2c3ddf55ea3ecf62a7c51d8c386623065'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b <new-branch-name> HEAD is now at 7dc24ac... First commit
Note that Git warns us that we are now in a “detached HEAD” state, meaning we have just created a new temporary branch. We can issue a git checkout -b command to create a new branch or just use this temporary branch as a sandbox to test any ideas!
If we issue a git log and a git status command we will get the following result:
fabiop@XPS15 ~/Development/gittest $ git log commit 7dc24ac2c3ddf55ea3ecf62a7c51d8c386623065 Author: Fábio Pereira <fabio.jve@gmail.com> Date: Sun Jun 18 14:15:36 2017 -0400 First commit fabiop@XPS15 ~/Development/gittest $ git branch * (HEAD detached at 7dc24ac) master version2
But if we choose to create a new branch (instead of that temporary one), we could have something like:
fabiop@XPS15 ~/Development/gittest $ git checkout -b version3 Switched to a new branch 'version3' fabiop@XPS15 ~/Development/gittest $ git branch master version2 * version3
Now we have three branches and instead of work in that detached branch we can now work in version3 branch.
But what if I don’t want to use that version3 branch anymore and I want to delete it? You can use git branch -d command:
fabiop@XPS15 ~/Development/gittest $ git branch -d version3 error: Cannot delete the branch 'version3' which you are currently on. fabiop@XPS15 ~/Development/gittest $ git checkout master Switched to branch 'master' fabiop@XPS15 ~/Development/gittest $ git branch -d version3 Deleted branch version3 (was 7dc24ac). fabiop@XPS15 ~/Development/gittest $ git branch * master version2
Note that you can’t delete a branch while you are currently using it!
For now, let’s just return to our master branch:
fabiop@XPS15 ~/Development/gittest $ git checkout master Previous HEAD position was 7dc24ac... First commit Switched to branch 'master'
Remote Repositories
Now that we know how to work with our local repository (or simply repo), it is time to learn how to deal with remote ones. There are several Git servers you can use and probably the first and most famous is GitHub. GitHub provides unlimited storage space for public repositories but charges for private ones. An interesting alternative to GitHub is BitBucket: it provides free storage for small teams (up to five users and up to 1GB per month) and allows both private and public repositories! I am going to use Bitbucket in this section but you can use whatever Git repository server you want.
The first thing to know is that in order to use a remote repository it must exist (or you can create it) and you must have access to change it. If you only need to copy it and use it as a starting point for your own project or repository, you can clone a remote repository. Let’s create a new repository in Bitbucket:
After creating the repo, you will get the following screen:
Now all you have to do is to copy your remote repository address (shown in the top right-corner of your repository overview) and instruct Git to use it as your remote repository with the command git remote: git remote add origin https://YOURUSER@bitbucket.org/YOURUSER/REPOSITORYNAME.git . And then issue a push command git push –u origin master command to push data into the remote server. You are going to be asked for your bitbucket password and after a while, Git will reply with something like this:
fabiop@XPS15 ~/Development/gittest $ git remote add origin https://fabiopjve@bitbucket.org/fabiopjve/gittest.git fabiop@XPS15 ~/Development/gittest $ git push -u origin master Password for 'https://fabiopjve@bitbucket.org': Counting objects: 10, done. Delta compression using up to 8 threads. Compressing objects: 100% (8/8), done. Writing objects: 100% (10/10), 825 bytes | 0 bytes/s, done. Total 10 (delta 2), reused 0 (delta 0) To https://fabiopjve@bitbucket.org/fabiopjve/gittest.git * [new branch] master -> master Branch master set up to track remote branch master from https://fabiopjve@bitbucket.org/fabiopjve/gittest.git.
From now on, every time you need to push new commits to your Bitbucket repository, you just have to issue a git push command:
fabiop@XPS15 ~/Development/gittest $ git push
And to pull from your remote repository you can use a git pull command:
fabiop@XPS15 ~/Development/gittest $ git pull Password for 'https://fabiopjve@bitbucket.org': From https://bitbucket.org/fabiopjve/gittest * branch master -> FETCH_HEAD Already up-to-date.
Once we have up-to-date local and remote repositories, let’s add a README.md file, but this time, we will create it using Bitbucket’s web interface. Go to your repository overview and press the “create a README” button:
Just use the template from Bitbucket and save it. Now your remote repository should have a README.md file which is not present in your local repo!
In order to synchronize your local repo with your remote one, all you have to do is to issue a git pull command:
fabiop@XPS15 ~/Development/gittest $ git pull Password for 'https://fabiopjve@bitbucket.org': remote: Counting objects: 3, done. remote: Compressing objects: 100% (3/3), done. remote: Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From https://bitbucket.org/fabiopjve/gittest * branch master -> FETCH_HEAD Updating 0a17f0a..29c08da Fast-forward README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 README.md
Our newly created README.md should be present within our project files:
fabiop@XPS15 ~/Development/gittest $ ls README.md test.c test.o
But if we pay attention to our remote repo, we will notice something wrong: we only have our master branch, where is the version2 branch? The answer is simple: it is still in our local repo, but was not pushed along with master to our remote repo. That’s because our git push command only pushed our current (master) repository. In order to push all branches we must include the –all option: git push –all
That brings another interesting question: what if I wanted to delete a branch?
We already saw that we can use git branch -d to delete a local repository, but it won’t affect the branch on a remote repository. In order to do that, you must use git push origin :BRANCHNAME command. So in order to delete version2 branch in the remote repo we could issue:
fabiop@XPS15 ~/Development/gittest $ git push origin :version2 Password for 'https://fabiopjve@bitbucket.org': To https://fabiopjve@bitbucket.org/fabiopjve/gittest.git - [deleted] version2
That is it! I believe this is a good starting point for anyone wanting to learn and use Git.
References
Git Tutorials and Training at Atlassian.com