This is my very first blog post and so I thought it should be about a tool that is indispensable for me – Git. I started using git about 10 months ago and looking back I can’t imagine how I managed to get work done without it. The purpose of this post, however, is not to sing git’s praises, there are lots of good articles on the web that do so much better than I ever could. Instead, I wish to share the work-flow I use on my projects. I developed this work-flow by trial and error over the months and is currently the most efficient and productive approach I can think of. If any experienced git users happen to stumble upon this post, please do provide suggestions/alternatives to help me improve my process.
The project I am currently working on requires me to maintain two parallel deployment branches. One is a “production branch” which is deployed on the live server and the other is a “development branch” which is deployed on a staging server. All enhancements and feature additions are done in the “development branch” and the only changes made in the “production branch” are production bug fixes that need urgent attention. Once the “development branch” is deemed stable it is merged into “production branch” and deployed.
I’ve divided my work-flow into two parts. The first part is a generic approach which I follow in all projects, the second part is specific to the case mentioned above and discusses how I manage the two branches using git. Although I mention “Rails” in the title of this post, the work-flow I define below is not “Rails” specific and can be applied to any project where multiple deployment branches need to be maintained. I assume the reader has a basic understanding of git and is comfortable with git branches. There are lots of configuration settings in git which help you set up things like you git author name, email and colors for git commands, etc. There are two I would like to mention here though:
git config --global push.default "tracking"
This command tells git to perform a push only to the tracked remote branch in which you are currently working.
git config --global pack.threads "0"
This command tells git to auto-detect the number of threads to use for packing repositories and is useful when working on a machine with more than one core.
1. Generic Workflow
a. Start with a clean master branch.
git checkout master git pull
You now have a clean master branch. Never work directly in the master branch, instead make all changes to a local branch.
b. Create a local branch.
git branch my_local_branch git checkout my_local_branch
or do both in one command:
git checkout -b my_local_branch
This creates a new local branch named “my_local_branch” and switches to it. You can now perform all changes in this branch. One good practice to follow when using git is to commit often and in small chunks. This gives you finer control on the work done and allows you to fix mistakes without losing other work. Once you are satisfied, all those smaller commits can be consolidated in a single meaningful commit.
c. Merge with master branch.
git checkout master git pull
if new changes were pulled do the following:
git checkout my_local_branch git rebase master
A rebase will apply the newly pulled changes in the master branch to your local branch and then apply local branch changes on top of that. This will ensure a clean merge with master. Conflicts are very common during a rebase and will need to be resolved before you can have a successful rebase. You can also pass the “-i” option to rebase to perform an interactive rebase which will alllow you to squash multiple commits into one if you so require. Once you are finished with the rebase do the following:
git checkout master git merge my_local_branch git push
Once you are satisfied with the push you can delete the local branch:
git branch -d my_local_branch
2. Managing Multiple Deployment Branches
The master branch is set as the branch for production deployment and a new remote branch is created for development phase. Since I use capistrano for project deployment I set it up to have three separate tasks: “production”, “production_staging” and “development_staging”. The “production” and “production_staging” tasks use the master branch and the “development_staging” task uses the newly created remote branch. For both the remote branches you must follow the above work-flow i.e: never work directly in the branches themselves, instead create local branches to work in.
a. Create a new remote branch.
First create a new remote branch from our master branch:
git push origin master:refs/heads/development_phase_1
b. Track the remote branch.
Track the newly created remote branch:
git checkout --track -b development_phase_1 origin/development_phase_1
This will create a new local branch named “development_phase_1” that is tracking the remote “development_phase_1” branch. Everyone who needs to work on development branch tracks this remote “development_phase_1” branch and pushes to it. The only pushes to master branch are production fixes.
c. Merge the development branch with the master branch.
Once work on development branch is finished we are ready to merge the two branches:
git checkout development_phase_1 git rebase master git checkout master git merge development_phase_1 git push git branch -d development_phase_1 git push origin :heads/development_phase_1
Normally a rebase is not a good idea in a remotely tracked branch but since our phase is finished and we will create a new remote branch for the next phase, I do so here. Alternatively, I could do a force push for the remote development branch and everyone working on it would need to remove and retrack it again. The second last line removes our locally tracked development branch and the last one removes it from the remote repository.
As I mentioned before, the above presented work-flow is not perfect by any means but it works for me. I would appreciate feedback from anyone who reads this and has some suggestions/recommendations to help me manage my projects better.