# How to programmatically create a commit on GitHub using the GitHub API and API Hero

The GitHub REST API is a useful behemoth, currently sitting at 878 endpoints (by our count) and growing. It contains all sorts of useful functionality, from [searching for code](https://docs.github.com/en/rest/search#search-code) to [rendering markdown](https://docs.github.com/en/rest/markdown#render-a-markdown-document), and then some.

You can also use it to interact with the raw git repository to read and modify the actual underlying data. One very useful thing you can do is create a new commit, but it's not exactly a straightforward `POST /commit` call. It helps if you understand a bit about how the [git internals](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects) work, but for the following post we're going break it down step-by-step.

We're also going to show how using [API Hero](https://apihero.run) can save us time by using the [GitHub](https://github.com/apihero-run/apihero-github) integration, and getting nicely typed requests and responses, as well as the ability to view and debug request data.

## What is API Hero?

Before we get started, if you aren't familiar with API Hero, you can quickly get up to speed with this 60 second explainer:

%[https://www.youtube.com/watch?v=nek3WHRUwow]

## Setup your API Hero project

Before we can create our first commit programmatically, we need to setup the project with API Hero and add the GitHub integration. For this post, we'll use [Node.js](https://docs.apihero.run/node-quick-start), and this [GitHub repo](https://github.com/apihero-run/apihero-github-commits) that you can clone to follow along:

```bash
git clone https://github.com/apihero-run/apihero-github-commits.git
cd apihero-github-commits
npm install
```

Next we'll run the `apihero` CLI to add API Hero and the GitHub integration to our project:

```bash
npx apihero@latest add git
```

Here is what that would look like (although you may also be asked to authenticate to API Hero, which doesn't happen below):

![CleanShot 2022-09-23 at 11.34.30.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1663929337845/bgQdNAYyE.gif align="left")

Next, copy the "project key" printed after running the previous command and create a `.env` file in the root of the repository with the following contents:

```
APIHERO_PROJECT_KEY="<your projectKey here>"
```

## 1. Fetch latest commit

The first request to the GitHub API that we need to make is to fetch the latest commit for the branch we want to commit to.  We can do that by using the [get a reference](https://docs.github.com/en/rest/git/refs#get-a-reference) endpoint, passing in the `owner`, `repo`, and `ref`. For branches, the ref is in the format of `heads/{branchName}`.

Update the `src/index.ts` file with the following code:

```ts
import { git } from "@apihero/github";
import { fetchEndpoint } from "@apihero/node";

async function createCommit(owner: string, repo: string, branch: string) {
  const ref = await fetchEndpoint(git.getRef, {
    owner,
    repo,
    ref: `heads/${branch}`,
  });
}

createCommit("apihero-run", "apihero-github-commits", "commit-playground").catch(console.error);
```

As you can see we're defining the `createCommit` function and then calling it, passing in the [apihero-github-commits repo](https://github.com/apihero-run/apihero-github-commits). It's using the `fetchEndpoint` function from `@apihero/node` and the `git.getRef` endpoint from the `@apihero/github` client library.

Now run this code in the terminal like so:

```bash
npm run dev
```

This runs the `src/index.ts` file using [tsx](https://github.com/esbuild-kit/tsx). Notice how we aren't logging out the request or response above, or setting up a breakpoint to see what the request or response is.  That's because we're using API Hero, and all that data is saved for us to inspect in your Request History. And our `@apihero/node` package will print out a link for you to use to jump right there, like so:

![CleanShot 2022-09-23 at 13.51.37.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1663937571056/6BLnqXsDp.gif align="left")

Visiting that link brings us to the request, where we can inspect the response body:

![CleanShot 2022-09-23 at 13.53.53@2x.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1663937681139/SE-aafyP0.png align="left")

## 2. Create a commit tree

Next, we'll need to create a tree using the [Create a tree](https://docs.github.com/en/rest/git/trees#create-a-tree) endpoint. A git tree is the internal git object that defines the hierarchy between files in a git repository. Think of it as the list of files in a git repo, along with their "sha" hash identifier.

There are a few ways to do this using the "Create a tree", but the easiest is to just give a list of files and their contents for only the files we want to change.

We'll also need to set the "base" of this newly created tree to the `sha` of the latest commit in the branch, which we now have from our previous request:

```ts
async function createCommit(owner: string, repo: string, branch: string) {
  const ref = await fetchEndpoint(git.getRef, {
    owner,
    repo,
    ref: `heads/${branch}`,
  });

  if (ref.status === "error") {
    throw ref.error;
  }

  const commitTree = await fetchEndpoint(git.createTree, {
    owner,
    repo,
    tree: {
      tree: [],
      base_tree: ref.body.object.sha,
    },
  });
}
```

So far so good, but we still need to add content to the `tree` array to actually make any changes in this commit. If we hover over the `git.createTree` endpoint in VS Code, we can see what data is expected in the tree array:

![CleanShot 2022-09-23 at 14.07.33@2x.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1663938555963/O8l1gUhAp.png align="left")

Writing out an example entry and then hovering over each property presents helpful documentation about each one:

![CleanShot 2022-09-23 at 14.14.00@2x.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1663938849000/JpVe9pGXZ.png align="left")

Let's update the `tree` array with a new file based on the current datetime:

```ts
const commitTree = await fetchEndpoint(git.createTree, {
    owner,
    repo,
    tree: {
      tree: [
        {
          path: `playground/${Date.now()}.txt`,
          mode: "100644",
          type: "blob",
          content: `The time is ${(new Date()).toISOString()}`,
        },
      ],
      base_tree: ref.body.object.sha,
    },
  });
```

Now let's run `npm run dev` again and inspect the request to view the response data:

![CleanShot 2022-09-23 at 14.17.12@2x.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1663939101723/itRVcJLbe.png align="left")

Oops, looks like something went wrong.  Usually with the GitHub API, receiving a 404 status means either we fat fingered the repo name, or we're trying to perform an authorized request but haven't added any authorization info to the request.

If we navigate to our project dashboard, we can easily add authentication in API Hero, without having to change anything in our code:

![CleanShot 2022-09-23 at 14.20.23@2x.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1663939257853/-wZkuga17.png align="left")

Let's use the Personal Access Token authentication strategy to make requests as ourself . Follow the directions [here](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) for creating a PAT in GitHub, and then add your GitHub username as the username and the PAT as the password, like so:

![CleanShot 2022-09-23 at 14.25.37@2x.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1663939568203/RmoJwBMPK.png align="left")

After hitting save, we can run `npm run dev` again and head back to the Request History page in API Hero and we should now see a 201 response:

![CleanShot 2022-09-24 at 21.48.33@2x.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1664052578103/_BNXcxe8a.png align="left")

## 3. Create a commit

Okay, now that we've created the tree content of the commit, it's time to actually create the commit itself, with a message, the commit tree, and a reference to the commit's parent:

```ts
async function createCommit(owner: string, repo: string, branch: string) {
  const ref = await fetchEndpoint(git.getRef, {
    owner,
    repo,
    ref: `heads/${branch}`,
  });

  if (ref.status === "error") {
    throw ref.error;
  }

  const commitTree = await fetchEndpoint(git.createTree, {
    owner,
    repo,
    tree: {
      tree: [
        {
          path: `playground/${Date.now()}.txt`,
          mode: "100644",
          type: "blob",
          content: `The time is ${new Date().toISOString()}`,
        },
      ],
      base_tree: ref.body.object.sha,
    },
  });

  if (commitTree.status === "error") {
    throw commitTree.error;
  }

  const commit = await fetchEndpoint(git.createCommit, {
    owner,
    repo,
    commit: {
      message: "Create a new file",
      tree: commitTree.body.sha,
      parents: [ref.body.object.sha],
    },
  });
}
```

After running `npm run dev` again, you can see the request should have succeeded with another 201 created:

![CleanShot 2022-09-24 at 22.02.58@2x.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1664053429803/NYbdxRHzr.png align="left")

But if we head over to the GitHub repo's [commit-playground branch](https://github.com/apihero-run/apihero-github-commits/tree/commit-playground), we can see that our commit isn't showing up:

![CleanShot 2022-09-24 at 22.04.39@2x.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1664053510634/4DaQpJBqs.png align="left")

## 4. Update the HEAD ref

That's because we have to do 1 more step before our commit is finalized: update the HEAD of the `commit-playground` branch to point to the new commit's `sha`, using the [Update a reference](https://docs.github.com/en/rest/git/refs#update-a-reference) endpoint:

```ts
async function createCommit(owner: string, repo: string, branch: string) {
  const ref = await fetchEndpoint(git.getRef, {
    owner,
    repo,
    ref: `heads/${branch}`,
  });

  if (ref.status === "error") {
    throw ref.error;
  }

  const commitTree = await fetchEndpoint(git.createTree, {
    owner,
    repo,
    tree: {
      tree: [
        {
          path: `playground/${Date.now()}.txt`,
          mode: "100644",
          type: "blob",
          content: `The time is ${new Date().toISOString()}`,
        },
      ],
      base_tree: ref.body.object.sha,
    },
  });

  if (commitTree.status === "error") {
    throw commitTree.error;
  }

  const commit = await fetchEndpoint(git.createCommit, {
    owner,
    repo,
    commit: {
      message: "Create a new file",
      tree: commitTree.body.sha,
      parents: [ref.body.object.sha],
    },
  });

  if (commit.status === "error") {
    throw commit.error;
  }

  const updatedRef = await fetchEndpoint(git.updateRef, {
    owner,
    repo,
    ref: `heads/${branch}`,
    payload: {
      sha: commit.body.sha,
    },
  });
}
```

After running `npm run dev` again, we should see the request to update the ref in the Request History:

![CleanShot 2022-09-24 at 22.12.31@2x.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1664054077687/qJKsT65hH.png align="left")

And then heading back over to the repo on GitHub should show the new commit (as well as the [new file](https://github.com/apihero-run/apihero-github-commits/blob/commit-playground/playground/1664053929256.txt):

![CleanShot 2022-09-24 at 22.13.20@2x.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1664054102790/87zgN-XOa.png align="left")

## Wrap up

Create a commit in GitHub programmatically can be extremely useful, especially for automating devops tasks or when generating code. In fact, API Hero internally does this exact thing for when we release a new integration package (like the [@apihero/github](https://www.npmjs.com/package/@apihero/github) one we just used).

If you have any questions or feedback, please hop in to our [discord channel](https://discord.gg/4XZ3wsCXnG) or feel free to email me @ eric@apihero.run.










