Saturday, April 17, 2010

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 though...you 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.

Wednesday, January 27, 2010

Fixing IE onChange event for checkboxes and radio buttons

In Internet Explorer (up to at least IE8) clicking a radio button or checkbox to change its value does not actually trigger the onChange event until the the input loses focus. On every other browser, when the value of the input changes, onChange is triggered.

This is quite annoying, for example, if you want to trigger some other page changes off of the selection of a radio button in a radio button group changing. Some may suggest using onClick instead, and while that may be sufficient for a checkbox, for radio buttons, this is not a good solution. The problem is that onClick will fire when you click the radio button that is already selected, so you'll get your code triggered even when the value doesn't change.

Here's a quick hack that worked for me. On IE, handle the onClick event of radio and checkboxes and have it blur and re-focus the input element. This will trigger it to signal the onChange event like you'd expect from a decent browser. Then, you can go about your business using the onChange event like you should, and it will work correctly for you across the popular browsers. (WARNING: Of course, if you have other code triggered off of the blur and focus events, your mileage may vary.)

Here's an example of how you might do this using jQuery:


if ($.browser.msie) {
$(function() {
$('input:radio, input:checkbox').click(function() {
this.blur();
this.focus();
});
});
}