Skip to content

compare: treat an empty value and an absent key as equal - #72

Merged
dev-milos merged 3 commits into
mainfrom
fix/compare-empty-vs-absent
Aug 13, 2026
Merged

compare: treat an empty value and an absent key as equal#72
dev-milos merged 3 commits into
mainfrom
fix/compare-empty-vs-absent

Conversation

@dev-milos

Copy link
Copy Markdown
Collaborator

Closes G-Research/gr-oss#1430

The importer stopped writing description and homepage_url when the value is empty —
nilIfEmpty(), introduced in f6454cb. Every existing config therefore differs from a fresh
import of the same unchanged repository, and compare, whose only job is answering
whether something changed, started answering wrongly.

Measured against a real config repository during release testing:

Where Noise
Committed configs carrying homepage_url: "" 10 of 19
Drift check pull request 7 of 14 entries
Bulk import pull request 8 of 12 entries

Three features filter through compare and all three degraded. import and bulk-import
stopped dropping unchanged repositories, which is the exact symptom #51 set out to remove.
drift-check began reporting repositories that had not drifted — awkwardly, since the change
arrived in the same pull request that built drift-check on top of compare.

Approach

Normalise in compare rather than revert the importer.

nilIfEmpty produces cleaner generated config and is worth keeping. More importantly this
covers the whole class: a future serialization change that moves a field between empty and
absent stops mattering, rather than this one instance being patched.

hashNormalizedYamlFile already establishes the pattern — it drops id,
branch_policy_ids and tag_policy_ids before hashing, then sorts keys. This is one more
step alongside those, reusing the same recursive shape.

Deliberately narrow

  • Only scalars. false and 0 are meaningful values and are kept; the YAML tag
    distinguishes them from an empty string.
  • Containers left empty are kept. If stripping empties leaves reviewers: {} it stays.
    Collapsing empty maps and lists asserts an equivalence this does not need, and
    over-normalising would hide real differences.

Only empty is ever collapsed against absent. A config saying description: "old" against a
fresh import that omits it still compares as different.

Tests

The existing table had four cases and all of them asserted equality, so an over-aggressive
normaliser would have passed every one silently. Two added:

  • empty and null equivalent to absent, with has_downloads: false present — dropping
    falsey values rather than empty ones is the obvious way to get this wrong
  • a genuine difference alongside an empty value, asserting inequality

Confirmed the first fails without the change and passes with it, so it actually guards the
regression.

Verification

go build, go vet, go test ./pkg/compare/... all pass.

Against the real config repository, importing six unchanged repositories:

identical
before 1 of 6
after 6 of 6

Then editing two of the fresh files — a description, and has_issues from true to false
returns exactly those two as different. The boolean case matters: it confirms falsey values
survive normalisation.

The importer stopped writing description and homepage_url when the value is empty,
so every existing config differs from a fresh import of the same unchanged
repository. compare exists to answer whether something changed, and it started
answering wrongly for half the fleet.

Three features filter through it and all three degraded: import and bulk-import
stopped dropping unchanged repositories from their pull requests, and drift-check
began reporting repositories that had not drifted.

Normalising in compare rather than reverting the importer keeps the cleaner
generated output and covers the whole class, so a future serialization change that
moves a field between empty and absent stops mattering.

The pass is deliberately narrow. Only empty string and null scalars are dropped;
false and 0 are meaningful and stay, which the YAML tag distinguishes. Containers
left empty are kept, since collapsing them asserts a broader equivalence than this
needs.
@dev-milos
dev-milos marked this pull request as ready for review August 13, 2026 13:17
Comment thread feature/github-repo-importer/pkg/compare/compare_test.go
mladjan-gadzic
mladjan-gadzic previously approved these changes Aug 13, 2026

@mladjan-gadzic mladjan-gadzic left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

(best effort/optional) i'd like to add one more test case (more info inline)

The falsey keys in the empty-versus-absent case sat on both sides, so stripping
them would have stripped both and the case would still have passed. The new pair
has the falsey key on one side only and asserts inequality, which fails if false
is ever treated as empty.
@dev-milos
dev-milos requested a review from bitwisecook August 13, 2026 14:01
@dev-milos
dev-milos merged commit 55fd8c9 into main Aug 13, 2026
1 check passed
@dev-milos
dev-milos deleted the fix/compare-empty-vs-absent branch August 13, 2026 14:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants