A GitHub CLI extension for validating GitHub organization and repository migrations by comparing key metrics between source and target repositories. Supports GitHub-to-GitHub and Bitbucket Server (Data Center) to GitHub migrations.
The GitHub Migration Validator helps ensure that your migration to GitHub has been completed successfully. It compares various repository metrics (issues, pull requests, tags, releases, commits) between source and target repositories and provides a detailed validation report.
Supported sources:
- GitHub (organization/repository) — full validation including migration archives
- Bitbucket Server / Data Center — API-based validation
- Migration Archive Support - Comprehensive guide for enhanced validation using GitHub migration archives
gh extension install mona-actions/gh-migration-validatorgh migration-validator \
--github-source-org "source-org" \
--github-target-org "target-org" \
--source-repo "my-repo" \
--target-repo "my-repo" \
--github-source-pat "ghp_xxx" \
--github-target-pat "ghp_yyy"gh migration-validator \
--github-source-org "source-org" \
--github-target-org "target-org" \
--source-repo "my-repo" \
--target-repo "my-repo" \
--github-source-pat "ghp_xxx" \
--github-target-pat "ghp_yyy" \
--markdown-table \
--markdown-file "validation-report.md"If you want to skip LFS object validation (useful for large repositories or when LFS is not used), use the --no-lfs flag:
gh migration-validator \
--github-source-org "source-org" \
--github-target-org "target-org" \
--source-repo "my-repo" \
--target-repo "my-repo" \
--github-source-pat "ghp_xxx" \
--github-target-pat "ghp_yyy" \
--no-lfsYou can use environment variables instead of flags. All environment variables use the GHMV_ prefix.
export GHMV_SOURCE_ORGANIZATION="source-org"
export GHMV_SOURCE_TOKEN="ghp_xxx"
export GHMV_SOURCE_REPO="my-repo"
export GHMV_SOURCE_HOSTNAME="https://github.example.com" # Optional: GitHub Enterprise Serverexport GHMV_TARGET_ORGANIZATION="target-org"
export GHMV_TARGET_TOKEN="ghp_yyy"
export GHMV_TARGET_REPO="my-repo"
export GHMV_TARGET_HOSTNAME="https://github.example.com" # Optional: GitHub Enterprise Serverexport GHMV_MARKDOWN_TABLE="true" # Output as markdown table
export GHMV_MARKDOWN_FILE="validation-report.md" # Write markdown to file
export GHMV_NO_LFS="true" # Skip LFS validation
export GHMV_STRICT_EXIT="true" # Exit code 2 on validation failures
export GHMV_RATE_LIMIT_THRESHOLD="100" # GitHub API rate limit warning threshold (default: 50, 0 to disable)gh migration-validatorUse strict exit mode when you need shell pipelines to detect validation failures. Enable it with the --strict-exit flag or set GHMV_STRICT_EXIT=true to return exit code 2 whenever any validation fails. Without this option the command exits 0 while still reporting failures in the output.
gh migration-validator \
--github-source-org "source-org" \
--github-target-org "target-org" \
--source-repo "my-repo" \
--target-repo "my-repo" \
--github-source-pat "ghp_xxx" \
--github-target-pat "ghp_yyy" \
--strict-exitFor GitHub App authentication, use environment variables:
# Source GitHub App
export GHMV_SOURCE_APP_ID="123456"
export GHMV_SOURCE_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n..."
export GHMV_SOURCE_INSTALLATION_ID="987654"
# Target GitHub App
export GHMV_TARGET_APP_ID="123457"
export GHMV_TARGET_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n..."
export GHMV_TARGET_INSTALLATION_ID="987655"For GitHub Enterprise Server:
export GHMV_SOURCE_HOSTNAME="https://github.example.com"The tool provides both export and validation capabilities that work together to enable point-in-time migration validation:
- Export: Capture repository data at a specific point in time
- Validate-from-Export: Validate target repositories against exported snapshots
This workflow is particularly useful when:
- The source repository continues to receive changes during migration
- You need to validate against the exact state when migration occurred
- You want to create audit trails of migration validation
gh migration-validator export \
--github-source-org "source-org" \
--source-repo "my-repo" \
--github-source-pat "ghp_xxx" \
--format json \
--output ".exports/my-export.json"The tool can also download and analyze migration archives to include additional validation metrics. See the Migration Archive Documentation for detailed information.
--github-source-org(required): Source organization name--source-repo(required): Source repository name--github-source-pat(required): GitHub token with read permissions--source-hostname(optional): GitHub Enterprise Server URL--format(optional): Export format -jsonorcsv(default:json)--output(optional): Output file path (auto-generated if not specified)--download(optional): Download and analyze migration archive automatically--download-path(optional): Directory to download migration archives to (default: ./migration-archives)--archive-path(optional): Path to an existing extracted migration archive directory--no-lfs(optional): Skip LFS object validation
Note: --download and --archive-path are mutually exclusive. For detailed migration archive usage, see Migration Archive Documentation.
JSON Format:
{
"export_timestamp": "2025-10-13T14:49:08Z",
"repository_data": {
"owner": "source-org",
"name": "my-repo",
"issues": 42,
"pull_requests": {
"open": 5,
"closed": 10,
"merged": 15,
"total": 30
},
"tags": 8,
"releases": 3,
"commits": 150,
"latest_commit_sha": "abc123def456",
"branch_protection_rules": 4,
"webhooks": 2
},
"migration_archive": {
"issues": 42,
"pull_requests": 30,
"protected_branches": 1,
"releases": 3
}
}When migration archive data is included, the export will contain additional migration_archive metrics. See Migration Archive Documentation for details.
CSV Format:
Contains the same data in CSV format with headers for easy analysis in spreadsheet applications.
When no output file is specified, exports are automatically saved to .exports/ directory with timestamped filenames:
.exports/{owner}_{repo}_export_{timestamp}.{format}
Example: .exports/mona-actions_my-repo_export_20251002_144908.json
The validate-from-export command allows you to validate a target repository against a previously exported snapshot of source repository data. This is essential for validating migrations when the source repository may have changed since the migration occurred.
gh migration-validator validate-from-export \
--export-file ".exports/mona-actions_my-repo_export_20251002_144908.json" \
--github-target-org "target-org" \
--target-repo "my-repo" \
--github-target-pat "ghp_yyy"If you already have an extracted migration archive directory:
gh migration-validator export \
--github-source-org "source-org" \
--source-repo "my-repo" \
--github-source-pat "ghp_xxx" \
--archive-path "path/to/extracted/migration-archive"--export-file(required): Path to the exported JSON file containing source data--github-target-org(required): Target organization name--target-repo(required): Target repository name--github-target-pat(required): GitHub token with read permissions for target--target-hostname(optional): GitHub Enterprise Server URL for target--markdown-table(optional): Output results in markdown format--markdown-file(optional): Write markdown output to the specified file; uses the same content without the surrounding ```markdown fences--no-lfs(optional): Skip LFS object validation--strict-exit(optional): Exit with status 2 on validation failures
export GHMV_TARGET_ORGANIZATION="target-org"
export GHMV_TARGET_TOKEN="ghp_yyy"
export GHMV_TARGET_REPO="my-repo"
export GHMV_MARKDOWN_TABLE="true"
export GHMV_MARKDOWN_FILE="validation-report.md"
export GHMV_NO_LFS="true" # Optional: skip LFS validation
gh migration-validator validate-from-export --export-file "path/to/export.json"-
Export source data before migration:
gh migration-validator export \ --github-source-org "source-org" \ --source-repo "my-repo" \ --github-source-pat "ghp_xxx"
-
Perform your migration (using GitHub's migration tools)
-
Validate against the export:
gh migration-validator validate-from-export \ --export-file ".exports/source-org_my-repo_export_20251002_144908.json" \ --github-target-org "target-org" \ --target-repo "my-repo" \ --github-target-pat "ghp_yyy"
This ensures you're validating against the exact state of the source repository when the migration occurred, regardless of any subsequent changes.
The bitbucket subcommand validates migrations from Bitbucket (Server / Data Center) to GitHub by comparing API metrics between the source Bitbucket instance and the target GitHub repository. This is useful for verifying that repository data was migrated correctly when moving from Bitbucket to GitHub.
gh migration-validator bitbucket \
--bbs-server-url "https://bitbucket.example.com" \
--bbs-project "PROJ" \
--bbs-repo "my-repo" \
--bbs-token "your-bbs-token" \
--github-target-org "target-org" \
--target-repo "my-repo" \
--github-target-pat "ghp_yyy"export GHMV_BBS_SERVER_URL="https://bitbucket.example.com"
export GHMV_BBS_PROJECT="PROJ"
export GHMV_BBS_REPO="my-repo"
export GHMV_BBS_TOKEN="your-bbs-token"
export GHMV_TARGET_ORGANIZATION="target-org"
export GHMV_TARGET_TOKEN="ghp_yyy"
export GHMV_TARGET_REPO="my-repo"
gh migration-validator bitbucket--bbs-server-url/-H(required): Bitbucket Server URL (aligned with GEIbbs2gh)--bbs-project/-p(required): Project key (use~usernamefor personal repos)--bbs-repo/-r(required): Repository slug--bbs-token/-k(required): Personal access token (orGHMV_BBS_TOKEN)
--github-target-org/-t(required): Target GitHub organization--github-target-pat/-b(required): Target GitHub token (orGHMV_TARGET_TOKEN)--target-repo(required): Target repository name--target-hostname/-v: GitHub Enterprise Server URL (optional)--markdown-table/-m: Output results in markdown format--markdown-file: Write markdown output to the specified file--no-lfs: Skip LFS object validation--strict-exit: Exit with status 2 on validation failures
| Metric | Status | Notes |
|---|---|---|
| Pull Requests (Total, Open, Merged, Declined→Closed) | ✅ Compared | Bitbucket "Declined" maps to GitHub "Closed" |
| Tags | ✅ Compared | |
| Commits | ✅ Compared | Default branch only |
| Latest Commit SHA | ✅ Compared | |
| Branch Permissions vs Branch Protection Rules | ℹ️ Advisory | Different concepts — shown for reference only |
| Webhooks | ✅ Compared | |
| Issues | ⏭️ Skipped | Bitbucket uses Jira, not native issues |
| Releases | ⏭️ Skipped | Bitbucket has no equivalent |
| LFS Objects | ⏭️ Skipped | TODO |
- Requires Bitbucket Server 5.5+ (uses Bearer token PAT authentication)
- The branch permissions comparison is advisory only (ℹ️ INFO) since Bitbucket branch permissions and GitHub branch protection rules are fundamentally different concepts
The tool supports working with GitHub migration archives for enhanced validation capabilities. Migration archives provide three-way validation comparing Source API ↔ Archive ↔ Target API data.
For comprehensive documentation on migration archive features, workflow, and usage examples, see Migration Archive Documentation.
The tool compares the following metrics between source and target repositories:
- Issues: Total count (expects +1 in target for migration log issue)
- Pull Requests: Total, Open, Merged, and Closed counts
- Tags: Total count of Git tags
- Releases: Total count of GitHub releases
- Commits: Total commit count on default branch
- Branch Protection Rules: Total count of branch protection rules configured for the repository
- Webhooks: Total count of active repository webhooks
- LFS Objects: Total count of Git LFS (Large File Storage) objects referenced in the repository (can be skipped with
--no-lfsflag) - Latest Commit SHA: Ensures both repositories have the same latest commit in default branch
- ✅ PASS: Metrics match expected values
- ❌ FAIL: Target is missing data from source
⚠️ WARN: Target has more data than source (usually acceptable)- ℹ️ INFO: Advisory comparison only (e.g., Bitbucket branch permissions vs GitHub branch protection)
The tool provides a formatted table with colored status indicators and a summary.
Example:
📊 Migration Validation Report
🔄 Source vs Target Validation
Metric | Status | Source Value | Target Value | Difference
Issues (expected +1 for migration log) | ⚠️ WARN | 2 (expected target: 3) | 7 | Extra: 4
Pull Requests (Total) | ✅ PASS | 29 | 29 | Perfect match
Pull Requests (Open) | ✅ PASS | 0 | 0 | Perfect match
Pull Requests (Merged) | ✅ PASS | 27 | 27 | Perfect match
Tags | ✅ PASS | 25 | 25 | Perfect match
Releases | ✅ PASS | 25 | 25 | Perfect match
Commits | ✅ PASS | 64 | 64 | Perfect match
Branch Protection Rules | ✅ PASS | 1 | 1 | Perfect match
Webhooks | ✅ PASS | 0 | 0 | Perfect match
LFS Objects | ✅ PASS | 15 | 15 | Perfect match
Latest Commit SHA | ✅ PASS | d11552345ad4ffea894b59d9a4145a5119d77dba | d11552345ad4ffea894b59d9a4145a5119d77dba | N/A
📦 Migration Archive vs Source Validation
Metric | Status | Source API Value | Archive Value | Difference
Archive vs Source Issues | ❌ FAIL | 2 | 6 | Missing: 4
Archive vs Source Pull Requests | ✅ PASS | 29 | 29 | Perfect match
Archive vs Source Protected Branches | ✅ PASS | 1 | 1 | Perfect match
Archive vs Source Releases | ✅ PASS | 25 | 25 | Perfect match
🎯 Migration Archive vs Target Validation
Metric | Status | Archive Value | Target Value | Difference
Archive vs Target Issues (expected +1 for migration log) | ✅ PASS | 6 (expected target: 7) | 7 | Perfect match
Archive vs Target Pull Requests | ✅ PASS | 29 | 29 | Perfect match
Archive vs Target Protected Branches | ✅ PASS | 1 | 1 | Perfect match
Archive vs Target Releases | ✅ PASS | 25 | 25 | Perfect match
📊 Passed: 16 📊 Failed: 1 📊 Warnings: 1
❌ Migration validation FAILED - Some data is missing in target
Use the --markdown-table flag to generate copy-paste ready markdown for documentation.
- Go 1.20 or higher
- Key dependencies:
- Cobra - CLI framework
- Viper - Configuration management
- go-github - GitHub REST API client
- githubv4 - GitHub GraphQL API client
- go-githubauth - GitHub App authentication
- go-github-ratelimit - Rate limit handling
- pterm - Terminal styling and formatting
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.