Skip to content

fix: accumulate all username removals instead of only preserving the last one#382

Merged
zkoppert merged 5 commits intomainfrom
fix/accumulate-multiple-user-removals
Apr 8, 2026
Merged

fix: accumulate all username removals instead of only preserving the last one#382
zkoppert merged 5 commits intomainfrom
fix/accumulate-multiple-user-removals

Conversation

@zkoppert
Copy link
Copy Markdown
Collaborator

@zkoppert zkoppert commented Apr 7, 2026

Pull Request

Proposed Changes

Fixes the username removal loop in main() so that removing multiple users from a CODEOWNERS file works correctly. Four issues are addressed:

  1. Accumulation bug (issue bug: only last username removal preserved when multiple users need removal from CODEOWNERS #380): The loop re-derived each removal from the original content, so only the last removal survived. Now initializes codeowners_file_contents_new = codeowners_decoded before the loop and replaces from that accumulator each iteration.

  2. Substring matching bug (discovered during self-review): bytes.replace(b"@bob", b"") corrupts @bobsmith into smith. Replaced with re.sub() using a lookahead assertion (?=\s|$) so only exact handles are matched. The accumulation fix made this pre-existing issue worse because corrupted content now persists across iterations instead of being overwritten.

  3. Whitespace cleanup, scoped to changed lines (discovered during self-review, refined per review feedback): After removing a username, leftover double spaces and trailing whitespace are cleaned up. Uses [ \t]{2,} to collapse horizontal whitespace and [ \t]+(?=\r?$) lookahead to strip trailing whitespace without consuming \r in CRLF files. Cleanup is scoped to only lines where a username was actually removed, preserving intentional alignment on unrelated lines.

  4. Extracted helper functions (per review feedback): remove_username_from_content() and cleanup_whitespace() reduce nesting depth in main() and make the removal/cleanup logic directly testable.

Closes #380

Testing

  • test_multiple_username_removals_are_cumulative - verifies removing @alice and @bob preserves all removals
  • test_username_removal_does_not_corrupt_similar_names - verifies removing @bob leaves @bobsmith intact
  • test_username_removal_cleans_up_whitespace - verifies no double spaces left after removal
  • test_username_removal_handles_crlf_line_endings - verifies cleanup works with \r\n endings without consuming \r
  • test_whitespace_cleanup_scoped_to_changed_lines - verifies unrelated lines with intentional alignment are not modified
  • All 68 tests pass, 96% code coverage
  • make lint clean (pylint 9.99/10, mypy, flake8, isort, black all pass)

Readiness Checklist

Author/Contributor

  • If documentation is needed for this change, has that been included in this pull request
  • run make lint and fix any issues that you have introduced
  • run make test and ensure you have test coverage for the lines you are introducing

@zkoppert zkoppert self-assigned this Apr 7, 2026
@github-actions github-actions bot added the fix label Apr 7, 2026
…last one

When multiple non-org-member usernames need to be removed from a CODEOWNERS
file, the removal loop was replacing from the original content on each
iteration. This meant only the last username's removal survived in the
resulting PR.

Initialize codeowners_file_contents_new with the decoded content and
accumulate replacements so all removals are preserved.

Fixes #380

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Zack Koppert <zkoppert@github.com>
@zkoppert zkoppert force-pushed the fix/accumulate-multiple-user-removals branch from 1b90ed2 to 186600f Compare April 7, 2026 16:19
zkoppert and others added 3 commits April 7, 2026 09:33
Replace bytes.replace() with re.sub() using a lookahead assertion
(?=\s|$) so that removing @bob does not corrupt @bobsmith. The
previous substring matching was a pre-existing issue, but the
accumulation fix made it worse because corrupted content now
persists across iterations instead of being overwritten.

Also update the existing regression test to use the new regex
pattern and add a dedicated test for the substring edge case.

Closes #380

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Zack Koppert <zkoppert@github.com>
Collapse runs of multiple spaces/tabs to a single space and strip
trailing whitespace from each line after usernames are removed.
Without this, removing @bob from '@alice @bob @charlie' would leave
a double space between @alice and @charlie.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Zack Koppert <zkoppert@github.com>
Change trailing whitespace pattern from [ \t]+$ to [ \t]+\r?$ so
that spaces before \r\n are also stripped. Without this, Windows-
style CRLF files would retain trailing spaces after username removal.

Add dedicated CRLF test case to cover this edge case.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Zack Koppert <zkoppert@github.com>
@zkoppert zkoppert marked this pull request as ready for review April 7, 2026 17:02
@zkoppert zkoppert requested a review from jmeridth as a code owner April 7, 2026 17:02
Copy link
Copy Markdown
Collaborator

@jmeridth jmeridth left a comment

Choose a reason for hiding this comment

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

Reviewed the PR — the core accumulation fix and substring matching fix are both correct and well-motivated. Two issues in the whitespace cleanup block worth addressing before merge.

@zkoppert
Copy link
Copy Markdown
Collaborator Author

zkoppert commented Apr 7, 2026

Great catches on both of those!! Addressing now. 🏃🏻

Address review feedback from jmeridth:

1. CRLF preservation: Change trailing whitespace regex from
   [\ \t]+\r?$ (which consumed \r) to [\t]+(?=\r?$) (lookahead
   preserves \r).

2. Scoped whitespace cleanup: Track which line indices had username
   removals via a changed_lines set, and only normalize whitespace on
   those lines. This prevents collapsing intentional alignment on
   unrelated CODEOWNERS lines.

Also extract remove_username_from_content() and cleanup_whitespace()
helper functions to reduce nesting depth in main() and make the logic
directly testable.

Add test_whitespace_cleanup_scoped_to_changed_lines to verify unrelated
lines with intentional alignment are preserved.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Zack Koppert <zkoppert@github.com>
@zkoppert zkoppert merged commit cd9ec7d into main Apr 8, 2026
38 checks passed
@zkoppert zkoppert deleted the fix/accumulate-multiple-user-removals branch April 8, 2026 00:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: only last username removal preserved when multiple users need removal from CODEOWNERS

2 participants