# 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 permissions: contents: write pull-requests: write 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@dcd71f646680f2efd8db4afa5ad64fdcba30e748 # Set up npm and run npm ci to run husky to get githooks for LFS - name: Setup node uses: actions/setup-node@17f8bd926464a1afa4c6a11669539e9c1ba77048 with: node-version: '16.15.0' 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 }} 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 uses: repo-sync/pull-request@65785d95a5a466e46a9d0708933a3bd51bbf9dde 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: automated-reposync-pr github_token: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }} # This will exit 0 if there's no difference between `repo-sync` # and `main`. And if so, no PR will be created. pr_allow_empty: false - 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 # 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: Check pull request file count after updating if: ${{ steps.find-pull-request.outputs.number }} uses: actions/github-script@2b34a689ec86a68d8ab9478298f91d5401337b7d id: pr-files env: PR_NUMBER: ${{ steps.find-pull-request.outputs.number }} with: github-token: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }} result-encoding: string script: | const { data: prFiles } = await github.pulls.listFiles({ ...context.repo, pull_number: process.env.PR_NUMBER, }) core.setOutput('count', (prFiles && prFiles.length || 0).toString()) # Sometimes after updating the branch, there aren't any remaining files changed. # If not, we should close the PR instead of merging it and triggering deployments. - name: Close the pull request if no files remain if: ${{ steps.find-pull-request.outputs.number && steps.pr-files.outputs.count == '0' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh pr close ${{ steps.find-pull-request.outputs.number }} --repo $GITHUB_REPOSITORY - name: Approve pull request if: ${{ steps.find-pull-request.outputs.number && steps.pr-files.outputs.count != '0' }} uses: juliangruber/approve-pull-request-action@c530832d4d346c597332e20e03605aa94fa150a8 with: github-token: ${{ secrets.GITHUB_TOKEN }} number: ${{ steps.find-pull-request.outputs.number }} - name: Admin merge the pull request if: ${{ steps.find-pull-request.outputs.number && steps.pr-files.outputs.count != '0' }} env: GITHUB_TOKEN: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }} PR_NUMBER: ${{ steps.find-pull-request.outputs.number }} run: | gh pr merge $PR_NUMBER --admin --merge - name: Send Slack notification if workflow fails uses: someimportantcompany/github-actions-slack-message@f8d28715e7b8a4717047d23f48c39827cacad340 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