Setting the git commit author to pair programmers' names

Are you using git? Are you writing all your code that needs to be maintained in pairs? Great. Your commits probably look something like this:

commit a5ab4d39be608280fbea6ba9710aea4593f20bc6
Author: Bryan Helmkamp <bryan@brynary.com>
Date:   Fri Aug 29 18:47:29 2008 -0400

    Some important refactoring LM/BH

Subversion has limited support for tracking authors, so we used to manually add the initials of the programmers working on a changeset for future reference. With git, however, we can specify arbitrary author names for each commit. Now our commits look like this:

commit a5ab4d39be608280fbea6ba9710aea4593f20bc6
Author: Luke Melia and Bryan Helmkamp <developers@weplay.com>
Date:   Fri Aug 29 18:47:29 2008 -0400

    Some important refactoring

To simplify updating the git author configuration as we switch pairs, I whipped up a quick Ruby shell script called pair. Called with a list of developers' initials, it configures the git author name and email fields. Called with no arguments, it unsets the per-project git author configuration so your global settings take effect.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#!/usr/bin/env ruby

# Configures the git author to a list of developers when pair programming
# 
# Usage: pair lm bh (Sets the author to 'Luke Melia and Bryan Helmkamp')
#        pair       (Unsets the author so the git global config takes effect)
# 
# Author: Bryan Helmkamp (http://brynary.com)

#######################################################################
## Configuration

PAIR_EMAIL = "developers@weplay.com"

AUTHORS = {
  "bh" => "Bryan Helmkamp",
  "lm" => "Luke Melia"
  # ...
}

## End of configuration
#######################################################################

unless File.exists?(".git")
  puts "This doesn't look like a git repository."
  exit 1
end

authors = ARGV.map do |initials|
  if AUTHORS[initials.downcase]
    AUTHORS[initials.downcase]
  else
    puts "Couldn't find author name for initials: #{initials}"
    exit 1
  end
end

if authors.any?
  if authors.size == 1
    authors = authors.first
  elsif authors.size == 2
    authors = authors.join(" and ")
  else
    authors = authors[0..-2].join(", ") + " and " + authors.last
  end
  
  `git config user.name '#{authors}'`
  `git config user.email '#{PAIR_EMAIL}'`
  
  puts "user.name = #{authors}"
  puts "user.email = #{PAIR_EMAIL}"
else
  `git config --unset user.name`
  `git config --unset user.email`
  
  puts "Unset user.name and user.email"
end

Note: If you're using GitHub, it's important to choose a PAIR_EMAIL value that does not correspond to a GitHub account. Otherwise, GitHub will resolve the email address to a GitHub user and display their username instead of the value of the author.name. We just made one up.

Our Git deployment workflow

At weplay, we recently switched to pure git version control from git-svn. Now that we've had a couple weeks for the dust to settle, I'd like to share our workflow for managing deployments to our staging and production clusters.

We started by outlining the goals of our system:

  • All code that's pushed to our staging and production environments must be in GitHub. Nothing goes straight from a local repository to our servers.

  • Any developer can deploy our most recent work to staging.

  • Any developer can deploy the code on staging into production. We (try to) avoid deploying anything to production that hasn't been pushed to staging first.

  • Any developer can see a diff between "What is deployed" and "What I'm about to deploy."

  • Any developer can branch from the production codebase for time sensitive tweaks and fixes. These need to be staged before they're deployed to production too.

  • That functionality is available in an automated, safe, easy to use form. (Hint: rake)

We ruled out having "production" and "staging" tags because updating tags across a tree of remotes doesn't seem to work smoothly. We also ruled out doing git merges from master into our staging branch. Our staging server/codebase jumps around from the latest changes to the production code (up to a week old) based on what we need to test, so we really just want to replace it.

We settled on using remote git branches for production and staging on origin and hard resetting them to other branches to simulate a copy. We treat these branches more like tags and never commit to them directly. This has worked great so far.

Here's what our rake tasks look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
class GitCommands

  def diff_staging
    `git fetch`
    puts `git diff origin/production origin/staging`
  end
  
  def tag_staging(branch_name)
    verify_working_directory_clean
    
    `git fetch`
    `git branch -f staging origin/staging`
    `git checkout staging`
    `git reset --hard origin/#{branch_name}`
    `git push -f origin staging`
    `git checkout master`
    `git branch -D staging`
  end
  
  def tag_production
    verify_working_directory_clean
    
    `git fetch`
    `git branch -f production origin/production`
    `git checkout production`
    `git reset --hard origin/staging`
    `git push -f origin production`
    `git checkout master`
    `git branch -D production`
  end

  def branch_production(branch_name)
    verify_working_directory_clean
    
    `git fetch`
    `git branch -f production origin/production`
    `git checkout production`
    `git branch #{branch_name}`
    `git checkout #{branch_name}`
    `git push origin #{branch_name}`
  end
  
protected

  def verify_working_directory_clean
    return if `git status` =~ /working directory clean/
    raise "Must have clean working directory"
  end
end


namespace :tag do
  desc <<-DESC
    Update the staging branch to prepare for a staging deploy.
    Defaults to master. Optionally specify a BRANCH=name
  DESC
  
  task :staging do
    branch_name = ENV['BRANCH'] || "master"
    GitCommands.new.tag_staging(branch_name)
  end

  desc "Update the remove production branch to prepare for a release"
  task :production => ['diff:staging'] do
    GitCommands.new.tag_production
  end
end

namespace :diff do
  desc "Show the differences between the staging branch and the production branch"
  task :staging do
    GitCommands.new.diff_staging
  end
end

namespace :branch do
  desc "Branch from production for tweaks or bug fixs. Specify BRANCH=name"
  task :production do
    branch_name = ENV['BRANCH']
    raise "You must specify a branch name using BRANCH=name" unless branch_name
    GitCommands.new.branch_production
  end
end

namespace :deploy do
  desc "Tag and deploy staging"
  task :staging => "tag:staging" do
    `cap staging deploy:long`
  end
end

The last one (rake deploy:staging) simply wraps up the common task of tagging our latest code to be pushed to staging and initiating a staging deploy.

Note: To use most of these commands, your local working directory must be clean. If we have outstanding changes in our tree when we need to run them, we use git-stash to temporarily move them out of the way.

Thanks to Scott Chacon for helping us work this out. Be sure to check out his GitCasts and Git Internals PDF.

How I got started programming

Mr. Paul C. Dix tagged me to answer these...

How old were you when you started programming?

I started to understand the concept of programming around age nine or ten. I remember how amazing I thought it was that making the computer do new things didn't require rooms full of special equipment.

How did you get started programming?

A lot of my experiences around this time are blurred together.

My parents got me into a beginner programming course for kids at the local community college. The instructor taught the basics of variables and control structures while the students swapped QBasic games they downloaded at home.

The first program I distinctly remember working on calculated golf handicaps from a series of scores. I wrote it with an elementary school friend in his basement using a copy of Borland C++ he found. We fumbled around with functions like getch and cout without any idea that they stood for.

What was your first language?

Beginners All-purpose Symbolic Instructional Code.

What was the first real program you wrote?

When I joined the website development team at my high school half way though the ninth grade, I took on the task of creating a system to post football scores and statistics to our website. After a frustrating first attempt in Perl, I discovered PHP and rewrote it.

The program allowed our statistician to enter the numbers into a form, saving the web team from having to manually enter all the data from paper print outs. It calculated simple averages and YTD totals.

What languages have you used since you started programming?

In no particular order: QBasic, Visual Basic, Bash, Java, JavaScript, C, C++, Ruby, Perl, PHP, Python, and SQL.

So far, Ruby is my favorite hands down. I'm fortunate enough to say I've never written a line of real production code in either Java or .NET.

What was your first professional programming gig?

The summer before my senior year of high school, I interned in a local government IT applications department. The other developers were doing mostly ASP work, but I built their first PHP application which I deployed on their first Linux server.

Unsatisfied with the maintainability of PHP apps I had built in the past, I developed a Struts-like framework in the process. At this point I was feeling pain with the PHP language and the lack of a suitable web development framework. After a brief exploration into Python, I would soon find relief in Ruby and Rails.

If there is one thing you learned along the way that you would tell new developers, what would it be?

Engage in the community, both locally and online, as much as possible. Attend user group meetings, contribute to mailing lists, blog, go to conferences that look interesting. Surround yourself with other programmers, especially those you admire. You won't regret it.

What's the most fun you've ever had programming?

Solving interesting problems with great developers while building applications with an impact. 'Nuff said.

Up Next

It's entirely optional, of course.

Story Driven Development slides posted

Just wrapped up my Story Driven Development talk at GoRuCo 2008. There were some great questions at the end, and I'm looking forward to more hallway track conversations about SDD.

Download the slides as a PDF (1.6 MB)

Note: Confreaks is recording the talks today, so a full video of my presentation should be online soon.

weplay launches

The online community for youth sports I’ve been working on for the last month or so, weplay, is now in public beta. It’s been a roller coaster, but entirely worth it.

As part of this launch, we’re set to appear on the front page of the New York Times business section in tomorrow’s paper. The online version of the article is already available, and we’ve been busy all night responding to our first production load…. ever.

Check it out, don’t mind a few bumps and bruises as we settle in, and expect more to come soon. Oh, and be sure to checkout my weplay profile.

Presenting at GoRuCo in NYC

I just received word that my proposal to speak at NYC’s very own second annual GoRuCo has been accepted. The conference is set for Saturday, April 26th at Pace University downtown. My topic will be the same as my Scotland on Rails presentation.

I’m really excited to have the opportunity to present at home, in front of many of my friends and colleagues. Now I just need to make sure I don’t suck. Better get back to working on my slide deck…

Presenting at Scotland on Rails

I’m going to be presenting on Story Driven Development at the first Scottish Ruby on Rails conference, Scotland on Rails. I’ll be exploring the concept of Story-first development from both an agile process and implementation standpoint.

Topics will include:

  • Collaborating with the product owner on stories
  • What makes good stories and scenarios?
  • Driving stories with a browser simulator like Webrat
  • Potential pitfalls and ways to avoid them

If you’re going to be in Edinburgh for the conference, let me know.