specguard-ruby 0.3.8 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +16 -0
- data/lib/specguard/rspec/file_selector.rb +75 -7
- data/lib/specguard/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6266c2bcd8775838ab93bbb3963cb007465575ee038f67490161fa64e10e982e
|
|
4
|
+
data.tar.gz: 7799e565338f59d3a76b4b19e19a0877dd172072e772e8cc81909f5d040d3ec7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3e811640885a89c54dec7bd69702ca429b0f28f5bfdd7c76fd049d15621da4ce0b48323af914448236877f9bc40df78df642232cda89dbfef5a0adcd85612ed7
|
|
7
|
+
data.tar.gz: 28e11252d3051943734247a9878a50680ee719cff789a1ea53da11041574529ed5d9e3761c5445f33b84b0ae42994ae121d35895ec6c0f33fd2c8045163c329e
|
data/README.md
CHANGED
|
@@ -54,6 +54,22 @@ bundle exec specguard-lint --changed # CI mode: only files in the current diff
|
|
|
54
54
|
bundle exec specguard-lint # one-off audit: every *_spec.rb
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
`--changed` is the CI selection mode: it diffs against the **merge base with
|
|
58
|
+
the default branch** (`origin/HEAD`, then `origin/main`/`origin/master`, then
|
|
59
|
+
their local names) — never a bare working-tree-vs-index `git diff`, which is
|
|
60
|
+
empty on a clean checkout. `--changed=<base>` overrides the base for pipelines
|
|
61
|
+
that know better. In a **shallow** checkout — the default depth-1 `git clone`
|
|
62
|
+
behind `actions/checkout@v4` — the merge base with the default branch is not
|
|
63
|
+
in the clone's history, so the derived base is HEAD itself and the selection
|
|
64
|
+
comes up empty: it still exits `0`, but its stderr note (and `--json`
|
|
65
|
+
`selection.note`) names the checkout as **shallow** and the remedy — fetch the
|
|
66
|
+
default branch (`fetch-depth: 0`) or pass `--changed=<base>` naming a base the
|
|
67
|
+
checkout contains. An explicit `--changed=<base>` that is not in a shallow
|
|
68
|
+
checkout's history is a usage error (exit `2`) naming that cause and the same
|
|
69
|
+
remedy, not the bare "could not diff against" a full clone reports for a
|
|
70
|
+
genuinely bad ref. A selection that comes up empty stays exit `0` and says WHY
|
|
71
|
+
on stderr — the exit contract is unchanged; only the WHY is truthful now.
|
|
72
|
+
|
|
57
73
|
Files are **positional** (`specguard-lint spec/order_spec.rb`); there is no `--source` flag —
|
|
58
74
|
that belongs to `validate-intent`, not to this one.
|
|
59
75
|
|
|
@@ -43,6 +43,16 @@ module SpecGuard
|
|
|
43
43
|
# There is no diff base that makes "what changed on this build" non-empty
|
|
44
44
|
# there.
|
|
45
45
|
#
|
|
46
|
+
# A **shallow** checkout — the depth-1 clone CI defaults to
|
|
47
|
+
# (`actions/checkout@v4`) — reaches the same thin base by a third road the
|
|
48
|
+
# ref-resolution above cannot see: every `DEFAULT_BRANCH_REFS` probe
|
|
49
|
+
# *resolves* (the fetched tip is in the clone), but the history behind it
|
|
50
|
+
# is not, so the merge base with the default branch is outside the clone
|
|
51
|
+
# and `merge-base` collapses onto HEAD. The notes below therefore consult
|
|
52
|
+
# `git rev-parse --is-shallow-repository` — lazily and memoized, only
|
|
53
|
+
# where the answer changes the message — and in a shallow clone say that,
|
|
54
|
+
# never "default-branch build".
|
|
55
|
+
#
|
|
46
56
|
# That is why the load-bearing requirement is the **loud empty selection**,
|
|
47
57
|
# not the base. The caller must never be unable to tell "checked 12 files,
|
|
48
58
|
# found no annotations" from "checked 0 files" — {Selection} carries the
|
|
@@ -157,17 +167,21 @@ module SpecGuard
|
|
|
157
167
|
"and no HEAD commit); pass --changed=<base> explicitly"
|
|
158
168
|
end
|
|
159
169
|
|
|
160
|
-
|
|
170
|
+
# One memoized probe per run, shared by the two message branches that
|
|
171
|
+
# consult it (a failed diff, a thin base note).
|
|
172
|
+
is_shallow = shallow_probe(root)
|
|
173
|
+
|
|
174
|
+
files, stats = changed_files(resolved, root, is_shallow)
|
|
161
175
|
|
|
162
176
|
Selection.new(files: files, mode: :changed, base: resolved,
|
|
163
|
-
note: base_note(resolved, base_kind, root), stats: stats)
|
|
177
|
+
note: base_note(resolved, base_kind, root, is_shallow), stats: stats)
|
|
164
178
|
end
|
|
165
179
|
|
|
166
180
|
# Resolves git's repo-root-relative output into paths relative to `root`,
|
|
167
181
|
# dropping (and counting) everything the two scoping rules exclude.
|
|
168
182
|
# @return [[Array<String>, Stats]]
|
|
169
|
-
def changed_files(base, root)
|
|
170
|
-
names = diff_names(base, root)
|
|
183
|
+
def changed_files(base, root, is_shallow)
|
|
184
|
+
names = diff_names(base, root, is_shallow)
|
|
171
185
|
specs = names.select { |name| CHANGED_PATTERNS.any? { |pattern| File.fnmatch?(pattern, name) } }
|
|
172
186
|
|
|
173
187
|
top = toplevel(root)
|
|
@@ -198,9 +212,23 @@ module SpecGuard
|
|
|
198
212
|
# them, and they then fail to open. `-z` makes the output machine-readable:
|
|
199
213
|
# NUL-separated and never `core.quotePath`-quoted, so a non-ASCII path
|
|
200
214
|
# survives intact and a path containing a newline cannot split a record.
|
|
201
|
-
def diff_names(base, root)
|
|
215
|
+
def diff_names(base, root, is_shallow)
|
|
202
216
|
out, ok = git(%W[diff -z --name-only --diff-filter=d #{base} --], root)
|
|
203
|
-
|
|
217
|
+
unless ok
|
|
218
|
+
if is_shallow.call
|
|
219
|
+
# SPGD-1035: in a shallow checkout the overwhelmingly likely cause
|
|
220
|
+
# is that <base> is simply not in the clone's history (git's own
|
|
221
|
+
# stderr says `fatal: bad object` / `unknown revision`), and the
|
|
222
|
+
# fix is a fetch — a cause the bare message never named, leaving
|
|
223
|
+
# "bad ref" and "shallow history" indistinguishable. Non-shallow
|
|
224
|
+
# failures keep the original message.
|
|
225
|
+
raise UsageError,
|
|
226
|
+
"--changed could not diff against #{base.inspect}: this checkout is shallow and " \
|
|
227
|
+
"#{base.inspect} is not in its history — fetch it (fetch-depth: 0, or " \
|
|
228
|
+
"git fetch origin #{base}) or pass a base the checkout contains"
|
|
229
|
+
end
|
|
230
|
+
raise UsageError, "--changed could not diff against #{base.inspect}"
|
|
231
|
+
end
|
|
204
232
|
|
|
205
233
|
out.split("\0").reject(&:empty?)
|
|
206
234
|
end
|
|
@@ -241,20 +269,60 @@ module SpecGuard
|
|
|
241
269
|
# `git diff HEAD`, i.e. the working-tree-vs-HEAD no-op this class exists
|
|
242
270
|
# to avoid. Reporting the first when the second is true would be a
|
|
243
271
|
# confidently wrong explanation.
|
|
244
|
-
|
|
272
|
+
#
|
|
273
|
+
# SPGD-1035: a shallow (depth-limited) checkout reproduces both thin
|
|
274
|
+
# shapes with a third cause those two stories miss — the merge base with
|
|
275
|
+
# the default branch is not in the clone's history at all — so whenever
|
|
276
|
+
# the repo IS shallow the note tells that story and names the remedy
|
|
277
|
+
# (fetch the default branch, or pass a base the checkout contains),
|
|
278
|
+
# retiring the "default-branch build" guess for shallow repos in both
|
|
279
|
+
# shapes.
|
|
280
|
+
def base_note(resolved, base_kind, root, is_shallow)
|
|
281
|
+
shallow_remedy =
|
|
282
|
+
"fetch the default branch (fetch-depth: 0) or pass --changed=<base> naming a base this checkout contains"
|
|
245
283
|
case base_kind
|
|
246
284
|
when :head_fallback
|
|
285
|
+
if is_shallow.call
|
|
286
|
+
return "this checkout is a shallow (depth-limited) clone and no default-branch ref " \
|
|
287
|
+
"(#{DEFAULT_BRANCH_REFS.join(', ')}) is in its history, so the diff base fell " \
|
|
288
|
+
"back to HEAD; --changed can only select uncommitted changes here — #{shallow_remedy}"
|
|
289
|
+
end
|
|
290
|
+
|
|
247
291
|
"no default-branch ref (#{DEFAULT_BRANCH_REFS.join(', ')}) could be found, so the diff base " \
|
|
248
292
|
"fell back to HEAD; --changed can only select uncommitted changes here"
|
|
249
293
|
when :merge_base
|
|
250
294
|
head, ok = git(%w[rev-parse HEAD], root)
|
|
251
295
|
return nil unless ok && head.strip == resolved
|
|
252
296
|
|
|
297
|
+
if is_shallow.call
|
|
298
|
+
return "this checkout is a shallow (depth-limited) clone, so the merge base with the " \
|
|
299
|
+
"default branch is not in its history and the diff base is HEAD itself — only " \
|
|
300
|
+
"uncommitted changes can be selected; #{shallow_remedy}"
|
|
301
|
+
end
|
|
302
|
+
|
|
253
303
|
"the diff base is HEAD itself (this looks like a default-branch build), " \
|
|
254
304
|
"so only uncommitted changes can be selected"
|
|
255
305
|
end
|
|
256
306
|
end
|
|
257
307
|
|
|
308
|
+
# Memoized per-run `git rev-parse --is-shallow-repository` probe.
|
|
309
|
+
# SPGD-1035: shallowness is consulted only in the branches where it
|
|
310
|
+
# changes the message (a derived base of HEAD, a failed diff), so the
|
|
311
|
+
# full-clone happy path makes zero new git invocations and the memo caps
|
|
312
|
+
# the probe at one per run. An unreadable answer (old git without the
|
|
313
|
+
# flag) reads as not shallow, keeping today's messages exactly.
|
|
314
|
+
# @return [Proc] zero-argument; true iff git reports a shallow repository
|
|
315
|
+
def shallow_probe(root)
|
|
316
|
+
cached = nil
|
|
317
|
+
lambda do
|
|
318
|
+
if cached.nil?
|
|
319
|
+
out, ok = git(%w[rev-parse --is-shallow-repository], root)
|
|
320
|
+
cached = ok && out.strip == "true"
|
|
321
|
+
end
|
|
322
|
+
cached
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
258
326
|
def git_repository?(root)
|
|
259
327
|
out, ok = git(%w[rev-parse --is-inside-work-tree], root)
|
|
260
328
|
ok && out.strip == "true"
|
data/lib/specguard/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: specguard-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- specguard Agent
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|