(Note this is using github's v3 API. They have a new v4 version that uses graphql. Haven't tried that at the time of writing.)
Using github API is a bit weird at first, because uses the same lower-level git commands that are abstracted away to the normal user. However, this is actually a good thing to some degree, because it means you can do things like changing a single file without copying out an entire branch or repo to your disk.
I will be referencing git objects (blobs, trees, refs, etc.) in this short tutorial, but not covering them in gory detail. You can learn more about them here.
A blob is exactly what its name describes, meaning it's mostly just a file's contents (without a file path) and an associated sha. It is later referenced by trees (which are not git branches... you can think of them as directories), in order to create the association to a file path. In addition, adding a new file and updating a new file within a tree will always require a new blob. The difference between adding and updating a file is merely whether the file path is overwritten within a tree or whether you create a new unique path.
You can create a new file as shown below. I used base64 encoding in this example. I don't know how other encodings work.
curl -H "Authorization: token $GITHUB_API_TOKEN" -H 'Accept: application/vnd.github.v3+json' -H 'Content-Type: application/json' 'https://api.github.com/repos/<your_org>/<your_repo>/git/blobs' -d "{
\"content\":\"$(cat your-file.txt | base64)\",
\"encoding\":\"base64\"
}" | python -m 'json.tool'
{
"sha": "<blob_sha>",
"url": "https://api.github.com/repos/<your_org>/<your_repo>/git/blobs/<blob_sha>"
}
You will need to pull the SHA of the base branch your plan to create a PR against. In this example I pull down master
, which I will be using in the next steps. See how to pull it below.
curl -H "Authorization: token $GITHUB_API_TOKEN" -H 'Accept: application/vnd.github.v3+json' -H 'Content-Type: application/json' 'https://api.github.com/repos/<your_org>/<your_repo>/git/ref/heads/master' | python -m 'json.tool'
{
"ref": "refs/heads/master",
"node_id": "<some_id>",
"url": "https://api.github.com/repos/<your_org>/<your_repo>/git/refs/heads/master",
"object": {
"sha": "<master_sha>",
"type": "commit",
"url": "https://api.github.com/repos/<your_org>/<your_repo>/git/commits/<master_sha>"
}
}
Remember how I mentioned that trees are sort of like directories? Well in this step we create the filepath association to the blob_sha
in the first step and then reference the master_sha
as the base_tree
. Here we create a tree with only one update: the blob and a path to that blob. Depending on whether it's an existing path within base branch or whether it's a new path will determine whether it's a file update or a file create. Note that here we are not updating master but creating a new tree that is derived from master.
curl -H "Authorization: token $GITHUB_API_TOKEN" -H 'Accept: application/vnd.github.v3+json' -H 'Content-Type: application/json' 'https://api.github.com/repos/<your_org>/<your_repo>/git/trees' -d '{
"base_tree":"<master_sha>",
"tree":[{
"path":"<file_path_to_update_using_blob_you_uploaded>",
"mode":"100644",
"type":"blob",
"sha":"<blob_sha>"
}]
}' | python -m 'json.tool'
{
"sha": "<the_new_tree_sha>",
"url": "https://api.github.com/repos/<your_org>/<your_repo>/git/trees/<the_new_tree_sha>",
"tree": [ <a list of of files> ],
"truncated": false
}
Here we create a commit based on your tree. It is pretty straightforward as shown below.
curl -H "Authorization: token $GITHUB_API_TOKEN" -H 'Accept: application/vnd.github.v3+json' -H 'Content-Type: application/json' 'https://api.github.com/repos/<your_org>/<your_repo>/git/commits' -d '{
"message": "<your_commit_message>",
"author": {
"name": "<your_name>",
"email": "<your_email>",
"date": "2020-02-14T17:48:30-05:00" # can be any timestamp
},
"parents": [
"<master_sha>"
],
"tree": "<the_new_tree_sha>"
}' | python -m 'json.tool'
{
"sha": "<your_commit_sha>"
# ... lots of other info
}
Now that we have created a commit, we can create a new git ref that points to it. In this case, we're creating a new one, but it's also possible to update an existing ref to point to the new commit. The reason we're creating the ref/branch here only after we've done the commit (which is the opposite of the normal git workflow you would do locally), is that it saves operations... Otherwise we would have to create the ref, do a commit, then update the ref with the new commit to roll the ref forward, which is an extra step.
curl -H "Authorization: token $GITHUB_API_TOKEN" -H 'Accept: application/vnd.github.v3+json' -H 'Content-Type: application/json' 'https://api.github.com/repos/<your_org>/<your_repo>/git/refs' -d '{
"ref": "refs/heads/<your_new_branch_name>",
"sha": "<your_commit_sha>"
}' | python -m 'json.tool'
# you get the ref back here, but you won't need any info from it.
Finally, this call will create your PR. You merely reference the base
which would be master
in this case, and your new ref
from the last step.
curl -H "Authorization: token $GITHUB_API_TOKEN" -H 'Accept: application/vnd.github.v3+json' -H 'Content-Type: application/json' 'https://api.github.com/repos/<your_org>/<your_repo>/pulls' -d '{
"title": "<your_pr_title>",
"body": "Please pull this benign change in!",
"head": "<your_new_branch_name>", # note that you don't prefix this with `refs/heads/` like in the other case
"base": "master"
}' | python -m 'json.tool'
{
# a lot of info...
"number": <your_pr_number>,
# a lot of info...
}
Github has separate APIs to create pull requests and to add labels to pull requests. I'm not sure if it's possible to do it in one shot, but the pulls
endpoint definitely does not support adding labels. There might be a different endpoint that you can hit instead, but for now you can use this method.
# use the PR number to label your pr
curl -H "Authorization: token $GITHUB_API_TOKEN" -H 'Accept: application/vnd.github.v3+json' -H 'Content-Type: application/json' 'https://api.github.com/repos/<your_org>/<your_repo>/issues/<your_pr_number>/labels' -d '{
"labels": ["<your_label>"]
}' | python -m 'json.tool'
# output not important
... and that's it!!! Hope this was helpful.