Thursday, April 22, 2010

Find the SHA1 of the latest commit in a git branch

Here's a way to get the SHA1 of the last commit in a given branch in a way that can be used in a script easily. For humans `git log branchname` is probably fine - just read the first line. For a shell script that wants to get that value into a variable, here's a quick way to do that:

Seems like there ought to be an easier way...but not that I could find in less time than it took to write this command.

Saturday, April 17, 2010

GitHub Redemption

Moments after posting that last blog post about full directory compare of a git branch, I found out GitHub supports this already!

My post didn't explicitly call out GitHub, because I really think of this mostly as a local working tree issue for me. But, as I went to tweet about it, I realized I would probably be happy enough if GitHub supported something like this, so I called them out in the tweet...and they answered!

Many thanks to Tom Preston-Werner (@mojombo) for correcting me, and pointing out probably the one link on GitHub I have never clicked: The "Branch List" sub-tab:

I guess I just assumed that it was going to be just a list of all the branches. You know, like `git branch -a`. So I never clicked on it. Stupid. It turns out it's quite useful.

The stats are pretty cool, but the "Compare" button is what I've been looking for. Clicking that takes you to a page that shows the full directory diff between the head of the branch relative to the "base branch", which in this case is "master". Better yet, it gives you three optio
ns on how to view this diff:

You can see the diffs as a series of commits, the overall file changes (the one I was pining for), or the set of commit comments. This is great. Other than the fact that it doesn't cover
un-pushed commits and uncommitted changes in my local clone, and the fact that I really prefer side-by-side diff format ala SmartGit or Trac (but that's a story for another day), this is pretty much exactly what I wanted -- and if I had known about this 8 hours ago, I probably wouldn't have built the stuff I did.

But it gets even better. These buttons that show the two branches being compared is clickable.
Clicking one of these buttons bring up a modal that allows you to specify a specific commit. This lets you do a full tree diff between any two commits. Awesome!

In the end, I still think my solution is useful for the case where you want to deal with branches/commits that have not been pushed to GitHub, or you are working in a repository that is not on GitHub. (But who in their right mind does that, right?)

But, as soon as SmartGit covers this territory, I see no need for my solution...unless you don't use SmartGit (even though you should).

See a visual diff of work done in a git branch with git-branch-diff

I have developed a Ruby script called git-branch-diff that allows you to use a graphical directory compare tool to compare the working tree of a branch with the state of the tree from which it was originally branched. For download, setup, and usage instructons, see:
Background - My Workflow
I don't know about you, but a big part of my development workflow involves constantly diff'ing the changes I have made and am in the progress of making, as a way to constantly keep in touch with the state of what I am working on, and even as a reminder of what I am supposed to be doing. I like to write notes in contextual comments that I never intend to push out to the master branch, to help remind me of TODOs that need to be done before calling a feature sprint done.

In my world of constant multi-tasking and task-switching, I frequently have numerous topic branches in progress on the same repository. I like to end the day with no uncommitted changes, and even no un-pushed changes (i.e.: let github handle backups). So, after a task-switch, it's an integral part of my workflow to be able to see a good diff of the cumulative work I've done in a branch that hasn't been merged back in the master yet. This is usually pretty easy when we're talking about uncommitted changes. But, when I want to see the set of changes in that branch that would be merged if I were to do it now, things get a little more difficult.

The Problem
Git is a powerful tool with a crazy number of features. I probably only know 1% of them...and everywhere I searched, I could not find a way to do a visual directory compare of what has changed in my branch. Perhaps I've been spoiled by a great line of tools from syntevo GmbH that I've been using for years. I started with SmartCVS, then moved on to SmartSVN, and now to SmartGit. I am, always have been, and always will be a command-line guy. I'll admit that most of my cvs/svn/git usage happens from the keyboard. But I have come to rely on these GUI tools as an excellent addition to the command-line when it comes to visualizing my working tree status, performing directory and file diffs, editing line-by-line diffs, 3-way-merge conflict resolution, and easy selective add/commit/revert operations. Over the years I have come to rely on them as a key part of my routine - especially the directory diff view of all the changes in my working tree.

SmartGit has that capability for comparing the index, HEAD, and working tree, but not (yet) for arbitrary commits...or what I want most: comparing the HEAD of a topic-branch to the commit from which it was branched. The folks at syntevo assure me this will be included in a forthcoming point release, but what am I to do in the meantime?

Attempted Solution
I can get the data I want with a command like:
git checkout my_topic_branch
git diff `git merge-base master HEAD`
That shows me exactly the diffs I am interested in, but in a primitive textual (albeit colorized) context diff that I have to navigate with less.

Well, the folks at syntevo have another awesome tool called SmartSynchronize. This is their directory/file diff/sync/merge tool from their source control products as a standalone diff tool. So...why not just pump this diff data into that? To do that, you need to edit your ~/.gitconfig file to specify the custom difftool. For example, on Mac OS X, these additions look like:

The problem with this is that git invokes the difftool once per file being diff'ed. That's no good. I don't want to open my external visual diff tool 100 times. I want to open it once with a list of all the files added/modified/removed and be able to click on them individually to see their diffs, and ideally even be able to edit, synchronize, revert, etc. SmartSynchronize gives me all this...but git doesn't give it two directories to compare, it gives it two files to compare, one file at a time.

The Solution
So, I developed a tool called git-branch-diff that does all this for me. It works by finding the names and statuses of the differing files and constructing two temporary directories that match the original directory structure with the old and new versions, respectively. Then it passes those directories to SmartSynchronize, and voila! I've got a beautiful directory compare that represents the state of my topic-branch:

It only takes a few seconds to download and setup, and using it is as simple as:
git checkout my_topic_branch
git branch-diff
This script would be pretty easy to modify to handle comparing arbitrary commits...but once syntevo adds this to SmartGit, I won't need this anymore anyway! But, for those of you that won't be using SmartGit, this script can still be useful to you, and you can hook it up to whatever directory compare tool you want (e.g.: opendiff or BeyondCompare).

If you think this tool would be a valuable addition to your workflow, you can get the code and instructions for this tool at:
It can easily plug in any directory compare tool, but I highly recommend you give SmartSynchronize a spin with its 30-day free trial.

NOTE: One current limitation in this script is that after making and saving modifications in SmartSynchronize, this script does not apply those back to your working tree. I'll probably implement that soon, so if you're interested, stay tuned...

How to detect what other sites your visitors have been to

When someone visits your site, do you want to know where else they've been? Maybe you want to know if they've been to a competitor or affiliate site so you can make them special offers. Maybe you want to intelligently suggest they signup for your site with Facebook Connect, Twitter, Google OpenID, or Yahoo! ID, based on which of those sites you know they frequent. Maybe you're just nosy.

Regardless, there are pretty good controls in place with most browser to prevent you from developing javascript on your site that can sniff the user's browser history. There's one small loophole can stick all the URLs you want to know about in your page, and test to see if they have been visited, using the CSS selector features of javascript frameworks such as jQuery and Prototype.js. This is not a fool-proof way to tell where they've been, but it might just be good enough for the kinds of suggestions you want to make to your user.

Based on ideas from Spyjax (described in this blog post), I whipped up a simpler demonstration of this using Haml to make a hidden list of URLs and, instead of trying to sniff links by their colors, using Prototype.js to detect which of them have been visited. This could have just as easily been done in jQuery:

One extension to this technique, if you have lots of URLs to test, might be to dynamically load 100 at a time via AJAX periodically, and asynchronously trigger actions in your page when visited URLs are detected.

Isn't that a little creepy? What if I don't want sites to get this info about me?

The Spyjax blog post linked to earlier has a decent answer for this:

The less browser history you store the fewer URLs someone can steal from that history. In Firefox you can change the amount of browser history by going to Tools -> Options -> Privacy and then either uncheck the “Remember visited pages” checkbox or change the number of days that history is stored for.