All writing
GitHubBashMigration

Automating GitHub Repository Transfers Between Organisations

May 29, 20266 min readMuaz Balogun Adeleye

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:

bash
SOURCE_ORG="source-org-name" TARGET_ORG="target-org-name"

List of repositories to transfer:

bash
REPOS=( "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.

Muaz Balogun Adeleye
AWS Solutions Architect · Remote · Lagos, Nigeria
Connect