Git Workflow

Vinisha Mittal
Cornell DTI
Published in
11 min readFeb 24, 2018

Git is a powerful tool for teams to use as they build their codebase. Some of that power lies in its flexibility, which makes it important to establish concrete rules about modification of your codebase with Git within your team. Here, I will discuss the Git workflow that my team, CU Reviews, implemented to streamline our development practices. Our team is building an application that allows students to post reviews about university courses as part of Cornell Design and Tech Initiative, a student-run project team at Cornell University.

Motivation

An organized Git workflow is vital to efficient software development. Before implementing a regimented Git workflow, all developers on our team contributed to a single version of the code. Each developer would work from the master branch, develop an assigned feature locally, and push back to the master branch once their feature was complete (with no code review). This led to 2 main problems:

  1. Broken Code: often, Developer B would push changes to the master branch between the time Developer A began and completed their feature. When it came time to push code, Developer A would face merge conflicts. They would resolve these conflicts, but unknowingly break a feature added by Developer B in the process. Although the application was not live at the time, the codebase was effectively left this “broken” state until Developer B noticed their feature no longer worked.
Figure 1: Developer A and Developer B develop separate features directly on the master branch.
  1. Repetitive and Unorganized Code: sometimes, Developer A and Developer B would write a helper function or CSS class that did the same thing. Developers used different naming conventions and didn’t always leave comments, so the repetitive code was overlooked. Over time, this lead to an increasingly unorganized codebase.

Correcting these problems took time. We were removing duplicate CSS classes and functions, retroactively adding code comments, and rushing to fix broken code, leaving less time to work on improving features.

To combat these problems, we utilized a Git workflow with distinct branches for each state of the code and mandatory peer reviews. This ensured one version of the code was always working, reduced errors due to merge conflicts, and helped us maintain an organized codebase. These changes allowed our team to become more efficient and spend time improving the product, not just fixing it.

Prerequisites

The following tutorial expects a working knowledge of basic Git, including the concept of branches, merging, and the use of command line calls such as clone, commit, add, pull and push.

Repository Structure

The first step in setting up an effective Git workflow is organizing your Git repository. Your codebase should be kept in at least 3 stages, each within a separate branch, where each stage tells you something about the state of the code in that branch. We utilize 3 branches:

  • master: The master branch holds the lastest live version of the application. This is the code that our application’s users see, so it must be fully functional and passes all of the application’s tests.
  • development: The development branch holds the most up-to-date version of application, including all approved changes made by developers over an iteration period. Only working changes are approved, so the development branch is also fully functional and passes all tests. The development branch begins as a copy of the master branch, and is merged back into the master branch at the end of the iteration period.
  • feature branches: Feature branches hold code relating to a single new feature. Every feature branch begins as copy of the development branch, and each developer makes a new feature branch (with a descriptive name) for every feature or fix. Once a feature is fully functional, a pull request is used to merge its feature branch into the development branch. Pull requests give warnings about merge conflicts and are peer reviewed before they are approved, so only working feature branches without merge conflicts are pushed into the development branch. We will discuss pull requests in detail later on.

To see this in action, think back to the example in Figure 1 with Developer A and Developer B. Now, each developer will work on their own branch, and request a review before pushing their changes to the development branch. Here, Developer A’s merge error will be caught by Pull Request A. The pull request will warn that a merge conflict exists, and Developer A’s erroneous conflict resolution will be reviewed by Developer B (or another developer on the team) before it is approved to move to the development branch. The master branch will remain untouched until both Developer A and Develper B’s changes are approved and working.

Figure 2: Developer A and Developer B’s workflow with a master, development and 2 feature branches.

To implement this repositiory structure, we will make use of branches.

Using Branches

The next step to establishing a Git workflow is getting comfortable with using branches. This requires using Git’s checkout and branch commands:

  • Checkout [branchName] moves you to a different version of the codebase, changing your local repository to match the state stored by Git at that time. Here, we will move to the branch named brachName. This command can also be used to move to a specific commit using the commit’s unique id instead of branchName.
  • Branch [branchName] creates a new local branch named branchName that is a copy of the current branch. This command can also be used to list and delete branches.

Let’s reconstruct the actions taken by Developer B to explore this further.

Creating a Feature Branch

Suppose Developer B was tasked with adding an admin login page. Since this is a new feature, we will first need to create a new feature branch for Developer B to work on. This branch must begin as a copy of the development branch — we will use the branch command to do this!

Assume the repository already has a master and development branch in place, and that the local repository is currently on the master branch. Note that the development branch is a copy of the master branch, also created with branch.

Initial state of the repository, before Developer B begins work.

To start, first move to the development branch using the checkout command. The local repository must be on the development branch in order for the feature branch to copy it.

git checkout development 
The current state of the local repository now matches the development branch.

We are now on the develpoment branch. Now, using the branch command, make a new branch named feature-adminLogin that branches off of the development branch:

git branch feature-adminLogin
State of the local repository after creating a new branch. Note that the current branch is still development!

This creates a new branch that is a copy of the development branch! To start working on the new branch, we need to move to it; the local repository is still on the development branch. To do this, use the checkout command to move to the new branch.

git checkout feature-adminLogin
The local repository is now on the new feature-adminLogin branch, and you are ready to being developing!

We are now in a new branch whose current state is a copy of development. Any changes made to this branch will grow along this branch, and have no effect on the master or development branches. Let’s test this out by making a commit to feature-adminLogin.

To start, make a small change to the codebase. Add the following comment to any file:

// first change for feature-adminLogin

Save the change using add and commit.

git add .
git commit "first commit for feature-adminLogin"
The local repository now has a new commit.

As of now, the feature-adminLogin branch and the new commit exist only in our local repository. To make these changes visible to other developers on the team, we must push them out to the remote repository. Remote is a Git term for a repository hosted on a remote server such as Github, in contrast the local repository which is hosted on your computer.

To add this branch to a remote repository, we use a modified push command that specifies the remote branch to push to. Here, we will push to a new branch named feature-adminLogin on the origin repository. Origin is the default name given to the remote repository that your project was cloned from, and is useful in differentiating local and remote branches of the same name. Use the following command:

git push origin feature-adminLogin

Note that the feature-adminLogin branch does not exist on the origin repositoy the first time this is called — Git creates a new remote branch named feature-adminLogin at that time.

Your changes will now be visible on your very own feature-adminLogin branch in the remote repository! Other developers can view and pull your code, push to your branch, and even branch off of it if needed.

Viewing Branches

If you forget what branch you are on, simply run

git branch 

for a quick visual of all local branches. Your current branch will have an * before it, as shown below.

Note that this will only show local branches, not global ones on the remote repository. To see a list of all available branches, run

git branch -a

Remote branches will begin with remotes/origin.

Pull Requests

The final step in our Git workflow is using pull requests to manage code changes. A pull request is an alert that signifies that a developer has completed work on a feature and is requesting code review. These are initialized from the remote repository as a request to merge two remote branches. As such, we will be using Github to make and approve requests.

All collaborators on a Github repository should receive an email when a new pull request is opened. For small teams, you should also send a message to the team to let them know you have opened a pull request and are ready for a code review. Asking another developer to review your code mitigates errors and ensures that everyone follows similar code structure and styles. Note that a pull request can only be approved by a different developer than the one who opened it.

Let’s explore this process by creating a pull requst for Developer B’s feature-adminLogin branch. We will need to utilize a new merge command:

  • Merge [branchName] manually performs a merge between the current branch and branchName, saving to the current branch.

Prepare the feature branch

Suppose we have completed the Admin Login feature.

We are now ready to merge the feature-adminLogin into the development branch. We first prepare the feature branch to merge with the development branch by cheking for merge conflicts between the two branches.

First, pull the latest code for the development branch using a modified pull command that specifies the branch to pull from.

git pull origin development

Now, merge the development branch into the feature branch using the merge command. Recall that the local repository is on the feature-adminLogin branch, so changes will be saved to this branch.

git merge development

Since other developers have not changed the development branch in this scenario, there is nothing new to merge. However, if changes had been made to developement, they would need to be resolved before continuing. This was the case for Developer A’s branch, where merge conflicts were resolved and saved as a new commit.

Prepare feature branches for Pull Requests by merging, as shown by the dotted arrows. The feature-adminLogin branch had nothing new to merge, as shown by the dotted purple arrow. Feature Branch A had to merge in the changes from feature-adminLogin, as shown by the dotted pink arrow.

Create a pull request

Once the feature-adminLogin branch is ready, we move to Github’s online interface to create a pull request. First, view this branch on the remote repository hosted on GitHub by selecting it from the branch dropdown. Then click the New pull request button.

On the pull request page, ensure the source and destination branches are configured such that the compare branch will be merged into the base branch. Be sure to add a summary ofthe changes being made, and scroll down to review all files that differ between the base and compare branch.

Once you are ready, click Create pull request! Let you team know that you have opened a new pull request and are awaiting review.

Review a pull request

Aside from making pull requests, you should also review other developer’s pull requests! Recall that a pull request can only be approved by another developer, so all developers should participate in peer reviews. To view open pull requests, navigate to the Pull Requests tab in the homepage of your repository and select the name of the pull request you want to review.

View all open pull requsts. Select the pull request name to review it.

When reviewing another developer’s pull request, first ensure the code passes all tests. You should also look over their implementation and documentation, and suggest more optimal methods or request more code comments as necessary. If your application is in production, test the changes in a staging environment (a “playground” environment within the hosting platform that runs the new code but doesn’t push it to users). Finally, make sure there are no merge conflicts between the feature branch and the base branch — the pull request will tell you if there are!

This branch has no conflicts, so it is ready to be merged if the branch passes tests and peer review.

If there are conflicts, or the code is not ready to ship, the pull request can be closed. Alternativly, you can leave the pull request open and guide the developer to make improvements. Pull requests are structured like a forum — you can request changes from the developer simply by leaving comments on the pull request. The developer can then implement changes and push to their feature branch without re-opening the pull request!

Ask the developer to add more code documentation by adding a comment to the pull request.

If you are ready to approve the pull request, select the Merge pull request button to automatically merge the feature branch into the development branch! You may also wish to delete the feature-branch after merging, as long as the original developer no longer needs it.

The development branch will now be updated with the merged feature branch. Pull the development branch locally so you have the updated code! From here, you can start the process over with a new feature!

That’s it!

With the right repository structure, branches and pull requests, it’s easy to set up an organized Git workflow for your team. By maintaining multiple versions of the codebase and perfoming careful peer reviews of each other’s code, your team can develop efficiently and effectively. You can minimize errors and code redundancy, and put your full energy into creating the best product possible!

Remember that this Git workflow is only as effective as the disipline with which it is used. To ensure workflow is as effective as possible, keep the following notes in mind:

  • Developers should never review (or approve) their own pull requests. The point of reviewing code is to detect mistakes in implementation and code style, which requires another set of eyes. You can edit the Github repository’s settings to ensure this happens.
  • When reviewing, all developers should perform the same tests and look for the same styling conventions. Be sure to outline these beforehand so reviews are consistent!

If you run into problems with Git commands, branches or merging, take a look at Git’s command line documentation. For a more conceptual walkthrough, check out Atlassian’s Git Tutorials.

Now, git going!

--

--