Skip to content

Commit 679f6aa

Browse files
committed
Add a workflow definition to upload Git for Windows' snapshots
This is another step in the migration from the Azure Pipeline that produced Git for Windows' snapshot builds and the Azure Release Pipeline that then uploaded them to Azure Blobs. With this new GitHub workflow, all we need are the successful `git-artifacts` runs (one per CPU architecture), and the workflow will upload them to the new snapshot location reachable via https://gitforwindows.org/git-snapshots. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
1 parent 600d642 commit 679f6aa

File tree

2 files changed

+271
-0
lines changed

2 files changed

+271
-0
lines changed
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
name: upload-snapshot
2+
run-name: "Upload Git for Windows snapshot"
3+
4+
on:
5+
workflow_dispatch:
6+
inputs:
7+
git_artifacts_i686_workflow_run_id:
8+
description: 'ID of the git-artifacts (i686) workflow run'
9+
required: true
10+
git_artifacts_x86_64_workflow_run_id:
11+
description: 'ID of the git-artifacts (x86_64) workflow run'
12+
required: true
13+
git_artifacts_aarch64_workflow_run_id:
14+
description: 'ID of the git-artifacts (aarch64) workflow run'
15+
required: true
16+
17+
env:
18+
OWNER: "${{ github.repository_owner }}"
19+
REPO: git
20+
SNAPSHOTS_REPO: git-snapshots
21+
ARTIFACTS_REPO: git-for-windows-automation
22+
I686_WORKFLOW_RUN_ID: "${{ github.event.inputs.git_artifacts_i686_workflow_run_id }}"
23+
X86_64_WORKFLOW_RUN_ID: "${{ github.event.inputs.git_artifacts_x86_64_workflow_run_id }}"
24+
AARCH64_WORKFLOW_RUN_ID: "${{ github.event.inputs.git_artifacts_aarch64_workflow_run_id }}"
25+
CREATE_CHECK_RUN: "true"
26+
NODEJS_VERSION: 16
27+
28+
jobs:
29+
upload-snapshot:
30+
runs-on: ubuntu-latest
31+
steps:
32+
- uses: actions/checkout@v4
33+
- name: download `bundle-artifacts`
34+
id: bundle-artifacts
35+
uses: actions/github-script@v7
36+
with:
37+
script: |
38+
const {
39+
getWorkflowRunArtifactsURLs,
40+
downloadAndUnZip,
41+
} = require('./github-release')
42+
43+
const token = ${{ toJSON(secrets.GITHUB_TOKEN) }}
44+
const workflowRunId = process.env.X86_64_WORKFLOW_RUN_ID
45+
const urls = await getWorkflowRunArtifactsURLs(
46+
console,
47+
token,
48+
process.env.OWNER,
49+
process.env.ARTIFACTS_REPO,
50+
workflowRunId
51+
)
52+
core.setOutput('x86_64-urls', urls)
53+
54+
const dir = 'bundle-artifacts-x86_64'
55+
await downloadAndUnZip(token, urls['bundle-artifacts'], dir)
56+
57+
const fs = require('fs')
58+
const sha = fs.readFileSync(`${dir}/git-commit-oid`, 'utf-8').trim()
59+
core.notice(`git-commit-oid: ${sha}`)
60+
61+
const githubApiRequest = require('./github-api-request')
62+
const { commit: { committer: { date } } } = await githubApiRequest(
63+
console,
64+
token,
65+
'GET',
66+
`/repos/${process.env.OWNER}/${process.env.REPO}/commits/${sha}`
67+
)
68+
69+
// emulate Git's date/time format
70+
core.setOutput('date', new Date(date).toLocaleString('en-US', {
71+
weekday: 'short',
72+
month: 'short',
73+
day: 'numeric',
74+
year: 'numeric',
75+
hour: '2-digit',
76+
minute: '2-digit',
77+
second: '2-digit',
78+
timeZoneName: 'longOffset',
79+
}).replace(/^(.*,.*),(.*),(.* )((PM|AM) GMT)([-+]\d\d):(\d\d)$/, '$1$2$3$6$7'))
80+
core.setOutput('git-commit-oid', sha)
81+
core.setOutput('ver', fs.readFileSync(`${dir}/ver`, 'utf-8').trim())
82+
- name: Mirror Check Run to ${{ env.OWNER }}/${{ env.REPO }}
83+
if: env.CREATE_CHECK_RUN != 'false'
84+
uses: ./.github/actions/check-run-action
85+
with:
86+
app-id: ${{ secrets.GH_APP_ID }}
87+
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
88+
owner: ${{ env.OWNER }}
89+
repo: ${{ env.REPO }}
90+
rev: ${{ steps.bundle-artifacts.outputs.git-commit-oid }}
91+
check-run-name: "tag-git"
92+
title: "Upload snapshot ${{ steps.bundle-artifacts.outputs.ver }}"
93+
summary: "Upload snapshot ${{ steps.bundle-artifacts.outputs.ver }}"
94+
text: "For details, see [this run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id}})."
95+
details-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id}}"
96+
- name: download remaining artifacts
97+
id: download-artifacts
98+
uses: actions/github-script@v7
99+
with:
100+
script: |
101+
const {
102+
getWorkflowRunArtifactsURLs,
103+
downloadAndUnZip,
104+
architectures
105+
} = require('./github-release')
106+
107+
const token = ${{ toJSON(secrets.GITHUB_TOKEN) }}
108+
const directories = ['bundle-artifacts-x86_64']
109+
for (const arch of architectures) {
110+
const architecture = arch.name
111+
112+
const urls = architecture === 'x86_64'
113+
? ${{ steps.bundle-artifacts.outputs.x86_64-urls }}
114+
: await getWorkflowRunArtifactsURLs(
115+
console,
116+
token,
117+
process.env.OWNER,
118+
process.env.ARTIFACTS_REPO,
119+
process.env[`${architecture.toUpperCase()}_WORKFLOW_RUN_ID`]
120+
)
121+
for (const name of Object.keys(urls)) {
122+
if (name === 'bundle-artifacts' && architecture === 'x86_64') continue // already got it
123+
if (!name.match(/^(installer|portable|mingit|bundle)/)) continue
124+
const outputDirectory = name.endsWith(`-${architecture}`) ? name : `${name}-${architecture}`
125+
console.log(`Downloading ${name} and extracting to ${outputDirectory}/`)
126+
await downloadAndUnZip(token, urls[name], outputDirectory)
127+
directories.push(outputDirectory)
128+
}
129+
}
130+
131+
const fs = require('fs')
132+
const assetsToUpload = directories
133+
.map(directory => fs
134+
.readdirSync(directory)
135+
.filter(file => file.match(/^(Min|Portable)Git-.*\.(exe|zip)$/))
136+
.map(file => `${directory}/${file}`))
137+
.flat()
138+
if (assetsToUpload.length === 0) throw new Error(`No assets to upload!`)
139+
console.log(JSON.stringify(assetsToUpload, null, 2))
140+
return assetsToUpload
141+
- name: update check-run
142+
if: env.CREATE_CHECK_RUN != 'false'
143+
uses: ./.github/actions/check-run-action
144+
with:
145+
app-id: ${{ secrets.GH_APP_ID }}
146+
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
147+
append-text: 'Downloaded all artifacts'
148+
- name: validate
149+
id: validate
150+
uses: actions/github-script@v7
151+
with:
152+
script: |
153+
const fs = require('fs')
154+
155+
const { architectures } = require('./github-release')
156+
for (const arch of architectures) {
157+
const ver = fs.readFileSync(`bundle-artifacts-${arch.name}/ver`, 'utf-8').trim()
158+
if (${{ toJSON(steps.bundle-artifacts.outputs.ver) }} !== ver) {
159+
core.error(`Mismatched version between x86_64 and ${arch.name}: ${{ toJSON(steps.bundle-artifacts.outputs.ver) }} != "${ver}"`)
160+
process.exit(1)
161+
}
162+
}
163+
164+
const githubApiRequest = require('./github-api-request')
165+
const { ahead_by } = await githubApiRequest(
166+
console,
167+
${{ toJSON(secrets.GITHUB_TOKEN) }},
168+
'GET',
169+
`/repos/${process.env.OWNER}/${process.env.REPO}/compare/HEAD...${{ steps.bundle-artifacts.outputs.git-commit-oid }}`
170+
)
171+
if (ahead_by !== 0) {
172+
core.error(`The snapshots are built from a commit that is not reachable from git-for-windows/git's default branch!`)
173+
process.exit(1)
174+
}
175+
- name: configure token
176+
id: snapshots-token
177+
uses: actions/github-script@v7
178+
with:
179+
script: |
180+
const { callGit, getPushAuthorizationHeader } = require('./repository-updates')
181+
const header = await getPushAuthorizationHeader(
182+
console,
183+
core.setSecret,
184+
${{ secrets.GH_APP_ID }},
185+
${{ toJSON(secrets.GH_APP_PRIVATE_KEY) }},
186+
process.env.OWNER,
187+
process.env.SNAPSHOTS_REPO
188+
)
189+
console.log(callGit([
190+
'config',
191+
'--global',
192+
`http.https://${{ github.server_url }}/${process.env.OWNER}/${process.env.SNAPSHOT_REPO}.extraHeader`,
193+
header
194+
]))
195+
return Buffer.from(header.replace(/^Authorization: Basic /, ''), 'base64').toString('utf-8').replace(/^PAT:/, '')
196+
- name: figure out if we need to push commits
197+
uses: actions/github-script@v7
198+
with:
199+
script: |
200+
// Since `git-snapshots` is a fork, and forks share the same object store, we can
201+
// assume that `git-commit-oid` is accessible in the `git-snapshots` repository even
202+
// if it might not yet be reachable.
203+
const githubApiRequest = require('./github-api-request')
204+
const token = ${{ toJSON(steps.snapshots-token.outputs.result) }}
205+
const sha = ${{ toJSON(steps.bundle-artifacts.outputs.git-commit-oid) }}
206+
const { ahead_by, behind_by } = await githubApiRequest(
207+
console,
208+
token,
209+
'GET',
210+
`/repos/${process.env.OWNER}/${process.env.SNAPSHOT_REPO}/compare/${sha}...HEAD`
211+
)
212+
if (ahead_by > 0) throw new Error(`The snapshots repository is ahead of ${sha}!`)
213+
if (behind_by > 0) {
214+
await githubApiRequest(
215+
console,
216+
token,
217+
'PATCH',
218+
`/repos/${process.env.OWNER}/${process.env.SNAPSHOT_REPO}/git/refs/heads/main`, {
219+
sha,
220+
force: false // require fast-forward
221+
}
222+
)
223+
}
224+
- name: upload snapshots to ${{ env.SNAPSHOTS_REPO }}
225+
env:
226+
GH_TOKEN: ${{ steps.snapshots-token.outputs.result }}
227+
run: |
228+
gh release create \
229+
-R ${{ github.repository }} \
230+
--target "${{ steps.bundle-artifacts.outputs.git-commit-oid }}" \
231+
--title "${{ steps.bundle-artifacts.outputs.date }}" \
232+
${{ steps.bundle-artifacts.outputs.ver }} \
233+
${{ steps.download-artifacts.outputs.result }} # no quotes needed here
234+
- name: update check-run
235+
if: env.CREATE_CHECK_RUN != 'false'
236+
uses: ./.github/actions/check-run-action
237+
with:
238+
app-id: ${{ secrets.GH_APP_ID }}
239+
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
240+
append-text: 'Created release at https://${{ github.server_url }}/${{ env.OWNER }}/${{ env.SNAPSHOTS_REPO }}/releases/tag/${{ steps.bundle-artifacts.outputs.ver }}'
241+
- name: clone gh-pages
242+
uses: actions/checkout@v4
243+
with:
244+
repository: ${{ env.OWNER }}/${{ env.SNAPSHOTS_REPO }}
245+
ref: gh-pages
246+
path: gh-pages
247+
token: ${{ steps.snapshots-token.outputs.result }}
248+
- name: update index.html
249+
uses: actions/github-script@v7
250+
with:
251+
script: |
252+
const urlPrefix = `https://${{ github.server_url }}/${{ env.OWNER }}/${{ env.SNAPSHOTS_REPO }}/releases/download/${{ steps.bundle-artifacts.outputs.ver }}/`
253+
process.chdir('gh-pages')
254+
const main = require('./add-entry')
255+
await main(
256+
'--date=${{ steps.bundle-artifacts.outputs.date }}',
257+
'--commit=${{ steps.bundle-artifacts.outputs.git-commit-oid }}',
258+
...${{ steps.download-artifacts.outputs.result }}
259+
.map(path => `${urlPrefix}${path.replace(/.*\//, '')}`)
260+
)
261+
- name: push gh-pages
262+
run: git -C gh-pages push
263+
- name: mark check run as completed
264+
if: env.CREATE_CHECK_RUN != 'false' && always()
265+
uses: ./.github/actions/check-run-action
266+
with:
267+
app-id: ${{ secrets.GH_APP_ID }}
268+
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
269+
append-text: "${{ job.status == 'success' && 'Done!' || format('Completed: {0}', job.status) }}."
270+
conclusion: ${{ job.status }}

github-release.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,4 +349,5 @@ module.exports = {
349349
pushGitTag,
350350
downloadReleaseAssets,
351351
downloadReleaseAssetsFromURL,
352+
architectures,
352353
}

0 commit comments

Comments
 (0)