SchmidtHappens

My Git Workflow

Are you new to Git and have no clue what a git workflow might look like? Have you been using git for a while but are still not sure how to use it effectively in your daily development work? Worried the cool kids are making fun of you? For the low price of free, I'll share my Git workflow with you and help you on your way to git-jitsu.

When I first switched to git, I used it like I would have subversion. It's what I knew and it was pretty easy to continue working in that mindset. The problem with this is that it's kind of a slap in the face. I mean seriously, it'd be like getting an iPhone and treating it like it were a Nokia 5110 (ah, snap covers).

Sure, subversion was good and it worked well, but git offered a new way of doing things. Some might argue that it is a better way of doing it too. The following is where I am today with my git workflow. It's still a work in progress, but I find that it's really helping me become a better developer.

First, a Disclaimer

This article will not teach you about installing git, setting it up, creating an account somewhere awesome or letting you in on the meaning of life. If you need help with any of the above, let me know in the comments and I will do my best to get you an answer. For the sake of this article, I will assume that you are using Github. Let's begin...

Getting Started

The first thing that I do in any project that I'm working on is to create a master branch and a development branch. The first it pretty simple. All you need to do is initialize git inside the directory you want to manage.

# cd into the root folder of your project
cd /my/awesome/project

# initiate git
git init

# add everything that currently exists in the project
git add .

# making the first commit
git commit -m "first commit"

# adding the remote repo
git remote add origin git@github.com:username/project_name.git

# pushing local commits to the remote
git push -u origin master

I know, I know. I just threw a shit ton of code at you without even the slightest warning. Don't worry, I'm about to break it down.

Step 1: The first thing that we did was to cd into the project that we want to manage using git.

Step 2: Then we initialize git with a call to git init. This will create the appropriate files and directories necessary for git to know what's happening to our project.

Step 3: Once you've initialized git in your project, you will need to add the files and folders of your project so you can track their changes. If this is a brand spankin' new project, then you hopefully won't have too many things to commit. In fact, you may not have anything to commit. If that is the case you need to have at least one thing to commit. If I find myself in this situation, I simply type touch README to create a README file and use that as my first commit. To find out what will be added when you run git add . you can first run git status. This will list out all of the files that are currently not under version control. Also note that the '.' in git add . is a wildcard character that means "everything".

Step 4: Making your first commit. Now, the word "commit" may seem a little scary to those of you just getting started with git, but don't be. When you "commit" in git - rhyming was not intentional - you are actually just committing on your local machine. In order for your fellow developers to see these changes, you need to push them to the server. We'll get there in a second. First, a few notes about the command git commit -m "initial commit".

The -m flag is just a shortcut for adding a message. In order to create a commit you must specify a message. If you were to just type git commit, your editor of choice would be opened and you would need to enter a commit message and then save and close the file. I find that using the shortcut is not only faster but prevents me from getting chatty in my commit messages.

Step 5: Before we can actually push our commits to the remote branch, we need to tell our local branch where that is. To do this we type git remote add origin git@github.com:username/project_name.git. You will obviously need to change `username' to your github username and `project_name' to your projects name. In order for this to work, you would have already created this project on Github as well.

Step 6: Now that we've told our local repo how to talk to our remote repo, we can push our commits. The first time you do this it's a good idea to add the -u option so that you do not have to reference which local branch you are pushing. If you didn't add this option, every time you wanted to push changes from your local master branch you would need to type git push origin master.

The Development Branch

After running all of the commands above, we have a single branch in our git repository called 'master'. You can see this by running git branch. The asterisk (*) next to the branch name is a visual indicator of which branch you are currently on.

In my workflow, the master branch should always represent a pristine state. That means that you should be able to deploy your project to a production ready environment from the master branch. Does that sound like an environment you should be in when you are developing a new feature? Hells-to-the-no it isn't (if you answered yes, slap yourself).

For this reason, I create a new branch. I'm very clever and as such I use the name "develop" for the new branch. I know, completely tongue-in-cheek...

Anyway, you could really name this whatever makes the most sense to you. It could be called "staging" or "development" or "thisshitaintready". Whatever. The important thing is that whatever name you choose, it should be clear that this new, as-yet-to-be-created branch is where development work will take place.

To create this branch locally, we will first need to create it on the remote server.

git push origin master:refs/heads/develop

If you chose to go in a different direction with your name, I won't hold it against you. But you will need to update the above code to reflect your new branches name and not "develop" as I have chosen.

Next, you will need to create a local version of that branch and set it to track the remote version.

git branch --track develop origin/develop

The first argument after --track is the name of the local branch while the second is the remote branch you want it to be associated with. You could have chosen a different name but why the hell would you want to confuse youreslf like that. Really, that's just dumb.

Now that you've created this new branch, you can run git branch again and you will see both the master and develop branches. The current branch is still 'master' so you will need to checkout the 'develop' branch with git checkout develop. Can you guess what the command to checkout the 'master' branch again is? Hopefully you said git checkout master.

If you are like me, you like shortcuts. And git offers one when it comes to creating a new local branch and checking it out. To do what we just did you could've typed git checkout -b develop --track origin/develop. The -b option tells the git checkout command to create a new branch, while the --track option tells it what remote brach to associate the new local branch with.

There you have it. I know it seems like a lot of work, but really it's not that bad when you see it all together. I've combined the entire process for you to see it in one code snippet. For this example, we are assuming that I have already created a project in Github called "proposals".

mkdir proposals
cd proposals
touch README
git init
git add .
git commit -m "Initial commit"
git remote add origin git@github.com/tschmidt/proposals.git
git push -u origin master

git push origin master:refs/heads/develop
git checkout -b develop --track origin/develop

The Development Cycles

Now that we have everything set up and ready to go, I can talk about my development cycles. This includes 3 different types of local branches:

Before I discuss each of these in detail, here's how I structure my release number: major.minor.hotfix

The major and minor number relate to features that have been competed in development. How you choose to increase these is really up to you but I look at it this way: If you create a new widget for the side panel, I would consider that a minor release. If you completely revamp the UI for an application, then I would consider that a major release. Just use your own judgment as this is really something that depends on the project you are developing. The one thing that I will say is firmly defined is the hotfix number. This will increase each time you fix a bug in the project you are working on. Since I am the only developer, this is pretty straight forward for me. I'm the only one doing hotfixes. If, however, you work with a team then you will need to designate who should handle hotfixes and how they get pushed to production. Since that is a whole other blog post in itself, we are going to focus on my situation; the single developer syndrome.

Feature Branches

Feature branches should contain only one feature. That's why I call them "feature" branches and not "features" branches. Now, I know this my seem a bit strange at first, but I really do believe that developing a single idea or concept per feature branch allows you to really focus on what needs to be done. If you are developing several features at once and something breaks, it's much harder to figure out which feature the break will affect. By focusing on one feature and only one feature, there is no question.

Now, let's say that I want to add a login module to this application I'm developing. I would first start by making sure that my development branch was up to date.

git checkout develop
git pull

Once I know that I have the latest code I would run any tests that are part of the project. How you run tests will vary on your own situation. For me, it's typically through Cucumber and RSpec. Once I know all the tests are passing and my code is up-to-date, it's time to tackle this new feature.

The first step is to create a new branch based on the develop branch.

git checkout -b feature-login-module develop

Here, we are using the -b option for the checkout command to create and checkout a new branch in one step. The difference here is that we are not associating it with a remote branch. This means that it only exists on our local machine. The last argument we pass in is the branch that we want this new branch to be based on, which in this instance is the "develop" branch.

From here, all development that is required for the new "login module" will be done in this branch. After you complete a task for the feature, make sure to commit those changes to the feature branch.

# hack, hack, hack
git add .
git commit -m "Commit message"

# hack, hack, hack
git add .
git commit -m "Another commit message"

How do you know when to add a new commit? I typically do this after I get a spec or scenario to pass. That means I write a single spec or scenario, then I write the code necessary to make it pass, and then I add and commit those changes to the branch.

You've done all your hacking at the feature and now it's beautiful and shiny and complete. You've also run all your tests and made sure they are still passing (right?). Now what? Now, it's time to merge it back into the develop branch.

git checkout develop
git merge --no-ff feature-login-module

Once the merge has been completed, we no longer need the feature branch. Let's go ahead and remove it.

git branch -d feature-login-module

There you have it, my workflow for feature branches. Here is a consolidated snippet of all the commands.

# make sure develop branch is up-to-date
git checkout develop
git pull

# create feature branch based on the develop branch
git checkout -b feature-some-descriptive-name develop

# test, hack, hack, hack
git add .
git commit -m "Description of change"

# test, hack, hack, hack
git add .
git commit -m "Description of change"

# merge the feature back into the develop branch and remove feature branch
git checkout develop
git merge --no-ff feature-some-descriptive-name
git branch -d feature-some-descriptive-name

See, that wasn't too bad was it. The only other thing that I will mention is that after I merge a feature branch back into my develop branch, I make sure I merge develop back into any of the feature branches I am working on. This ensures that everything is up to date and I catch any collisions that might occur in the feature branch and not in the develop branch.

Release Branches

When I'm finally ready to release a new version of the code base I use a similar technique to the feature branch workflow. The difference here is that there is no actual development done in a release branch. As a naming convention I will call the branch "release-major#.minor#". So for example I may create something like "release-1.3". To find out which release is next I will just run git tag.

git tag
-> 1.0
-> 1.0.1
-> 1.0.2
-> 1.1
-> 1.3

git checkout -b release-1.3 develop

The only thing that I will update in a release branch is the VERSION file and the HISTORY file for the project. These are just regular text files that I place in the root of the project.

# Example VERSION file
Major = 1
Minor = 2
Hotfix = 0

# Example HISTORY file
January 1st, 2012
  - Feature one
  - Feature two
  - Feature three
  
January 18th, 2012
  - Feature one
  - Feature two

Once I've updated the HISTORY and VERSION files with the appropriate information I do a single commit with a commit message of "Bumped version X.X.X". The release is then merged into master, tagged, merged into develop and finally deleted.

# add and commit changes to the VERSION and HISTORY files
git add .
git commit -m "Bumped version 1.3.0"

# merge into master
git checkout master
git merge --no-ff release-1.3
git push origin master

# tag the commit
git tag -a 1.3.0
git push --tags

# merge into develop
git checkout develop
git merge --no-ff release-1.3
git push origin develop

# remove the release branch
git branch -d release-1.3

Hopefully, most of that will look familiar to you. The only thing that is new here is where we tag the release. Tags are really useful for capturing your project at a particular milestone. I highly recommend using them.

That's pretty much it for release branches. They are pretty straight-forward and just as easily implemented.

Hotfix Branches

The final branch type that I work with is the branch that I am hopefully not having to create on a regular basis. No matter how we try, as developers we will always encounter bugs. When this happens, I turn to this workflow for quickly squashing and patching the bug.

Unlike feature and release branches, a hotfix branch will be based on the "master" branch. It's also critical that when you address the bug that you do not allow yourself to creep on the scope of what you are doing. Fix the bug and the bug only.

# create hotfix branch
git checkout -b hotfix-squash-bug-x master

# hack, hack, hack
git add .
git commit -m "Commit message"

I will also update the VERSION and HISTORY files right before I'm ready to merge the hotfix branch back into master. I personally do this with hotfix branches because I want everything pertaining to the hotfix in one branch.

# update VERSION and HISTORY files and commit changes
git add .
git commit -m "[HOTFIX] Explanation of hotfix"

# merge the hotfix back into master
git checkout master
git merge --no-ff hotfix-squash-bug-x
git push origin master

# tag the hotfix
git tag -a 1.3.1
git push --tags

# merge the hotfix into develop branch
git checkout develop
git merge --no-ff hotfix-squash-bug-x

# remove the hotfix branch
git branch -d hotfix-squash-bug-x

And there you have it, my git workflow. I know that was a lot to take in so I've create a TL;DR version that you can use for reference once you are comfortable with everything. Feed back - as always - is welcome.

Something missing? Need more explanation? Let me know in the c-c-c-comments!

blog comments powered by Disqus