Skip to content

Commit

Permalink
fix: git commit using github API
Browse files Browse the repository at this point in the history
Changes the way that we are creating GitHub trees, because we are hitting
some problems in the GitHub side receiving 502 error with no more information
that "Server Error" and this kind of errors are not documented.

This changes the way that we create the new tree in GitHub. Now, instead
of creating the full tree to commit, we creates a new tree with only the
changes and referring to the tree of the previous commit.
  • Loading branch information
alvarezfr committed Sep 4, 2023
1 parent a632a16 commit e6e07f5
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 78 deletions.
4 changes: 2 additions & 2 deletions dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as core from '@actions/core'
import * as yaml from 'js-yaml'
import * as fs from 'fs-extra'
import fs from 'fs-extra'
import * as path from 'path'
import { getInput } from 'action-input-parser'

Expand Down
162 changes: 89 additions & 73 deletions src/git.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import * as github from '@actions/github'
import { GitHub, getOctokitOptions } from '@actions/github/lib/utils.js'
import { throttling } from '@octokit/plugin-throttling'
import * as path from 'path'
import * as fs from 'fs/promises'

import config from './config.js'

Expand Down Expand Up @@ -200,13 +199,6 @@ export default class Git {
}
}

async getBlobBase64Content(file) {
const fileRelativePath = path.join(this.workingDir, file)
const fileContent = await fs.readFile(fileRelativePath)

return fileContent.toString('base64')
}

async getLastCommitSha() {
this.lastCommitSha = await execCmd(
`git rev-parse HEAD`,
Expand Down Expand Up @@ -243,56 +235,69 @@ export default class Git {
}

// Returns a git tree parsed for the specified commit sha
async getTree(commitSha) {
const output = await execCmd(
`git ls-tree -r --full-tree ${ commitSha }`,
async getTreeId(commitSha) {
core.debug(`Getting treeId for commit ${ commitSha }`)
const output = (await execCmd(
`git cat-file -p ${ commitSha }`,
this.workingDir
)
)).split('\n')

const tree = []
for (const treeObject of output.split('\n')) {
const [ mode, type, sha ] = treeObject.split(/\s/)
const file = treeObject.split('\t')[1]

const treeEntry = {
mode,
type,
sha,
path: file
}

tree.push(treeEntry)
}
const commitHeaders = output.slice(0, output.findIndex((e) => e === ''))
const tree = commitHeaders.find((e) => e.startsWith('tree')).replace('tree ', '')

return tree
}

// Creates the blob objects in GitHub for the files that are not in the previous commit only
async createGithubBlobs(commitSha) {
core.debug('Creating missing blobs on GitHub')
const [ previousTree, tree ] = await Promise.all([ this.getTree(`${ commitSha }~1`), this.getTree(commitSha) ])
const promisesGithubCreateBlobs = []

for (const treeEntry of tree) {
// If the current treeEntry are in the previous tree, that means that the blob is uploaded and it doesn't need to be uploaded to GitHub again.
if (previousTree.findIndex((entry) => entry.sha === treeEntry.sha) !== -1) {
continue
}

const base64Content = await this.getBlobBase64Content(treeEntry.path)
async getTreeDiff(referenceTreeId, differenceTreeId) {
const output = await execCmd(
`git diff-tree ${ referenceTreeId } ${ differenceTreeId } -r`,
this.workingDir
)

// Creates the blob. We don't need to store the response because the local sha is the same and we can use it to reference the blob
const githubCreateBlobRequest = this.github.git.createBlob({
owner: this.repo.user,
repo: this.repo.name,
content: base64Content,
encoding: 'base64'
const diff = []
for (const line of output.split('\n')) {
const splitted = line
.replace(/^:/, '')
.replace('\t', ' ')
.split(' ')

const [
newMode,
previousMode,
newBlob,
previousBlob,
change,
path
] = splitted

diff.push({
newMode,
previousMode,
newBlob,
previousBlob,
change,
path
})
promisesGithubCreateBlobs.push(githubCreateBlobRequest)
}

// Wait for all the file uploads to be completed
await Promise.all(promisesGithubCreateBlobs)
return diff
}

// Creates the blob objects in GitHub for the files that are not in the previous commit only
async uploadGitHubBlob(blob) {
core.debug(`Uploading GitHub Blob for blob ${ blob }`)
const fileContent = await execCmd(
`git cat-file -p ${ blob }`,
this.workingDir
)

// Creates the blob. We don't need to store the response because the local sha is the same and we can use it to reference the blob
return this.github.git.createBlob({
owner: this.repo.user,
repo: this.repo.name,
content: fileContent.toString('base64'),
encoding: 'base64'
})
}

// Gets the commit list in chronological order
Expand All @@ -313,25 +318,10 @@ export default class Git {
)
}

// Returns an array of objects with the git tree and the commit, one entry for each pending commit to push
async getCommitsDataToPush() {
const commitsToPush = await this.getCommitsToPush()

const commitsData = []
for (const commitSha of commitsToPush) {
const [ commitMessage, tree ] = await Promise.all([ this.getCommitMessage(commitSha), this.getTree(commitSha), this.createGithubBlobs(commitSha) ])
const commitData = {
commitMessage,
tree
}
commitsData.push(commitData)
}
return commitsData
}

// A wrapper for running all the flow to generate all the pending commits using the GitHub API
async createGithubVerifiedCommits() {
const commitsData = await this.getCommitsDataToPush()
core.debug(`Creating Commits using GitHub API`)
const commits = await this.getCommitsToPush()

if (SKIP_PR === false) {
// Creates the PR branch if doesn't exists
Expand All @@ -350,8 +340,8 @@ export default class Git {
}
}

for (const commitData of commitsData) {
await this.createGithubTreeAndCommit(commitData.tree, commitData.commitMessage)
for (const commit of commits) {
await this.createGithubCommit(commit)
}

core.debug(`Updating branch ${ SKIP_PR === false ? this.prBranch : this.baseBranch } ref`)
Expand Down Expand Up @@ -502,21 +492,47 @@ export default class Git {
})
}

async createGithubTreeAndCommit(tree, commitMessage) {
async createGithubCommit(commitSha) {
const [ treeId, parentTreeId, commitMessage ] = await Promise.all([
this.getTreeId(`${ commitSha }`),
this.getTreeId(`${ commitSha }~1`),
this.getCommitMessage(commitSha)
])

const treeDiff = await this.getTreeDiff(treeId, parentTreeId)
core.debug(`Uploading the blobs to GitHub`)
const blobsToCreate = treeDiff
.filter((e) => e.newMode !== '000000') // Do not upload the blob if it is being removed

await Promise.all(blobsToCreate.map((e) => this.uploadGitHubBlob(e.newBlob)))
core.debug(`Creating a GitHub tree`)
const tree = treeDiff.map((e) => {
if (e.newMode === '000000') { // Set the sha to null to remove the file
e.newMode = e.previousMode
e.newBlob = null
}

const entry = {
path: e.path,
mode: e.newMode,
type: 'blob',
sha: e.newBlob
}

return entry
})

let treeSha
try {
core.debug(`Tree: ${JSON.stringify(tree)}`)
const request = await this.github.git.createTree({
owner: this.repo.user,
repo: this.repo.name,
tree
tree,
base_tree: parentTreeId
})
treeSha = request.data.sha
} catch (error) {

// error.request = {} // DEBUG: temporal
error.message = `Cannot create a new GitHub Tree: ${ JSON.stringify(error) }` // DEBUG: temporal
error.message = `Cannot create a new GitHub Tree: ${ error.message }`
throw error
}

Expand Down
4 changes: 2 additions & 2 deletions src/helpers.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as fs from 'fs-extra'
import fs from 'fs-extra'
import readfiles from 'node-readfiles'
import { exec } from 'child_process'
import * as core from '@actions/core'
import * as path from 'path'
import * as nunjucks from 'nunjucks'
import nunjucks from 'nunjucks'

nunjucks.configure({ autoescape: true, trimBlocks: true, lstripBlocks: true })

Expand Down

0 comments on commit e6e07f5

Please sign in to comment.