| name | cncf-migration-ghes-to-github |
| description | Technically migrate a clean repository from GitHub Enterprise Server (GHES) to a public github.com org using squash-to-milestones or mirror-push, then configure branch protection and least-privilege RBAC. Specific to CNCF OSS migration.
|
When to Use This Skill
Use this skill when:
- Performing the final push of a sanitized GHES repo to github.com
- Configuring branch protection on the newly public
main branch
- Setting up GitHub Teams with CNCF-recommended role separation
Do NOT use for general git push or branch operations.
Approach A — Squash to Milestone Commits (Vionix pattern)
Produces a minimal, clean public history from a set of hand-picked milestones.
No internal commit messages, author churn, or in-progress work leaks through.
How it works
git archive --format=tar <source-ref> | tar -x -C /tmp/snapshot
python3 .migrate/scrub.py <source-ref>
GIT_AUTHOR_DATE="<milestone-date>" \
GIT_COMMITTER_DATE="<milestone-date>" \
git commit -m "feat: v1.0.0 — <milestone summary>"
git tag -a v1.0.0 -m "Vionix v1.0.0" HEAD
Run .migrate/build_history.sh to reproduce all five Vionix milestones:
v0.0.1 → v1.0.0 → v1.0.1 → v1.5.0 → v2.0.0
Approach B — Mirror Push with Author Rewrite
Preserves full commit history. Requires running cncf-migration-author-mapper
and cncf-migration-secrets-scrubber first.
git clone --bare <GHES_URL> source-repo.git
cd source-repo.git
git filter-repo --mailmap /path/to/.mailmap
git remote add public git@github.com:<OrgName>/<RepoName>.git
git push --mirror public
GHES mirror org decode (required before push)
Internal GHES encodes external action origins in its org naming:
github/<org>-<repo> → <org>/<repo> on github.com
viarise/<action> → actions/<action> (GitHub official)
seceng-devsecops-platform/<org>-<repo> → <org>/<repo> if org exists publicly
seceng-devsecops-platform/<custom-docker-action> → Viasat/vionix/actions/... (bring in-repo)
Viasat/<org>-<repo> → <org>/<repo> (Viasat-held mirror)
All uses: references must be decoded BEFORE the public push. See .migrate/scrub.py.
Step 1 — Create the Public Repository
gh repo create <OrgName>/<RepoName> \
--public \
--description "<Project description>" \
--homepage "https://<project-url>"
Step 2 — Push History
git push --force origin main
git push origin --tags
Force-push is only acceptable when:
- The repo was just created (no downstream consumers yet), OR
- All collaborators have been notified and have re-cloned
Never force-push after a public release tag is live and consumed.
Step 3 — Branch Protection (CNCF minimum)
gh api -X PUT "repos/<OrgName>/<RepoName>/branches/main/protection" \
--input - <<'EOF'
{
"required_status_checks": {
"strict": true,
"contexts": []
},
"enforce_admins": false,
"required_pull_request_reviews": {
"required_approving_review_count": 1,
"dismiss_stale_reviews": true,
"require_code_owner_reviews": true
},
"restrictions": null,
"required_linear_history": true,
"allow_force_pushes": false,
"allow_deletions": false,
"required_conversation_resolution": true,
"require_signed_commits": false
}
EOF
CNCF recommendation: enable signed commits (require_signed_commits: true)
for incubating and graduated projects.
Step 4 — RBAC with GitHub Teams (CNCF roles)
gh api -X POST "orgs/<OrgName>/teams" \
-f name="<project>-maintainers" \
-f description="Merge rights and release authority" \
-f privacy="closed"
gh api -X POST "orgs/<OrgName>/teams" \
-f name="<project>-reviewers" \
-f description="PR review rights, no merge" \
-f privacy="closed"
gh api -X PUT "orgs/<OrgName>/teams/<project>-maintainers/repos/<OrgName>/<RepoName>" \
-f permission="maintain"
gh api -X PUT "orgs/<OrgName>/teams/<project>-reviewers/repos/<OrgName>/<RepoName>" \
-f permission="write"
| CNCF Role | GitHub Permission | Rights |
|---|
| Maintainer | maintain | Merge PRs, manage issues, no admin |
| Reviewer | write | Review and approve PRs, push to branches |
| Contributor | read (default public) | Fork, open PRs, file issues |
Step 5 — Verify Public Accessibility
gh repo view <OrgName>/<RepoName> --json isPrivate,description,url,defaultBranchRef
gh api "repos/<OrgName>/<RepoName>/tags" | python3 -c "
import json,sys
[print(t['name']) for t in json.load(sys.stdin)]
"
gh api "repos/<OrgName>/<RepoName>/branches/main/protection" \
| python3 -c "import json,sys; p=json.load(sys.stdin); print(json.dumps(p, indent=2))"
Vionix Push Commands (final step)
cd /Users/mdesales/dev/github.com/Viasat/vionix
git push --force origin main
git push origin --tags
Related Skills
cncf-migration-secrets-scrubber — run before push
cncf-migration-author-mapper — run before push (Approach B)
cncf-migration-health-monitor — run after push to validate score