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:
<br />
heroku create
git push heroku master
heroku config:add GH_USER=joshuaflanagan GH_PASSWORD=password TOKEN=sometoken
<br />
(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.
- Go to the repo you want to monitor on github
- Click Admin, select Service Hooks
- In Post-Recieve URLS, specify: http://myapp.heroku.com/reopen/sometoken
- 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 https://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 https://github.com/joshuaflanagan/ghpong and deploy it. Send me a pull request if you add anything cool.