Make a Pull Request using GitHub API and OAuth App

August 3, 2018    GSoC SPDX Django GitHub FOSS

Make a Pull Request using GitHub API and OAuth App

This post describes how I made the GitHub integration of my GSoC project. The main motive of this feature was, when the user is done editing the XML document, he can submit his changes through a pull request in the license-xml repository with just a click of a button.

This post will be helpful if you want to learn about:

  • integrating Third Party OAuth in Django
  • GitHub OAuth Apps
  • GitHub API

Basic Idea

Since the pull request had to be made on behalf of the user, that’s why I choose to go with the GitHub OAuth App instead of general GitHub Apps. When the user registers using these OAuth apps, GitHub provide us with a unique access token, using which we access the API on behalf of that user and make the Pull Request. A simpler way of doing this was to make a bot account, generate API access token for it and use it to make all the pull requests but that could be very easily spammed and there would be no way to know who actually made the pull request.

Integrating Third Party OAuth in Django

The OAuth feature can be added to a Django website using the social-auth-app-django package. This is very easy to use, supports both Python 2 and 3 and also has a good documentation. Install it using pip install social-auth-app-django. In the settings.py file, add this package to the INSTALLED_APPS and also update the configurations such as MIDDLEWARE_CLASSES, context_processors, AUTHENTICATION_BACKENDS. Migrate the database to add all the tables of this package. For a descriptive info about the configs refer to the docs

After this go to your GitHub settings and from the Developer Settings, make a new OAuth App. Fill up all the required entries. The callback URL is the address where the app will redirect after the user is done with the authentication. If you want for testing purpose use http://localhost:8000/oauth/complete/github else replace the localhost with your site’s domain name (can be changed later). Once your app is made, copy the Client ID and Client Secret and assign them to SOCIAL_AUTH_GITHUB_KEY and SOCIAL_AUTH_GITHUB_SECRET variable in settings.py file.

By default the OAuth App only gives access to user’s info, if we want more access then we can request access permission from users by defining scopes for our GitHub App. For making the pull request we would need access to the users repos so add the SOCIAL_AUTH_GITHUB_SCOPE variable to settings.py file and set it to ['public_repo'].

Your basic GitHub OAuth is ready, you can add more features to the authentication process by using Pipelines. Pipelines allow us to add our custom functions in the default authentication process of the social-auth-app-django. In my case I wanted to store user’s info in one more table in the database, so made a function which did that and then added it to the pipeline.

Pull Request Process

The requests package can be used to make different type of API requests to the GitHub API. All the requests must include the following headers, where token is the user token that is generated after authentication with OAuth App:

{
    "Accept":"application/vnd.github.machine-man-preview+json",
    "Authorization":"bearer "+token,
    "Content-Type":"application/json",
}

To make the pull request I followed the following workflow:

  • Check if the user has the original repository forked, if not then fork it using the Forks API
  • If the user already has the repo forked for some time then it might be behind the upstream/master by some commits. To update it first get the SHA of the upstream/master using the get request at https://api.github.com/repos/:owner/:repo/git/refs/head/master and then send a patch request at https://api.github.com/repos/:username/:repo/git/refs/head/master with the SHA included in the request body. Again send a get request at https://api.github.com/repos/:username/:repo/git/refs/head/master and store the SHA in a variable as this will be used later.
  • Then we create a branch by sending a post request at https://api.github.com/repos/:username/:repo/git/refs with the following body (SHA is the one that we stored above):
    {
      "ref":"refs/heads/"+branchName,
      "sha":sha,
    }
    

    From the response of this request copy the SHA of the new branch created.

  • Commit can be easily created using the Contents API.
  • After we have made the commit with all changes in it, we can finally make the Pull Request using the Pull Request API.

My Implementation

The way I integrated this feature into the XML Editor, was by first authenticating the user to the GitHub App, then providing him with a form which included Branch Name, File Name ,Commit Message, Pull Request Title, Pull Request Body. After the user has filled valid info in the form then sending an Ajax Request to a Django View which would handel the pull request process and return the results (error or success). While making the pull request I also checked if the given branch name exists, if so then appending some random text to the name. Also I checked if the given file name already exists, if it exists then use the Update File API else use the Create File API.

You can have a look at the project here. Please post your doubts and questions in the comments below.