Using GIT with SVN

Over the last two and a half years, since the day I presented on Streamlined Web Development with SVN, I’ve been hearing about the Source Control Management tool called “git.” I kept reading about it online, seeing it on the schedules for conferences, and hearing it about it in the different Open Source groups here in Utah. I have been a big time Subversion (SVN) user for several years now, and it has solved my problems

Background

I have tried to use it in the past two or three times, but each time I had basically given up. There were a few reasons why in the past I didn’t really stick with using Git:

  • I really didn’t understand the differences between Git and SVN. Most importantly, I didn’t understand the reasoning for why Git did things different, and the advantages it did.
  • I used to rely heavily on GUI based SVN tools. Over time, especially the last year or so, I’ve moved away from these GUI tools to the command line. I found it a lot easier to do merges, switches, and even commits from the command line. After being much more comfortable with the command line, it made it easier to use Git, which has far few GUI options.
  • Subversion is still a great tool with great adoption and support. While I might be willing to switch to use Git, I wasn’t looking forward to helping the rest of my team members learn how to use it. Especially those who required a GUI for Subversion. Also, all the tools we had were built on Subversion, it would be a lot of work switching everything to Git, and some things at the time didn’t interface with Git.
  • git-svn was missing a few features two years ago that really make it a viable interface with Subversion. Also, because of how versatile Git is with supporting all sorts of workflows, the tutorials for using it with SVN all seemed missing a complete picture. Even now, I had to Google and piece meal a few different tutorials to get my setup to work correctly.
  • Most arguments for Git over SVN that I heard focused on Subversion’s short comings, instead of the benefits of using Git. I was happy with Subversion, and it was working well in production, so I assumed Git users were just people who didn’t like SVN.

So, as I write this post, I’m not writing as a newly made Git fanboy, but a Subversion user who has found a lot of advantages with using Subversion and Git together. I plan on to keep using Subversion for the remote repositories with my current projects, and in the future when I deploy new repositories, I’ll consider using Git fully, or still use Subversion if it is better suited.

Newly Found Advantages

To understand the power in Git, it is a “distributed version control system” where each “Git clone is a full-fledged repository.” This is also where a lot of hangups happen with Subversions users. I kept expecting to “checkout” a “working copy.” What I needed to remind myself is I was “cloning” a version of that git or svn repository. This enables several features that aren’t really viable on Subversion alone.


Understanding Git Branches

One thing to understand about the differences between git and svn branches (and tags) is this: SVN treats them as just a foldering structure, Git manages them for you. This means Git can handle branching, tagging, and merging much more cleanly. With SVN, while I could do merging alright, I always felt like I was looking up different revision number, double checking my commands, and it felt very, very fragile. With Git, it felt like branching and merging were very solid.

When I first really saw Git branches in action, my mouth dropped somewhat. The light bulb clicked, and I saw the real advantages. Because my local git repository isn’t tightly coupled with the branches on the remote server, I can make a branch locally without having to have it on the server. Almost all of the svn repositories I work with are large, one 200MB and another 605MB, and branching them is a complete pain. Either I have the server branch them and download the branch, or branch locally and then upload the branch through committing. It was a complete pain. However, that same 605MB Subversion trunk, takes milliseconds to create a local branch in Git. Even if I want to create a remote branch, Git is smart enough to issue the SVN copy command, but branch locally without having to download the entire thing from the svn server.

So now, how often do I branch? A lot more often. How often do I merge? I a lot more often. Now if it isn’t a quick 15 minute change, I’ll branch it. That way, if I need to do any hotfixes quickly, I can switch back to my master branch without any worries.

Stashing

This would be another great Git feature is the ability to “stash” a set of uncommitted changes. So lets say I was working on my master branch. Then I need to do a hotfix, but I hadn’t made a branch. Git allows you to “stash” the change. It is like an “unoffical commit.” So you can stash a set of changes, and apply them later. You can have multiple stashes, and apply them, or just the ones you want. When you’re done with them, you can clear them all out.

These are only a handful of the things I’ve found so far, but look forward to learning more and more.

Getting Started

Warning: at this point and time I’m still learning and feeling out a workflow, so what I have below might not be 100% accurate or the best. Feedback is appreciated and welcome.

Alright, so now that I’ve explained some of the things I’ve been impressed with for Git, here are my notes for getting started from scratch. First off, if you use Windows, you can go to the git website and download the binaries. If you have Mac OS X, I recommend installing it through MacPorts. If you have Linux, then use the package manager of your distribution.

Configuration

Next, a few configurations to make your life better. Run the command:

git config -e --global

Then, you can put your settings, here are mine:

[user]
        name = Justin Carmony
        email = justin@justincarmony.com
[core]
        warnambiguousrefs = false
[merge]
        tool = opendiff
[color]
        diff = auto
        status = auto
        branch = auto
        interactive = auto
        ui = true
        pager = true

[color "branch"]
        current = yellow reverse
        local = yellow
        remote = green

[color "diff"]
        meta = yellow bold
        frag = magenta bold
        old = red bold
        new = green bold

[color "status"]
        added = yellow
        changed = green
        untracked = cyan

[alias]
        co = checkout
        ci = commit
        br = branch
        st = status
[branch]
        autosetupmerge = true

Cloning

Now, when using Git with SVN, there are a few special things you need to keep in mind when cloning, you’ll need to let Git know where to find the branches and tags. If you use the standard trunk, branches, tags layout, then you can use the –stdlayout flag. If not, you will have to declare where trunk(flag -T), branches(flag -b), and tags(flag -t) are located. So here is how you can clone your SVN repository:

git svn clone --stdlayout http://www.example.com/svn/ local_folder

Alright, now, this will take a long time especially if it is a large repository with a lot of history. Reason being is it is cloning the complete history, not just the current files. So be prepared to wait. Now, you’d think it would take up a ton of space, but Git uses compression to condense the history very well.

Once it is done, you are ready to begin. Before you even start, I highly recommend making a backup of your main folder, especially if cloning too a very long time. The reason is if you mess something up, you can just delete the entire folder, copy over your backup, and start again. This will avoid you having to re-clone and wait for an hour for 20,000 revisions to download.

Basic Committing

Now, lets first take a look at the current branches you have locally:

justin$ git branch
* master
justin$ 

Now, if you want to see all the branches, including remote branches, then add a -a flag for “all.”

justin$ git branch
* master
remotes/feature_one
remotes/feature_two
remotes/big_rewrite
justin$ 

So freature_one would be the corresponding svn location: http://www.example.com/svn/branches/feature_one

Now, a little Git 101 with SVN. In the Git world, master is what SVN calls trunk. It is the master branch. So lets do some basic committing. After making some changes, lets commit them to the local Git repository:

git commit -am "Here are some changes I made! I hope they work!"

The -a is to add any new files to the repository, and the -m is for the message (and you can combined them to -am). You can make several commits, but you haven’t pushed them to the SVN repository yet. To do this, you do the following:

git svn dcommit

And that is it. This will take each commit you made, and commit them to the svn repository. Now, you will also need to get the changes from the SVN repository. The terminology is different, but functions pretty much the same:

git svn rebase

This is pretty much the same as doing “svn update”. From what I’ve read, there is just a little bit difference, in the sense that git will get the changes from the current HEAD, apply them, and then apply and uncommitted changes.

Now, if you have any conflicts, you can use the following command to use a merge tool to resolve the conflicts:

git mergetool

If you want to check the status, you can run:

git status

Branching & Merging

Alright, now if you want to make a new branch to work off of, and you are branching locally, just execute this command.

git checkout -b new_branch_name master

This will make a branch from master. This will also switch you to using the new branch. One great thing with Git is you don’t mess with any switch commands, worrying about paths, or anything else like that. It makes developing PHP applications that much easier, since I’ll setup my environment just once.

So as you are working, you can switch between the branches easily with a:

git checkout branch_name

Now, when your code is ready to merge back into master, just do a:

git merge new_branch_name master -m "Merging my new changes"

Resolve any conflicts if you have any, and then perform a git svn dcommit.

Now, if you want to do a remote branch so you can collaborate this branch with others, you issue an svn branch. Now, you’ll want to make sure you are on branch you wish to branch from, and it is linked to the correct svn path. During all my testing and playing around, I somehow got my master branch pointing to a svn branch instead of trunk. So issue an “git svn info” and verify the “Path” is pointing to trunk. If it isn’t, then issue the command “git reset –hard remotes/trunk”.

When ready to make your new remote branch, just issue this command:

git svn branch -m "branching message" name_of_branch

Then when it is done (which normally is pretty quick), you can see see your new branch by “git branch -a” and it should be called “remotes/name_of_branch”. Now, create a local branch from that remote branch:

git branch -b local_name remotes/name_of_branch
git checkout local_name

Now you can follow the same process as you would on master: git svn rebase (svn up), git commit -am “message”, git dcommit. When you are ready to merge it back into trunk, you’ll want to do a merge like before, but this time use the “–no-ff” option. There is a great, longer explanation as to why, but here is the short answer: Fast-Forwarding (ff) is basically a Git trick to make your merge appear like it happened in time with the normal branch you are merging into. Well, there is a bug where this also can re-assign the url path of your master branch from trunk to an svn branch. So:

git checkout master
git merge --no-ff local_name

There is no way to delete the svn branch from your local Git. However, you can easily issue an svn command: “svn del http://www.example.com/svn/branches/name_of_branch”. However, afterward, even after you do a “git svn rebase”, you will still see the remotes/name_of_branch in your branch list (git branch -a). There is an explanation of why it will stay, if you want to remove it, you do it by:

git branch -r -D remotes/name_of_branch

This will remove it from your list.

Non-Traditional SVN Layouts

For one SVN repository, we have the traditional trunk, branches, and tags. However, we also have a “production” directory, which contains our current production code for the website. Our workflow is we develop against trunk, get it stable, and merge it to production. Then, all of our push scripts pull from the production folder. With Git, this folder was not showing up. There is a simple fix for this, by adding another config to your Git repository.

git config -e

You should see an entry like this:

[svn-remote "svn"]
        url = http://www.example.com/svn
        fetch = trunk:refs/remotes/trunk
        branches = branches/*:refs/remotes/*
        tags = tags/*:refs/remotes/tags/*

So what you can do is add another svn-remote entry

[svn-remote "svn"]
        url = http://www.example.com/svn
        fetch = trunk:refs/remotes/trunk
        branches = branches/*:refs/remotes/*
        tags = tags/*:refs/remotes/tags/*
[svn-remote "svnprod"]
        url = http://www.example.com/svn/production
        fetch = :refs/remotes/production

Then, to have it show up in your remote branches list, call a one time fetch:

git svn fetch svnprod

You will now see remotes/production in your “git branch -a”. Now, to get a local copy:

git branch -b production remotes/production
git checkout production

There are a few caveats. You will have to do a separate git svn rebase to do an “svn update”, since that command only updates the current svn-remote you are working on. So if I wanted to update both, I would:

git checkout master
git svn rebase
git checkout production

Reverting & Reseting (Yikes! I messed something up…)

With SVN it seemed every now and then I would bork my working copy, and I would have to try to revert. If that didn’t work, sometimes I would just delete my local copy and re-checkout.

With Git, given that cloning from SVN can take a very long time, will repeat my recommendation from before. Make a copy of your local repository every now and then, incase if you really mess things up you don’t have to clone the entire thing again, and instead just do a “git svn rebase”.

But before you have to resort to that, lets take a look at some options. There are some good articles on the topic. One is git reset which works well for uncommitted changes. Did I really mess something up and want to undo to the previous commit?

git reset --hard head

This will do a hard reset to the exact files as of the last commit. Lets say you have some local commits that are just bad, or a merge that went wrong:

git reset --hard remotes/trunk

This will let you restore to what is on remotes/trunk. I haven’t done much with “git revert” but it seems fairly easy to use.

Summary

The bottom line, is once learned, Git seems to enable myself as a developer to do more with my source code. Its interface with SVN allows me to gain almost all the benefits of Git without causing major disruption to my fellow co-workers and environments.

Thanks to a handful of UTOS (Utah Open Source) and UPHPU (Utah PHP Usergroup) people. They’ve helped fill in a few gaps I’ve had in my understanding on how Git works.

Notes

Here is a list of just some of the articles, blog posts, and web pages to help me figure everything out.

And some random Git Commands so I personally don’t forget them:

  • git log –graph –oneline –decorate – Show me my log in a scrollable version with branches shown.

Justin is currently the Director of Development for the Deseret News. He is active in the Utah Open Source community. He is an advisory member of the Utah Open Source Foundation, and helps with the anual Utah Open Source Conference. He primarily focuses on PHP, MySQL, Redis, HTML, CSS, jQuery, and JavaScript. When he gets the time, he enjoys to play jazz piano. Read More

Tagged with: , , , ,
Posted in Articles, Programming