From 498f4bc98c8db7aad517bd7de940fd368ee2a72f Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Mon, 23 Jun 2025 22:26:43 -0700 Subject: [PATCH] build: rewrite push-patch to use the github API instead of local git commits to ensure commits are signed (#47401) * build: rewrite push-patch to use the github API instead of local git commits to ensure commits are signed * again (cherry picked from commit a21afc3e45d15f88c1f754d5990908f248909b41) * use pr head ref (cherry picked from commit 0edcc985fadcce64f01fb77b1c15653d5e66e864) --- .github/actions/checkout/action.yml | 12 +-- script/patch-up.js | 128 ++++++++++++++++++++++++++++ script/push-patch.js | 38 --------- 3 files changed, 134 insertions(+), 44 deletions(-) create mode 100644 script/patch-up.js delete mode 100644 script/push-patch.js diff --git a/.github/actions/checkout/action.yml b/.github/actions/checkout/action.yml index 2d8cf38e00c0..3ff4b4b06d02 100644 --- a/.github/actions/checkout/action.yml +++ b/.github/actions/checkout/action.yml @@ -117,12 +117,7 @@ runs: git update-index --refresh || true if ! git diff-index --quiet HEAD --; then # There are changes to the patches. Make a git commit with the updated patches - git add patches - GIT_COMMITTER_NAME="PatchUp" GIT_COMMITTER_EMAIL="73610968+patchup[bot]@users.noreply.github.com" git commit -m "chore: update patches" --author="PatchUp <73610968+patchup[bot]@users.noreply.github.com>" - # Export it - mkdir -p ../../patches - git format-patch -1 --stdout --keep-subject --no-stat --full-index > ../../patches/update-patches.patch - if node ./script/push-patch.js; then + if node ./script/patch-up.js; then echo echo "======================================================================" echo "Changes to the patches when applying, we have auto-pushed the diff to the current branch" @@ -130,6 +125,11 @@ runs: echo "======================================================================" exit 1 else + git add patches + GIT_COMMITTER_NAME="PatchUp" GIT_COMMITTER_EMAIL="73610968+patchup[bot]@users.noreply.github.com" git commit -m "chore: update patches" --author="PatchUp <73610968+patchup[bot]@users.noreply.github.com>" + # Export it + mkdir -p ../../patches + git format-patch -1 --stdout --keep-subject --no-stat --full-index > ../../patches/update-patches.patch echo echo "======================================================================" echo "There were changes to the patches when applying." diff --git a/script/patch-up.js b/script/patch-up.js new file mode 100644 index 000000000000..0b6d91a824c1 --- /dev/null +++ b/script/patch-up.js @@ -0,0 +1,128 @@ +const { appCredentialsFromString, getAuthOptionsForRepo } = require('@electron/github-app-auth'); + +const { Octokit } = require('@octokit/rest'); + +const fs = require('node:fs'); +const path = require('node:path'); + +const { PATCH_UP_APP_CREDS } = process.env; + +const REPO_OWNER = 'electron'; +const REPO_NAME = 'electron'; + +async function getAllPatchFiles (dir) { + const files = []; + + async function walkDir (currentDir) { + const entries = await fs.promises.readdir(currentDir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(currentDir, entry.name); + + if (entry.isDirectory()) { + await walkDir(fullPath); + } else if (entry.isFile() && (entry.name.endsWith('.patch') || entry.name === 'README.md' || entry.name === 'config.json')) { + const relativePath = path.relative(process.cwd(), fullPath); + const content = await fs.promises.readFile(fullPath, 'utf8'); + files.push({ + path: relativePath, + content + }); + } + } + } + + await walkDir(dir); + return files; +} + +function getCurrentCommitSha () { + return process.env.GITHUB_SHA; +} + +function getCurrentBranch () { + return process.env.GITHUB_HEAD_REF; +} + +async function main () { + if (!PATCH_UP_APP_CREDS) { + throw new Error('PATCH_UP_APP_CREDS environment variable not set'); + } + + const currentBranch = getCurrentBranch(); + + if (!currentBranch) { + throw new Error('GITHUB_HEAD_REF environment variable not set. Patch Up only works in PR workflows currently.'); + } + + const octokit = new Octokit({ + ...await getAuthOptionsForRepo({ + name: REPO_OWNER, + owner: REPO_NAME + }, appCredentialsFromString(PATCH_UP_APP_CREDS)) + }); + + const patchesDir = path.join(process.cwd(), 'patches'); + + // Get current git state + const currentCommitSha = getCurrentCommitSha(); + + // Get the tree SHA from the current commit + const currentCommit = await octokit.git.getCommit({ + owner: REPO_OWNER, + repo: REPO_NAME, + commit_sha: currentCommitSha + }); + const baseTreeSha = currentCommit.data.tree.sha; + + // Find all patch files + const patchFiles = await getAllPatchFiles(patchesDir); + + if (patchFiles.length === 0) { + throw new Error('No patch files found'); + } + + console.log(`Found ${patchFiles.length} patch files`); + + // Create a new tree with the patch files + const tree = patchFiles.map(file => ({ + path: file.path, + mode: '100644', + type: 'blob', + content: file.content + })); + + const treeResponse = await octokit.git.createTree({ + owner: REPO_OWNER, + repo: REPO_NAME, + base_tree: baseTreeSha, + tree + }); + + // Create a new commit + const commitMessage = 'chore: update patches'; + const commitResponse = await octokit.git.createCommit({ + owner: REPO_OWNER, + repo: REPO_NAME, + message: commitMessage, + tree: treeResponse.data.sha, + parents: [currentCommitSha] + }); + + // Update the branch reference + await octokit.git.updateRef({ + owner: REPO_OWNER, + repo: REPO_NAME, + ref: `heads/${currentBranch}`, + sha: commitResponse.data.sha + }); + + console.log(`Successfully pushed commit ${commitResponse.data.sha} to ${currentBranch}`); +} + +if (require.main === module) { + main().catch((err) => { + console.error(err); + process.exit(1); + }); +} diff --git a/script/push-patch.js b/script/push-patch.js deleted file mode 100644 index 7265a10f5961..000000000000 --- a/script/push-patch.js +++ /dev/null @@ -1,38 +0,0 @@ -const { appCredentialsFromString, getTokenForRepo } = require('@electron/github-app-auth'); - -const cp = require('node:child_process'); - -const { PATCH_UP_APP_CREDS } = process.env; - -async function main () { - if (!PATCH_UP_APP_CREDS) { - throw new Error('PATCH_UP_APP_CREDS environment variable not set'); - } - - const token = await getTokenForRepo( - { - name: 'electron', - owner: 'electron' - }, - appCredentialsFromString(PATCH_UP_APP_CREDS) - ); - - const remoteURL = `https://x-access-token:${token}@github.com/electron/electron.git`; - - // NEVER LOG THE OUTPUT OF THIS COMMAND - // GIT LEAKS THE ACCESS CREDENTIALS IN CONSOLE LOGS - const { status } = cp.spawnSync('git', ['push', '--set-upstream', remoteURL], { - stdio: 'ignore' - }); - - if (status !== 0) { - throw new Error('Failed to push to target branch'); - } -} - -if (require.main === module) { - main().catch((err) => { - console.error(err); - process.exit(1); - }); -}