Automating GitHub Repository Transfers Between Organisations
Migrating repositories from one GitHub organisation to another manually is straightforward for one or two repos — but when you're dealing with dozens, it becomes repetitive and error-prone. This script automates the entire process: creating the destination repos, mirror-cloning the source, and pushing everything across in one run.
What the script does
The script copies a list of repositories from one GitHub organisation to another by mirror-cloning each repo and pushing it to a newly created private repo in the target organisation. The source repos are left completely untouched throughout the process.
How to use it
bash./transfer-github-repo-from-org-to-org.sh
Before running, update the three configuration variables at the top of the script — source org, target org, and the list of repos to transfer.
The full script
bash#!/bin/bash # ============================================================================= # GitHub Org-to-Org Repository Transfer # ============================================================================= # Description: # Copies a list of repositories from one GitHub organisation to another by # mirror-cloning each repo and pushing it to a newly created private repo in # the target org. The source repos are left untouched. # # Prerequisites: # - GitHub CLI (gh) installed and authenticated: gh auth login # - git installed # - The authenticated user must have: # * read access to each repo in SOURCE_ORG # * permission to create repos in TARGET_ORG # ============================================================================= # Source organisation to copy repos from SOURCE_ORG="source-org-name" # Destination organisation to copy repos into TARGET_ORG="target-org-name" # List of repository names to transfer REPOS=( "GithubRepo1" "GithubRepo2" ) # Counters to track results across all repos SUCCESS=0 FAILED=0 for REPO in "${REPOS[@]}"; do echo "==============================" echo "Processing $REPO..." # Skip if the repo already exists in the target org to avoid overwriting it if gh repo view "$TARGET_ORG/$REPO" >/dev/null 2>&1; then echo "⚠️ $REPO already exists in target, skipping..." continue fi # Create a new private repo in the target org before pushing content into it if ! gh repo create "$TARGET_ORG/$REPO" --private --confirm; then echo "❌ Failed to create repo $REPO" ((FAILED++)) continue fi # Mirror-clone fetches all refs (branches, tags, etc.) without a working tree if ! git clone --mirror "https://github.com/$SOURCE_ORG/$REPO.git"; then echo "❌ Failed to clone $REPO" ((FAILED++)) continue fi # Enter the bare mirror directory to run the push cd "$REPO.git" || continue # Push all refs to the target; --mirror overwrites everything in the destination if git push --mirror "https://github.com/$TARGET_ORG/$REPO.git"; then echo "✅ $REPO copied successfully" ((SUCCESS++)) else echo "❌ Failed to push $REPO" ((FAILED++)) fi # Return to the parent directory and clean up the temporary mirror clone cd .. rm -rf "$REPO.git" done # Print a final summary of how many repos succeeded or failed echo "==============================" echo "🎉 Done!" echo "✅ Success: $SUCCESS" echo "❌ Failed: $FAILED"
Configuration
There are only three things to update before running the script. Source and target organisations:
bashSOURCE_ORG="source-org-name" TARGET_ORG="target-org-name"
List of repositories to transfer:
bashREPOS=( "GithubRepo1" "GithubRepo2" )
Add as many repository names as needed. Each one is processed in sequence. By default, all created repos are set to private — change --private to --public in the gh repo create line if you need them public.
How it works
For each repository in the list, the script:
- Checks if the repo already exists in the target org — if it does, it skips it automatically to avoid overwriting anything
- Creates a new private repo in the target organisation using the GitHub CLI
- Mirror-clones the source repo — this captures all branches, tags, and refs without creating a working directory
- Pushes everything to the newly created target repo using git push --mirror
- Cleans up the temporary local mirror clone after a successful push
At the end of the run, a summary shows exactly how many repos were transferred successfully and how many failed.
What gets transferred and what doesn't
- Transferred: all branches, all tags, full git history, all refs
- Not transferred: issues, pull requests, wikis, GitHub Actions secrets
This script performs a git-level mirror — it's a complete copy of the repository's version history, but GitHub-specific metadata lives outside git and cannot be moved this way.
Sample output
text============================== Processing GithubRepo1... ✅ GithubRepo1 copied successfully ============================== Processing GithubRepo2... ⚠️ GithubRepo2 already exists in target, skipping... ============================== 🎉 Done! ✅ Success: 1 ❌ Failed: 0
Prerequisites
- GitHub CLI installed and authenticated (gh auth login)
- git installed on your machine
- The authenticated GitHub user must have read access to each repository in SOURCE_ORG and permission to create repositories in TARGET_ORG
Conclusion
What would otherwise mean repeating the same clone-and-push steps across every repository is reduced to editing a list and running one command. The script handles existence checks, error tracking, and cleanup automatically — giving you a clean summary at the end of every run.