How We Work - Git Branching Strategy and Multistage Capistrano Deployment - Part 1

Posted on by Michael Orr

This is the first in a set of blog posts about how we use Git and Capistrano in our project workflow at Cloudspace. Everyone can agree that using source control and automated deployment are important for any web project. We work in small teams within Cloudspace or alongside our clients' engineers, so it's even more important to have a consistent strategy for maintaining branches and handling deployment to keep everything working smoothly. We generally choose Git for source control and Capistrano for deployment at Cloudspace. We've been known to use other tools when a project requires it but if we are starting a new project from scratch we'll choose Git and Capistrano 9 times out of 10. Git has some great branching features that give it a real leg up on svn and the hosted Git service offered by Github makes it easy to work with other coders the world over. This first post will concentrate on Git and the process leading up to a deploy.

Long Term & Short Term Branches

Every Git project starts with one branch called "master" and for most Cloudspace projects this is the only long term branch. This branch represents the code running on the production web servers. We never commit code to the master branch. Code is only merged into this branch once it has been tested on a local machine and on the staging server. Code is merged into the master branch when it is ready to be live on the website. When new code is merged into the master branch, it is time to deploy to the production web server using capistrano.

We work on short focused branches within our application and then merge them into the master branch when they are ready to be deployed to the live website. We create a short term branch for each ticket and then delete it after the work has been finally merged into the master branch and deployed to the production server. The short term branches are typically named something like 38665325_token_pool_low_notice, with the number representing the related ticket number and the words being a short description for the ticket. Short term branches are created based off of the master branch.

Some branching strategies have a second long running branch typically called "development" that represents the code on a staging server. We also use a development branch between our feature branches and the master branch, however we prefer to regularly delete the development branch and always create a new branch as a fresh copy of master each time that we are doing a new staging deploy. Once we've created the fresh development branch we merge the feature branches we want to test out into it. This way the development branch mimics the exact state that will happen when we merge a feature branch into the master branch to deploy it to production. We never merge our development branch into the master branch. We always merge our feature branch into development and then merge our feature branch into master at a later point. This strategy prevents us from accidentally deploying another person's incomplete code that they recently tested on the development branch.

New Feature, New Branch

At Cloudspace, everything we work on is specified by a ticket in a project system. When we start work on a new ticket we create a new branch in Git based off of the master branch. We name branches based on the ticket number and a short description of the feature. If there is not a ticket our system for what we are about to do, we add one to make sure that the work we are doing is tracked. For this example let's assume Git is installed, the project is already cloned onto my machine, and a new ticket is ready to be started. We'll choose the new branch name "12345_my_new_feature" for this example.

Open up a Terminal window and change directories to go to the repository:

cd /path/to/the/repository

First I check to make sure that I am on the master branch. I can check what branch I am on with the command:

git status

If I am not on the master branch I need to check out the master branch with this command:

git checkout master

Then make sure I have the latest version of the code on the master branch by pulling in the latest changes:

git pull origin master

Then I create the new branch (the -b flag means that I am creating a new branch, note I didn't use it when I checked out the already existing branch master above):

git checkout -b 12345_my_new_branch

Occasionally a new ticket may require me to do work based off a branch of code that has not been merged into master and I do not create my branch based off master but another feature branch by checking out and pulling that other feature branch before creating my new branch. I have now moved to the new branch and I am ready to work. Code. Write Tests. Code some more. I commit my work to the feature branch frequently.

Working on an Existing Feature Branch

Sometimes I do not create a new branch when starting work on ticket. Perhaps someone already started the work and I want to use their branch. If I am going to work on an existing branch it is important that I have an up to date copy of the branch.

Let's say I want to work on 12346_new_feature, first I need to checkout the branch:

git checkout 12346_new_feature

This command may fail if the branch has been created recently and my local Git repository does not know about the existence of the branch I am trying to switch to.

To make sure my local copy of the Git repository is aware of all the existing branches I need to do a fetch command and then try the checkout command again:

git fetch git checkout 12346_new_feature

Once I've checked out the branch I want to make sure that it is up to date with the latest code. It is a good idea to do a pull command whenever I switch branches though it should already be up to date if this is my first time checking out the branch:

git pull origin 12346_new_feature

Committing to a Feature Branch

When I am ready to commit work, first I want to look at what files I have changed:

git status

The "git status" command will show all of the files that have been changed: created, updated or deleted. It has several sections:

  • Changes to be committed - files that have been "added" and are ready to commit
  • Changed but not updated - files that have been changed since the last commit (or since the creation of the branch if no commits have been made) but have not been "added" and are not ready to be committed
  • Untracked files - new files that are not "added" and are not ready to be committed

When I want to commit code, first I have to tell Git what files I would like to commit. I do this by "adding" files or folders. To add a file or group of files I issue an add command to Git and specify which files should be added. I can add a specific file, a directory of files, or all changed and new files in one command.

To add a specific file:

git add app/models/user.rb

To add all changed or new files within the folder app/models:

git add app/models/

To add all files that have been updated:

git add .

Then I can do the "git status" command again after the "git add" command to see which files have been added or are now "staged" for commit. Once everything looks correct I commit with a message like this:

git commit -m "this is the content of my message"

Pushing to a Feature Branch

Now I have made my commit but it is just on my local computer. I want to "push" my commit up to the remote Git server (Github):

git push origin 12345_my_new_branch

I commit often, but committing isn't enough because that only saves my work to my local computer.  After committing I always push the code to the remote branch on Github. This means my code is backed up as soon as I push to Github and if my computer dies I haven't lost my work.

Once I have completed the work for the ticket, I run the test suite to make sure that everything is working properly. In most Cloudspace projects there will be a mix of cucumber and rspec tests.

To run cucumber tests:

rake cucumber

To run rspec tests:

rake spec

If I have been working on a feature for a long time it is possible that new code has been pushed to the master branch. I want to update my branch with the code that has been pushed to the master branch since I started to make sure that my new code still works with the new version and to make sure that I didn't break any other functionality. This is called rebasing.

To rebase my feature branch based on the master branch, first make sure to commit any work on the feature branch and then switch to the master branch and do a pull:

git checkout master git pull origin master

Then I switch back to my feature branch and do the rebase:

git checkout 12345_my_new_branch git rebase master

I often run the test suite again after a rebase.

When I finish my work for a ticket I always check "git status" and make sure that I have committed and pushed all of my work. It is now ready merged and deployed.

Merging a Feature Branch

We deploy using the capistrano gem. Deployment is set up in the config/deploy.rb file and for each type of deployment there is an additional configuration file, located in places like config/deploy/staging.rb and config/deploy/production.rb . These files tell capistrano how and where to deploy code.

For each project it is best if one person is in charge of merging branches and the deploy or at least that everyone coordinates their merges and deploys on the project. It is very frustrating when two people are trying to merge and deploy at the same time. It important to know what branches are deployed at what time and it is important that the branches are merged properly into development and master. Let's say that there is a branch 12345_my_new_branch that was finished by another developer and it needs to be deployed to the staging server.

First I delete the existing development branch by pushing "nothing" to the remote development branch and then deleting the local copy of the branch:

git push origin :development git branch -D development

I check out the branch:

git fetch git checkout 12345_my_new_branch

Then I create a fresh development branch based off of master: I check out the master branch, make sure the code in the master branch is up to date, and then create my new development branch based off of master:

git checkout master git pull origin master git checkout -b development

Then I merge the new branch into development:

git merge 12345_my_new_branch

Sometimes Git can not figure out how to apply a commit to a branch and this results in a merge conflict. Git will mark off the sections of the file that it is unsure about how to merge together and list which files have a problem. Open the files that have a conflict and determine how the code should be merged together and save the updated file. I check the whole file to make sure there are not multiple conflicts and then commit the updates. It is important to not alter any other code except for the sections noted as in conflict when resolving merge conflicts because it is not properly tracked.

After the merge I run the test suite before I push the code up to Github and prepare to deploy:

rake cucumber rake test

And when the test pass I push my code up to Github:

git push origin development

Next, Deployment

Now I am ready to deploy to the staging server. I'll cover our deployment process for staging and production in the next blog post.

Enhanced by Zemanta
 
comments powered by Disqus