π Sync GitHub repositories from source to target organizations using mirror cloning. Creates target repositories if they don't exist, with support for visibility control, Actions disabling, and archiving.
Please refer to the release page for the latest release notes.
- π Mirror cloning - Complete repository sync including all branches and tags
- ποΈ Automatic repository creation - Creates target repos if they don't exist
- ποΈ Visibility control - Set repository visibility per repo (private/public/internal)
- π« GitHub Actions management - Disable Actions on target repositories
- π¦ Repository archiving - Archive repositories after sync (with smart unarchive/re-archive)
- π Multi-server support - Sync between GitHub.com and GitHub Enterprise Server
- π Post-run analysis - Detailed sync summary with statistics
Tip
This example uses personal access tokens for simplicity. See the GitHub Apps section below for the recommended approach using GitHub Apps.
- uses: actions/checkout@v6
- name: Bulk GitHub Repository Sync
uses: joshjohanning/bulk-github-repo-sync-action@v2
with:
repo-list-file: repos.yml
source-github-token: ${{ secrets.SOURCE_GITHUB_TOKEN }}
target-github-token: ${{ secrets.TARGET_GITHUB_TOKEN }}
overwrite-repo-visibility: true # overwrite repo visibility with what is in yml file; defaults to false
force-push: false # force push to target repos (overwrites history); defaults to false
### only needed if either your source or target is NOT github.com
# target-github-api-url: https://ghes.domain.com/api/v3 # API URL for GHES
# source-github-api-url: https://api.github.com # only needed if source is not github.comThe repository list uses YML format with per-repository configuration:
repos:
- source: source-org/source-repo-1
target: target-org/target-repo-1
visibility: private # Optional: private, public, or internal (defaults to private)
disable-github-actions: true # Optional: disable Actions on target repo (defaults to true)
archive-after-sync: false # Optional: archive repo after sync (defaults to false)See sample file.
You can use a personal access token, but it is recommended to use GitHub Apps instead:
Note
Required GitHub App Permissions:
- Source App: Repository Read access to
contents - Target App: Repository Read and Write access to
administration,contents, andworkflows
- uses: actions/checkout@v6
# source
- uses: actions/create-github-app-token@v3
id: source-app-token
with:
app-id: ${{ vars.SOURCE_APP_ID }}
private-key: ${{ secrets.SOURCE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
# target
- uses: actions/create-github-app-token@v3
id: target-app-token
with:
app-id: ${{ vars.TARGET_APP_ID }}
private-key: ${{ secrets.TARGET_APP_PRIVATE_KEY }}
owner: joshjohanning-emu
- name: Bulk GitHub Repository Sync
uses: joshjohanning/bulk-github-repo-sync-action@v2
with:
repo-list-file: repos.yml
source-github-token: ${{ steps.source-app-token.outputs.token }}
target-github-token: ${{ steps.target-app-token.outputs.token }}
overwrite-repo-visibility: true # overwrite repo visibility with what is in yml file; defaults to false
# force push to target repos (overwrites history); defaults to false
# target-github-api-url: https://ghes.domain.com/api/v3 # only needed if target is GHES| Setting | Description | Default |
|---|---|---|
source |
Source repository in owner/repo format |
- |
target |
Target repository in owner/repo format |
- |
visibility |
Repository visibility (private/public/internal) | private |
disable-github-actions |
Disable GitHub Actions on target repository | true |
archive-after-sync |
Archive repository after successful sync | false |
| Input | Description | Required | Default |
|---|---|---|---|
repo-list-file |
YML file with repository configurations | Yes | - |
source-github-token |
GitHub PAT for source repositories | Yes | - |
target-github-token |
GitHub PAT for target repositories | No | (uses source token if not specified) |
source-github-api-url |
Source GitHub API URL (e.g., https://api.github.com or https://ghes.domain.com/api/v3). Instance URL is auto-derived. |
No | ${{ github.api_url }} |
target-github-api-url |
Target GitHub API URL (e.g., https://api.github.com or https://ghes.domain.com/api/v3). Instance URL is auto-derived. |
No | ${{ github.api_url }} |
overwrite-repo-visibility |
Force update visibility of existing repos | No | false |
force-push |
Force push to target repositories (overwrites history) | No | false |
You can also run the script directly:
export SOURCE_GITHUB_TOKEN=ghp_abc
export TARGET_GITHUB_TOKEN=ghp_xyz
node src/index.js --file=repos.yml=== SYNC SUMMARY ===
Total repositories: 5
β
Successful: 5
β Failed: 0
π Created: 2
π Updated: 3
ποΈ Visibility updated: 1
π¦ Archived: 2