Skip to content

pythoninthegrasses/github-migration

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GitHub Account and Organization Migration Toolkit

Automated tools for migrating repositories from a personal GitHub account to an organization, then swapping account names.

✅ Successfully migrated 594/600 repositories (99% success rate) on 2026-01-26

Overview

This toolkit automates GitHub repository migration between accounts and organizations.

Example use case (completed 2026-01-26):

  1. Transfer 600 repositories from personal account → organization
  2. Rename personal account to free up original username
  3. Rename organization to take original username
  4. Delete forks if needed to clear naming conflicts

All while preserving stars, forks, issues, and repository settings with 99% success rate.

Features

  • Automated transfers using GitHub API
  • Parallel processing - 3-5x faster with concurrent operations
  • Caching system - 30-minute cache for instant reruns
  • Dry-run mode for safe testing
  • Pilot mode for testing with small batches
  • Fork deletion - bulk delete forks that block org renames
  • Rate limiting to avoid API bans
  • Error handling with automatic retry logic
  • Progress tracking with detailed reports
  • Pre-migration audit for backup and reconciliation
  • Post-migration validation to verify success
  • PEP 723 scripts - self-contained, no venv needed

Prerequisites

  • uv - For running PEP 723 scripts (already installed)
  • GitHub CLI (gh) v2.76.2+ - For GitHub API access (already installed)
  • Python 3.13+ - For script execution (already installed)
  • Authenticated GitHub session - Run gh auth login if needed

Verify prerequisites:

uv --version        # Should show uv version
gh --version        # Should show gh version 2.76.2+
python --version    # Should show Python 3.13+
gh auth status      # Should show: Logged in to github.com as pythoninthegrass

Quick Start

1. Setup

# Navigate to the project directory
cd github-migration

# Copy environment configuration
cp .env.example .env

# Edit configuration (set your values)
nano .env  # or use your preferred editor

Required .env values:

SOURCE_OWNER=pythoninthegrass
TARGET_ORG=pythoninthegrass2
DRY_RUN=true  # Start with dry-run mode!

2. Day 1: Pre-Migration Audit

Generate inventory of all repositories:

uv run scripts/00_pre_migration_audit.py

This creates:

  • backup/repos_inventory_YYYYMMDD_HHMMSS.csv - CSV report
  • backup/repos_full_YYYYMMDD_HHMMSS.json - Complete backup

Review the CSV file to identify any repos you want to exclude.

3. Day 2: Pilot Test

Test with 5-10 low-risk repositories first:

# Edit .env and set:
# PILOT_MODE=true
# PILOT_REPOS=repo1,repo2,repo3,repo4,repo5
# DRY_RUN=true

# Run dry-run first
uv run scripts/01_transfer_repos.py

# Review output, then run for real
# Edit .env: DRY_RUN=false
uv run scripts/01_transfer_repos.py

Verify pilot repos in organization:

gh repo list pythoninthegrass2 --limit 10

Wait 24 hours and monitor for any issues before proceeding.

4. Day 3-4: Full Migration

Transfer all remaining repositories:

# Edit .env:
# PILOT_MODE=false
# DRY_RUN=false
# BATCH_SIZE=5
# DELAY_BETWEEN_BATCHES=10

# Run the transfer (will take 4-6 hours for 619 repos)
uv run scripts/01_transfer_repos.py

The script will:

  • Fetch all repositories
  • Filter based on your settings
  • Ask for confirmation before transferring
  • Transfer in batches with rate limiting
  • Generate a detailed report

Monitor the process - you can stop and restart if needed.

If any transfers fail:

uv run scripts/03_retry_failed_transfers.py

5. Day 5: Manual Rename Operations

⚠️ CRITICAL: These steps MUST be done via GitHub web UI.

Follow the detailed guide:

cat docs/MANUAL_RENAME_PROCEDURE.md

Steps summary:

  1. Rename personal account: pythoninthegrasspythoninthegrass_og

  2. Rename organization: pythoninthegrass2pythoninthegrass

  3. Verify:

    gh api /user --jq '.login'  # Should show: pythoninthegrass_og
    gh api /orgs/pythoninthegrass --jq '.login'  # Should show: pythoninthegrass

6. Day 6: Validation and Cleanup

Verify migration success:

# Run validation
uv run scripts/02_post_migration_validation.py

Review the validation report to ensure all repos transferred correctly.

Note: GitHub redirects handle URL changes for 90 days, so local git clones automatically work without updates.

Project Structure

github-migration/
├── README.md                           # This file
├── .env.example                        # Configuration template
├── .gitignore                         # Git ignore rules
│
├── scripts/                           # PEP 723 self-contained scripts
│   ├── 00_pre_migration_audit.py     # Generate pre-migration inventory
│   ├── 01_transfer_repos.py          # Main transfer automation
│   ├── 02_post_migration_validation.py  # Verify migration success
│   ├── 03_retry_failed_transfers.py  # Retry failed transfers
│   └── 04_delete_forks.py            # Delete forks from organization
│
├── docs/
│   └── MANUAL_RENAME_PROCEDURE.md # Detailed rename guide
│
├── backup/                            # Pre-migration backups (gitignored)
├── results/                           # Transfer reports (gitignored)
└── logs/                              # Execution logs (gitignored)

Script Usage

00_pre_migration_audit.py

Generates comprehensive inventory of all repositories.

# Configuration: .env
# - SOURCE_OWNER (default: pythoninthegrass)

uv run scripts/00_pre_migration_audit.py

Output:

  • CSV report with repo metadata
  • JSON backup with complete data
  • Summary statistics

01_transfer_repos.py

Main automation script for repository transfers.

# Configuration: .env
# - SOURCE_OWNER, TARGET_ORG
# - DRY_RUN (true/false)
# - BATCH_SIZE, DELAY_BETWEEN_TRANSFERS, DELAY_BETWEEN_BATCHES
# - EXCLUDE_FORKS, EXCLUDE_ARCHIVED
# - PILOT_MODE, PILOT_REPOS

# Dry run (recommended first)
DRY_RUN=true uv run scripts/01_transfer_repos.py

# Live transfer
DRY_RUN=false uv run scripts/01_transfer_repos.py

Features:

  • Batch processing with configurable delays
  • Rate limiting (avoids API bans)
  • Error handling and logging
  • Progress tracking
  • Confirmation prompts for safety

02_post_migration_validation.py

Validates migration success and generates reconciliation report.

# Configuration: .env
# - SOURCE_OWNER (for loading pre-migration inventory)
# - TARGET_ORG (org after rename, default: pythoninthegrass)

uv run scripts/02_post_migration_validation.py

Checks:

  • Repository count matches pre-migration
  • Sample metadata preserved (stars, forks, issues)
  • Access control intact
  • Missing repositories reported

03_retry_failed_transfers.py

Retries failed transfers with exponential backoff.

# Configuration: .env
# - SOURCE_OWNER, TARGET_ORG
# - MAX_RETRIES (default: 3)
# - INITIAL_DELAY (default: 5 seconds)

uv run scripts/03_retry_failed_transfers.py

Handles:

  • Temporary rate limit errors
  • Timeout errors
  • Network issues
  • Identifies permanent failures (pending transfers, not found)

04_delete_forks.py

Deletes all forked repositories from the target organization.

# Configuration: .env
# - TARGET_ORG
# - DRY_RUN (true/false)
# - MAX_CONCURRENT_DELETIONS (default: 5)

# Dry run first (recommended)
uv run scripts/04_delete_forks.py

# Live deletion (requires typing "DELETE" to confirm)
DRY_RUN=false uv run scripts/04_delete_forks.py

Features:

  • Parallel deletion (5 concurrent by default)
  • Batch processing with progress tracking
  • Confirmation prompt for safety
  • GraphQL API for efficient fork discovery

Use case: Bulk delete forks that may block organization renames or cause naming conflicts.

Configuration

All scripts use environment variables from .env file:

# Required
SOURCE_OWNER=pythoninthegrass      # Your personal account
TARGET_ORG=pythoninthegrass2       # Destination organization

# Transfer settings
DRY_RUN=true                       # Always test with dry-run first!
BATCH_SIZE=9                       # Repos per batch
DELAY_BETWEEN_TRANSFERS=1          # Seconds between transfers
DELAY_BETWEEN_BATCHES=3            # Seconds between batches
MAX_CONCURRENT_TRANSFERS=3         # Parallel transfers
MAX_CONCURRENT_DELETIONS=5         # Parallel deletions

# Filters
EXCLUDE_FORKS=false                # Transfer forks too
EXCLUDE_ARCHIVED=false             # Transfer archived repos

# Pilot testing
PILOT_MODE=false                   # Enable for testing
PILOT_REPOS=repo1,repo2,repo3      # Comma-separated list

Safety Features

  • Dry-run mode: Test without making changes
  • Pilot mode: Test with small batch first
  • Confirmation prompts: Prevents accidental transfers
  • Rate limiting: Avoids GitHub API bans
  • Error handling: Graceful failure with detailed logs
  • Retry logic: Exponential backoff for transient failures
  • Pre-migration backup: Complete inventory for rollback
  • Validation: Verify migration success

Timeline

  • Day 1: Audit (1-2 hours automated)
  • Day 2: Pilot test (30 minutes + 24h wait)
  • Day 3-4: Full migration (4-6 hours automated)
  • Day 5: Manual renames (15 minutes manual)
  • Day 6: Validation (1-2 hours automated)

Total active time: ~8-12 hours (mostly automated) Total calendar time: ~6 days

Troubleshooting

"gh: command not found"

Install GitHub CLI:

brew install gh  # macOS
# or visit: https://cli.github.com/

"uv: command not found"

Install uv:

curl -LsSf https://astral.sh/uv/install.sh | sh

"Permission denied" errors

Ensure you're authenticated:

gh auth login
gh auth status

"Rate limit exceeded"

Increase delays in .env:

DELAY_BETWEEN_TRANSFERS=5
DELAY_BETWEEN_BATCHES=30

Transfers failing

Run retry script:

uv run scripts/03_retry_failed_transfers.py

Check detailed error messages in:

  • Console output
  • results/transfer_results_*.csv

"Pending transfer" errors

Wait 24 hours and retry - GitHub imposes this limit.

Important Notes

GitHub API Limitations

  • Rate limits: 5,000 requests/hour (authenticated)
  • Transfer cooldown: 24 hours between transfer attempts
  • Rename restrictions: 90-day redirect period
  • Size limits: Repos >100GB may fail (rare)

What's Preserved

✅ Repository metadata (stars, watchers, forks) ✅ Issues and pull requests ✅ Commits and history ✅ Branch protection rules ✅ Webhooks (may need URL updates) ✅ Deploy keys ✅ Repository settings

What's NOT Preserved

❌ GitHub Actions secrets (must be re-added) ❌ Some third-party integrations (may need reconfiguration)

Redirects

  • GitHub maintains redirects for 90 days after rename
  • Old URLs automatically redirect to new ones
  • Update URLs within 90 days to avoid breakage

Security

  • Never commit .env - contains sensitive configuration
  • Review audit output before sharing - may contain private repo names
  • Limit access to generated reports - contain repo metadata
  • Use .gitignore provided to prevent accidental commits

Support

GitHub Documentation

GitHub Status

Check for ongoing issues:

Contact

If you encounter persistent issues:

License

This toolkit is provided as-is for personal use.

Changelog

2026-01-26 - Initial Release

  • Automated repository transfer script
  • Pre-migration audit and backup
  • Post-migration validation
  • Local clone cleanup automation
  • Retry logic for failed transfers
  • Manual rename procedures
  • PEP 723 self-contained scripts (no venv needed)

About

Migrate repos from a user to an org in ~5 easy steps ✅

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages