1. Software development
  2. Learn Git Collaboration

Git tutorial

An essential guide to a powerful integration

Git

Project and code management together.

Try it free

Using branches

In a collaborative environment, it is common for several developers to share
and work on the same source code. While some developers will be fixing bugs,
others will be implementing new features, etc. With so much going on, there
needs to be a system in place for managing different versions of the same code
base.

Branching allows each developer to branch out from the original code base
and isolate their work from others. It also helps Git to easily merge versions
later on.

What is a Git branch?

A Git branch is essentially an independent line of development. You can take
advantage of branching when working on new features or bug fixes because it
isolates your work from that of other team members.

Branch overview
A git branch is an independent line of development taken from the same source code.

Different branches can be merged into any one branch as long as they belong
to the same repository.

The diagram below illustrates how development can take place in parallel
using branches.

Working parallelly using branches
Multiple development projects taking place using the same source code.

Branching enables you to isolate your work from others. Changes in the
primary branch or other branches will not affect your branch, unless you decide
to pull the latest changes from those branches.

It is a common practice to create a new branch for each task (i.e., a branch
for bug fixing, a branch for new features, etc.). This method allows others to
easily identify what changes to expect and also makes backtracking simple.

Create a branch

Creating a new branch does not change the repository; it simply points out
the commit

For example, let’s create a branch called “issue1” using the command git
branch.

git branch issue1

The illustration below provides a visual on what happens when the branch is
created. The repository is the same, but a new pointer is added to the current
commit.

Before new branch is created

Switch branches

The git checkout command allows you to switch branches by updating the files in your working tree to match the version stored in the branch that you wish to switch to.

You can think of it as a way of switching between different workspaces.

Git HEAD

HEAD is used to represent the current snapshot of a branch. For a new repository, Git will by default point HEAD to the master branch. Changing where HEAD is pointing will update your current active branch.

The ~(tilde) and ^(caret) symbols are used to point to a position relative to a specific commit. The symbols are used together with a commit reference, typically HEAD or a commit hash.

~ refers to the th grandparent. HEAD~1 refers to the commit’s first parent. HEAD~2 refers to the first parent of the commit’s first parent.

^ refers to the the th parent. HEAD^1 refers to the commit’s first parent. HEAD^2 refers to the commit’s second parent. A commit can have two parents in a merge commit.

~(tilde) and ^(caret) symbols point to a position relative to the commit
~(tilde) and ^(caret) symbols point to a position relative to the commit

Git Stash

Whenever you switch to another branch with uncommitted changes (or new files added) in your working tree, these uncommitted changes will also be carried to the new branch that you switch to. Changes that you commit will be committed to the newly switched branch.

However, if Git finds a conflict between the files from the newly switched branch and the uncommitted changes from the previous branch, you will not be allowed to switch to the other branch. You must commit or stash those changes first before switching branches.

You can think of stash as a drawer to store uncommitted changes temporarily. Stashing allows you to put aside the “dirty” changes in your working tree and continue working on other things in a different branch on a clean slate.

Uncommitted changes that are stored in the stash can be taken out and applied to the original branch and other branches as well.

Remote branches

Although Git is local on your computer, you can also have remote copies of a repository. Remote repositories can be on a private central server or even simply on a coworker’s computer.

You can also have a remote repository hosted using an online service–such as Backlog.

You can retrieve others’ changes to the repository or move your local copy of a repository to the remote server.

Pull remote branch

You can apply the latest changes from a remote repository to your local repository using the git pull command.

For example, say the remote branch is upstream of your local branch. The
remote branch would include all of the changes that belong to the local branch
as shown below.

Remote branch is upstream from local branch.
Remote branch is upstream from local branch.

In this case, if we were to apply a merge from the remote branch (origin/master) into our local branch (master), it would be a fast-forwardwar merge.

However, if there are changes in the local master branch that are not present in the remote origin/master branch, the git pull command will execute a merge and create a merge commit that ties those changes together.

Git must merge and commit before a pull if the local branch is different from the remote branch.
Git must merge and commit before a pull if the local branch is different from the remote branch.

When a pull is executed, a merge commit will be automatically created in the local repository. If there is a conflict, you will have to resolve the conflict and commit the merge manually.

If there is no conflict, the commit will be merged automatically.
If there is no conflict, the commit will be merged automatically.

Fetch remote branch

When you execute a pull, the changes from the remote branch automatically merge into your current local branch. If you want to obtain the remote changes but not have them merged into your current local branch, you can execute the git fetch command.

Fetch will download the changes from remote that do not yet exist on your local branch. The FETCH_HEAD ref can be used to track the fetched changes from the remote repository.

The revision history will look like below when both the remote and local branch contain different descendants.

Fetch respective origins of both local repository and remote repository under a state where both repositories have commits advanced from B
Revision history when remote and local branches have different masters.

Once changes are fetched, you can apply those changes to your local repository by merging in FETCH_HEAD or by executing a pull.

Merge FETCH_HEAD
After merging, changes will be applied to the local repo.

Once FETCH_HEAD has been merged, the revision history will yield the same result as a git pull operation. Pull is essentially a simultaneous execution of fetch and merge operations.

Push branch to remote

All of your commits are available to you until you push your local branch to the remote repository. That is, you can work on your own local branch at your own pace without affecting other members of the team.

When you push your local branch to remote, Git will do a fast-forward merge to the destination repository.

However, if the push results in a non-fast-forward merge, Git will decline your push to prevent you from overwriting previous commits. In that case, you have to pull the latest remote changes and push again.

Push
Use git push to add your local changes to the remote repository.

You must not overwrite or change commits that have already been committed to the remote repository. Doing so will cause other team members’ local repositories to desynchronize with the remote repository.

Branching workflows

Let’s take a look at the Gitflow Workflow as outlined in A successful Git branching model.

This workflow consists of five types of branches, each with different roles:

  • Master
  • Feature branch (aka Topic branch)
  • Release branch
  • Hotfix branch
  • Develop branch (aka Integration branch)
Branch model at Git
Basic Git branching workflow with master, topic, release, and hotfix branches.

Master

Upon making the first commit in a repository, Git will automatically create a master branch by default. Subsequent commits will go under the master branch until you decide to create and switch over to another branch.

Codebase residing in the master branch is considered to be production-ready. When it is ready for a specific release, the latest commit will be given a release tag.

master branch
Changes are committed to the master branch.

Feature/Topic branch

When you start working on a new feature/bug fix, you should create a feature/topic branch. A feature/topic branch is normally created off a develop/integration branch. This feature/topic branch can reside in your local machine throughout the entire development lifecycle of the feature.

You will push this branch to the remote repository whenever you are ready to merge the change set with the develop/integration branch.

Image of a topic branch

Release branch

When you roll out a new release, you create a release branch. A release branch helps you to ensure that the new features are running correctly.

By convention, release branch names normally start with the prefix “release-“.

The release branch is typically created off the develop/integration branch when it’s close to being production-ready.

Only bug fixes and release related issues should be addressed on this branch. Having this branch will allow other team members to continue pushing new features to the develop/integration branch without interrupting the release workflow.

When you are ready to release, merge the release branch with the master branch and tag a release number to the newly created merge commit.

You should also merge the release branch with the develop/integration branch so that both the master and develop/integration branches receive the latest changes/bug fixes from the release branch.

Hotfix branch

When you need to add an important fix to your production codebase quickly, you can create a Hotfix branch off the master branch.

By convention, hotfix branch names normally start with the prefix “hotfix-“.

The advantage of a hotfix branch is that it allows you to quickly issue a patch and have the change merged with the master branch without having to wait for the next release.

A hotfix branch should be merged with the develop/integration branch as well.

Develop/Integration branch

A develop/integration branch should be kept stable at all times. This is important because new branches are created off of this branch, and this branch could eventually go out live on production. Continuous integration tools such as Jenkins can be used to help do just that.

When some changes need to be merged into the develop/integration branch, it is generally a good idea to create a feature/topic branch to work on independently.

Sample feature branch workflow

You will need to create a branching strategy that works best for your team, of course. But here is a quick example of how to follow a branching strategy workflow involving two types of branches: a develop/integration branch and a feature/topic branch.

Let’s say you are working on a new feature. Suddenly, somebody finds a bug in the production and you are now tasked to fix that bug parallel to working on the new feature.

On the way of work on a topic branch to add functions, it becomes necessary to fix bugs.

Before starting on the bug fix, you can create a new branch off of the develop/integration branch. This new branch will isolate the bug fix from the new feature that you were working on.

You can start working independently from the addition of functions by creating a new topic branch for fixing bugs.

When you are ready to release the bug fix, merge the bug fix feature/topic branch into the develop/integration branch.

You can make it public by including it in the original branch

Then you can switch back to your original feature/topic branch and continue working on the new feature.

You can go back to the original branch to continue working on the addition of functions

On your feature/topic branch, you will notice that commit “X”, which is the bug fix commit, is needed to continue implementing the new feature. In other words, you will have to synchronize your current branch with the changes on the develop/integration branch.

There are two options to go about doing this. The first is to merge the develop/integration branch that includes commit “X” with the current branch.

The second option is to rebase the current branch to the develop/integration branch that includes commit “X”.

For this example, we will use the rebase approach.

Rebase a unified branch

Once you commit “X” on our current branch, you can safely proceed to work on your new feature.

Integrating branches

Once you are done working on a feature/topic branch (i.e., new feature or bug fix), you would typically merge it with a develop/integration branch. You can accomplish that by using the git merge or git rebase commands, although both commands will give you different results.

Merge: Retains all changes to and history of the merged branch. The revision history can become complicated after many merge commits.

Rebase: Maintains a clean revision history since merged commits are appended at the end of the target branch. Conflicts may occur more often than in the merge method, and they need to be resolved immediately.

You and your team should decide on which method of merging to use. If you want to keep your revision history simple, you can do the following :

  • Use rebase on your feature/topic branch when you want to pull the latest change from the develop/integration branch.
  • If you want to merge the change from your feature/topic branch to the develop/integration branch, rebase the feature/topic branch onto the develop/integration branch first. After which, merge the changes from the feature/topic branch into the develop/integration branch. This will be a fast-forward merge with no extra merge commits being created.

Merge branch

You can integrate several branches by using the git merge command.

Consider the situation below. There are two branches: a “bugfix” branch with a few commits coming off the “master” branch.

Branch

In this case, merging “bugfix” back into “master” is not much of an issue. That’s because the state of “master” has not changed since “bugfix” was created. Git will merge this by moving the “master” position to the latest position of “bugfix”. This merge is called a “fast-forward”.

Fast-forward merge

In the example below, however, “master” has been updated several times since “bugfix” was branched out. The changes from “bugfix” and “master” need to be combined when a merge is executed on these two branches.

It has advanced more than when a branch is divided

For this sort of merge, a “merge commit” will be created and the “master” position will be updated to the newly created merge commit.

Merge commit incorporating both changes

Even when a fast-forward merge is possible, you could still explicitly force it to merge without a fast-forward merge.

Non fast-forward merge

As shown above, a non fast-forward merge leaves the “bugfix” branch as it is. This leaves you with a clearer picture of the feature/topic branch “bugfix”. You can easily find where the feature/topic branch starts or ends and also track the changes that are made to the feature/topic branch.

Rebase branch

For a cleaner revision history, you can use the git rebase command to integrate your branches.g

Say we have two branches with a non fast-forward merge scenario.

Branch

Doing a rebase will result in the branch history looking similar to the example below.

Unify branches by using rebase

When you rebase a bugfix branch to the master branch, commits from the bugfix branch will be replayed and appended to the end of the master branch. The end result is a single simple stream of commits in the bugfix branch history.

In the event of a conflict when the commit is being appended, you will be asked by Git to fix the conflict before proceeding with rebasing the other commits.

Unify branches by using rebase

A rebase does not move the position of the master. In any case, you will be able to do a fast-forward or a clean merge from bugfix to master after rebasing.

Unify branches by using rebase

Tags

A Git tag is used to label and mark a specific commit in the history. Tags are commonly used to indicate release versions, with the release name (i.e., v1.0) being the name of the tag.

There are two types of Git tags:

  • Lightweight tag
  • Annotated tag

A lightweight tag is similar to a branch that does not change. It just points directly to a specific commit in the history. Lightweight tags are mainly used temporarily in your local workspace.

An annotated tag is checksummed and often used when you are planning to mark an important commit. You can add comments, a signature, the date, plus the tagger’s name and e-mail.

A tag with a note, a light tag
Git tags in the master branch

Pull requests

There are many questions and opinions when it comes to reviewing source code. Code review can be difficult to stick with because people become busy with other tasks or feel it is too time consuming to go through the changes that have been made. Often, code review becomes a neglected task.

It can be challenging to make code review an integral part of your team’s workflow; pull requests make that easier.

Development without pull request

A pull request notifies other development team members of changes made in your local repository. Pull requests provide the following functions:

  • Notify team members when a review or merge of work is needed
  • Display changes made to source code in an easy-to-understand manner
  • Provide a platform for communicating about source code

Pull requests are not a Git function. They were created by GitHub to make it easier for developers to participate in open source development, and, as a result, enabled them to create higher-quality source code. Pull requests are available in most major Git hosting services, such as Backlog and Bitbucket.

Benefits of pull requests

Below are a list of pull requests in Backlog. You can easily see which ones are open or have not been completed. This makes it easy for team members to review pull requests.

Pull request list in Backlog, a Git hosting service with bug tracking features.
Pull request list in Backlog, a Git hosting service with bug tracking features.

The creator and reviewer of a pull request can have discussions right on the page using comments. These comments are recorded on the server, so they can be revisited at a later time.g

You can also commit and push changes to a specific branch. The pushed commit will automatically be reflected in the pull request.

This structured code review leads to higher quality source code and provides greater context for future discussions.

Pull request comments in Backlog.
Pull request comments in Backlog.

Pull request clearly display what changes have been made to source code. The pull request creator can also add notes about what their goal was for the source code and provide supplementary information. This info helps inform the reviewer.

Changes are highlighted in the source code.

Development process with pull requests

Here is a simple development workflow with pull requests your team can follow:

  1. [Developer] Clone or pull the source of the work target.
  2. [Developer] Create a branch for the work.
  3. [Developer] Perform development work such as adding and modifying functions.
  4. [Developer] Push after the task is completed.
  5. [Developer] Create a pull request.
  6. [Review / Merge Personnel] Check the changes from the notified pull request and review.
  7. [Review / Merge Personnel] Judge the work and send a feedback to the developer if necessary.
  8. [Review / Merge Personnel] Merge if there is no problem as a result of the review.
  9. [Review / Merge Personnel] Close if the pull request itself becomes unnecessary as a result of the review.

Repeat steps 3 through 7 as often as needed to improve the quality of the source code.

developer - review pr

Related

Subscribe to our newsletter

Learn with Nulab to bring your best ideas to life