Stretch0
Stretch0

Reputation: 9251

How to commit a folder and open a Pull Request via Github API?

I want to commit a pipeline config for a user via the Github API.

So far I am able to commit just a file named main.yaml into the root directory of the repo but I need the file to sit within .github/workflows/main.yaml.

The code I have so far is this:

const commitWorkflowConfig = async ({
  ownerName,
  repoName
}) => {

  const pipelineConfig = fs.readFileSync(__dirname + '/../../../templates/main.yaml', { encoding: "utf-8" })

  // Create Blob from the content
  const blob = await axios.post(`https://api.github.com/repos/${ownerName}/${repoName}/git/blobs`, {
    content: pipelineConfig,
    encoding: "utf-8"
  })

  // Get last commit hash of Master
  const {
    commit: {
      sha: masterSha
    }
  } = await axios.get(`https://api.github.com/repos/${ownerName}/${repoName}/branches/master`)

  // Create new branch from master
  const branch = await axios.post(`https://api.github.com/repos/${ownerName}/${repoName}/git/refs`, 
  {
    "ref": "refs/heads/workflow-pipeline",
    "sha": masterSha
  })

  // Create commit to new branch
  const commit = await axios.put(`https://api.github.com/repos/${ownerName}/${repoName}/contents/main.yaml`, {
    message: "New commit",
    content: pipelineConfig,
    sha: blob.sha,
    branch: "workflow-pipeline"
  })

  // Open Pull Request
  const response = await axios.post(`https://api.github.com/repos/${ownerName}/${repoName}/pulls`, {
    title: "New PR",
    head: "workflow-pipeline",
    base: "master"
  })
  
}    const commitWorkflowConfig = async ({
  ownerName,
  repoName
}) => {

  const pipelineConfig = fs.readFileSync(__dirname + '/../../../templates/main.yaml', { encoding: "utf-8" })

  // Create Blob from the content
  const blob = await axios.post(`https://api.github.com/repos/${ownerName}/${repoName}/git/blobs`, {
    content: pipelineConfig,
    encoding: "utf-8"
  })

  // Get last commit hash of Master
  const {
    commit: {
      sha: masterSha
    }
  } = await axios.get(`https://api.github.com/repos/${ownerName}/${repoName}/branches/master`)

  // Create new branch from master
  const branch = await axios.post(`https://api.github.com/repos/${ownerName}/${repoName}/git/refs`, 
  {
    "ref": "refs/heads/workflow-pipeline",
    "sha": masterSha
  })

  // Create commit to new branch
  const commit = await axios.put(`https://api.github.com/repos/${ownerName}/${repoName}/contents/main.yaml`, {
    message: "New commit",
    content: pipelineConfig,
    sha: blob.sha,
    branch: "workflow-pipeline"
  })

  // Open Pull Request
  const response = await axios.post(`https://api.github.com/repos/${ownerName}/${repoName}/pulls`, {
    title: "New PR",
    head: "workflow-pipeline",
    base: "master"
  })
  
}

This feels like a lot of API calls all for just committing one file and opening a PR, I suspect I am doing something wrong?

When I create the new commit, it doesn't allow me to add any path or add .github/workflows/main.yaml to the end of the URL as I get a 404. Is there any way I can update this commit to commit to a folder instead of the root directory?

To summarise, How can I make a simple commit of .github/workflows/main.yaml to a new branch and open a PR for it?

Create tree example:

const tree = await this.post(`https://api.github.com/repos/${ownerName}/${repoName}/git/trees`, {
  base_tree: masterSha,
  tree: [
    {
      path: ".github",
      mode: "040000",
      type: "tree"
    },
    {
      path: ".github/workflow",
      mode: "040000",
      type: "tree"
    },
    {
      path: ".github/workflow/main.yaml",
      mode: "100755",
      type: "tree",
      content: pipelineConfig
    },
  ]
})

Creating the tree

const { tree } = await axios.post(`https://api.github.com/repos/${ownerName}/${repoName}/git/trees`, {
  base_tree: masterSha,
  tree: [
    {
      path: "workflows/main.yaml",
      mode: "100644", 
      type: "blob",
      sha
    }
  ]
})

const workflowTree = tree.find(t => t.path === "workflows");

await axios.post(`https://api.github.com/repos/${ownerName}/${repoName}/git/trees`, {
  base_tree: masterSha,
  tree: [
    {
      path: ".github/workflows",
      mode: workflowTree.mode, // "040000"
      type: workflowTree.type, // "tree"
      sha: workflowTree.sha 
    }
  ]
})

Upvotes: 4

Views: 1349

Answers (2)

dimaslz
dimaslz

Reputation: 515

I hope this can help you, I am working on the same topic where I need to update files inside .github/workflows folder by API and, I think I found a better and easy way.

I don't know since when but, with this endpoint https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents with the right permissions (scope workflow in your authToken), you can create and update files into the .github/workflows folder.

// to create a new file
await octokit.request('PUT /repos/{owner}/{repo}/contents/{path}', {
  owner: 'octocat',
  repo: 'hello-world',
  path: '.github/workflows/your-file.yml',
  message: 'Your commit message',
  content: 'contentInBase64'
});

// to update a file

// get the sha of the file that you want to update
const { 
  data: contentData 
} = await octokit.request('GET /repos/{owner}/{repo}/contents/{path}', {
  owner: 'octocat',
  repo: 'hello-world',
  path: '.github/workflows/your-file.yml',
});

const shaOfCurrentFileToUpdate = contentData.sha;

await octokit.request('PUT /repos/{owner}/{repo}/contents/{path}', {
  owner: 'octocat',
  repo: 'hello-world',
  path: '.github/workflows/your-file.yml',
  message: 'Your new commit message',
  content: 'newContentInBase64'
  sha: shaOfCurrentFileToUpdate
});

With this, I removed a lot of lines of code and solved the issue on my side, I hope is helpful for you too.

Upvotes: 1

programmerRaj
programmerRaj

Reputation: 2058

The following code does not use the GitHub API properly

// Create commit to new branch
  const commit = await axios.put(`https://api.github.com/repos/${ownerName}/${repoName}/contents/main.yaml`, {
    message: "New commit",
    content: pipelineConfig,
    sha: blob.sha,
    branch: "workflow-pipeline"
  })

You cannot directly edit the contents of a file. You need to use the trees API to create an entirely new tree based off of the original tree.

Steps

  1. Create the blob for the new file (https://docs.github.com/en/rest/reference/git#create-a-blob). You already did this step good job.

  2. Get the tree that you want to make your new branch based off of (https://docs.github.com/en/rest/reference/repos#get-a-branch). Note that you have to get the tree sha, not the commit sha.

  3. Create a new tree with that file added (https://docs.github.com/en/rest/reference/git#create-a-tree). I think this step will be the most complicated, because for each 'folder' tree created, a parent 'folder' tree will also need to be created to contain the newly created 'folder' tree. So if you want to modify the .github/workflows folder, you'll first have to create a new tree based on.github/workflows. Let's say that tree sha was abc.... Then you'll need to create a new tree based on the .github folder, and make the workflows dir abc..., and not the old one.

  4. Create a commit (https://docs.github.com/en/rest/reference/git#create-a-commit). Use the sha of the root tree you created in the previous step.

  5. Create a new branch (https://docs.github.com/en/rest/reference/git#create-a-reference). In the question code, you created it before you created the commit, which doesn't make sense. You need to create it after you create the commit, so that it's head will point to the sha of the commit you created.

  6. Create the pull request (https://docs.github.com/en/rest/reference/pulls#create-a-pull-request). You already have this step in your code.

Here is a visual that explains the steps 2 and 3 for adding a main.yml file to .github/workflows:

Original tree        | New Tree (but the '.github' tree references ^b, not b)
- sha: a           --> - sha: ^a
- files:               - files:
  - .github        -->   - .github (but the 'workflows' tree references ^c, not c)
    - sha: b               - sha: ^b
    - files:               - files
      - workflows  -->       - workflows (but with the main.yml)
        - sha: c               - sha: ^c
        - files:               - files:
                                 - main.yml (reference the sha of the blob you created)
          ...
      ...
  ...

There are three -->s in the visual. Each --> is a request to make.

  1. Start with creating the ^c tree, which is based off of the c tree and has the added main.yml file.
  2. Create the ^b tree, which is based off of b but has ^c in it.
  3. Create the ^a tree, which is based off of a but has ^b in it.

And those are the steps to creating a simple simple, but complicated to create pull request.

It's surprising how many API calls are needed for this. 5 + {how deep the file you want to add is}

Upvotes: 3

Related Questions