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.
- Git Crash Course for SVN Users – This is a wiki page on the basics of Git. Doesn’t cover itegration with SVN Remote Repositories however.
- Learning git-svn in 5 min – Great short article on getting started with git-svn
- Git SVN Tutorial – A good, basic tutorial on using a Git workflow with SVN.
- Git: how my life has improved since last month when I used SVN – Another blog post outlining how to use Git from and SVN user standpoint.
- How to Convert a Local Branch in Git to a Remote SVN Branch – A walkthrough on how to share a local branch to a remote branch on SVN.
- Git Tutorial : Starting with git using just 10 commands – A great intro to some useful commands, such as git log, git status, git diff, gitk, and has another post with even more useful commands.
- Git Community Book – Great for clear instructions on how to do beginner and intermediate things. This is where I figured out how to use the stash command.
- Pro Git – Another excellent book about using Git, free online and available for print as well. Very clear and easy to understand. Has a good chapter on working with Git & Subversion
- Git Man Page – Good luck. Its good after you know how Git works and need to read up on some details.
- Git Cheet Sheet – Great quick summary of common tasks in Git.
- Git SVN Tutorial – While this is for the parrot project, it does have a good example of a Git/SVN Workflow
- Five advanced Git merge techniques – Five more advanced tricks with Git and merging, not sure if I’ll actually use all of them.
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.
Welcome to the git club man! Thought I’d give you a couple useful tips too when working with local changes before they’re pushed to the repository.
If you’ve already committed your local changes on your master branch, then you obviously cannot stash those changes to push a hotfix without pushing those commits along with the hotfix.
I’m sure there are numerous options, but a couple spring to mind in my usual workflow:
1) Remove the commits and apply the changes to your working directory.
$ git reset HEAD^ # pop off the last commit (use ~2 ~3 etc for more than just one) and apply changes
NOTE: this is the same as using the –mixed option, which is the default option
$ git stash # stash your changes .. ready to work on hotfix
2) Retain the commits, but move them to a new local branch
$ git branch new_local_branch HEAD # creates branch pointing to the latest commits on master
$ git reset –hard HEAD^ # remove previous commit and don’t apply changes
Now you can switch to new_branch and you’ll see your original commits are retained in the local branch. if you wish to merge in your hotfix into that branch later after committing to master, you can issue either a merge or rebase depending on your preferences:
$ git checkout new_branch
$ git rebase master # pop off commits on this branch.. apply hotfix from master.. replay commits on again on top
OR
$ git merge master (if either branch has diverged, you’ll get a merge commit, otherwise a fast forward, which will be similar to the rebase option).
Another tip is amending a previous commit that you haven’t pushed to the repository yet, such as if you forgot an image that’s required for the new feature branch.
simply add the file to your index and issue:
$ git commit –amend
instead of the typical git commit. This will open your default editor to update the commit message and will merge any of the changes you added into the previous commit.
There are ways to undo or split out commits at a later time as well, but that’s another story.
Finally, interactive index staging:
$ git add -p
This will show you chunks of files that have changed that are currently being tracked and let you choose to include that change into the staging index to be committed. This effectively lets you commit parts of a file that have been changed and decline other parts. Pretty handy. NOTE: this won’t ask you about new files, only files that have changed that are being tracked.
If you’d like a quick overview, I’d recommend looking at Pro Go (http://progit.org/book/) if you haven’t already.
LikeLike
Ah I missed your notes section. I’d recommend ProGit. Good selection of references thanks.
LikeLike