Repo Sync #48913
This file contains 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
# The docs.github.com project has two repositories: github/docs (public) and github/docs-internal (private) | |
# | |
# This GitHub Actions workflow keeps the `main` branch of those two repos in sync. | |
# | |
# For more details, see https://github.com/repo-sync/repo-sync#how-it-works | |
name: Repo Sync | |
# **What it does**: | |
# - close-invalid-repo-sync: Close repo sync pull requests not created by Octomerger or a Hubber. | |
# - repo-sync: Syncs docs and docs-internal. | |
# **Why we have it**: | |
# - close-invalid-repo-sync: Another form of spam prevention for the open-source repository. | |
# - repo-sync: To keep the open-source repository up-to-date, while still having an internal | |
# repository for sensitive work. | |
# **Who does it impact**: Open-source. | |
on: | |
workflow_dispatch: | |
schedule: | |
- cron: '10,40 * * * *' # every 30 minutes | |
jobs: | |
close-invalid-repo-sync: | |
name: Close invalid Repo Sync PRs | |
runs-on: ubuntu-latest | |
steps: | |
- name: Find pull request | |
if: ${{ github.repository == 'github/docs' }} | |
uses: juliangruber/find-pull-request-action@db875662766249c049b2dcd85293892d61cb0b51 | |
id: find-pull-request | |
with: | |
github-token: ${{ secrets.DOCS_BOT_SPAM_VISION }} | |
branch: repo-sync | |
base: main | |
state: open | |
- name: Close pull request if unwanted | |
if: ${{ github.repository == 'github/docs' && steps.find-pull-request.outputs.number }} | |
uses: actions/github-script@2b34a689ec86a68d8ab9478298f91d5401337b7d | |
with: | |
github-token: ${{ secrets.DOCS_BOT_SPAM_VISION }} | |
script: | | |
const { owner, repo } = context.repo | |
const { data: pr } = await github.pulls.get({ | |
owner, | |
repo, | |
pull_number: parseInt(${{ steps.find-pull-request.outputs.number }}) | |
}) | |
const prCreator = pr.user.login | |
// If the PR creator is the expected account, stop now | |
if (prCreator === 'Octomerger') { | |
return | |
} | |
try { | |
await github.teams.getMembershipForUserInOrg({ | |
org: 'github', | |
team_slug: 'employees', | |
username: prCreator | |
}) | |
// If the PR creator is a GitHub employee, stop now | |
return | |
} catch (err) { | |
// An error will be thrown if the user is not a GitHub employee. | |
// That said, we still want to proceed anyway! | |
} | |
// Close the PR and add the invalid label | |
await github.issues.update({ | |
owner, | |
repo, | |
issue_number: pr.number, | |
labels: ['invalid'], | |
state: 'closed' | |
}) | |
// Comment on the PR | |
await github.issues.createComment({ | |
owner, | |
repo, | |
issue_number: pr.number, | |
body: "Please leave this `repo-sync` branch to the robots!\n\nI'm going to close this pull request now, but feel free to open a new issue or ask any questions in [discussions](https://github.com/github/docs/discussions)!" | |
}) | |
repo-sync: | |
needs: close-invalid-repo-sync | |
if: github.repository == 'github/docs-internal' || github.repository == 'github/docs' | |
name: Repo Sync | |
runs-on: ubuntu-latest | |
steps: | |
- name: Check out repo | |
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f | |
# Set up npm and run npm ci to run husky to get githooks for LFS | |
- name: Setup node | |
uses: actions/setup-node@38d90ce44d5275ad62cc48384b3d8a58c500bb5f | |
with: | |
node-version: 16.8.x | |
cache: npm | |
- name: Install dependencies | |
run: npm ci | |
- name: Sync repo to branch | |
uses: repo-sync/github-sync@3832fe8e2be32372e1b3970bbae8e7079edeec88 | |
env: | |
GITHUB_TOKEN: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }} | |
CI: true | |
with: | |
source_repo: ${{ secrets.SOURCE_REPO }} # https://${access_token}@github.com/github/the-other-repo.git | |
source_branch: main | |
destination_branch: repo-sync | |
github_token: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }} | |
- name: Create pull request | |
id: create-pull | |
uses: repo-sync/pull-request@65194d8015be7624d231796ddee1cd52a5023cb3 | |
env: | |
GITHUB_TOKEN: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }} | |
with: | |
source_branch: repo-sync | |
destination_branch: main | |
pr_title: 'repo sync' | |
pr_body: "This is an automated pull request to sync changes between the public and private repos.\n\n:robot: This pull request should be merged (not squashed) to preserve continuity across repos, so please let a bot do the merging!" | |
pr_label: autoupdate,automated-reposync-pr | |
github_token: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }} | |
- name: Find pull request | |
uses: juliangruber/find-pull-request-action@db875662766249c049b2dcd85293892d61cb0b51 | |
id: find-pull-request | |
with: | |
github-token: ${{ secrets.GITHUB_TOKEN }} | |
branch: repo-sync | |
base: main | |
author: Octomerger | |
state: open | |
- name: Approve pull request | |
if: ${{ steps.find-pull-request.outputs.number }} | |
uses: juliangruber/approve-pull-request-action@c530832d4d346c597332e20e03605aa94fa150a8 | |
with: | |
github-token: ${{ secrets.DOCUBOT_REPO_PAT }} | |
number: ${{ steps.find-pull-request.outputs.number }} | |
# Because we get far too much spam ;_; | |
- name: Lock conversations | |
if: ${{ github.repository == 'github/docs' && steps.find-pull-request.outputs.number }} | |
uses: actions/github-script@2b34a689ec86a68d8ab9478298f91d5401337b7d | |
with: | |
script: | | |
try { | |
await github.issues.lock({ | |
...context.repo, | |
issue_number: parseInt(${{ steps.find-pull-request.outputs.number }}), | |
lock_reason: 'spam' | |
}) | |
console.log('Locked the pull request to prevent spam!') | |
} catch (error) { | |
// Log the error but don't fail the workflow | |
console.error(`Failed to lock the pull request. Error: ${error}`) | |
} | |
# There are cases where the branch becomes out-of-date in between the time this workflow began and when the pull request is created/updated | |
- name: Update branch | |
if: ${{ steps.find-pull-request.outputs.number }} | |
uses: actions/github-script@2b34a689ec86a68d8ab9478298f91d5401337b7d | |
with: | |
github-token: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }} | |
script: | | |
const mainHeadSha = await github.git.getRef({ | |
...context.repo, | |
ref: 'heads/main' | |
}) | |
console.log(`heads/main sha: ${mainHeadSha.data.object.sha}`) | |
const pull = await github.pulls.get({ | |
...context.repo, | |
pull_number: parseInt(${{ steps.find-pull-request.outputs.number }}) | |
}) | |
console.log(`Pull request base sha: ${pull.data.base.sha}`) | |
if (mainHeadSha.data.object.sha !== pull.data.base.sha || pull.data.mergeable_state === 'behind') { | |
try { | |
const updateBranch = await github.pulls.updateBranch({ | |
...context.repo, | |
pull_number: parseInt(${{ steps.find-pull-request.outputs.number }}) | |
}) | |
console.log(updateBranch.data.message) | |
} catch (error) { | |
// When the head branch is modified an error with status 422 is thrown | |
// We should retry one more time to update the branch | |
if (error.status === 422) { | |
try { | |
const updateBranch = await github.pulls.updateBranch({ | |
...context.repo, | |
pull_number: parseInt(${{ steps.find-pull-request.outputs.number }}) | |
}) | |
console.log(updateBranch.data.message) | |
} catch (error) { | |
// Only retry once. We'll rely on the update branch workflow to update | |
// this PR in the case of a second failure. | |
console.log(`Retried updating the branch, but an error occurred: ${error}`) | |
} | |
} else { | |
// A failed branch update shouldn't fail this worklow. | |
console.log(`An error occurred when updating the branch: ${error}`) | |
} | |
} | |
} else { | |
console.log(`Branch is already up-to-date`) | |
} | |
- name: Enable GitHub auto-merge | |
if: ${{ steps.find-pull-request.outputs.number }} | |
uses: actions/github-script@2b34a689ec86a68d8ab9478298f91d5401337b7d | |
with: | |
github-token: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }} | |
script: | | |
const pull = await github.pulls.get({ | |
...context.repo, | |
pull_number: parseInt(${{ steps.find-pull-request.outputs.number }}) | |
}) | |
const pullNodeId = pull.data.node_id | |
console.log(`Pull request GraphQL Node ID: ${pullNodeId}`) | |
const mutation = `mutation ($id: ID!) { | |
enablePullRequestAutoMerge(input: { | |
pullRequestId: $id, | |
mergeMethod: MERGE | |
}) { | |
clientMutationId | |
} | |
}` | |
const variables = { | |
id: pullNodeId | |
} | |
const graph = await github.graphql(mutation, variables) | |
console.log('GraphQL mutation result:\n' + JSON.stringify(graph)) | |
if (graph.errors && graph.errors.length > 0) { | |
console.error('ERROR! Failed to enable auto-merge:\n - ' + graph.errors.map(error => error.message).join('\n - ')) | |
} else { | |
console.log('Auto-merge enabled!') | |
} | |
- name: Send Slack notification if workflow fails | |
uses: someimportantcompany/github-actions-slack-message@0b470c14b39da4260ed9e3f9a4f1298a74ccdefd | |
if: failure() | |
with: | |
channel: ${{ secrets.DOCS_ALERTS_SLACK_CHANNEL_ID }} | |
bot-token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} | |
color: failure | |
text: The last repo-sync run for ${{github.repository}} failed. See https://github.com/${{github.repository}}/actions?query=workflow%3A%22Repo+Sync%22 |