Skip to main content

YAML-config-driven GitHub repository manager

Project description

Repo Management

Declarative, YAML-driven management of GitHub repository configuration. Describe how a repo should be configured in a YAML file; repo-management reads the live state, shows you the diff, and reconciles it through the GitHub API (via PyGithub).

It is declarative and idempotent: re-running when nothing has changed does nothing. A section you don't mention is left unmanaged; a section you do declare is authoritative — it's the complete desired set, so anything on the repo not listed in it is removed.

Full documentation lives at nivintw.github.io/repo-management; the source code, issue tracker, and annotated example configs live in the nivintw/repo-management repository on GitHub.

Install

uv tool install repo-management    # or: pip install repo-management

Requires Python 3.14+. Authentication uses a GitHub token, read from $GITHUB_TOKEN (or --token). The token needs the scopes for whatever you manage (repo administration, Actions secrets, etc.).

Usage

Write a config file first — the repos.yaml below; see Config format for the schema and the repository's annotated examples/ for a working pair. Then:

export GITHUB_TOKEN=ghp_...

repo-management validate  -c repos.yaml   # check the YAML (no network)
repo-management plan      -c repos.yaml   # show the diff (read-only)
repo-management apply     -c repos.yaml   # reconcile (prompts before writing)
repo-management list-repos                # the managed fleet across a config dir (no network)

Useful flags:

  • --repo owner/name — limit the run to a single repo from the config.
  • --yes / -y — skip the confirmation prompt on apply.
  • --token — pass a token explicitly instead of using $GITHUB_TOKEN.

plan prints one line per change: + create, ~ update, - delete. Secret values are always redacted.

list-repos prints the managed fleet — the union of the repos: lists across every applied *.yml file in a config directory (--config-dir, default config/; *.yaml files are treated as base layers, not applied configs, and are skipped). --format names emits a single comma-separated line of owner-relative names, which the repo-management repository uses to scope its central Renovate runner's App token (see Fleet automation).

Config format

A config file lists the repositories to manage and one shared block of config sections applied to every one of them:

repos:
  - owner/repo
  - owner/other
settings: { ... }
rulesets: [ ... ]
labels: [ ... ]

Composing configs with extends

A file may extends: one or more base files (a string or a list, relative paths, resolved recursively). The bases are merged underneath, then this file is merged on top:

  • scalars — the override wins;
  • list sections (rulesets, labels.items, collaborators, webhooks, secrets) — merge by each item's natural key (ruleset/label/secret name, collaborator username, webhook url): a same-key item in the override replaces the base's item, and new items are appended.
# repos.yaml
extends: base.yaml
repos: [owner/repo]
settings:
  description: Managed by repo-management   # added on top of base.settings

See examples/base.yaml + examples/repos.yaml in the repository for a fully-annotated, working pair.

extends: reads and merges local files by relative path, so only point it at config you trust — treat a base file the same as the config that includes it.

What it manages

Section Manages
settings description, homepage, topics, visibility, features (issues/wiki/projects/discussions), merge options (squash/merge/rebase, auto-merge, delete-branch-on-merge), default branch, Actions workflow approval permission (can_approve_pull_request_reviews)
rulesets repository rulesets (branch/tag): the full rule set (pull_request, required_status_checks, required_linear_history, non_fast_forward, deletion, creation, update, required_deployments, merge_queue, required_signatures, the *_pattern rules, file_path/extension/size restrictions, workflows, code_scanning), plus bypass_actors and ref-name conditions
labels create/update/delete labels to match the listed set exactly
collaborators add/re-permission direct collaborators; remove those not listed
webhooks create/update/delete webhooks, matched by URL
secrets Actions secrets (write-only; libsodium-encrypted by PyGithub); delete those not listed
variables Actions repository variables (plain text; updated only when the value differs); delete those not listed

Design notes / limitations

  • A declared section is authoritative. Each section you include is the complete desired set: items present on the repo but absent from the section are removed (labels, webhooks, secrets, variables deleted; direct collaborators removed; repo rulesets deleted). A section you omit is left entirely unmanaged. This means a declared section can revoke access or delete a secret by omission — declare deliberately.
  • Only directly-granted collaborator access is managed. Pruning uses affiliation="direct", so access inherited from org or team membership is never listed and never touched (the repo API can't revoke it anyway).
  • Rulesets are matched by name and updated to the full declared spec. PyGithub has no ruleset support, so this is driven through its authenticated requester against the REST rulesets API. On update, a ruleset's rules/conditions/bypass actors are PUT to exactly what the config declares. A declared ruleset's lists (rules, bypass actors, ref-name patterns) must match the live ones exactly — a manually-added rule is drift that triggers an update — while server-supplied metadata the config never sets (item integration_id, bypass actor_id, timestamps) is ignored, so it can't cause churn. Listing passes includes_parents=false, so inherited org/enterprise rulesets are never matched or deleted through the repo.
  • Secrets and webhook secrets are write-only. The API never returns a secret value, so a change to only a secret can't be diffed: an Actions secret already present on the repo is left untouched by default (pass --force-secrets to re-push values for rotation), and a webhook with a configured secret is always re-sent (shown as (set) in the plan). Values are sourced from the environment (value_from_env / secret_from_env) and never printed. A literal value: is supported for secrets but should never be committed.
  • Variables, by contrast, are readable. An Actions variable value is returned by the API, so a variable is updated only when its value actually differs and the value is shown in plain text in the plan. Variables take a literal value: or value_from_env: (same shape as secrets, minus the secrecy).

Fleet automation

Beyond the CLI, the nivintw/repo-management repository — the tool's home — is itself a working deployment: a control plane that manages its author's repositories (its "fleet") with scheduled GitHub Actions, authenticating as a CI GitHub App. It doubles as a reference for running the tool this way:

  • apply-config / plan-config — reconcile the live repos to the repository's config/*.yml: apply on push to main, plan as a read-only PR preview.
  • renovate — a central, self-hosted Renovate runner that opens dependency-update PRs across the managed fleet on a schedule, replacing the hosted Renovate app (and unlocking postUpgradeTasks, so a bumped binary's checksum is refreshed inside Renovate's own commit). It scopes its App token to exactly the fleet — repo-management list-repos --format names derives that set from the same config/*.yml that drives apply, so one config is the single source of truth for what Renovate touches. Its global behaviour lives in the repository's .github/renovate-global.json, separate from the repository's own dependency policy in .github/renovate.json. Dispatch it manually with dryRun to preview the scope and proposed changes without opening any PRs.

Development

Clone the repository, then:

uv sync                      # create the venv + install everything
uv run pytest                # tests + coverage (gate: 90%; currently 100%)
uvx prek run --all-files     # the full quality gate (ruff, format, REUSE, typos, …)

Quality checks run identically locally and in CI via prek hooks: git hygiene, gitleaks, typos, rumdl, SPDX/REUSE headers, and ruff. Conventional Commits (gitmoji) are enforced at commit-msg time. A few hooks shell out to system tools prek can't bootstrap — install them locally too (most are in Homebrew): hawkeye, taplo, osv-scanner.

License

MIT — and REUSE-compliant.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

repo_management-1.3.0.tar.gz (132.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

repo_management-1.3.0-py3-none-any.whl (34.0 kB view details)

Uploaded Python 3

File details

Details for the file repo_management-1.3.0.tar.gz.

File metadata

  • Download URL: repo_management-1.3.0.tar.gz
  • Upload date:
  • Size: 132.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for repo_management-1.3.0.tar.gz
Algorithm Hash digest
SHA256 6c3b3be052672cd5e2fdb437c72b231dc526b56d0261daa1f306810585c32f3c
MD5 061375527a3cc6026b496359b9e97bda
BLAKE2b-256 f806a5d755a8e72a9388d6a81feff37c20af86e13c4337ac1d93bf538caab181

See more details on using hashes here.

File details

Details for the file repo_management-1.3.0-py3-none-any.whl.

File metadata

  • Download URL: repo_management-1.3.0-py3-none-any.whl
  • Upload date:
  • Size: 34.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for repo_management-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e97abe2f2bcf45d13cdc48dd9fb68f10010c39f737c0180430edcaf837f741bb
MD5 b7a1a3be8f838ca7cb0e14d930ed26d0
BLAKE2b-256 ccba8c7ddff98d33d3f797ec74deec25ac327ae40a3c5c93faae350ad0a01f4f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page