Skip to content

fix: make compare_lines a valid total order (#105)#106

Merged
dduugg merged 2 commits into
mainfrom
fix/compare-lines-total-order
May 18, 2026
Merged

fix: make compare_lines a valid total order (#105)#106
dduugg merged 2 commits into
mainfrom
fix/compare-lines-total-order

Conversation

@dduugg

@dduugg dduugg commented May 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fixes codeowners-rs generates nondeterministic CODEOWNERS ordering due to invalid comparator in compare_lines #105: compare_lines in src/ownership/file_generator.rs was not antisymmetric, so Vec::sort_by produced different CODEOWNERS orderings on macOS vs Linux for the same input.
  • For two lines that share a prefix ending just before ** (e.g. /foo/**/*bar* and /foo/**/baz/**/*), the old comparator returned Less in both directions, leaving sort output platform/build dependent.
  • Replaced the comparator with a path-component-wise compare where ** is treated as a sentinel that sorts before any non-** component, with full-line lexical compare as a deterministic tiebreaker. The shared comparator is reused by codeowners_file_parser, so generated and parsed orderings agree.

Test plan

  • cargo test — full suite passes (existing sort tests unchanged)
  • cargo clippy --all-targets --all-features — clean
  • cargo fmt --all -- --check — clean
  • New regression test for the exact two-line case from the issue
  • New antisymmetry sweep across the special-character set
  • New test confirming the # prefix on disabled entries does not perturb path ordering

The previous comparator could return Ordering::Less for both
compare_lines(a, b) and compare_lines(b, a) when two lines shared a
prefix ending just before "**". That violated antisymmetry, so
Vec::sort_by produced different orderings on different platforms,
breaking "CODEOWNERS out of date" validation.

Compare path components left-to-right with "**" treated as a sentinel
that sorts before any non-"**" component, falling back to full-line
lexical compare as a deterministic tiebreaker. The shared comparator
is reused by codeowners_file_parser, so both generation and parse
agree.
- Rename the antisymmetry sweep so the name matches what it asserts.
- Add a transitivity sweep over the same special-character set; this is
  the property that was actually missing from coverage of the Ord
  contract.
- Add a length-asymmetry case (shorter path vs longer extension) and
  an equal-path tiebreaker case, exercising the (None, Some) /
  (Some, None) and full-line fallback arms.
@dduugg dduugg marked this pull request as ready for review May 18, 2026 20:26
@dduugg dduugg requested a review from a team as a code owner May 18, 2026 20:26
@dduugg dduugg enabled auto-merge (squash) May 18, 2026 20:28
assert_eq!(compare_lines(&enabled, &other), compare_lines(&disabled, &other));
assert_eq!(compare_lines(&other, &enabled), compare_lines(&other, &disabled));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Love these tests!

@dduugg dduugg merged commit b43cc8a into main May 18, 2026
8 checks passed
@dduugg dduugg deleted the fix/compare-lines-total-order branch May 18, 2026 20:34
@github-project-automation github-project-automation Bot moved this from Triage to Done in Modularity May 18, 2026
@perryqh perryqh mentioned this pull request Jun 10, 2026
dduugg pushed a commit that referenced this pull request Jun 10, 2026
Ships the compare_lines total-order fix (#105/#106), which landed after
the 0.3.2 release. That fix makes CODEOWNERS sort order deterministic
across platforms (macOS arm64 / Linux x86_64).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

codeowners-rs generates nondeterministic CODEOWNERS ordering due to invalid comparator in compare_lines

2 participants