Upload Git for Windows snapshot #16
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: upload-snapshot | |
| run-name: "Upload Git for Windows snapshot" | |
| on: | |
| push: | |
| env: | |
| OWNER: "${{ github.repository_owner }}" | |
| REPO: git | |
| SNAPSHOTS_REPO: git-snapshots | |
| ARTIFACTS_REPO: git-for-windows-automation | |
| I686_WORKFLOW_RUN_ID: 12969105876 | |
| X86_64_WORKFLOW_RUN_ID: 12965293373 | |
| AARCH64_WORKFLOW_RUN_ID: 12965294344 | |
| CREATE_CHECK_RUN: "true" | |
| NODEJS_VERSION: 16 | |
| jobs: | |
| upload-snapshot: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: download `bundle-artifacts` | |
| id: bundle-artifacts | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const { | |
| getWorkflowRunArtifactsURLs, | |
| downloadAndUnZip, | |
| } = require('./github-release') | |
| const token = ${{ toJSON(secrets.GITHUB_TOKEN) }} | |
| const workflowRunId = process.env.X86_64_WORKFLOW_RUN_ID | |
| const urls = await getWorkflowRunArtifactsURLs( | |
| console, | |
| token, | |
| process.env.OWNER, | |
| process.env.ARTIFACTS_REPO, | |
| workflowRunId | |
| ) | |
| core.setOutput('x86_64-urls', urls) | |
| const dir = 'bundle-artifacts-x86_64' | |
| await downloadAndUnZip(token, urls['bundle-artifacts'], dir) | |
| const fs = require('fs') | |
| const sha = fs.readFileSync(`${dir}/git-commit-oid`, 'utf-8').trim() | |
| core.notice(`git-commit-oid: ${sha}`) | |
| const githubApiRequest = require('./github-api-request') | |
| const { commit: { committer: { date } } } = await githubApiRequest( | |
| console, | |
| token, | |
| 'GET', | |
| `/repos/${process.env.OWNER}/${process.env.REPO}/commits/${sha}` | |
| ) | |
| // emulate Git's date/time format | |
| core.setOutput('date', new Date(date).toLocaleString('en-US', { | |
| weekday: 'short', | |
| month: 'short', | |
| day: 'numeric', | |
| year: 'numeric', | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| second: '2-digit', | |
| timeZoneName: 'longOffset', | |
| }).replace(/^(.*,.*),(.*),(.* )((PM|AM) GMT)([-+]\d\d):(\d\d)$/, '$1$2$3$6$7')) | |
| core.setOutput('git-commit-oid', sha) | |
| core.setOutput('ver', fs.readFileSync(`${dir}/ver`, 'utf-8').trim()) | |
| - name: Mirror Check Run to ${{ env.OWNER }}/${{ env.REPO }} | |
| if: env.CREATE_CHECK_RUN != 'false' | |
| uses: ./.github/actions/check-run-action | |
| with: | |
| app-id: ${{ secrets.GH_APP_ID }} | |
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| owner: ${{ env.OWNER }} | |
| repo: ${{ env.REPO }} | |
| rev: ${{ steps.bundle-artifacts.outputs.git-commit-oid }} | |
| check-run-name: "tag-git" | |
| title: "Upload snapshot ${{ steps.bundle-artifacts.outputs.ver }}" | |
| summary: "Upload snapshot ${{ steps.bundle-artifacts.outputs.ver }}" | |
| text: "For details, see [this run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id}})." | |
| details-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id}}" | |
| - name: download remaining artifacts | |
| id: download-artifacts | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const { | |
| getWorkflowRunArtifactsURLs, | |
| downloadAndUnZip, | |
| architectures | |
| } = require('./github-release') | |
| const token = ${{ toJSON(secrets.GITHUB_TOKEN) }} | |
| const directories = ['bundle-artifacts-x86_64'] | |
| for (const arch of architectures) { | |
| const architecture = arch.name | |
| const urls = architecture === 'x86_64' | |
| ? ${{ steps.bundle-artifacts.outputs.x86_64-urls }} | |
| : await getWorkflowRunArtifactsURLs( | |
| console, | |
| token, | |
| process.env.OWNER, | |
| process.env.ARTIFACTS_REPO, | |
| process.env[`${architecture.toUpperCase()}_WORKFLOW_RUN_ID`] | |
| ) | |
| for (const name of Object.keys(urls)) { | |
| if (name === 'bundle-artifacts' && architecture === 'x86_64') continue // already got it | |
| if (!name.match(/^(installer|portable|mingit|bundle)/)) continue | |
| const outputDirectory = name.endsWith(`-${architecture}`) ? name : `${name}-${architecture}` | |
| console.log(`Downloading ${name} and extracting to ${outputDirectory}/`) | |
| await downloadAndUnZip(token, urls[name], outputDirectory) | |
| directories.push(outputDirectory) | |
| } | |
| } | |
| const fs = require('fs') | |
| const assetsToUpload = directories | |
| .map(directory => fs | |
| .readdirSync(directory) | |
| .filter(file => file.match(/^(Min|Portable)Git-.*\.(exe|zip)$/)) | |
| .map(file => `${directory}/${file}`)) | |
| .flat() | |
| if (assetsToUpload.length === 0) throw new Error(`No assets to upload!`) | |
| console.log(JSON.stringify(assetsToUpload, null, 2)) | |
| core.setOutput('paths', assetsToUpload.join(' ')) | |
| return assetsToUpload | |
| - name: update check-run | |
| if: env.CREATE_CHECK_RUN != 'false' | |
| uses: ./.github/actions/check-run-action | |
| with: | |
| app-id: ${{ secrets.GH_APP_ID }} | |
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| append-text: 'Downloaded all artifacts' | |
| - name: validate | |
| id: validate | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs') | |
| const { architectures } = require('./github-release') | |
| for (const arch of architectures) { | |
| const ver = fs.readFileSync(`bundle-artifacts-${arch.name}/ver`, 'utf-8').trim() | |
| if (${{ toJSON(steps.bundle-artifacts.outputs.ver) }} !== ver) { | |
| core.error(`Mismatched version between x86_64 and ${arch.name}: ${{ toJSON(steps.bundle-artifacts.outputs.ver) }} != "${ver}"`) | |
| process.exit(1) | |
| } | |
| } | |
| const githubApiRequest = require('./github-api-request') | |
| const { ahead_by } = await githubApiRequest( | |
| console, | |
| ${{ toJSON(secrets.GITHUB_TOKEN) }}, | |
| 'GET', | |
| `/repos/${process.env.OWNER}/${process.env.REPO}/compare/HEAD...${{ steps.bundle-artifacts.outputs.git-commit-oid }}` | |
| ) | |
| if (ahead_by !== 0) { | |
| core.error(`The snapshots are built from a commit that is not reachable from git-for-windows/git's default branch!`) | |
| process.exit(1) | |
| } | |
| - name: configure token | |
| id: snapshots-token | |
| uses: actions/github-script@v7 | |
| with: | |
| result-encoding: string | |
| script: | | |
| const { callGit, getPushAuthorizationHeader } = require('./repository-updates') | |
| const header = await getPushAuthorizationHeader( | |
| console, | |
| core.setSecret, | |
| ${{ secrets.GH_APP_ID }}, | |
| ${{ toJSON(secrets.GH_APP_PRIVATE_KEY) }}, | |
| process.env.OWNER, | |
| process.env.SNAPSHOTS_REPO | |
| ) | |
| console.log(callGit([ | |
| 'config', | |
| '--global', | |
| `http.https://${{ github.server_url }}/${process.env.OWNER}/${process.env.SNAPSHOTS_REPO}.extraHeader`, | |
| header | |
| ])) | |
| return Buffer.from(header.replace(/^Authorization: Basic /, ''), 'base64').toString('utf-8').replace(/^PAT:/, '') | |
| - name: figure out if we need to push commits | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| // Since `git-snapshots` is a fork, and forks share the same object store, we can | |
| // assume that `git-commit-oid` is accessible in the `git-snapshots` repository even | |
| // if it might not yet be reachable. | |
| const githubApiRequest = require('./github-api-request') | |
| const token = ${{ toJSON(steps.snapshots-token.outputs.result) }} | |
| const sha = ${{ toJSON(steps.bundle-artifacts.outputs.git-commit-oid) }} | |
| const { ahead_by, behind_by } = await githubApiRequest( | |
| console, | |
| token, | |
| 'GET', | |
| `/repos/${process.env.OWNER}/${process.env.SNAPSHOTS_REPO}/compare/${sha}...HEAD` | |
| ) | |
| if (ahead_by > 0) throw new Error(`The snapshots repository is ahead of ${sha}!`) | |
| if (behind_by > 0) { | |
| await githubApiRequest( | |
| console, | |
| token, | |
| 'PATCH', | |
| `/repos/${process.env.OWNER}/${process.env.SNAPSHOTS_REPO}/git/refs/heads/main`, { | |
| sha, | |
| force: false // require fast-forward | |
| } | |
| ) | |
| } | |
| - name: upload snapshots to ${{ env.SNAPSHOTS_REPO }} | |
| env: | |
| GH_TOKEN: ${{ steps.snapshots-token.outputs.result }} | |
| run: | | |
| gh release create \ | |
| -R "$OWNER/$SNAPSHOTS_REPO" \ | |
| --target "${{ steps.bundle-artifacts.outputs.git-commit-oid }}" \ | |
| --title "${{ steps.bundle-artifacts.outputs.date }}" \ | |
| ${{ steps.bundle-artifacts.outputs.ver }} \ | |
| ${{ steps.download-artifacts.outputs.paths }} | |
| - name: update check-run | |
| if: env.CREATE_CHECK_RUN != 'false' | |
| uses: ./.github/actions/check-run-action | |
| with: | |
| app-id: ${{ secrets.GH_APP_ID }} | |
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| append-text: 'Created release at https://${{ github.server_url }}/${{ env.OWNER }}/${{ env.SNAPSHOTS_REPO }}/releases/tag/${{ steps.bundle-artifacts.outputs.ver }}' | |
| - name: clone gh-pages | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ env.OWNER }}/${{ env.SNAPSHOTS_REPO }} | |
| ref: gh-pages | |
| path: gh-pages | |
| token: ${{ steps.snapshots-token.outputs.result }} | |
| - name: update index.html | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const urlPrefix = `https://${{ github.server_url }}/${{ env.OWNER }}/${{ env.SNAPSHOTS_REPO }}/releases/download/${{ steps.bundle-artifacts.outputs.ver }}/` | |
| process.chdir('gh-pages') | |
| const main = require('./add-entry') | |
| await main( | |
| '--date=${{ steps.bundle-artifacts.outputs.date }}', | |
| '--commit=${{ steps.bundle-artifacts.outputs.git-commit-oid }}', | |
| ...${{ steps.download-artifacts.outputs.result }} | |
| .map(path => `${urlPrefix}${path.replace(/.*\//, '')}`) | |
| ) | |
| - name: push gh-pages | |
| run: git -C gh-pages push | |
| - name: mark check run as completed | |
| if: env.CREATE_CHECK_RUN != 'false' && always() | |
| uses: ./.github/actions/check-run-action | |
| with: | |
| app-id: ${{ secrets.GH_APP_ID }} | |
| private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} | |
| append-text: "${{ job.status == 'success' && 'Done!' || format('Completed: {0}', job.status) }}." | |
| conclusion: ${{ job.status }} |