rsmultigit 0.1.30

Manage multiple git repositories at once
# Example rsmultigit config. This same text is available from a running binary
# via `rsmultigit config-example`, so to bootstrap:
#
#   mkdir -p ~/.config/rsmultigit
#   rsmultigit config-example > ~/.config/rsmultigit/config.toml
#
# The config location is fixed — there is no --config flag. Every rsmultigit
# command reads this file to decide which repositories to operate on.
#
# `repos` is a list of shell-expanded glob patterns that resolve to repo roots.
# Matches that aren't git repos are filtered out automatically.
#
# `[[check]]` blocks are consumed only by `rsmultigit check-same`, which verifies
# that a given path has identical content across a selection of repos.
#
# Selection primitives (per check rule):
#   select     — glob over repo names (required)
#   exclude    — glob of repo names to drop from the selection
#   marker     — only include repos that contain this file (relative to repo root)
#   marker_absent — the inverse: drop repos that contain this file. This is how a
#                repo opts out of an invariant from inside itself (e.g. a `.noci`
#                file in a repo that deliberately has no CI) instead of being
#                named in an `exclude` glob that lives far away from it.
#   path       — file inside each repo to compare (required)
#   enabled    — bool (default true). Disabled rules are silently skipped, but can
#                still be forced on with `--checks <name...>` for ad-hoc checks.
#   must_have  — bool (default false). When true, every in-scope repo must contain
#                `path`; missing files become rule violations (reported as "missing in:"
#                and counted as mismatches). `check-same --fix-missing` will walk
#                those violators interactively and create the file from a chosen
#                seed group.
#
# A rule passes when every surviving repo's `path` hashes the same, *and*
# (when must_have = true) every in-scope repo contains the file.
# Repos that don't contain `path` are silently skipped when must_have = false.
#
# A rule that ends up matching no files at all fails ("no files matched") —
# that usually means a stale select/path. Pass `check-same --allow-empty` to
# let such rules pass as `ok (0 files)` instead.

repos = [
    "~/git/*",
]

# ── shared dev-tooling configs across all repos ───────────────────────────────

[[check]]
name = "pylintrc"
select = "*"
path = ".pylintrc"

[[check]]
name = "gitignore"
select = "*"
path = ".gitignore"

[[check]]
name = "editorconfig"
select = "*"
path = ".editorconfig"
must_have = true

[[check]]
name = "shellcheckrc"
select = "*"
path = ".shellcheckrc"

[[check]]
name = "jshintrc"
select = "*"
path = ".jshintrc"

[[check]]
name = "jshintignore"
select = "*"
path = ".jshintignore"

# rsconstruct.toml: the long-term goal is byte-identical in every repo
# (canonical copy: ~/git/rsconstruct.canonical.toml). Currently the files
# diverge by design (src_dirs pruned per repo, rsconstruct.local.toml merged
# in and removed on 2026-08-24), so this rule reports many groups; it stays
# as the tracker for the goal.
[[check]]
name = "rsconstruct-toml"
select = "*"
path = "rsconstruct.toml"
enabled = false

[[check]]
name = "license"
select = "*"
path = "LICENSE"
must_have = true

[[check]]
name = "readme-template"
select = "*"
path = "tera.templates/README.md.tera"

[[check]]
name = "requirements-thawed-template"
select = "*"
path = "templates/requirements.thawed.txt.mako"

[[check]]
name = "github-funding"
select = "*"
path = ".github/FUNDING.yml"

[[check]]
name = "sphinx-conf"
select = "*"
path = "tera.templates/sphinx/conf.py.tera"

[[check]]
name = "sphinx-index"
select = "*"
path = "sphinx/index.rst"

# Disabled: no repo has mdl configs any more (markdown linting moved to
# rumdl via rsconstruct), so these rules match nothing.
[[check]]
name = "mdlrc"
select = "*"
path = ".mdlrc"
enabled = false

[[check]]
name = "mdl-style"
select = "*"
path = ".mdl.style.rb"
enabled = false

# ── GCP repos ─────────────────────────────────────────────────────────────────

[[check]]
name = "gcp-gcloudignore"
select = "gcp-*"
marker = ".gcp.conf"
path = ".gcloudignore"

# Disabled: app.yaml is per-app deployment config. gcp-machines' copy carries
# env_variables (ADMIN_EMAILS/ACCESS_TOKEN) that its own src/main.py reads, so
# the three files legitimately differ and byte-identity is the wrong invariant.
[[check]]
name = "gcp-app-yaml"
select = "gcp-*"
marker = ".gcp.conf"
path = "app.yaml"
enabled = false

[[check]]
name = "workflow-build-yml"
select = "*"
marker = "rsconstruct.toml"
path = ".github/workflows/build.yml"

# ── shared lua config for the tera renderer ──────────────────────────────────

# config/*.lua are read by rsconstruct's tera template renderer. personal.lua
# carries no per-repo values, so it is byte-identical by design. (project.lua
# and version.lua legitimately differ per repo and are deliberately not
# checked.)
[[check]]
name = "config-personal-lua"
select = "*"
# veltzer.github.io carries a zero-byte config/personal.lua (and an empty
# project.lua) but has no tera.templates at all, so it never renders anything
# and the placeholder is excluded rather than filled with unused config.
exclude = "veltzer.github.io"
path = "config/personal.lua"

# yamllint config: one shared ruleset wherever YAML is linted.
[[check]]
name = "yamllint"
select = "*"
path = ".yamllint.yaml"

# The test package initializer is boilerplate, identical in every repo that
# has a tests/ package.
[[check]]
name = "tests-init"
select = "*"
path = "tests/__init__.py"

# ── rs* Rust repos: canonical files, synced from rsconstruct ──────────────────

[[check]]
name = "rs-release-info"
select = "rs*"
path = "docs/src/release-info.md"
must_have = true

[[check]]
name = "rs-workflow-ci"
select = "rs*"
path = ".github/workflows/ci.yml"
must_have = true

[[check]]
name = "rs-build-rs"
select = "rs*"
path = "build.rs"
must_have = true

[[check]]
name = "rs-release-toml"
select = "rs*"
path = "release.toml"
must_have = true