diff --git a/.github/repositories-to-ignore.json b/.github/repositories-to-ignore.json new file mode 100644 index 00000000..7488f5d2 --- /dev/null +++ b/.github/repositories-to-ignore.json @@ -0,0 +1,6 @@ +[ + "homebrew-core", + "winget-pkgs", + "Virtual-Gamepad-Emulation-Client", + "Virtual-Gamepad-Emulation-dotnet" +] diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 683f272d..69a9f990 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -9,3 +9,9 @@ respective repositories. Workflows that prefixed with a single `_` are replicated to other repositories and this indicates that the workflow should not be modified when inside the other repositories. + +Repositories excluded from both the global replicator and GitLab mirror are listed in +`.github/repositories-to-ignore.json`. + +The GitLab mirror also excludes GitHub temporary security advisory forks matching +`-ghsa-xxxx-xxxx-xxxx`. diff --git a/.github/workflows/__global-replicator.yml b/.github/workflows/__global-replicator.yml index 71af0390..233dcb46 100644 --- a/.github/workflows/__global-replicator.yml +++ b/.github/workflows/__global-replicator.yml @@ -25,11 +25,6 @@ jobs: BOT_BRANCH_NAME: 'bot/update-files-from-global-repo' COMMIT_MESSAGE: 'chore: update global workflows' EXCLUDE_PRIVATE: true - REPOS_TO_IGNORE: >- - homebrew-core, - winget-pkgs, - Virtual-Gamepad-Emulation-Client, - Virtual-Gamepad-Emulation-dotnet permissions: contents: read runs-on: ubuntu-latest @@ -37,6 +32,23 @@ jobs: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Load repositories to ignore + shell: bash + run: | + set -euo pipefail + + ignore_file=".github/repositories-to-ignore.json" + repos_to_ignore="$( + jq -er ' + if type == "array" and all(.[]; type == "string" and length > 0) + then join(",") + else error("Expected an array of non-empty repository names") + end + ' "${ignore_file}" + )" + echo "REPOS_TO_IGNORE=${repos_to_ignore}" >> "${GITHUB_ENV}" + echo "Loaded $(jq -r 'length' "${ignore_file}") repositories to ignore." + - name: Removing files uses: derberg/manage-files-in-multiple-repositories@beecbe897cf5ed7f3de5a791a3f2d70102fe7c25 # v2.1.0 with: diff --git a/.github/workflows/__mirror-github-to-gitlab.yml b/.github/workflows/__mirror-github-to-gitlab.yml index d04befbd..68cacadb 100644 --- a/.github/workflows/__mirror-github-to-gitlab.yml +++ b/.github/workflows/__mirror-github-to-gitlab.yml @@ -16,16 +16,38 @@ concurrency: jobs: mirror: name: Mirror GitHub to GitLab - permissions: {} + permissions: + contents: read runs-on: ubuntu-latest steps: # Process repositories in one job so project creation, privacy checks, and mirroring stay together. + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Get repositories uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: github-token: ${{ secrets.GH_BOT_TOKEN }} script: | const fs = require('fs'); + const ignoredRepositoryNames = JSON.parse( + fs.readFileSync('.github/repositories-to-ignore.json', 'utf8'), + ); + if ( + !Array.isArray(ignoredRepositoryNames) + || !ignoredRepositoryNames.every((name) => typeof name === 'string' && name.length > 0) + ) { + throw new Error('Expected an array of non-empty repository names.'); + } + const ignoredRepositories = new Set(ignoredRepositoryNames); + const securityAdvisoryIdSegment = '[23456789cfghjmpqrvwx]{4}'; + const securityAdvisoryForkPattern = new RegExp( + `-ghsa-${securityAdvisoryIdSegment}-${securityAdvisoryIdSegment}-${securityAdvisoryIdSegment}$`, + 'i', + ); + const shouldIgnoreRepository = (repo) => ( + ignoredRepositories.has(repo.name) || securityAdvisoryForkPattern.test(repo.name) + ); const opts = github.rest.repos.listForOrg.endpoint.merge({ org: context.repo.owner }); const repos = await github.paginate(opts); const gitlabTarget = (repo) => { @@ -53,15 +75,18 @@ jobs: targetPath: transformed ? `${targetName}-${repo.id}` : repo.name, }; }; - const repositoryData = repos.map((repo) => ({ - cloneUrl: repo.clone_url, - name: repo.name, - sizeKiB: repo.size, - targetVisibility: repo.visibility === 'public' ? 'public' : 'private', - ...gitlabTarget(repo), - })); + const repositoryData = repos + .filter((repo) => !shouldIgnoreRepository(repo)) + .map((repo) => ({ + cloneUrl: repo.clone_url, + name: repo.name, + sizeKiB: repo.size, + targetVisibility: repo.visibility === 'public' ? 'public' : 'private', + ...gitlabTarget(repo), + })); fs.writeFileSync('repositories.json', JSON.stringify(repositoryData), { mode: 0o600 }); + core.info(`Ignored ${repos.length - repositoryData.length} repositories.`); core.info(`Prepared ${repositoryData.length} repositories for mirroring.`); - name: Mirror repositories