Bulk updating repository settings with the API Jump to heading
Organisations accumulate repositories faster than anyone can configure them by hand. One allows merge commits and another only squash; half delete branches after merging; secret scanning is on wherever someone remembered. Fixing that through the web interface is hours of clicking with no record of what changed. The forge API turns it into a script — and turns a typo into a change applied to four hundred repositories in a minute. The difference between a useful bulk change and an incident is process: a dry run that shows exactly what would change, small batches, a log of before-and-after values, and a way back. This page builds that process around a few common settings, within forge API and webhook automation.
When to use this approach Jump to heading
- Repository settings differ across your organisation and should not.
- A new policy — delete merged branches, enable secret scanning, squash-only merging — needs applying everywhere.
- You need an auditable record of which repositories changed and when.
- For ongoing enforcement rather than one-off changes, combine this with managing repository permissions as code.
Step 1 — Select the target repositories explicitly Jump to heading
Write the list of repositories to a file and review it. Exclude archived repositories, forks and anything owned by another team unless you mean to include them.
gh repo list "$ORG" --limit 2000 --no-archived --source \
--json nameWithOwner,visibility --jq '.[] | "\(.nameWithOwner)\t\(.visibility)"' > targets.tsv
wc -l targets.tsv
cut -f1 targets.tsv | grep -E '^acme/(legacy-|sandbox-)' && echo "^ review these exclusions" Step 2 — Dry run: read the current value, print the intended change Jump to heading
A dry run reads each repository’s current settings and prints only the repositories that would change, with the old and new values. Nothing is written.
#!/bin/sh
# bulk-settings.sh [--apply] — enforce merge settings across targets.tsv
set -eu
apply=${1:-}
want='{"allow_squash_merge":true,"allow_merge_commit":false,"allow_rebase_merge":false,"delete_branch_on_merge":true}'
while IFS="$(printf '\t')" read -r repo vis; do
now=$(gh api "repos/$repo" --jq '{allow_squash_merge,allow_merge_commit,allow_rebase_merge,delete_branch_on_merge}')
diff=$(jq -n --argjson a "$now" --argjson b "$want" '[$b|keys[] as $k | select($a[$k] != $b[$k]) | "\($k): \($a[$k]) -> \($b[$k])"] | join(", ")')
[ "$diff" = '""' ] && continue
printf '%s\t%s\n' "$repo" "$diff"
if [ "$apply" = --apply ]; then
printf '%s\t%s\t%s\n' "$(date -u +%FT%TZ)" "$repo" "$now" >> change-log.tsv
printf '%s' "$want" | gh api -X PATCH "repos/$repo" --input - >/dev/null
fi
done < targets.tsv sh bulk-settings.sh | tee dry-run.txt | head
wc -l dry-run.txt # how many repositories would change Attach the dry-run output to the pull request that adds or changes the script. Reviewers then approve a concrete list of changes, not an abstract intention.
Step 3 — Apply in small batches and watch Jump to heading
Apply to a handful of repositories first — ideally ones owned by the team making the change — and check nothing unexpected happens. Then proceed in batches of ten or twenty with a pause between them, so a problem is noticed before it reaches everyone.
split -l 20 targets.tsv batch-
cp batch-aa targets.tsv && sh bulk-settings.sh --apply # first batch
# check: did anything break for those teams? then continue
for b in batch-a[b-z]; do cp "$b" targets.tsv && sh bulk-settings.sh --apply; sleep 60; done Batches also keep the job within API rate limits; the details are in handling API rate limits in Git automation.
Step 4 — Roll back from the change log Jump to heading
The change log records each repository’s previous values. Rolling back one repository, or the whole change, is the same script reading the log instead of the desired state.
# Restore the previous merge settings for one repository
awk -F'\t' '$2=="acme/payments"' change-log.tsv | tail -1 | cut -f3 |
gh api -X PATCH repos/acme/payments --input - ⚠️ SAFETY WARNING: Some settings changes have effects beyond the setting itself. Disabling a merge method breaks open pull requests whose authors relied on it; enabling required signatures blocks contributors without signing keys; deleting webhooks stops integrations immediately. Read the forge’s documentation for each setting before bulk-applying it, and announce changes that affect people’s daily work in advance.
Step 5 — Verify and keep it true Jump to heading
After the rollout, read every repository’s settings back and confirm they match. Then turn the script into a scheduled drift report, so new repositories and manual changes are caught.
# Verification: no target repository still differs from the desired settings
sh bulk-settings.sh | wc -l # expect 0 Scheduling the report is covered in scheduled workflows and cron triggers.
Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Should we use organisation-level defaults instead? Jump to heading
Where the forge offers them — default merge methods, organisation rulesets, security feature defaults — yes: they cover new repositories automatically. Bulk scripts are still needed to bring existing repositories in line and for settings with no organisation-level control.
How do we handle repositories that need exceptions? Jump to heading
Keep an exceptions file with the repository, the setting and the reason, and have the script skip those. Review it periodically; exceptions without a reason tend to outlive their need.
Is it safe to run bulk changes from a laptop? Jump to heading
Prefer running them from CI with an app identity and the script in version control, so the change is reviewed and logged. A laptop run with a personal token leaves no shared record.
Related Jump to heading
- Forge API & Webhook Automation — the parent topic.
- Designing Branch Protection Rulesets for an Org — organisation-level rules that reduce bulk work.
- Sharing One Hook Config Across Repositories — bulk changes to files rather than settings.
- Deleting Merged Branches Automatically — one of the settings commonly rolled out this way.