Extending GitHub Issues

The problem

My team recently decided to experiment with using the Issues feature of our private Github repository. We’ve avoided a bug tracking tool for a long time, but this one seemed just lightweight enough that it was worth a shot. We really like the feature that allows you to close issues via your commit message (include the text “closes gh-3” to close issue #3) because the website provides hyperlinks between the commit and issue. However, we weren’t crazy about the idea of issues being closed without verification from QA. We wanted the auto-linking capability without the auto-closing functionality. Others have asked for this feature, GitHub acknowledges it is on their TODO list, but it is not currently available. Lucky for us, GitHub exposes enough extension points for us to do it ourselves!

The plan

We want to automatically re-open any issue that is closed via a commit message, then add the “Verify” label to the issue. This will give us the links between commit and issue on the website, and provide QA visibility to issues that should be resolved.

The Code

Programmatically re-open an issue

Thankfully, GitHub exposes a well-documented API at http://develop.github.com/. Re-opening an issue is just a matter of crafting the correct URL and passing credentials along with the request. The GitHub docs do mention a couple existing Ruby libraries for accessing their API, but for such a simple scenario it was just as easy to write my own (with a little help from HTTParty). The relevant parts (or view the full class):

class GitHub
  include HTTParty
  base_uri "https://github.com/api/v2/json"

  def initialize(repo, user=nil, pass=nil)
    @user = user
    @pass = pass
    @repo = repo
  end

  def reopen_issue(issue)
    self.class.post("/issues/reopen/#{@repo}/#{issue}", options)
  end

  private
    def options
      @options ||= @user.nil? ? {} : { :basic_auth => {:username => @user, :password => @pass}}
    end
end

Re-opening an issue is as simple as:

github = GitHub.new "joshuaflanagan/ghpong", "joshuaflanagan", "password"
github.reopen_issue 3

Watch for commits that close issues

Since we want to re-open issues closed via a commit message, we need a way to keep an eye out for these commits. Again, GitHub makes integration easy by exposing Post-Recieve Hooks. Within your repository Admin control panel you can specify any number of services you want notified of each push, including any arbitrary URL.

I just needed a simple endpoint that GitHub could call so that I could invoke my API wrapper to re-open any closed issues. This is the perfect scenario for a Sinatra application. In fact, GitHub uses Sinatra for all of their “built-in” service hooks, and since the code is freely available, we have plenty of examples to learn from.

My Sinatra route to re-open issues closed by commits (view the full app):

post '/reopen/:token' do
  respond_to_commits do |commit|
    issue = GitHub.closed_issue(commit["message"])
    github.reopen_issue issue if issue
  end
end

This defines a route that will respond to any HTTP POST to /reopen/sometoken, where sometoken is a secret I’ve hardcoded in my environment. If you were to deploy this code, you would configure your own secret token and use it in your calls (this could easily be made much more robust and support multiple users with their own tokens, but I don’t need it yet). respond_to_commits is a simple helper method that parses the posted data and calls the supplied block for every commit included in the git push. GitHub.closed_issue parses commit messages looking for patterns like “closes gh-3”. If it finds the pattern, it returns the issue number, otherwise, nil. We then call the reopen_issue API wrapper if an issue was found.

I also wanted to add the “Verify” label to any issue that was re-opened. This was simply a matter of creating an additionaly which calls my GitHub.label_issue API for any issue closed via a commit message. See the app.rb and github.rb for details.

Deploy

Deploying this app is ludicrously simple using Heroku:


heroku create
git push heroku master 
heroku config:add GH_USER=joshuaflanagan GH_PASSWORD=password TOKEN=sometoken 


 

(Note that you can use use a GitHub API token for GH_PASSWORD if you append “/token” to the end of the username in GH_USER)

The final step is to tell GitHub about the endpoint.

  1. Go to the repo you want to monitor on github
  2. Click Admin, select Service Hooks
  3. In Post-Recieve URLS, specify: http://myapp.heroku.com/reopen/sometoken
  4. Click “Add another post-recieve URL” and specify: http://myapp.heroku.com/lable/closed/Verify/sometoken

If you think it would be useful, feel free to fork the code http://github.com/joshuaflanagan/ghpong and deploy

Summary

That was really too easy. If any of this was new to you, I strongly encourage you to experiment with http://develop.github.com, Sinatra, and Heroku. If you want the functionality described in this article, just grab the code from http://github.com/joshuaflanagan/ghpong and deploy it. Send me a pull request if you add anything cool.

Related Articles:

This entry was posted in git, ruby, sinatra. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://stevenharman.net Steven Harman

    nice! we’ve got a little Sintara app that we use to handle Github service hooks, translating & relaying those posts to our internal Hudson server to trigger builds of various repos/branches. At some point I’ll pull it apart into a gem that others can use…

  • http://www.lostechies.com/members/chadmyers/default.aspx chadmyers

    This is like “Mexican food” software development. Take your tortilla, your beans, your rice, your chicken, and some pepers and you can make just about anything!

  • Jill Ott

    Josh Flanagan is my hero!!! I’m the QA/Tester at Dovetail, and this GitHub tweak is really helpful for me. Thanks Josh!