Create a folder and push multiple files under a single commit through GitHub API
Tags: javascript, github, git, api
Published: 2022-03-06 12:34:33

## Intro
I could not understand how to push multiple files under a single commit, using GitHub API. Some googling landed me on [Git Database API Docs](https://docs.github.com/en/rest/reference/git). Wasn't very clear how it worked so I tried getting my hands dirty! Documenting it here for anyone who's looking for it.

Disclosure:
1. This API needs atleast 1 commit to work. Empty repositories don't work.

## Git Database API
The Git Database API enables you to read and write raw Git objects to your Git database on GitHub and to list and update Git references (branch heads and tags).

The whole process is as follows: create blobs corresponding to files, create tree, create a commit for the changes and finally updating refs to reflect the commit.



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1paoytdkvtczvkzhcg85.jpg)

## Creating blobs
A Git blob (binary large object) is the object type used to store the contents of each file in a repository. The file's SHA-1 hash is computed and stored in the blob object.

To create a blob we need to hit the following endpoint:
```
POST https://api.github.com/repos/{user}/{repo}/git/blobs
```

Let's create a blob for a file called `main.py` with 1 line of python code:

```json
{
    "content":"print(\"hello world !\")",
    "encoding": "utf-8"
}
```

We can create blobs for each file we want to upload. In the response, we get `SHA` of the blob which we need to send while creating the tree. Sample response:

```json
{
    "sha": "638eff25696b982124deeb1f3dfcceabfdc81a93",
    "url": "https://api.github.com/repos/BRO3886/git-db-example/git/blobs/638eff25696b982124deeb1f3dfcceabfdc81a93"
}
```

## Creating a tree
Now, we need to create a tree with the files we added. For that, first we need to get the SHA of the base tree. Then we need to create a tree for the files we need to add:

### Get SHA for `base_tree`
The base tree can be thought of as the folder where you'd like to create your commit. If you want to create it at the root folder, you can make request to:
```
GET https://api.github.com/repos/{user}/{repo}/git/trees/{branch}
```

It returns a response like:
```json
{
    "sha": "0d43a3b20104b4baa402c09a6c9c6c3298390e4a",
    "url": "{url of tree}",
    "tree": [
        {
            "path": "App",
            "mode": "040000",
            "type": "tree",
            "sha": "{sha}",
            "url": "{url of folder/tree}"
        },
        {
            "path": "README.md",
            "mode": "100644",
            "type": "blob",
            "sha": "{some SHA}",
            "size": 885,
            "url": "{some URL}"
        },
        ...
    ],
    "truncated": false
}
```

Here, we can extract the `SHA` from `response.sha` or SHA of the folder under `response.tree[i].sha`.

### Create tree
Then, we need to hit the create tree api:

```
POST https://api.github.com/repos/{user}/{repo}/git/trees
```

With the body as:

```json
{
    "tree":[
        {
            "path":"helloworld/main.py",
            "mode":"100644",
            "type":"blob",
            "sha":"638eff25696b982124deeb1f3dfcceabfdc81a93"
        },
        {
            "path":"helloworld/main2.py",
            "mode":"100644",
            "type":"blob",
            "sha":"638eff25696b982124deeb1f3dfcceabfdc81a93"
        }
        ...
    ],
    "base_tree":"3c408bafa55eda6b1c51de5df0fc36304f37414c"
}
```

Here `request.tree` would have an array of blobs that you want to push. For each blob we need the SHA that we got as a response from create blob API.

For the `mode`:
The file mode; one of `100644` for file (blob), `100755` for executable (blob), `040000` for subdirectory (tree), `160000` for submodule (commit), or `120000` for a blob that specifies the path of a symlink.

> GitHub doesn't store folders, so the only way to make a folder is to set the `path` for the blob as `{folderName}/{fileName}`

Here, we get another `SHA` in the response, which we need when we create a commit corresponding to the changes:

```json
{
    "sha": "a69117177bb067933189072b2b8799c63f388f32",
    "url": "https://api.github.com/repos/BRO3886/git-db-example/git/trees/a69117177bb067933189072b2b8799c63f388f32",
    "tree": [
        {
            "path": "README.md",
            "mode": "100644",
            "type": "blob",
            "sha": "bc7b1321063b4075c97bf16e6f8130b6f9fa6537",
            "size": 54,
            "url": "https://api.github.com/repos/BRO3886/git-db-example/git/blobs/bc7b1321063b4075c97bf16e6f8130b6f9fa6537"
        },
        {
            "path": "helloworld",
            "mode": "040000",
            "type": "tree",
            "sha": "82a82f6788b44fe93774597ff2e76ac66ae1e657",
            "url": "https://api.github.com/repos/BRO3886/git-db-example/git/trees/82a82f6788b44fe93774597ff2e76ac66ae1e657"
        }
    ],
    "truncated": false
}
```

Here we need `response.sha` for the next step.

## Add a commit
This step is pretty simple. We just need the `response.sha` from the previous step. We make a request to the following endpoint:
```
POST https://api.github.com/repos/{user}/{repo}/git/commits
```
Along with the body:
```json
{
    "tree":"a69117177bb067933189072b2b8799c63f388f32",
    "message":"some commit msg",
    "parents": ["3c408bafa55eda6b1c51de5df0fc36304f37414c"]
}
```
`parents`: The SHAs of the commits that were the parents of this commit. If omitted or empty, the commit will be written as a root commit. For a single parent, an array of one SHA should be provided; for a merge commit, an array of more than one should be provided.

In our case, for the parent, since we want to add another commit on a particular branch, we need to get the SHA of it.

For that, we need to make a request to the following endpoint:

```
GET https://api.github.com/repos/BRO3886/git-db-example/git/refs/heads/{branch}
```

It returns a `JSON` with details about the branch:
```json
{
    "ref": "refs/heads/main",
    "node_id": "REF_kwDOG87gc69yZWZzL2hlYWRzL21haW4",
    "url": "https://api.github.com/repos/BRO3886/git-db-example/git/refs/heads/main",
    "object": {
        "sha": "3c408bafa55eda6b1c51de5df0fc36304f37414c",
        "type": "commit",
        "url": "https://api.github.com/repos/BRO3886/git-db-example/git/commits/3c408bafa55eda6b1c51de5df0fc36304f37414c"
    }
}
```
For the parent SHA, we need `response.object.sha`.

In the response of create commit API, we'll get another SHA:
```json
{
    "sha": "544aa83c4d4a784c4c8490d6548c248b0e57d0ac",
    "node_id": "C_kwDOG87gc9oAKDU0NGFhODNjNGQ0YTc4NGM0Yzg0OTBkNjU0OGMyNDhiMGU1N2QwYWM",
    "url": "https://api.github.com/repos/BRO3886/git-db-example/git/commits/544aa83c4d4a784c4c8490d6548c248b0e57d0ac",
    "html_url": "https://github.com/BRO3886/git-db-example/commit/544aa83c4d4a784c4c8490d6548c248b0e57d0ac",
...
}
```
We'll need this SHA value for the last step.

## Updating ref
This step is for updating the ref from where we pulled the SHA under `parents` (create commit step). For my case, since the ref was main, I'll update it. Make a request to the following endpoint:

```
PATCH https://api.github.com/repos/{user}/{repo}/git/refs/heads/{branch}
```

Along with the body:
```json
{
    "sha":"544aa83c4d4a784c4c8490d6548c248b0e57d0ac"
}
```

If in the response the `object.sha` is same as the one sent as a part of the request, along with status code 200, that means your changes will be reflected on GitHub.

---

I hope you got some idea. Here is the GitHub link of the sample repo you can refer: [github.com/BRO3886/git-db-example](https://github.com/BRO3886/git-db-example)