Showing posts with label mercurial. Show all posts
Showing posts with label mercurial. Show all posts

Thursday, June 26, 2008

Mercurial Push from IntelliJ

I've been using IntelliJ on a recent project because of its Groovy and Mercurial support. The Mercurial support worked quite well except for pushing changes to a remote repository over ssh. A quick look at the Version Control Console revealed that things were getting hung up on: remote: ssh_askpass: exec(/usr/libexec/ssh-askpass): No such file or directory. After confirming that there was no ssh-askpass on my Mac OS X Leopard system, I turned to Google. After a few misses, I stumbled across Joe Mocker's blog post about VNC tunneled through SSH on OS X. Embedded in that post is this shell/AppleScript:

#! /bin/sh

#
# An SSH_ASKPASS command for MacOS X
#
# Author: Joseph Mocker, Sun Microsystems

#
# To use this script:
# setenv SSH_ASKPASS "macos-askpass"
# setenv DISPLAY ":0"
#

TITLE=${MACOS_ASKPASS_TITLE:-"SSH"}

DIALOG="display dialog \"$@\" default answer \"\" with title \"$TITLE\""
DIALOG="$DIALOG with icon caution with hidden answer"

result=`osascript -e 'tell application "Finder"' -e "activate" \
-e "$DIALOG" -e 'end tell'`

if [ "$result" = "" ]; then
exit 1
else
echo "$result" | sed -e 's/^text returned://' -e 's/, button returned:.*$//'
exit 0
fi


I dropped this code into a script at /usr/libexec/ssh-askpass and now when I push from IntelliJ:

Ugly, but it works. Now I just wish that the IntelliJ Mercurial plugin would consult the .hg/hgrc file for the remote repository or at least remember the value I type in when I pushed the last time, so I don't have to type in some long ssh://user@host.org/path/to/the/repo every time.

Monday, March 10, 2008

Simile Timeline

Over the last week or so, I've been working on a webapp for managing expedition sample requests. One of the bigger challenges has been to find an effective way to visualize the sample requests. Traditional histograms and line graphs are an option, but they offer limited interactivity. I was hoping to find something more "snazzy" to let the users really explore the data.

I mentioned this in passing to my friend Doug Fils and he pointed me to Simile Timeline. Timeline is something I had seen in passing before but hadn't considered for this particular project. Timeline provides a Google Maps-type interface for exploring time-based data. I really liked the interface, but needed a way to put depth on the scale instead of time. It turns out I wasn't the only one looking for different units.

After chiming in on enhancement request, the original reporter, Charlie Kershner, sent me an email with the solution he'd come up with: use years, decades, centuries, and millennia as proxies for metric depth units. The conversion factors are all right, it's just the units that are wrong. This is a really clever solution, so I was glad Charlie passed it along. Using it, I had a working demo in about an hour.

I decided to poke around in the Timeline code to see what it would take to properly support depths. It is a really coherently designed tool with well defined interfaces for extension and customization. I can't claim to be fully understand everything, but with a little monkey see, monkey do, and a bit of brute force, I got proper depth support:


This is the default rendering. I think for my application, I'll end up customizing the event painters. I need to find a way to simplify the rendering, especially in busy sections like this. There's also a fair number of errors in the data that I need to go back and fix. Being able to visualize it in this form makes it really easy to see the errors (Hint: those blue bars at the top and bottom of the main band represent typos in the data. There were no multi-meter samples...)

I don't have a live demo for people to play with because I'm working with "live" data that isn't public. However, there are numerous examples out at the Timeline site. There were a few gotchas I ran into while developing and testing things:
  1. You need to serve your own copy of Timeline because of cross-domain issues with mixing code from the Simile site and from a local extension.
  2. You need to tell Timeline to use your new unit in two different places--when you create the event source and when you actually create the timeline. Failure to do so will have you pulling out your hair chasing down errors.
  3. You need to use your customized methods to create your bands
  4. Not all of the rendering options (like showEventText: false) seem to work. I think this is because I don't fully understand the rendering pipeline and how it is customized. This is one area I'm going to be looking at in the future.
Here's an example of the Javascript to create the the example above. Note the bolded areas that were mentioned in #2 and #3:

var tl;
function onLoad() {
var eventSource = new Timeline.DefaultEventSource(new SimileAjax.EventIndex(Timeline.DepthUnit));

var theme = Timeline.ClassicTheme.create();
theme.event.bubble.width = 320;
theme.event.bubble.height = 220;
var d = Timeline.DepthUnit.wrapDepth(0)
var bandInfos = [
Timeline.Depth.createBandInfo({
eventSource: eventSource,
date: d,
width: "80%",
intervalUnit: Timeline.DepthUnit.METER,
intervalPixels: 100
}),
Timeline.Depth.createBandInfo({
eventSource: eventSource,
date: d,
width: "20%",
intervalUnit: Timeline.DepthUnit.DECAMETER,
intervalPixels: 25,
overview: true,
convertToBaseUnit: true
})
];
bandInfos[1].syncWith = 0;
bandInfos[1].highlight = true;

tl = Timeline.create(document.getElementById("tl"), bandInfos, Timeline.HORIZONTAL, Timeline.DepthUnit);
tl.loadJSON("samples.js", function(json, url) {
eventSource.loadJSON(json, url);
});
}
var resizeTimerID = null;
function onResize() {
if (resizeTimerID == null) {
resizeTimerID = window.setTimeout(function() {
resizeTimerID = null;
tl.layout();
}, 500);
}
}

The code for the depth extension is available in a Mercurial repository where you can grab a zip of the most recent version. Feel free to use it however you like. I've offered to contribute the code to the main Timeline repository so future versions may not require you to host your own Timeline.

Wednesday, March 05, 2008

Automatic change tracking with cron and Mercurial

A while back I had to setup a system for sharing ANDRILL's expedition data among a geographically distributed group of scientists. For simplicities sake, I settled on serving the data via HTTP (for read only access) and WebDAV (for read/write access). It's not the most robust system, but it works, is relatively well supported across modern operating systems, and doesn't require users use some special software.

The only problem is that allowing read/write access to a large group of people means that something is going to get inadvertently changed or deleted. To combat this, I needed an automatic way to track changes that would allow me to revert or rollback any accidents.

The first step was to put the expedition data into a standard version control system. My VCS of choice is Mercurial, so I opted to use that. The initial import took a bit of time because there was about 30GB of files.

Once I had all of the data safely imported into a Mercurial repository, I needed a way to automatically detect and commit changes. I worked up a fairly simple shell script that gets run periodically by cron to check for and commit any changes:

#!/bin/sh

REPOS="/home/projects/sms/"
DATE=`date`

for r in $REPOS
do
echo "Working on $r"

# clean up temporary files
find $r -type f -name "._*" \! -wholename "*.hg*" -exec rm {} \;

# find changes
hg addremove -R $r
hg commit -m "Automated commit @ $DATE" -R $r
done


The script is relatively straightforward. It will walk through a list of repositories (in this case, only 1 is configured). Fore each repository, it first cleans any dangling WebDAV lock files outside of the Mercurial metadata directory. Then it runs hg addremove to detect any added or deleted files. Finally it commits the changes with an automated commit message containing the current date and time.

The script is configured to run periodically (hourly) by cron. This has the side effect that if the same file is modified two separate times within the same hour window, the intermediate changes won't be caught. For my needs, this wasn't a big deal. You could always run the script more often to have a better chance of catching more changes.

The final piece of the puzzle was to send out a daily email to the science team to notify them of the changes. For this, I developed another script that is run once a day:

#!/bin/sh

REPOS="/home/projects/sms/"
DATE=`date`

for r in $REPOS
do
LOG=`hg log -d -1 --template '{rev}\n' -R $r`
if [ -z "$LOG" ]; then
echo "No changes"
else
echo -e "The following files have changed in the last 24 hours:\n" > /tmp/hg-tc-daily.log
for c in $LOG
do
hg log --rev $c --template 'Changeset {rev} ({date|isodate}):\n' -R $r >> /tmp/hg-tc-daily.log
hg status --rev `expr $c - 1`:$c -R $r >> /tmp/hg-tc-daily.log
echo "" >> /tmp/hg-tc-daily.log
done
cat /home/projects/hg-tc/summary.legend >> /tmp/hg-tc-daily.log
cat /tmp/hg-tc-daily.log | mail -s "Expedition Data Changes" foo@bar.com
fi
done


If you can grok that in a single glance, you're a better shell scripter than me. It took me a couple passes to write the script and get it working the way I wanted it to. There's a bit of magic going on here, but I'll break it down.

This script walks through a set of repositories like in the previous script. The first magical incantation we run into is:
LOG=`hg log -d -1 --template '{rev}\n' -R $r`

This line asks Mercurial to tell us the revision numbers for all changes in the last day (-d -1) and stores them, one to a line, in the LOG variable. We then check to see whether the LOG variable contains anything. If it doesn't, then we're done and can exit.

If the LOG variable does contain something, then we need to send out an email to notify people of the changes. We start building up our email in /tmp/hg-tc-log. Next we walk through our list of revision numbers, and for each one we include some information using this incantation:

hg log --rev $c --template 'Changeset {rev} ({date|isodate}):\n' -R $r >> /tmp/hg-tc-daily.log
hg status --rev `expr $c - 1`:$c -R $r >> /tmp/hg-tc-daily.log


The first line simply prints out "Changeset 53 (2008-03-04 15:01 -0600):" or something similar for every changeset that occurred in the last 24 hours. The second line prints out the list of changed files and the change type (added, deleted, modified) from the previous previous revision (--rev `expr $c - 1`:$c).

Finally the script adds a helpful legend to the email and sends it out.

Overall, it's a bit hacky, but it works. The team can continue to collaborate in a central location with the relative safety of using a VCS to track changes. The best part is they don't have to do anything different; the change tracking is all automatic. I've even had the occasion to revert inadvertent changes and deletions, so it was a good investment of an afternoon.