Thursday Mar 18, 2010

Java Posse Roundup

I've spending the week in beautiful Crested Butte, Colorado at the Java Posse Roundup. It's the fourth year in a row, with record attendance. And as always, it's been fantastic. It's hard to explain an Open Space conference - it doesn't sound like it would work, and I was personally surprised to see how well it worked when I attended the first time. Now I simply expect it, and it always delivers. One key reason why this open space conference works so well compared to say "unconference" events attached to major face-forward conferences is that you need to be away from everything and really immerse yourself - and here up in the Rockies we certainly are isolated!

The day before the conference started we had a "Languages Dojo" day where people pick different languages they're interested and go off building something. We had planned in advance to build a JavaFX version of the "Ohm Writer", a Zen-like text editor that is full screen, has nice relaxing background music, background sounds and typing sounds. We had a great time, and made a lot of progress. Unfortunately, we spent a lot of the day fighting with git (the version control system). I'm a very happy Mercurial user, but I've had a little bit of git-envy since I know it can combine local changesets into a single changeset to be pushed to the repository. I could see myself using that a lot. And setting things up on github for collaboration was very easy. But that's where the fun ended - nothing worked, simple merges aborted, error messages were completely unhelpful, and in general we repeatedly ended up checking out new clean workspaces and hand applying changes. I liked Mercurial before but now I appreciate it even more. (I hear the guys who were doing functional programming also were ripping their hair out with git. P.S. Joel Spolsky just posted a Mercurial tutorial).

The app is functional, and more importantly we made the editor start up immediately, load images and audio in the background and gradually fade in the image as soon as it's available. We also had difficulty playing the keyboard "click" sound until we realized you don't want to just repeatedly call play() on a media player -- you have to reset it -- either setting mediaPlayer.currentTime = 0s or calling mediaPlayer.playFromStart().

We also ran into another bug -- and this is a gotcha I've seen before, so it seems useful to highlight it here: For JavaFX Strings, null and "" are the same! Therefore, you don't want to write code like this:

while ((line = reader.readLine()) != null) {
// use line

because this will terminate the loop as soon as you reach an empty line!! Be very careful about checking for nulls explicitly when dealing with Strings.

We've also had Lightning Talks in the evenings. If you're not familiar with Lightning Talks, these are very quick presentations, one after the other, on any subject, but limited to 5 minutes. Yes, with a HARD 5 minute limit. The advantage of limiting the lightning talks to 5 minutes is that it forces the presenter to really focus on the interesting parts of the subject, and if it's really not interesting, at least you're only bored for a couple of minutes! The topics this year were really great though - there was even a fire-eating demonstration!
One way to keep track of the time is to use a countdown timer. We wrote one in JavaFX last year, which displays a classic countdown timer (imported Photoshop graphics) and a sound file playing as the timer is expiring. It's available as a webstart app if you want to run it for your own lightning talks, or you can study the code.

In addition to the morning open space conference sessions, the evening lightning talks, the progressive dinners, we've had some afternoon technical presentations and coding sessions -- check out our improvised projector screen in our living room:

Friday Oct 31, 2008

Mercurial Tip: Checking in regularly with uncommitted changes in your clone

NetBeans, like OpenJDK and OpenSolaris, uses the Mercurial distributed version control system. I'm a big fan of of distributed version control. However, one thing that drives me nuts is this error message:

% hg merge
abort: outstanding uncommitted changes

This isn't just going to be a rant - I've finally found a solution which is working extremely well for me. I've suggested it to some other developers who have also reported that it works well for them, so I thought I would share it with you.

The reason I run into this all the time is my preferred style of work:

  • I like to work on many things simultaneously. If while working on something, I come across a bug and I spot the problem, I just fix it right there and add a test for it. When I get ideas, I might go and put @todo tasks for myself, or leave editorial comments in various source files. I might also work on a couple of larger tasks simultaneously. Yes, I know the preferred Mercurial idiom for this is to have multiple clones, one for each task - but that isn't my preferred way of doing it. Each NetBeans clone is huge and takes a while to both clone and build - and I would have to switch my IDE editing context between these large source trees all the time.

  • I like to check things in in logical chunks with a message that pertains to that particular change. This means I often check in a subset of the edited files in the tree.

  • I like to check in code regularly rather than leaving all files edited until I'm ready to check everything in.

  • I like to keep up to date. I'm using the bits I'm working on and I report problems when I see them so I want to make sure these are truly new and current bugs. Therefore, I update to the tip at least every day.

It turns out that the above requirements are tricky to fulfill with a default setup. Here's what happens.

  • I'm working on something and a high priority bug comes in. I find and fix it. I check in just the fix for this one problem. I
    still have many other edited files in my clone since I was in the middle of something else.

  • Since it's a high priority bug, I want to push it. However, when I do, I get this:

    % hg push
    pushing to https://tor:\*\*\*
    searching for changes
    abort: push creates new remote heads!
    (did you forget to merge? use push -f to force)

    This tells me that somebody has checked in something since my last pull. That happens a lot with a project like NetBeans
    where there are checkins every few minutes.

  • No problem - I'll just pull down their changes, merge, and push again, right?

    % hg pull && hg update
    pulling from
    searching for changes
    adding changesets
    adding manifests
    adding file changes
    added 1 changesets with 1 changes to 1 files (+1 heads)
    (run 'hg heads' to see heads, 'hg merge' to merge)
    abort: crosses branches (use 'hg merge' or 'hg update -C')

  • Ah, I couldn't just update, I have to merge since I have checked in changesets of my own. Let's do that:

    % hg merge
    abort: outstanding uncommitted changes

    !@#@%\*#%$%&\*$ ! Now, if I have dozens of changes, what do I do? When I first started using Mercurial, I would either
    try to finish my other changes and check them in and then try the merge again, or if I really wasn't ready to do that,
    I would copy the contents of my modified files to another location, hg revert the changes, and try again.

  • It's not unreasonable of Mercurial to insist that you cannot merge with uncommitted changes. When it's merging, it will think
    file modifications in the clone are your manual edits to resolve any merge conflicts. However, it does make using
    my workflow difficult.

    The Solution

    It's really very simple: Use two clones. Do all your work in clone "main", and all your merging and pushing from clone "sync".
    Let's say the main repository is "master", and your two clones are "main" and "sync":

    To get set up, either clone the master repository twice, or, you can clone your main locally, but be sure to update
    the "parent" reference in your sync clone such that it points to the master instead of your working clone
    (I do that by copying main/.hg/hgrc to sync/.hg/hgrc.)

    Here's the new workflow:

    1. As before, work in main. Edit files at will, and check in logical changesets as needed.

    2. When you want to push a changeset, go to your sync clone, and pull your new changesets from your working clone:

      % cd ../sync
      % hg pull ../main && hg update

      At this point, it's best to try a build/test as well, to make sure changeset is complete. If you're working
      on many things in parallel, it's possible that your changeset is depending on a change in a file you haven't
      checked in yet.

    3. Now pull in the new changes from the master, and merge these:

      % hg fetch

      (Or if don't have the fetch extension installed, do it manually - hg pull && hg merge && hg ci -m "Merge").

    4. If something went wrong with the merge - no problem. You can just go and nuke the entire sync clone
      and try again! Your modified files, and your new changesets, are still sitting completely unaffected in the main
      clone. Just clone sync again and try more carefully :)

    5. Now you can push your merged changesets to the master repository:

      % hg push

    6. ...and now you can pull these changes back into your main repository:

      % hg pull ../sync && hg update

      This will give you all the tip changes into your working clone, but without the risk of causing multiple heads that you
      have to merge. You've already merged your local changesets over in the sync clone, and therefore there is no
      conflict between your local changesets and the new changesets in the tip!

    This process may seem tricky, but it's trivial once you try it:

    Work in main, push and merge in sync, and pull back into main.

    Finally, I want to call attention to item #4. Doing it this way means that it's trivial to try again if something wrong happens
    during the merge. I've had a couple of merges where I've really mucked things up. Unfortunately, this was in my tree that contained
    the changesets that I cared about. In the end I had to go and manually copy out the files I wanted to check in and try again. With
    the above approach, if something goes wrong, just nuke the sync clone and try again.

    This is the reason I'm suggesting this approach to anyone using Mercurial, not just people who want to work with edited files.
    Especially when you're new to distributed version control systems or Mercurial, it's great to be able to go back if you make
    a mistake. Just make sure you know what you're doing before you submit that final hg push command to push everything
    back to the master repository!


Tor Norbye


« July 2016