Skip to content

Commit f69d3e6

Browse files
committed
Add a scheduled workflow to synchronize branches from gitster/git
The Git maintainer publishes only the integration branches to `git/git`, leaving the individual contribution branches in `gitster/git`. We do want to make sure that the latter branches are available, and synchronized, in `gitgitgadget/git`, to facilitate contributions to patch series that are in flight. So let's synchronize the branches, making sure that the integration branches (`maint`, `master`, `next`, `seen`) are not synchronized from `gitster/git`; They are synchronized via the `sync-ref` workflow already. Signed-off-by: Johannes Schindelin <[email protected]>
1 parent d9544c8 commit f69d3e6

File tree

1 file changed

+112
-0
lines changed

1 file changed

+112
-0
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
name: sync-gitster-git-branches
2+
3+
on:
4+
schedule:
5+
- cron: '17 6 * * *'
6+
workflow_dispatch:
7+
8+
env:
9+
SOURCE_REPOSITORY: gitster/git
10+
TARGET_REPOSITORY: gitgitgadget/git
11+
12+
jobs:
13+
sync-gitster-git-branches:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: check which refs need to be synchronized
17+
uses: actions/github-script@v6
18+
id: check
19+
with:
20+
script: |
21+
const getRefs = async (repository) => {
22+
const [owner, repo] = repository.split('/')
23+
const { data } = await github.rest.git.listMatchingRefs({
24+
owner,
25+
repo,
26+
// We want to match `maint-*` as well as `[a-z][a-z]/*`
27+
// sadly, this is not possible via GitHub's REST API,
28+
// hence we do it below via the `filter()` call.
29+
ref: 'heads/'
30+
})
31+
return data
32+
.filter(e => e.ref.match(/^refs\/heads\/(maint-\d|[a-z][a-z]\/)/))
33+
.sort((a, b) => a.ref.localeCompare(b.ref))
34+
}
35+
36+
const sourceRefs = await getRefs(process.env.SOURCE_REPOSITORY)
37+
const targetRefs = await getRefs(process.env.TARGET_REPOSITORY)
38+
39+
const refspecs = []
40+
const toFetch = new Set()
41+
for (let i = 0, j = 0; i < sourceRefs.length || j < targetRefs.length; ) {
42+
const compare = i >= sourceRefs.length
43+
? +1
44+
: j >= targetRefs.length
45+
? -1
46+
: sourceRefs[i].ref.localeCompare(targetRefs[j].ref)
47+
if (compare > 0) {
48+
// no source ref => delete target ref
49+
refspecs.push(`:${targetRefs[j].ref}`)
50+
j++
51+
} else if (compare < 0) {
52+
// no corresponding target ref yet => push source ref (new)
53+
const sha = sourceRefs[i].object.sha
54+
toFetch.add(sha)
55+
refspecs.push(`${sha}:${sourceRefs[i].ref}`)
56+
i++
57+
} else {
58+
// the sourceRef's name matches the targetRef's
59+
if (sourceRefs[i].object.sha !== targetRefs[j].object.sha) {
60+
// target ref needs updating
61+
const sha = sourceRefs[i].object.sha
62+
toFetch.add(sha)
63+
refspecs.push(`+${sha}:${sourceRefs[i].ref}`)
64+
}
65+
i++
66+
j++
67+
}
68+
}
69+
70+
core.setOutput('refspec', refspecs.join(' '))
71+
targetRefs.forEach((e) => toFetch.delete(e.object.sha))
72+
core.setOutput('to-fetch', [...toFetch].join(' '))
73+
- name: obtain installation token
74+
if: steps.check.outputs.refspec != ''
75+
uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92
76+
id: token
77+
with:
78+
app_id: ${{ secrets.GITGITGADGET_GITHUB_APP_ID }}
79+
private_key: ${{ secrets.GITGITGADGET_GITHUB_APP_PRIVATE_KEY }}
80+
repository: ${{ env.TARGET_REPOSITORY }}
81+
- name: set authorization header
82+
if: steps.check.outputs.refspec != ''
83+
uses: actions/github-script@v6
84+
id: auth
85+
with:
86+
script: |
87+
// Sadly, `git push` does not work with 'Authorization: Bearer <PAT>', therefore
88+
// we have to use the `Basic` variant
89+
const auth = Buffer.from('PAT:${{ steps.token.outputs.token }}').toString('base64')
90+
core.setSecret(auth)
91+
core.setOutput('header', `Authorization: Basic ${auth}`)
92+
- name: sync
93+
if: steps.check.outputs.refspec != ''
94+
shell: bash
95+
run: |
96+
set -ex
97+
git init --bare
98+
99+
git remote add source "${{ github.server_url }}/$SOURCE_REPOSITORY"
100+
# pretend to be a partial clone
101+
git config remote.source.promisor true
102+
git config remote.source.partialCloneFilter blob:none
103+
104+
# fetch some commits
105+
printf '%s' '${{ steps.check.outputs.to-fetch }}' |
106+
xargs -d ' ' -r git fetch --depth 10000 source
107+
rm -f .git/shallow
108+
109+
# push the commits
110+
printf '%s' '${{ steps.check.outputs.refspec }}' |
111+
xargs -d ' ' -r git -c http.extraHeader='${{ steps.auth.outputs.header }}' \
112+
push "${{ github.server_url }}/$TARGET_REPOSITORY"

0 commit comments

Comments
 (0)