1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# 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)
# 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.
= [
"~/git/*",
]
# ── shared dev-tooling configs across all repos ───────────────────────────────
[[]]
= "mypy-ini"
= "*"
= ".mypy.ini"
[[]]
= "pylintrc"
= "*"
= ".pylintrc"
[[]]
= "gitignore"
= "*"
= ".gitignore"
[[]]
= "editorconfig"
= "*"
= ".editorconfig"
= true
[[]]
= "shellcheckrc"
= "*"
= ".shellcheckrc"
[[]]
= "jshintrc"
= "*"
= ".jshintrc"
[[]]
= "jshintignore"
= "*"
= ".jshintignore"
# ── python-module specifics (repos prefixed `py` with the veltzer tag) ────────
[[]]
= "py-makefile"
= "py*"
= ".veltzer.tag"
= "Makefile"
[[]]
= "py-license-template"
= "py*"
= ".veltzer.tag"
= "templates/LICENSE.mako"
[[]]
= "py-pyproject-template"
= "py*"
= ".veltzer.tag"
= "templates/pyproject.toml.mako"
[[]]
= "py-readme-template"
= "py*"
= ".veltzer.tag"
= "templates/README.md.mako"
# ── non-python repos (everything except `py*`) with the veltzer tag ───────────
[[]]
= "non-py-readme-template"
= "*"
= "py*"
= ".veltzer.tag"
= "templates/README.md.mako"
# ── repos with the veltzer tag (mix of py / non-py) ───────────────────────────
[[]]
= "build-workflow-template"
= "*"
= ".veltzer.tag"
= "templates/.github/workflows/build.yml.mako"
[[]]
= "requirements-thawed-template"
= "*"
= ".veltzer.tag"
= "templates/requirements.thawed.txt.mako"
[[]]
= "github-funding"
= "*"
= ".veltzer.tag"
= ".github/FUNDING.yml"
[[]]
= "sphinx-conf"
= "*"
= ".veltzer.tag"
= "sphinx/conf.py"
[[]]
= "sphinx-index"
= "*"
= ".veltzer.tag"
= "sphinx/index.rst"
[[]]
= "config-platform"
= "*"
= ".veltzer.tag"
= "config/platform.py"
[[]]
= "config-personal"
= "*"
= ".veltzer.tag"
= "config/personal.py"
[[]]
= "config-github"
= "*"
= ".veltzer.tag"
= "config/github.py"
[[]]
= "config-shared"
= "*"
= ".veltzer.tag"
= "config/shared.py"
[[]]
= "aspell-conf"
= "*"
= ".veltzer.tag"
= ".aspell.conf"
[[]]
= "mdlrc"
= "*"
= ".veltzer.tag"
= ".mdlrc"
[[]]
= "mdl-style"
= "*"
= ".veltzer.tag"
= ".mdl.style.rb"
# ── GCP repos ─────────────────────────────────────────────────────────────────
[[]]
= "gcp-gcloudignore"
= "gcp-*"
= ".gcp.conf"
= ".gcloudignore"
[[]]
= "gcp-makefile"
= "gcp-*"
= ".gcp.conf"
= "Makefile"
[[]]
= "gcp-app-yaml"
= "gcp-*"
= ".gcp.conf"
= "app.yaml"
# ── build.yml: byte-identical in every repo that has rsconstruct.toml ─────────
# The generic rsconstruct pipeline. It is deliberately the *same file*
# everywhere — the repo-specific parts live in rsconstruct.toml, not here — so
# any divergence is drift, not configuration. Scoped by the rsconstruct.toml
# marker rather than .veltzer.tag: 25 repos carry build.yml without that tag,
# and the old py*/non-py* split let the two halves drift past each other
# unnoticed. must_have stays false because two repos legitimately have no
# build.yml: demos-os-linux runs the same pipeline from cicd.yml inside its own
# CI container, and rsslide is a Rust repo covered by the shared ci.yml.
[[]]
= "build-workflow"
= "*"
= "rsconstruct.toml"
= ".github/workflows/build.yml"
# ── rs* Rust repos: canonical files, synced from rsconstruct ──────────────────
[[]]
= "rs-ci-workflow"
= "rs*"
= ".github/workflows/ci.yml"
= true
[[]]
= "rs-build-rs"
= "rs*"
= "build.rs"
= true
# ── disabled rules (were commented out in mg_check_same_files.sh) ─────────────
# Re-enable by flipping `enabled = true`, or run ad hoc via:
# rsmultigit check-same --checks <name>
[[]]
= "py-deps-config"
= "py*"
= ".veltzer.tag"
= "config/deps.py"
= false
[[]]
= "py-setup-template"
= "py*"
= ".veltzer.tag"
= "templates/setup.py.mako"
= false
[[]]
= "py-readme-rst-template"
= "py*"
= ".veltzer.tag"
= "templates/README.rst.mako"
= false
[[]]
= "py-idea-gitignore"
= "py*"
= ".veltzer.tag"
= ".idea/.gitignore"
= false