DEV Community

Paul Kinlan for Google Web Dev

Posted on • Originally published at paul.kinlan.me on

2 1

Creating a commit with multiple files to Github with JS on the web

My site is entirely static. It’s built with Hugo and hosted with Zeit. I’m pretty happy with the setup, I get near instant builds and super fast CDN’d content delivery and I can do all the things that I need to because I don’t have to manage any state.

I’ve created a simple UI for this site and also my podcast creator that enables me to quickly post new content to my statically hosted site.

So. How did I do it?

It’s a combination of Firebase Auth against my Github Repo, EditorJS to create edit the content (it’s neat) and Octokat.js to commit to the repo and then Zeit’s Github integration to do my hugo build. With this set up, I am able to have an entirely self hosted static CMS, similar to how a user might create posts in a database backed CMS like Wordpress.

In this post I am just going to focus on one part of the infrastructure - committing multiple files to Github because it took me a little while to work out.

The entire code can be seen on my repo.

If you are building a Web UI that needs to commit directly to Github, the best library that I have found is Octokat - it works with CORS and it seems to handle the entire API surface of the Github API.

Git can be a complex beast when it comes to understanding how the tree, branches and other pieces work so I took some decisions that made it easier.

  1. I will be only able to push to the master branch known as heads/master.
  2. I will know where certain files will be stored (Hugo forces me to have a specific directory structure)

With that in mind, the general process to creating a commit with multiple files is as follows:

Get a reference to the repo.

  1. Get a reference to the tip of the tree on heads/master branch.
  2. For each file that we want to commit create a blob and then store the references to the sha identifier, path, mode in an array.
  3. Create a new tree that contains all the blobs to add to the reference to the tip of the heads/master tree, and store the new sha pointer to this tree.
  4. Create a commit that points to this new tree and then push to the heads/master branch.

The code pretty much follows that flow. Because I can assume the path structure for certain inputs I don’t need to build any complex UI or management for the files.

const createCommit = async (repositoryUrl, filename, data, images, commitMessage, recording) => {
  try {
    const token = localStorage.getItem('accessToken');
    const github = new Octokat({ 'token': token });
    const [user, repoName] = repositoryUrl.split('/');

    if(user === null || repoName === null) {
      alert('Please specifiy a repo');
      return;
    }

    const markdownPath = `site/content/${filename}.markdown`.toLowerCase();
    let repo = await github.repos(user, repoName).fetch();
    let main = await repo.git.refs('heads/master').fetch();
    let treeItems = [];

    for(let image of images) {
      let imageGit = await repo.git.blobs.create({ content: image.data, encoding: 'base64' });
      let imagePath = `site/static/images/${image.name}`.toLowerCase();
      treeItems.push({
        path: imagePath,
        sha: imageGit.sha,
        mode: "100644",
        type: "blob"
        });
    }

    if (recording) {
      let audioGit = await repo.git.blobs.create({ content: recording.data, encoding: 'base64' });
      let audioPath = `site/static/audio/${recording.name}.${recording.extension}`.toLowerCase();
      treeItems.push({
        path: audioPath,
        sha: audioGit.sha,
        mode: "100644",
        type: "blob"
        });
    }

    let markdownFile = await repo.git.blobs.create({ content: btoa(jsonEncode(data)), encoding: 'base64' });
    treeItems.push({
      path: markdownPath,
      sha: markdownFile.sha,
      mode: "100644",
      type: "blob"
    });

    let tree = await repo.git.trees.create({
      tree: treeItems,
      base_tree: main.object.sha
    });

    let commit = await repo.git.commits.create({
      message: `Created via Web - ${commitMessage}`,
      tree: tree.sha,
      parents: [main.object.sha]});

    main.update({sha: commit.sha})

    logToToast('Posted');
  } catch (err) {
    console.error(err);
    logToToast(err);
  }
}

Let me know if you’ve done anything similar with static hosting. I’m very excited that I can build a modern frontend for what is an entirely server-less hosting infrastructure.

Image of Stellar post

🚀 Stellar Dev Diaries Series: Episode 1 is LIVE!

Ever wondered what it takes to build a web3 startup from scratch? In the Stellar Dev Diaries series, we follow the journey of a team of developers building on the Stellar Network as they go from hackathon win to getting funded and launching on mainnet.

Read more

Top comments (1)

Collapse
 
kienngo profile image
ken

Definitely gonna try this out. Thanks a lot!

👋 Kindness is contagious

Dive into this thoughtful article, cherished within the supportive DEV Community. Coders of every background are encouraged to share and grow our collective expertise.

A genuine "thank you" can brighten someone’s day—drop your appreciation in the comments below!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found value here? A quick thank you to the author makes a big difference.

Okay