Git is a free and open source distributed version control system known for its simplicity, speed, great branching models and efficient merging. It was created by Linus Torvalds in 2005 to manage the development of the Linux kernel. Since then, many companies and open source communities are using Git to manage their software projects.

Some people will argue that Git is too complex for beginners - Yet, I beg to differ. In this post, I'll go through the basics of Git - just what you need to get up and running and use Git to manage your projects.

Overview of Git

If you've worked with version control systems before, you're probably familiar with Subversion (SVN). While Git and SVN share similar functionalities (branching, merging, commit history, etc...), they work differently under the hood.

For starters, SVN is a centralized version control system where files are stored in a single central repository. When users checkout files from the repository, a working copy is created on their machine. All operations you'll do need a persistent connection to the central repository.

Centralized vs Distributed VCS

Git, on the other hand, gives each user a copy of the repository. Since you're getting a full-fledged repository, almost all operations are local - meaning you can do things like browsing the project's history, reverting files to previous revisions and performing commits without an Internet connection. This makes Git very fast as there's no network latency while performing these operations. The only time you'll actually need an Internet connection is when sharing your work with other collaborators or pulling other people's changes from a remote repository.

Another important aspect of Git is the way it stores data. While other VCS store data as a set of file-based changes, Git takes a picture of all your files every time you commit or change the state of any files.

Git also provides data integrity by hashing and storing contents using the SHA-1 algorithm. This functionality makes it impossible to change files without Git knowing about it. All commits are uniquely identified using a 40 character string which may look similar to this:

4818d80339b7fdc2cbee1a9bbe42c0b073fa87e

The Three States and Areas of Git

Git views your files in three different states: untracked/modified, staged or committed. Git views untracked and modified files the same way. Untracked means that a file is new to the repository. Modified means that a file is already present in the repository, but has since been altered and not yet committed. A staged file is when you mark a modified file to go in your next commit. Committed means your file is safely stored in your local repository.

The Three States and Areas of Git

In addition to the three states, your files can live in three different areas of Git: the working directory, the staging area (a.k.a the Index) and the .git directory (the repository). The working directory is a single checkout of one version of the project where you can use or modify files on disk. The staging area is a binary file in the .git directory which contains information about changes to go in the next commit snapshot. The .git directory is actually your local repository where Git stores the metadata related to your repository.

Knowing the status of your files and the areas they live in will help you identify which command(s) to use.

Installation

Mac and Windows users can head over to the download page and install Git using the binary installer.

Linux users will have to rely on the package manager that comes with their Linux distro to install Git. For example, if you’re on Ubuntu, use apt-get:

apt-get install git

On the Mac, I prefer to use Homebrew to keep things simple:

brew update && brew install git

If you’ve previously installed Git via Homebrew, run the following command to ensure you have the latest version:

brew upgrade git

After the installation, run git --version to check if Git successfully installed.

Configuration

The next step is to configure your Git environment. Git comes with a handy tool called git config which allows you to specify configuration options, covering everything from user information to personal preferences. These configurations are stored in a plain text format under three different locations, allowing you to scope options to a specific repository, a specific user, or the entire system:

  1. /etc/gitconfig (use git config --system to store system-wide settings for all users and repositories on a particular machine)
  2. ~/.gitconfig (use hot config --global to store configurations for a specific user. All repositories under this user account will inherit settings stored in that file)
  3. .git/config (use git config --local to store settings for a specific repository)

You can also edit these configuration files using a text editor if you prefer to do so.

Identity

The first thing you’ll want to do after installing Git is to setup your name and email. Git uses this information for all commits you do, and third-party services such as GitHub also use this to identify you. To setup your identity, use:

git config --global user.name "John Doe"
git config --global user.email "[email protected]"

Here we're using the --global flag to specify the identity of the current user, making it the default for new repositories. If you need to use a different identity on a specific repository, you'll want to use the --local flag instead.

Editor

Git relies on a text editor for inputs like commit messages. You can specify your preferred editor with the core.editor option:

git config --global core.editor emacs

If this option is not specified, Git will use the default text editor of your OS.

Colorful Git Output

Git supports colored terminal output with the color.ui option:

git config --global color.ui true

You can also specify which commands output in color:

git config --global color.branch false
git config --global color.diff true

Pretty log

Configure Git to display the log with just one line per commit:

git config --global format.pretty oneline

Aliases

You can create shortcuts for Git commands with the alias option. Here we're creating an alias called tree to display an ASCII art tree of all branches, tags and commit messages:

git config --global alias.tree "log --graph --oneline --decorate --all"

Getting a Repository

There are two ways to get a Git repository - you either create a new one or copy an existing repository from somewhere else.

To create a new Git repository, create a new directory (or use an existing unversioned project), open it, and perform a git init:

# Example:
mkdir git-project && cd git-project
git init

When you run git init, it will create a .git subdirectory in the project root containing all the necessary metadata for this repository.

git init

Alternatively, if you want to get an existing Git repository, you can copy it with the git clone command:

git clone [path]

The path can be an URL pointing to a remote repository (found on GitHub for example) or a local file path.

As an example, let’s say you want to clone the twitter bootstrap framework from GitHub:

# Example:
git clone https://github.com/twbs/bootstrap.git

Or maybe you simply want to clone an existing repository found on your local system:

# Example:
git clone /users/john/projects/bootstrap

Either of these commands will create a new directory named "bootstrap" containing all the files of the project and the .git subdirectory.

git clone

If you want the project directory to be named something else, you can specify that as a second argument to the command:

# Example:
git clone https://github.com/twbs/bootstrap.git custom-bootstrap

The git clone command is sort of like svn checkout, except you get a full-fledged repository instead of just a working copy. This means you get the project’s entire history with every version of every file.

Rather than using the HTTP/HTTPS protocol to transfer data from/to remote repositories, another popular option is to use SSH (Secure Shell). With SSH, your data transfer is encrypted and authenticated and you won't need to supply your password every time you push or pull to a repository. The downside is that you can’t serve anonymous access to your repository (not even read-only), which is why people tend to favor HTTPS over SSH, especially for open source projects.

Staging Files

Now that you have a Git repository, time to add some files to your project. When working with files in your working directory, they can be in either of the two states: untracked (new files which are not being tracked by Git) and tracked (unmodified, staged or committed). You can use the git status command to check the current status of your repository. Running this will show a list of all affected files and their states. So, if I add a new file called "app.js" for example, running git status will show you something like that:

git status - untracked

As you can see, the file is marked as Untracked, which means that Git is not tracking any files at this point. You need to tell Git which files to track, and the most basic command is:

git add [filename]

# Example:
git add app.js

By running git status again, you will see that Git has now started to track the new file:

git status - new

If you want to add multiple files to the staging area, use the following syntax:

# Example:
git add app.js README.md index.html

What if you want to add files found in subdirectories of your project? Easy - just specify the full path to the file:

git add [path/to/file]

# Examples:
git add assets/css/main.css
git add assets/css/print.css

Rather than specifying each file, you can also run

# This will add all files in the css directory
git add assets/css

You can also add all affected files (new, modified or deleted) recursively to the staging area using the git add . command. However, there might be some files (.DS_Store found on OS X or any compiled files) that you don’t want to include under version control. Running git add . will add those as well. The solution is to include a .gitignore file in your project. You can either create this file yourself or go to gitignore.io to generate one for your specific project. Below is a sample for a Java project:

# Created by https://www.gitignore.io/api/java

### Java ###
*.class

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.ear

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

There are times you might accidentally stage files you didn’t want Git to track - these might be log files generated by your application. In that case, you can use git rm to remove them. This will unstage the files but also remove them from the file system.

You can pass files, directories and glob patterns to the git rm command:

# files
git rm app.js

# Removing directories and files resursively with the -r flag
git rm -r assets/css

# Using glob pattern - this will remove all .js file in the current directory
git rm \*.js

On the other hand, if you want to unstage files, but keep them on your file system, use the following command:

git rm --cached [filename]
  
# Example:
git rm --cached app.js

Another situation is when you want to clear or remove a specific file from the staging area without undoing any changes in your files.

To clear the staging area, use:

git reset HEAD

To remove a specific file from the Index, use the following syntax:

git reset [path/to/file]

# Example:
git reset assets/css/main.css

Time to Commit

Once you have all your desired files in the staging area, it’s time to commit your changes in Git. A commit can be thought of as a snapshot, or a save point, which you can reference if you want to go back and access your repository at this stage. A commit must be associated with a commit message. Commit messages don’t have a specific format, but it’s considered good practice if you summarize your entire commit on the first line, in less than 50 characters, leave a blank line and give a detailed explaination on the next line. You can use a template like that for your commit messages:

# Subject in (preferably) less than 50 characters (Mandatory)
This is the subject of this commit message

# Detailed explaination (Optional)
This is the body of this commit message. The body is written after the subject line with one blank line in between. The blank line is used by various tools (such as 'git log', 'git show', etc.) to differentiate the subject from the body. Further paragraphs are also separated by blank lines.

Explain the problem being solved by this commit. More importantly, explain why these changes are being made, as opposed to how. The 'why' part is your responsibility, the 'how' part is code's responsibility.

To perform a commit, enter git commit on your terminal. This will open up your default text editor (nano on *nix and Notepad on Windows) if you haven't specified one in your .gitconfig file. Type your commit message and exit the editor. If your commit was successful, you should see a summary of the commit:

git commit

A much direct approach is to use the -m prefix, which lets you specify the commit message without an editor:

git commit -m "First commit"

After your initial commit, let's say you need to make some changes to your initial files. If you run git status again, you'll see that your files are currently in a modified state. Before you proceed with your commit, you can compare the changes you currently made to your tracked files from the last commit. To do that, run:

git diff

If you want to compare a specific file, run:

git diff [filename]

# Example:
git diff app.js

git diff

After reviewing your changes, you need to add the affected files to the Index. Use git add -u to add all tracked files, whether they have been modified or deleted, to the staging area. This command won't add new files, though. This is particularly handy if you have lots of new files in your project but want to commit them separately from your existing files.

It's also possible to skip the staging area completely by using git commit -a. This will automatically stage all tracked files (modified or deleted, but not new ones) and do the commit directly. It's like a combination of git add -u and git commit:

# This will open up your text editor where you'll need to enter your commit message
git commit -a

# Or you can directly use
git add -a -m "Your commit message here"

Viewing Your Commit History

Sometimes, you might want to look at your latest commit or any other commits previously done. To view your commit history, type:

git log

This will show you a list of all your commits with their associated information - a unique 40 character string, the author, date of the commit and the commit message. You’ll have to hit q on your keyboard to exit the log.

git log

You can also use the git log -p option which shows the differences introcuded in each commit for every file. If you want to view the detail of a specific commit, use:

# hash is the hex number associated with the commit
git show [hash]

# Example:
git show 3cdcea0399ef5805bfd2151e2e34f16e0aa29e81

You can think of the hashes that Git uses as revision numbers on SVN. They're both used to uniquely identify commits in your repository. You can also shorten the hex by using just the first 5 characters like that:

# Example:
git show 3cdce

Branching, Merging, and Tagging

Branching is a way isolate new features, bug fixes or experimental code from the main development line. It’s always a good practice to create branches because they allow you to work on a copy of the code without affecting the original code. It’s also very useful when several features are being developed and tested in parallel. When you create a branch in Git, it won’t create any directory like SVN does. Instead, it’s just a pointer that points to the latest commit (HEAD).

When you create a Git repository, you get a default branch called "master". From there, you can create any number of branches you want. When you’re done with your feature or bug fixes, you merge back your work to the master branch.

git branch

To create a new branch, enter the following command:

git branch [branch_name]
  
# Examples:
git branch develop
git branch feature_x

Branch names cannot contain spaces. Use an underscore to separate part of your branch name.

The above command will create a new branch based on your current active branch, i.e. master. To create a branch based on another branch, use the syntax below:

git branch [new_branch] [original_branch]
  
# Example:
git branch bug_fixes develop

Here, we’re creating a new branch called "bug_fixes" from the existing branch "develop".

To switch to another branch, run:

git checkout [branch_name]

# Example:
git checkout bug_fixes

It’s also more practical to create and automatically switch to a new branch. You can do that with:

git checkout -b [branch_name]

# Example: Create and switch to a new branch named "develop"
git checkout -b develop

If you run git branch, it will list all active branches in your repository. The asterisk (*) denotes the current branch.

# Here we have two branches, and "develop" is the current branch
* develop
  master

Now, let’s say you need to develop a new feature in your application. You create a new branch to isolate your work from the main development line. When you’re done with the feature, you commit everything in your new branch.

# Example:
git checkout -b feature_x
git add .
git commit -m "Feature x completed"

Upon completion of your feature, you now need to merge back your changes to the main branch (master). To do that, you need to switch to the "master" branch, then use the git merge command to automatically merge your changes:

# Example:
git checkout master
git merge feature_x

When Git is merging your changes, you should see something similar to that:

Updating 7af3bda..199d339
Fast-forward
 .tern-project |  7 +++++++
 app.js        | 12 ++++++------
 server.js     | 12 ++++++++++++
 3 files changed, 25 insertions(+), 6 deletions(-)
 create mode 100644 .tern-project
 create mode 100644 server.js

Sometimes you might run into conflicts when merging two branches. This happens if the same part of the same file is changed differently in the branches you're merging.

Auto-merging app.js
CONFLICT (content): Merge conflict in app.js
Automatic merge failed; fix conflicts and then commit the result.

As the message suggests, you'll have to fix the conflict manually and then proceed with the commit. When your merging is successfully completed, you can delete the "feature_x" branch because won't need it anymore.

git branch -d feature_x

Tags can be used to mark specific points in your commit history, and it’s recommended to create them for software releases. To create a new tag, use:

git tag [tag_name]

# Example:
git tag v1.0

This will create a tag using the latest commit (HEAD). If you want to create a new tag based on a specific commit, use:

# Example:
git tag v1.0 3cdcea0399

3cdcea0399 is the 10 first characters of the commit id you want to reference with your tag.

You can list all available tags in your repository by simply typing git tag, and delete a specific tag with:

git tag -d [tag_name]

# Example:
git tag -d v1.0

Pushing and Pulling Changes

So far, you’ve been working and committing your files in your local repository. However, if you want other people to collaborate on your project, you should host your repository on a remote server. Git doesn’t have a central server like SVN does, but third-party services such as GitHub, Bitbucket and GitLab will let you host your repository for free on their servers. You can create a project on one of the mentioned sites and push your code to your remote repository.

First, you’ll have to create a connection from your local repository to the remote one. You can do that with

git remote add [shortname] [url]

# Example with https:
git remote add origin https://github.com/djamseed86/git-project.git

# Example with ssh:
git remote add origin [email protected]:djamseed86/git-project.git

To view which remote repositories you have configured, you can use the git remote command. It will list the short names of each remote you configured. Adding the -v to git remote will list the short names and URLs of each configured remote.

git remote

After creating the remote, you can push your code to the remote server (origin):

git push [remote_name] [branch_name]

# Example:
git push origin master

git push

To update your local repository with the latest commit, run:

git fetch [remote_name]

# Example:
git fetch origin

The git fetch command will pull the latest commit from the remote repository and save it in your local repository. Note that it will not automatically merge with any of your current files - you will have to manually do that yourself. If however you want to fetch and merge, use:

git pull

The End

Git is a powerful distributed version control system with lots of features and commands not even covered in this post. But hopefully, this will get you started with versioning your projects with Git.

Image credits: Centralized Vs Distributed by Tower | Three states and areas of Git from the official Git website