The title of this article may be overstating my expertise with git slightly, but that’s how I feel having successfully tackled what is, I think, a fairly non-trivial task with git: managing changes across multiple branches and multiple remotes using a single working directory.
Of course, git is perfectly suited for complexity! But I don’t think people realize it, necessarily.
I am relatively new to git, compared to the number of years I’ve been using source code control systems. That said, I’ve been using git now for about 18 months. And I’ll admit that it has taken some time to really “get” it. I find that I am learning new things about it all the time, still.
We all know how easy it is to get started with git:
% mkdir myproject % cd myproject % git init % <write fabulous code> % git add . % git commit -m "Fabulous code!"
Presto! A local repository with all your code and commits and history. Easy peasy.
Getting this to a server requires adding something called a remote, which is simply the address of a server that is prepared to receive your commits. (I will not go into how to setup a git server. I use gitolite, which is easy to setup and easy to manage.)
% git remote add origin email@example.com:myproject.git
Now you can push your local repository to this remote, which here has been named “origin”, but you could name it anything meaningful to you (as we’ll see later).
% git push origin master
The reference to “master” here is the name of the branch, on the remote “origin”, to which you want your commits pushed. You could use another name here too (e.g. trunk), but the convention generally is to have a remote named “origin” and a main branch named “master”.
Ok, great. Everyone knows this stuff, and there are any number of online tutorials about it. What is not necessarily “out there” already is how to scale this for yourself.
Did you know you could have any number of remotes defined for a single working directory (local repository)?
I first ran into this when I had changes I wanted to make to a Ruby gem I had found on github. We needed to use this gem in our project, but it also made sense to push the changes back to github on a branch of my own forked copy. So I forked the project, and cloned the repo:
% git clone firstname.lastname@example.org:granoff/apnd.git
This automatically gave me one remote:
% git remote -v origin email@example.com:granoff/apnd.git (fetch) origin firstname.lastname@example.org:granoff/apnd.git (push)
When I had completed my work, and pushed back to github, I also wanted to push the code to our internal gem server. I simply had to add another remote:
% git remote add scm email@example.com/apnd.git
I named this second remote “scm” to distinguish it from “origin” (which I knew was github). Then all I had to do was push (again), but this time to “scm”:
% git push scm master
Pretty cool. And, all in one working directory.
Now for the fun part. I recently became the primary developer on the Ruby on Rails portion of a project for which I am the primary iOS developer. Luckily, the Rails project is in pretty good shape, and I have not had to wade too deeply into it. At least, until recently. A few issues have popped up and ultimately, I have had to face a challenge I knew was coming. But more on that in a bit.
When I took over the Rails project, I had the then primary developer push his git repo to a new repo I setup on my own server. That repo on my server is my origin for the project. Our Rails app is hosted on Heroku. But instead of just one instance, we have 3: production, beta, and test. Each of those is effectively a copy of our repo, but we only push to these repos when we want to deploy something. But they are all just git repos! That means locally, I can define them as remotes in my working directory!
% git remote add prod firstname.lastname@example.org:instance-three.git % git remote add beta email@example.com:instance-one.git % git remote add test firstname.lastname@example.org:instance-two.git % git remote -v beta email@example.com:instance-one.git (push) beta firstname.lastname@example.org:instance-one.git (fetch) origin email@example.com:the-project.git (fetch) origin firstname.lastname@example.org:the-project.git (push) prod email@example.com:instance-two.git (push) prod firstname.lastname@example.org:instance-two.git (fetch) test email@example.com:instance-three.git (fetch) test firstname.lastname@example.org:instance-three.git (push)
The first challenge I had was to push what we had on beta, to production. (This also involved a Heroku stack change, which seemed daunting, but in the end was painless. Thanks Heroku!)
Now, I am pretty comfortable at the command line; there are just some things that are done more easily from a shell prompt than by using a GUI. And sometimes, what you want to do cannot be done easily with a GUI simply because the task is non-trivial. That being said, being able to visualize a git repository graphically is priceless. There are at least a couple good tools for this. One is GitX, the source code for which is on github, and has been forked numerous times. The one I had been using, GitX(L), seems to be the one in active development, and works pretty well. But semi-recently, Atlassian released a free tool, aptly named SourceTree, that I have been using. It’s a little more full-featured than GitX, in my opinion.
Anyway, using a visual tool, I could see prod was tracking prod/master, and beta was tracking beta/master. My local repo was tracking origin/devo (an active development branch), and test was tracking test/master. (On Heroku, it is the master branch from which your app runs. If you had just one Heroku instance, it might make sense to have other branches that you work with locally, and then when it’s time to deploy, you push master.)
To push what I could see was actually on beta (beta/master) to prod, all I had to do was the following:
% git push prod beta:master
This command is effectively the same as
% git push prod beta/master:prod/master
What it is doing is pushing to prod what is locally in beta/master to the remote prod/master. Easy peasy, again!
But, it wasn’t before I spent a lot of time understanding this, with a colleague in tow, before I actually executed the command!
The point is that with commands like this you can push almost anything to anywhere. Very powerful.
The next challenge I had to face was applying a couple hot fixes, made locally on a development branch, to production.
My workflow included committing my specific fixes on my development branch, and then cherry picking those commits over to my (local) production master branch. For the cherry pick, I used SourceTree’s UI. Just easier, as that is a fairly common thing to do, and the UI handles it nicely. After testing locally, I pushed it easily:
<I had prod checked out at this point> % git push prod prod:master
Specifying prod:master was probably redundant, but I wanted to be specific about what I wanted from git. The command “git push prod” would have been sufficient, I think.
Git is a tool for both novices and ninjas alike. For the beginner (with git) it is easy to get started, and the basic concepts are fairly easy to grasp. For the experienced, it is extremely powerful, if unleashed.
Unleash your git ninja today.