Hanselminutes Git Podcast 0

Posted by adelcambre
on Thursday, April 10

A few of us from PLANET ARGON were interviewed for a podcast by Scott Hanselman earlier this week primarily talking about our use of Git and git-svn. Planet Argon is currently in the process of moving from Subversion to git. Most of us are using git-svn internally with our central subversion server for now but we are going to be moving to using Git for our central server soon. In preparing for the podcast I thought a lot about what makes git special. I came up with three major reasons.

Branching and Merging

This is my personal number one favorite feature about git. In subversion branching is very easy. It does a shallow copy and is very fast. But merging things back together is a major pain in the butt. You have to keep track of the revision that you branched from just so you can merge them back together.

In git all you do is create a new branch, work as long or as much as you like, then git merge new_branch to merge the changes back into master. There is no need to keep track of revision numbers and the default git merge strategy tends to only have conflicts where there are two changes to the same file in the same place.

Everybody can have their own repo

With subversion or other centralized tools, there is only one main server for each project. I can check the code out on my computer, but I am still tied to that central server for updates. I need to have commit rights to that server to publish my changes. With git, rather than just checking out the code from your repository I can just clone it to my own repo. I will probably have at least one clone on my development computer. But I can also have a complete clone on a server of my own.

I can then make all the radical changes I want and people can pull from me instead of you to get the new stuff. I can then send you an email requesting you to pull from my repo merging my new changes back into the “main” repository.

This is a feature that github really capitalizes on. The built in fork and pull request features are truly top notch.

Offline Commits

As your clone is a full repository just like any other, you can commit directly to it without the need for any central server. This is probably the feature of distributed version control system’s you have heard of the most. The proverbial commit from an airplane. Although it gets repeated a lot, this is actually a very cool feature, and not just for the offline commit.

This all gives you the ability to make commits you might not otherwise make to a central server so as to not clutter up the revision history with your playing around. You can feel free to try things out, commit them, back up to a previous commit, without the central server ever knowing about it.

Thanks again to Scott Hanselman for this opportunity to chat with him. I really enjoyed doing the interview. I think we talked about some good stuff and I am looking forward to listening to it when it gets posted.

Update: The podcast has been posted. Take a listen .

Also, I just forked rails on github!

git clone git://github.com/rails/rails.git

You can read my other articles about git here:

git reset In depth 4

Posted by adelcambre
on Wednesday, March 12

This is the second part of a series of articles on Git. This post is primarily going to focus on the git reset command and how to track multiple remote subversion branches.

First, a bit of background on git reset. In my opinion it is one of the cooler git commands that I use regularly, it’s function is a bit odd, there is no equivalent in subversion to compare to. What git reset does at a very high level is move a tag in the git graph that makes up the revision history.

So lets take the following sequence of commands as an example.


git init
touch README
git add README
git commit -a -m "initial import" 
git checkout -b new_branch
vi README
git commit -a -m "modified README" 
git checkout master
vi README
git commit -a -m "updated README" 
touch blah
git add blah
git commit -a -m "added blah" 

So we create a git repository, make a commit, branch from the commit. Make a change in the branch, and make two changes back in master. The git directed graph now looks like this:

You can see that there are two branches. Right now we are in the master branch so the current HEAD tag points to the 31281c1 commit. (This is important, git reset moves the current HEAD.)


adelcambre@hiro:/tmp/blah% cat .git/refs/heads/master
31281c194e505bf000f1d67c07b76255ac9370e9
adelcambre@hiro:/tmp/blah% cat .git/refs/heads/new_branch
0b6f5c586c257820f2ce94981f71a860107184ed

So now, lets say we want to make master follow the new_branch branch. (This is a bit contrived, bear with me.) So you use git reset --hard new_branch


adelcambre@hiro:/tmp/blah% git reset --hard new_branch
HEAD is now at 0b6f5c5... modified README

So the current HEAD in the master branch is pointing at the same commit as the new_branch branch. Let’s make a commit in each branch and see what happens.


vi README
git commit -a -m "changed README" 
git checkout new_branch
touch new_file
git add new_file
git commit -a -m "added new file" 

Which gets us this graph:

So, you basically just moved the master branch to be a branch of the new_branch branch. But what happened to those commits against the old master. Well, they aren’t reachable, so would get garbage collected if you did a repack (more on that in a later edition). But for now, they are still there, just not reachable from a tag. We happen to know the commit-id of the old master branch, but if you didn’t, you could use git lost-found.


adelcambre@hiro:/tmp/blah% git lost-found
[31281c194e505bf000f1d67c07b76255ac9370e9] added blah

So it found the old master branch! Let’s merge our current master back into the new_branch, and move master back to the old master.


git checkout new_branch
git merge master
git checkout master
git reset --hard 31281c1

Which results in the graph looking like:

So you can see that we recovered the unreachable commit, and merged back the changes we made on the master branch while it followed the new_branch.

Now, there are no unreachable commits, git lost-found doesn’t return anything, and we are good to go.

git reset options

There are three main options to use with git reset: --hard, --soft and --mixed. These affect what get’s reset in addition to the HEAD pointer when you reset.

First, --hard resets everything. Your current directory would be exactly as it would if you had been following that branch all along. The working directory and the index are changed to that commit. This is the version that I use most often. This is what we used in the above examples. It just says make the current HEAD and working directory exactly like commit “x”.

Next, the complete opposite,—soft, does not reset the working tree nor the index. It only moves the HEAD pointer. This leaves your current state with any changes different than the commit you are switching to in place in your directory, and “staged” for committing. I use this for only every once in a while, and mostly for correcting a commit message. If you make a commit locally but haven’t pushed the commit to the git server or subversion server, you can reset to the previous commit, and recommit with a good commit message. This would look something like:


touch test
git add test
git commit -m "bad commit" 
git reset --soft HEAD^
git commit -m "good commit" 

So, because git reset --soft doesn’t reset the index nor the working tree, you can just re-commit without having to add anything.

Finally, --mixed resets the index, but not the working tree. So the changes are all still there, but are “unstaged” and would need to be git add‘ed or git commit -a. I use this sometimes if I committed more than I meant to with git commit -a, I can back out the commit with git reset --mixed, add the things that I want to commit and just commit those.

The place that I really use git reset a fair amount is with remote branches. If I have a branch that I want to track a specific remote subversion branch, I can simply git reset --hard svn_branch_name and then git svn does the right thing. I have seen issues where for some reason the git master branch ended up following a subversion tag rather than trunk. A quick git reset --hard trunk cleaned everything up.

I really started liking this command once I realized what was happening. You really need to be aware of the nature of git’s directed graph to take full advantage of git reset, but once you do, you are really able to exploit git quite a lot further.

This is part of a series on git, other articles in the series are:
  • Git SVN
  • git rebase
  • git log
  • git stash
  • git merge
  • git mergetool

Do you have any git reset success stories? Horror stories? Lost work? Saved work? Let me know in the comments.

Update: Changed the terminology to only refer to the current branch as “HEAD”, per comment from Bob.

Git SVN Workflow 7

Posted by adelcambre
on Tuesday, March 04

I know, I know, this is blog post number one million about Git and git-svn. This is primarily for my co-workers who are wanting to switch to git for our SVN based projects like I have. (If I get them with git-svn, next we can start hosting with git directly, Muhahaha!)

First, a bit of background. Git was written by Linus Torvalds specifically for use by the linux kernel team. He had very specific ideas about how the version control system should work, and none of the offerings at that time satisfied his needs. Git is designed to be distributed (every clone is a full fledged git repository) and very very fast. It is also designed to be very easy not only to branch, but also to merge.

Disclaimer: This is not intended to be the “correct” way to use git + svn, only the way that I use it. That said, here we go.

First, we assume that you have a subversion repository at http://example.com/svn/my_proj. You will need to create the git repo setup to pull from this repo.


git svn init -s http://example.com/svn/my_proj

This will initialize the git repo for pulling from svn. The -s indicates that you have a “standard” setup for your subversion repository. I.e. trunk/ branches/ and tags/. This command will not yet import anything from subversion.


git svn fetch

This command will fetch all revisions from subversion that you have not yet received. The first time you run this could take quite a while. There are options to only fetch some of the revisions, but I prefer to fetch the whole history the first time. This way blame and log show the whole thing.

Now you will have both the whole revision history for trunk, but also all of the branches on the svn server. You can see all of the branches with

git branch -a

This will show all branches, including the remote branches.

Now that you have the entire subversion history stored locally, it can be useful to repack the repository. Right now, each revision has it’s own file, this command will pack those into bigger “pack” files. This will make the repository smaller, but also with many many fewer files


git repack -d

Next you will want to do some actual work on the repository. It is not recommended to make changes in your master branch, so lets make a branch for the new feature.


git checkout -b new_feature

This will make a new branch (the -b) and switch to it (checkout). Once you have your new branch you make a bunch of changes and add a new file or two. You need to add any new files so git knows to track them. You can do this with:


git add path/to/new_file

Now, once you are happy with this new feature and have it tested. You can commit it to git. This is one place where git diverges from subversion. Files you have changed aren’t automatically staged for committing. So right now if you try to commit, you will commit the file that you added in the above command, but nothing you changed (adding a file stages it). So you have two options at this point, you can manually stage each file you want to commit, you will want to do it this way if you don’t want to commit all the changes you have made. That will look something like


git add path/to/edited_file
git add another/edited/file
git commit -m "commit message" 

If you want to commit all of the changes you have made, and you have added all of the new files, you can automatically commit all changes, no need to stage. This is how I normally do things. That looks like:


git commit -a -m "commit message" 

The -a tells git to commit all staged and unstaged changes.

So now you have these commits in git but they have not been pushed to subversion yet. You will need to get this commit into master first. You need to checkout the master repo, then merge back with the new feature.


git checkout master
git merge new_feature

It is likely this merge will work without any conflicts, but if not you will need to fix the conflicts then commit.

Once you have the commit back in the master repo, you will need to resync the master branch to the svn repo to make sure you don’t commit conflicts.


git svn rebase

This will rebase the master branch to the subversion trunk. Next, you need to push the commit to subversion.


git svn dcommit

That’s it! That is the basic circle between an initial checkout back to a commit. I will be going through the different git commands in the next few weeks to go over how they work in more detail. Here are the things I am planning on covering:

  • git reset
  • git rebase
  • git log
  • git stash
  • git merge
  • git mergetool

Did I miss anything major? Let me know how you use git with svn in the comments.