Skip to content

repo_update

repo_update #304

Workflow file for this run

name: Sync Rest
on:
workflow_dispatch:
inputs:
repos:
description: 'List of repos to rerun the syncing workflow for, formatted as JSON array, e.g. ["exercism/configlet"]'
required: true
default: ""
repository_dispatch:
types: [appends_update, repo_update]
push:
branches:
- main
paths-ignore:
- README.md
- LICENSE
- .github/labels.yml
- .github/workflows/sync-labels.yml
env:
BOT_USERNAME: "exercism-bot"
GIT_USERNAME: "Exercism Bot"
GIT_EMAIL: "[email protected]"
jobs:
open-tracking-issue:
name: Open tracking issue
runs-on: ubuntu-20.04
outputs:
issue-url: ${{ steps.open-tracking-issue.outputs.result }}
steps:
- name: Open tracking issue
id: open-tracking-issue
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
with:
result-encoding: string
script: |
const title = `[Tracking] ${context.workflow} - ${context.eventName} - ${context.actor} - ${context.sha}`
const label = '👁 tracking issue'
const existingIssue = (await github.paginate(github.rest.issues.listForRepo, {
owner: context.repo.owner,
repo: context.repo.repo,
labels: label,
state: 'open',
})).find((issue) => issue.title === title)
if (existingIssue) {
console.log(`::notice::Existing issue found: ${existingIssue.html_url}`)
return existingIssue.html_url
}
const issue = (await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: title,
body: `Workflow run: https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
labels: [label]
})).data
return issue.html_url
fetch-repos:
name: Fetch & fork target repos
runs-on: ubuntu-20.04
outputs:
repos: ${{ steps.fetch-repos.outputs.result }}
steps:
- name: Fetch list of target repos
id: fetch-repos
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
with:
script: |
if (context.eventName === 'repository_dispatch') {
return context.payload.client_payload.repos
} else if (context.eventName === 'workflow_dispatch') {
return JSON.parse(context.payload.inputs.repos)
} else {
const allRepos = (await github.paginate(github.rest.search.repos, {
q: 'org:exercism+is:public+archived:false'
})).flatMap(({ full_name }) => [full_name])
const toolingRepos = (await github.paginate(github.rest.search.repos, {
q: 'org:exercism+topic:exercism-tooling+is:public+archived:false'
})).flatMap(({ full_name }) => [full_name])
const trackRepos = (await github.paginate(github.rest.search.repos, {
q: 'org:exercism+topic:exercism-track+is:public+archived:false'
})).flatMap(({ full_name }) => [full_name])
return allRepos.filter(r => !toolingRepos.includes(r) && !trackRepos.includes(r))
}
- name: Determine target repos needing to be forked
id: required-forks
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
with:
github-token: ${{ secrets.BOT_PERSONAL_ACCESS_TOKEN }}
script: |
const botRepos = await github.paginate(github.rest.repos.listForAuthenticatedUser, {
affiliation: 'owner'
})
// Replacing the bot username with exercism is slightly hacky, but necessary
// for the comparison below. Unfortunately, GitHub's API response does not seem
// to contain any reference to the upstream repo of the fork.
// TODO: The GraphQL API does have this info. Look into changing to that.
const botForks = botRepos.flatMap(({ full_name, fork }) => fork ? [full_name.replace(process.env.BOT_USERNAME, 'exercism')] : [])
const repos = ${{ steps.fetch-repos.outputs.result }}
return repos.filter(repo => !botForks.includes(repo))
- name: Fork repos that haven't been forked yet
if: steps.required-forks.outputs.result != '[]'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
with:
github-token: ${{ secrets.BOT_PERSONAL_ACCESS_TOKEN }}
script: |
const reposFullNames = ${{ steps.required-forks.outputs.result }}
const repos = reposFullNames.map((fullName) => fullName.split('/')[1])
for (let repo of repos) {
await github.rest.repos.createFork({
owner: 'exercism',
repo
})
// Sleep a bit to avoid rate-limiting
await new Promise(x => setTimeout(x, 2000))
}
// Forking repos happens asynchronously on GitHub, therefore
// we need to sleep a bit to ensure it has been created
await new Promise(x => setTimeout(x, 60000))
abort-timer:
needs: [fetch-repos, open-tracking-issue]
runs-on: ubuntu-20.04
name: ⚠ 5 MINUTES TO ABORT
environment: abort-timer
steps:
- run: exit 0
push-to-repos:
needs: [fetch-repos, abort-timer, open-tracking-issue]
name: Push to ${{ matrix.repo }}
runs-on: ubuntu-20.04
timeout-minutes: 30
# Launch one job per track.
# This is probably less efficient than running everything in one job
# and manually cloning and checking out the repos. However, launching a job
# lets us use actions like actions/checkout.
# It also gives us a pretty job overview that makes it easy to spot issues with
# particular tracks.
strategy:
fail-fast: false
max-parallel: 1
matrix:
repo: ${{ fromJson(needs.fetch-repos.outputs.repos) }}
steps:
- name: Check if pull request has already been opened
id: pr-already-exists
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
env:
TARGET_REPO_FULLNAME: ${{ matrix.repo }}
TRACKING_ISSUE_URL: ${{ needs.open-tracking-issue.outputs.issue-url }}
with:
github-token: ${{ secrets.BOT_PERSONAL_ACCESS_TOKEN }}
script: |
const repo = process.env.TARGET_REPO_FULLNAME.split('/')[1]
const existingPr = (
await github.paginate(github.rest.issues.listForRepo, {
owner: "exercism",
repo: repo,
state: 'open',
})
).find((pr) =>
pr.body && pr.body.includes(process.env.TRACKING_ISSUE_URL)
)
if (existingPr) {
console.log(`::notice::Existing pull request found: ${existingPr.html_url}`)
return true
}
return false
############################################################
# ONLY RUN THE REST OF THE SCRIPT IF THE PR DOES NOT EXIST #
# All following steps must have: #
# if: steps.pr-already-exists.outputs.result == 'false' #
###########################################################
- name: Checkout main repo
if: steps.pr-already-exists.outputs.result == 'false'
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
with:
path: main
- name: Checkout target repo
if: steps.pr-already-exists.outputs.result == 'false'
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
with:
repository: ${{ matrix.repo }}
token: ${{ secrets.BOT_PERSONAL_ACCESS_TOKEN }}
path: track-repo
fetch-depth: 0
- name: Checkout new branch on track repo
if: steps.pr-already-exists.outputs.result == 'false'
id: branch
run: |
cd track-repo
# github.run_id is a unique number for each run within a repository.
# WARNING: the run_id will NOT be updated if a workflow is re-run
branch="org-wide-files/${{ github.run_id }}"
git checkout -b "$branch"
echo "name=$branch" >> $GITHUB_OUTPUT
- name: Copy globally synced files to target repo
if: steps.pr-already-exists.outputs.result == 'false'
run: |
cp -a main/global-files/. track-repo/
- name: Determine if repo is a tooling repo
if: steps.pr-already-exists.outputs.result == 'false'
id: is-tooling-repo
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
env:
TARGET_REPO_FULLNAME: ${{ matrix.repo }}
with:
github-token: ${{ secrets.BOT_PERSONAL_ACCESS_TOKEN }}
script: |
const repoTopics = await github.paginate(github.rest.repos.getAllTopics, {
owner: 'exercism',
repo: process.env.TARGET_REPO_FULLNAME.split('/')[1]
})
return repoTopics[0].names.includes('exercism-tooling')
- name: Copy tooling-only synced files to target repo
if: steps.is-tooling-repo.outputs.result == 'true'
run: |
cp -a main/tooling-files/. track-repo/
- name: Determine if repo is a track repo
if: steps.pr-already-exists.outputs.result == 'false'
id: is-track-repo
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
env:
TARGET_REPO_FULLNAME: ${{ matrix.repo }}
with:
github-token: ${{ secrets.BOT_PERSONAL_ACCESS_TOKEN }}
script: |
const repoTopics = await github.paginate(github.rest.repos.getAllTopics, {
owner: 'exercism',
repo: process.env.TARGET_REPO_FULLNAME.split('/')[1]
})
return repoTopics[0].names.includes('exercism-track')
- name: Copy tracks-only synced files to target repo
if: steps.is-track-repo.outputs.result == 'true'
run: |
cp -a main/tracks-files/. track-repo/
- name: Apply transformations based on track config
if: steps.pr-already-exists.outputs.result == 'false'
env:
TRACK: ${{ matrix.repo }}
run: julia --color=yes main/scripts/apply-track-config.jl
- name: Check for changes
if: steps.pr-already-exists.outputs.result == 'false'
id: changes
run: |
cd track-repo
if [ -z "$(git status --porcelain)" ]; then
echo "No files have changed."
exit 0
fi
echo "changes=true" >> $GITHUB_OUTPUT
########################################################
# ONLY RUN THE REST OF THE SCRIPT IF THERE ARE CHANGES #
# All following steps must have: #
# if: steps.changes.outputs.changes == 'true' #
########################################################
- name: Configure the git user
if: steps.changes.outputs.changes == 'true'
run: |
git config --global user.email "$GIT_EMAIL"
git config --global user.name "$GIT_USERNAME"
- name: Commit and push changes
if: steps.changes.outputs.changes == 'true'
env:
TARGET_REPO_FULLNAME: ${{ matrix.repo }}
BOT_PERSONAL_ACCESS_TOKEN: ${{ secrets.BOT_PERSONAL_ACCESS_TOKEN }}
run: |
cd track-repo
# Show diff for debugging
git diff
git add .
# The commit message assumes that one push maps to exactly one commit
# If a push contains several commits, the commit message will only link to the last one
git commit -m "🤖 Sync org-wide files to upstream repo" -m "More info: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/commit/$GITHUB_SHA"
# Push commit
target_repo="${TARGET_REPO_FULLNAME/exercism/$BOT_USERNAME}"
git push --force "$GITHUB_SERVER_URL/$target_repo.git"
- name: Open pull request on track repo
if: steps.changes.outputs.changes == 'true'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea
env:
TARGET_REPO_FULLNAME: ${{ matrix.repo }}
BRANCH_NAME: ${{ steps.branch.outputs.name }}
TRACKING_ISSUE_URL: ${{ needs.open-tracking-issue.outputs.issue-url }}
with:
github-token: ${{ secrets.BOT_PERSONAL_ACCESS_TOKEN }}
script: |
const repo = process.env.TARGET_REPO_FULLNAME.split('/')[1]
github.rest.pulls.create({
owner: 'exercism',
repo,
title: '🤖 Sync org-wide files to upstream repo',
head: `exercism-bot:${process.env.BRANCH_NAME}`,
base: 'main',
body: `ℹ More info: ${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/commit/${process.env.GITHUB_SHA}\n👁 Tracking issue: ${process.env.TRACKING_ISSUE_URL}`,
maintainer_can_modify: false
})