GitHub makes it relatively simple to contribute to open source projects, just fork the repository, make your changes, submit a pull request. Couldn't be simpler.
Accepting those Pull requests, is dead simple too, most of the time. But what if you want to build and test the pull request first, before accepting the request. Fortunately the nature of GitHub Pull requests (or more to the point, Git itself) makes this possible.
Git References
Git References are a complex topic all on it's own, but lets take a quick look at a typical cloned repository. In the .git folder, open config file in notepad and take a look at the [remote "origin"] section, here's what mine looks like :
[remote "origin"]
url = https://github.com/VSoftTechnologies/playground.git
fetch = +refs/heads/*:refs/remotes/origin/*
The key entry here is the fetch. Quoting from the git documentation :
"The format of the refspec is an optional +
, followed by :
, where
is the pattern for references on the remote side and
is where those references will be written locally. The +
tells Git to update the reference even if it isn’t a fast-forward."
The default fetch refspec will pull any branches from the original repository to our clone. But where are our pull requests?
Anatomy of a pull request
When a pull request is submitted, GitHub make use of Git References to essentially "attach" your pull request to the original repository. But in my local clone, I won't see them because the default fetch refspec doesn't include them. You can see the pull requests by using the git ls-remote command on the origin :
$ git ls-remote origin
$ git ls-remote origin
27dfaaf83f60ac26a6fe465042f2ddb515667ff1 HEAD
654b98d6eb862e247e5c043460e9f9a64b2f0972 refs/heads/Test
27dfaaf83f60ac26a6fe465042f2ddb515667ff1 refs/heads/master
b333438310a56823f1938071af8c697b202bf855 refs/pull/1/head
95cb80af1330e73188ea32659d7744dcfe37ab43 refs/pull/2/head
90ba13b8edaab04505396dbcb1853f6f9bdaed64 refs/pull/2/merge
Notice something odd there. There are two pull requests, but pull request 2 has two entries in the list, whilst pull request 1 has only 1 entry. refs/pull/2/head is a reference to the head commit of your pull request, whilst refs/pull/2/merge is a reference to the result of the automatic merge that GitHub does. On pull request 1, there was a merge conflict, so the the /merge reference was not created, on pull request 2, the merge succeeded. On the pull request page, you would typically see something like this if the merge succeeded :
Getting Continua CI to see the Pull Requests
The main reason for building pull requests on your CI server is to see if they build, and to run your unit tests against that build. You can chose to build the original pull request, or the result of the automatic merge, or both. In reality, if the automatic merge failed, then the person who submitted the pull request has some more work to do, so there's really no point building/testing the original pull request. What you really want to know, is "if I accept this request, will it build and the tests pass", so it's generally best to only build the automatic merge version of the pull request. Continua CI makes this quite simple. On the Git Repository settings, check the "Fetch other Remote Refs" option. This will show the Other Refs text area, which already has a default RefSpec that will fetch the pull requests (the merged versions), and create local (to Continua CI) branches with the name pr/#number - so pull request 1 becomes branch pr/1.
You can modify this to taste, for example if you are fetching both the merge and the head versions of the pull requests, you might use a refespec like this :
+refs/pull/*/merge:refs/remotes/origin/pr-merge/*
+refs/pull/*/head:refs/remotes/origin/pr-head/*
Building the pull Requests
Now we have gotten this far (which is to say, you enabled one option and clicked on save!) we can build the pull requests (it may take a few minutes to fetch the pull requests). If you manually start a build, you can select the pull request from the branch field for the github repository using the intellsense, just start typing pr/ and you will see a list :
Now we can add a trigger to build pull requests (we are talking continuous integration after all). Using the Pattern Matched Branch feature on Continua CI Triggers you can make your trigger start builds when a pull request changeset is fetched from Github. The pattern is a regular expression, so ^pr/.* would match our pull request branches (assuming we use the default refspec)
Adding a trigger specific to the pull requests allows you to set variables differently from other branches, and you will then be able to tailor your stages according to whether you are building a pull request or not. For example, you probably don't want to run your deploy stage when building a pull request).
Updating GitHub Pull Request Status
One last thing you might like to add, is to update the Pull Request Status. This can be done using the Update GitHub Status Action in Continua CI (In a future update this will done via build event handlers, a new feature currently in development). This is what the pull request might look like after the status is updated by Continua CI :