Thursday, June 12, 2008

git, svn and our current dev workflow

At iZoca, we are currently working in a distributed development environment, using a hybrid workflow that utilizes both subversion and git. There are already quite a few blog post out there of how people are integrating git into their existing subversion based development workflow, and most of our approach has been learned from reading these.

However, we've had some small hurdles to deal with when it comes to freezing rails versions and plugins, and applying patches (either our own, or from the community) back into these plugins before they make it back into their respective master branches. We've also had some small hurdles just figuring out the workflow that feels the best for keeping rails versions and plugins up to date. So, I thought I'd share the approach that seems to be working best for us right now.

First to set the background. Like most people that have a hybrid subversion/git workflow, we are primarily using our subversion repository as our "staging" repository if you will for our deployment and as a push/pull like conduit for getting and keeping everyone's local git masters fresh.

We are following the common recipe of using git-svn as our bridge. This recipe is pretty well documented (a quick 'git svn google' will get you some examples) The recipe goes something like this:
cd my_rails_app

git checkout master <<== get yourself to master  

git svn rebase
<<== make sure master is up to date

git checkout -b my_new_feature_branch


test, code, fix, test, code, fix...

git commit -a -m "my new feature stuff" <<== maybe preceded by some individual git add x, git rm x

git checkout master <<==I think you got it now, we are switching back to the master branch

git svn rebase <<== get any newly committed changes to the shared master trunk
git checkout my_new_feature_stuff <<== switch back to the feature branch
git rebase master <<== attempt to apply our changes on to the latest trunk of code
fix conflicts if any

git checkout master

git merge my_new_feature_branch <<== bring our new feature into the master (maybe --squash if there were a number of individual commits to get this branch done and we want them to show as a single commit)
git svn rebase <<== make sure things are good
git svn dcommit <<== push our modified master up to subversion trunk. This is the basic day in day out workfow. Some of the assumptions being that "git master" and "svn/trunk" are analogous to one another. If you are working on code that is from an origin other then trunk (like subversion branch/Version_x_y_z) you will be working in a local git branch that you create by: git branch local-branch_x_y_z VERSION_x_y_z


The workflow above is basically the same with local-branch_x_y_z playing the part of master. Once your bug_fix_branch, small_feature_branch is merged back in, you probably want to keep things tidy by cleaning up when your done:

git branch -d my_new_feature_branch
So, this is all pretty well documented and seems to be the approach working best for most using a subversion / git hybrid approach. As I mentioned earlier, the hurdles we've had are usually related to plugins and vendor/rails versioning and sourcing.

One of the problems is that the common approach for those using a git only workflow is to use sub-modules for managing externally versioned and maintained sources. So, lets say you want to include the version of rails you are using within your project instead of relying on installed gems; freezing rails as we all know it. Well, one approach is to "git clone" the rails branch/master(edge) you want directly into your vendor directory. The problem here is that now within your project, you also have another complete git repository; remember, git clone gets the whole repository and history. So, you could always choose to just include a depth of 1, but you still got a repository within yours. Additionally, if you try to treat it as a git submodule, well then "git-svn dcommit" is going to have a problem the next time you rebase/dcommit. There are some published work around to the git-svn dcommit/rebase bug.

But, when I stepped back and looked at things with iZoca, I questioned if submodules were the right answer even if they worked with git-svn. The problem is that we want to be on the edge. We want to be active in the community. We want to contribute to open source, collaborate, and naturally harvest the benefits of what others are doing with open source. And, no matter how we slice it up, when we step back and look at it, submodules doesn't seem to be the answer for us (even if they worked with git-svn.) It seems like this approach works well if you want to freeze to a particular version, and at sometime in the future pull latest features, or checkout latest branch. But, for actively working in any of these projects, work feels more like monkey patching then it should...at least to us it did.

We are taking a bit more of a distributed development approach with these external projects. Rails, Rails plugins, javascript frameworks, etc. can simply be archived back into their respective locations in our core application by the respective developer that happens to be working with that source. They will maintain separate project folders outside of our core application that are clones of the source they work with. They can pull, branch, diff, patch at will in this project and collaborate with the community at large without interfering with iZoca proper. When changes from this work are ready to come into iZoca proper, the respective developer can simply:

git archive the respective project branch back into iZoca proper, run the test and call it a day.

From an iZoca perspective this "copied in" archive just becomes part of a local iZoca git branch changeset, and then merged back into master when ready. This lets the iZoca core branch stay a little less complicated.

The alternative to having submodules, with multiple branches all taking place within core branches at various versions all seems a bit more complicated then we want it to be, even if it worked with svn. Maybe I just don't comprehend submodules properly to begin with, but the approach we are taking now seems to be working well.

Not every developer will necessarily have a clone of each of the plugins, or even rails all of their own. It all depends on what they will be working on, contributing too, etc. We don't try to keep a centralized version of each of these separate repositories, because it kind of goes against the grain of distributed version control anyway. Depending on the scope of each of these projects, some may even be forks of github repos, but it isn't a requirement.

Rails prefers diff patches via Lighthouse, so a github Fork of Rails doesn't really seem necessary for this kind of work. But, one of our developers at some point in time might find that helpful and it doesn't really matter. The point is at some point in time we may need, want, desire to get their branch of plugin xyz into our core iZoca master to fix or enhance something. And when we do, the developer that needs, wants, desires the fix or pull of a recent change set can either collaborate with another developer that is familiar with that plugin or section of rails and get a diff patch from that person, or works on the that source themselves in their local branch of that respective project, and then when done simply archives the result of that patched plugin into a local branch of our core iZoca application, tests, then merges and eventually git-svn dcommits.

An example of the archive/copy...lets assume that I want to fix a bug in some_cool_plugin.
Well, I would either have or create a local git clone of some_cool_plugin:

git clone git://github.com/rails/rails.git
or if I already have the clone:
git checkout master

git pull
git branch my_fix_to_some_cool_plugin

test, code, fix, test, code, fix

git commit -a -m "patching bug with my cool hack"

git checkout master

git pull

git checkout my_fix_to_some_cool_plugin

git rebase master
git format-patch master --stdout > my-cool-patch-file.diff

#email patch or submit via lighthouse, or git push to forked github
#then for me to archive my latest patched version of the plugin back into our core application
#before the patch has been officially approved or committed back to the plugin/or rails master
#we can do something the following:
git archive --prefix=some_cool_plugin/ HEAD | (cd ~/scott/Projects/izoca/vendor/plugins && tar -x)

This gets the latest patch code, without including the .git repo, back into our core application branch to then be committed, merged and dcommitted just as normal.

Hope this approach helps somebody else, it seems to be working pretty good for us right now.

No comments:

Post a Comment