Version Control at FoxyCart
Here at FoxyCart our business is code. And, as anyone who's written code in the last 10 years knows, version control systems are the best place to keep your code safe. Having a living record of "what worked when" is invaluable in the day-to-day development and running of any software project. That, tied to a bug database, makes it possible to manage the huge complexity that comes with software development.
We're in the middle of transitioning from Subversion to Git. Five years ago, when we started writing FoxyCart, Subversion was the VCS to use and Git was no more than a twinkle in Linus' eyes.
Our main application started in Subversion and, 5 years later, we've got more than 7000 commits over a dozen branches and are still actively developing our latest features on top of SVN. It's a good, battle-tested version control system, and it's served us well — but it's starting to show its age.
We switched to Git for all new projects about 2 years ago due to popular demand by our team. Not only has a lot of open source development moved in that direction, but there are too many nice things about Git for us to ignore: lightweight repositories and branching, staging, distributed development, and more. In fact, using
etckeeper, all of our servers' configuration files are kept in Git as well, which makes it a snap to find a change on one server and apply it to a different one. (Interested? Check out this article I wrote about etckeeper)
"Everyone must read from and commit to the main repository always" looks different when your team is spread across 7 timezones. The "one master" approach of SVN is a nice, simple approach. It works great if everyone always has a fast connection to the repository server. However, reality is never ideal! Depending on my connection I've seen between 2 and 10 seconds to do a simple "show me the version history" with
svn log. When you take 2–10 seconds and multiply by each developer running that command several times a day, that time starts to add up.
No easy "undo". We've all fat fingered a commit by now, and the SVN way to undo it is painful: commit an inverse patch that undoes the screw-up. And it's easy to mess up, as well —
svn commitgrabs all modified files by default!
The result is that committing feels brittle and and dangerous. It's easy to accidentally forget a file, or to include something by mistake.
Also, as we've become more familiar with Git, it's easy to forget that SVN has no concept of a 'staging area', be working along, and, ARGH… did it again! Another boneheaded commit is recorded in version control for posterity.
Subversion likes to talk to the server a lot, and make changes there immediately. Branching means an immediate modification to the central repository and so, like committing, feels dangerous.
Even after years of SVN use, I still reach for the SVN manual every time I want to manipulate branches. There's too much at stake.
We've given up on making a lot of branches and consolidated on two main branches:
Releaseis always what's running on production right now (or will be soon).
Trunkis where new development happens. Once we've reviewed and tested a new change in
trunk, one of the team leads merges the change to
releaseand then deploys it to production.
This works well, but makes it hard to do something like "hey, I'm going to try adding caching to all of these different things" without stepping on someone else's changes. Having worked with both Subversion's and Git's branching, Git's lightweight branches are a clear win here as it's possible to commit changes to a local branch and test before pushing them back to the server.
Subversion authentication is kind of old school. We use it over HTTPS, and it requires a username and password for all operations. (We're small enough that LDAP or similar wouldn't be worth the effort to implement it.) Having worked with password-based and SSH key-based authentication, I much prefer the latter. Public keys are fine to store in plain text, whereas passwords need to be encrypted and obfuscated — a feature which often doesn't work out of the box in Subversion!
We use a mix of
gitosisand GitHub for our git repositories, which both have simple key-based authentication schemes. I can also create read-only "deploy" keys for code rollouts in production.
As mentioned above, there are a lot of reasons we like Git. So why don't we switch over our main repo right now? Well, there is a lot of process and familiarity attached to Subversion — 5 years of almost daily use for development & deployment. We're moving fast, and it's hard to put things on hold while we all get familiar with development on the new tool.
With that in mind, we've started a gradual transition, with the goal of being completely on Git by the end of the year. Here's the plan:
Thanks to these Atlassian articles this was fairly straightforward to achieve:
I'm thankful that we didn't have anywhere near the number of repositories or team members as they did to transition. Despite that, it still took a fair while to import the history, and I re-did it a couple times to get the version history & authors just right.
Right now, there's a cron job that runs every 10 minutes that does the following: 1. Import the latest Subversion changes from both branches. 2. Push all changed git branches to the gitosis-served repository. (
git push -a origin)
It's been ticking along beautifully for a few months now, so it's almost time to move to step 2.
Compared to Subversion, deploys with Git are much, much quicker. For one thing, it's nice to have a full local version history that doesn't require talking to a remote server. And while Subversion and Git both have compressed wire protocols, Git does it faster.
As I mentioned we have read-only deployment keys for each server. With Ansible, our configuration management software, updating a git repository on a server is almost trivial to do.
This one will be harder for us, and there may be a follow up post after make the switch. Thanks to the preparation we've done, and the skills of our team, it shouldn't hold up development long at all.
We're very much looking forward to using GitHub-style feature branches and pull requests for new development. On other projects where we've tried it, it's been extremely smooth and pleasant.
This is one of my favorite little tricks, and a good illustration of the advantage of having a local copy of the entire repository.
Remember what I was saying about not being able to undo a change in Subversion without making a patch? Well, in Git, so long as you haven't pushed your changes back to the remote repository, you can undo a change you've made to the wrong branch.
Here I have two branches,
add-new-feature. I just finished editing my README to make it more readable and went ahead and committed my changes. I want those changes on master, not in my feature branch.
% git commit -m "Expanded readme" [add-new-feature f96ffae] Expanded readme 1 file changed, 1 insertion(+)
Wait… why does it say
add-new-feature there? I wanted that on master. Whoops!
Actually, this is pretty easy to fix. Note the commit version of the last commit:
% git log -1 commit f96ffae3fee0fc433f93c64635b83bc38a1f125c Author: Fred Alger <email@example.com> Date: Sat Feb 22 08:15:55 2014 -0600 Expanded readme
OK, now switch back to
% git checkout master
cherry-pick to merge just that ONE commit:
% git cherry-pick f96ffae3fee0fc433f93c64635b83bc38a1f125c
OK, great, now that commit is in master where it belongs. But now I need to get that commit OUT of my feature branch, or I'll get conflicts later on.
First I switch to the feature branch:
% git checkout add-new-feature
Then I use
reset --hard to put my branch history back to where it was before I made that change.
% git reset --hard HEAD~
HEAD is a reference to the last commit on the branch, and
HEAD~ is shorthand for "one before the last commit".
And there! Mistake fixed.
I hope you found this post helpful and interesting. Please share any comments or questions below or on Twitter!
The views expressed in the above post are the author's own, and may not reflect those of FoxyCart.com LLC.