This repository contains two Bash scripts (macOS-compatible) that help you migrate GitHub forks from your personal account into an organization, and then update your local Git remotes to point to the new org repositories.
Both scripts are safe by default (dry-run mode) and fully compatible with Bash 3.2 (the macOS default Bash).
| Script | Purpose |
|---|---|
transfer-forks.sh |
Scans your personal forks, transfers eligible ones to an organization, and records a mapping file (~/.fork_transfer_map.tsv). |
fix-local-remotes.sh |
Reads that mapping file and updates local clones’ origin URLs to point to the new organization repos. |
| Script | Flag / Argument | Description |
|---|---|---|
transfer-forks.sh |
<your-username> |
Your GitHub username (owner of the forks). |
<target-org> |
Target organization to transfer forks into. | |
--execute |
Actually perform transfers (otherwise dry-run). | |
--delete-inactive |
Delete inactive forks left in your account. | |
--inactive-days N |
Days since last push to consider a fork inactive (default: 120). | |
--limit N |
Process at most N eligible (post-skip) repos in this run. | |
--debug |
Enable detailed execution trace and logging to ~/transfer-forks-debug.log. |
|
fix-local-remotes.sh |
<local-clone-root> |
Directory under which your local clones live. |
--execute |
Apply remote changes (otherwise dry-run). | |
--debug |
Enable xtrace and logging to ~/fix-remotes-debug.log. |
./transfer-forks.sh myuser myorg --limit 10./transfer-forks.sh myuser myorg --execute./transfer-forks.sh myuser myorg --execute --delete-inactive --inactive-days 90./fix-local-remotes.sh ~/projects/forks
./fix-local-remotes.sh ~/projects/forks --executeTo successfully perform transfers using the GitHub API:
- You must have admin access to each repository being transferred.
- You must have create repository permission in the target organization.
- The organization must not already have:
- A repository with the same name, or
- Another fork in the same fork network.
- Private forks cannot be transferred if their upstream repository is also private.
When authenticating via the GitHub CLI (gh auth login), use one of the following:
Required scopes:
- repo — Full control of private and public repos.
- admin:org — To create repos and perform transfers into the target org.
- If the org enforces SSO, the PAT must be SSO-authorized for that org.
- Required repository permissions:
- Administration: Read & Write
- Contents: Read & Write
- Organization access must be explicitly granted to the target org.
You can verify your token’s scopes with:
gh auth status
gh auth tokenIf a transfer fails with 403 or 404, confirm that your token covers both repo administration and org creation rights.
- Dry-run by default – no irreversible changes unless
--executeis given. - Mapping file:
~/.fork_transfer_map.tsvstores tab-separated linesold_full<TAB>new_full. - Deduplication: Runs automatically after each transfer batch.
- Logging: Timestamps printed for every action.
- Limit (
--limit) applies to attempted transfers, not scanned forks. - Error handling: Failures are logged but don’t stop other repos.
- PR protection: Forks with open PRs you authored in their upstream are skipped.
Both scripts support a --debug flag.
When enabled:
- Turns on Bash xtrace (
set -x). - Captures stdout + stderr to:
~/transfer-forks-debug.log~/fix-remotes-debug.log
- Still echoes output to the console.
- Useful for troubleshooting gh API failures, permission errors, or unexpected skips.
./transfer-forks.sh myuser myorg --execute --limit 5 --debug
./fix-local-remotes.sh ~/projects/forks --execute --debugIf you performed a partial transfer (e.g. --limit 10) and want to restart fresh:
- Delete transferred repos from your org (if you want to re-transfer them).
- Remove the mapping file:
rm -f ~/.fork_transfer_map.tsv- Run transfer-forks.sh again for a clean run. To automate this, you can use clean-run.sh:
#!/usr/bin/env bash
# clean-run.sh – removes all transferred forks from the org and resets mapping.
MAPPING_FILE="${HOME}/.fork_transfer_map.tsv"
[ ! -f "$MAPPING_FILE" ] && echo "No mapping file found." && exit 0
read -p "Delete all mapped repos from org and reset mapping? (y/N) " resp
[[ "$resp" != [yY] ]] && echo "Aborted." && exit 0
while IFS=$'\t' read -r old new; do
[ -z "$new" ] && continue
echo "Deleting $new ..."
gh repo delete "$new" --yes || echo "Warning: failed to delete $new"
done < "$MAPPING_FILE"
rm -f "$MAPPING_FILE"
echo "Mapping cleared. Ready for a clean run."When a repo is transferred, GitHub automatically redirects all git and HTTP operations from the old path to the new one.
However, updating remotes is still recommended because:
- Redirects introduce slight latency and confusion in
git remote -v. - Your local config continues to reference the outdated location.
- Redirects are removed if a new repo later occupies the old path.
- GitHub Pages (username.github.io/repo) do not redirect after transfer.
Reference: GitHub Docs — About repository transfers
- Private upstream forks generally cannot be transferred.
- Transfers fail if name conflicts or existing forks exist in the target org.
- Old repo URLs continue working via redirects, but updating is cleaner.
- Scripts assume clone directory names match repo names exactly.
- Only the origin remote is updated; custom remote names are ignored.
transfer-forks.sh → Moves forks from your personal account to an org, logs results to ~/.fork_transfer_map.sh.
- Reads
~/.fork_transfer_map.tsv. - For each mapping:
- Finds local directory named after the repo.
- Checks
.gitpresence. - Updates or adds origin URL, preserving SSH/HTTPS scheme.
- Skips already-correct remotes.
- Provides a concise per-repo log and a summary at the end.
[2025-10-14T00:30:02Z] TRANSFER user/foo → org/foo [dry-run=true]
[2025-10-14T00:30:02Z] OK transferred: user/foo → org/foo
[2025-10-14T00:30:02Z] Done. Transferred: 1, Deleted: 0, Attempted: 1, Scanned: 3 (execute=false, limit=1)
[2025-10-14T00:30:02Z] Mapping persisted at: /Users/you/.fork_transfer_map.tsv| Script | Function | Key Options |
|---|---|---|
transfer-forks.sh |
Transfers forks to an organization and records mappings. | --execute, --limit, --debug |
fix-local-remotes.sh |
Updates local clones’ origin URLs to match transferred repos. | --execute, --debug |
clean-run.sh |
Deletes mapped repos and clears state for a clean rerun. | (no flags) |
All scripts are safe, idempotent, and designed for incremental migration of large numbers of forks.
