Skip to content

Conversation

@tsvikas
Copy link
Owner

@tsvikas tsvikas commented Oct 26, 2025

  • 🧪 Add tests for line ending preservation in pre-commit config processing
  • 🧪 Add test for preserving line endings when writing files
  • 🐛 Preserve line endings when processing pre-commit config
  • 📝 Update CHANGELOG

Summary by Sourcery

Preserve original line endings when reading, processing, and writing pre-commit configuration to avoid altering line ending formats.

Bug Fixes:

  • Preserve original CRLF/LF line endings when processing pre-commit config files and updating hook versions

Enhancements:

  • Process pre-commit content with splitlines(keepends=True) and bytes-based I/O while refining regex matching to retain original line endings

Documentation:

  • Record line ending preservation feature in CHANGELOG

Tests:

  • Add parametrized tests for line ending preservation in pre-commit text processing
  • Add parametrized CLI test for line ending preservation when writing files

@sourcery-ai
Copy link

sourcery-ai bot commented Oct 26, 2025

Reviewer's Guide

This PR fixes issue #24 by ensuring the original line endings (LF/CRLF/CR) are preserved when processing and writing pre-commit config files. It updates the core text‐processing logic to retain end-of-line characters, modifies the CLI file reading mechanism to avoid newline translation, adds targeted tests for all common line endings in both processing and CLI execution, and updates the changelog accordingly.

Sequence diagram for pre-commit config file processing with preserved line endings

sequenceDiagram
    participant CLI
    participant FileSystem
    participant "process_precommit_text()"
    CLI->>FileSystem: Read pre-commit config file as bytes
    FileSystem-->>CLI: Return file contents (with original line endings)
    CLI->>"process_precommit_text()": Pass file contents
    "process_precommit_text()"->>"process_precommit_text()": Split lines (preserving line endings)
    "process_precommit_text()"-->>CLI: Return processed text (with original line endings)
    CLI->>FileSystem: Write processed config file (preserving line endings)
Loading

Class diagram for updated pre-commit config processing functions

classDiagram
    class CLI {
        +process_precommit()
        -precommit_filename.read_bytes()
    }
    class process_precommit_text {
        +process_precommit_text(precommit_text, uv_data, user_repo_mappings, user_version_mappings)
        -repo_header_re
        -repo_rev_re
        -lines: splitlines(keepends=True)
        -new_lines: list[str]
        +returns: (str, dict)
    }
    CLI --> process_precommit_text
Loading

File-Level Changes

Change Details Files
Preserve original line endings in process_precommit_text
  • Switched from split("\n") to splitlines(keepends=True)
  • Updated repo header and rev regex to use match() with anchors
  • Returned joined lines via "".join(new_lines) instead of inserting newlines
src/sync_with_uv/sync_with_uv.py
Preserve original line endings in CLI file reading
  • Read .pre-commit-config.yaml in binary mode and decode to avoid newline normalization
  • Added comment noting Python>=3.13 can simplify with read_text(newline="")
src/sync_with_uv/cli.py
Add tests for line ending preservation
  • Parameterized tests in test_sync.py for LF/CRLF/CR in process_precommit_text with and without version changes
  • Parameterized CLI test in test_cli.py verifying writes preserve original endings
tests/test_sync.py
tests/test_cli.py
Update changelog to document line ending preservation
  • Added entry noting preservation of original line endings when processing pre-commit configs
CHANGELOG.md

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codecov
Copy link

codecov bot commented Oct 26, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (05074dd) to head (4c6b55f).
⚠️ Report is 9 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff            @@
##              main       #26   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            4         4           
  Lines          149       149           
  Branches        29        29           
=========================================
  Hits           149       149           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • The docstring for test_process_precommit_text_preserves_line_endings doesn’t match its behavior—it tests a version bump, not ‘no version change’, so please update it for clarity.
  • In cli.py, consider writing the fixed pre-commit content in binary mode or with write_text(newline='') so the original line endings are guaranteed preserved when saving the file.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The docstring for test_process_precommit_text_preserves_line_endings doesn’t match its behavior—it tests a version bump, not ‘no version change’, so please update it for clarity.
- In cli.py, consider writing the fixed pre-commit content in binary mode or with write_text(newline='') so the original line endings are guaranteed preserved when saving the file.

## Individual Comments

### Comment 1
<location> `tests/test_sync.py:311-334` </location>
<code_context>
+    ["\n", "\r\n", "\r"],
+    ids=["LF", "CRLF", "CR"],
+)
+def test_process_precommit_text_preserves_line_endings_no_version_change(
+    line_ending: str,
+) -> None:
+    """Test that line endings are preserved when version is already correct."""
+    precommit_text = line_ending.join(
+        [
+            "repos:",
+            "- repo: https://github.com/psf/black-pre-commit-mirror",
+            "  rev: 23.11.0",
+            "  hooks:",
+            "    - id: black",
+            "- repo: https://github.com/unchanged/unchanged",
+            "  rev: 1.2.3",
+            "  hooks:",
+            "    - id: unchanged",
+        ]
+    )
+    # Package exists in uv.lock but version is already correct
+    uv_data = {"black": "23.11.0"}
+
+    result, _changes = process_precommit_text(precommit_text, uv_data)
+
+    # Result should be identical to input when version is already correct
+    assert result == precommit_text
+
+
</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding a test case for mixed line endings within the same file.

Testing files with both LF and CRLF line endings will help ensure your logic handles mixed scenarios correctly.

```suggestion
def test_process_precommit_text_preserves_line_endings_no_version_change(
    line_ending: str,
) -> None:
    """Test that line endings are preserved when version is already correct."""
    precommit_text = line_ending.join(
        [
            "repos:",
            "- repo: https://github.com/psf/black-pre-commit-mirror",
            "  rev: 23.11.0",
            "  hooks:",
            "    - id: black",
            "- repo: https://github.com/unchanged/unchanged",
            "  rev: 1.2.3",
            "  hooks:",
            "    - id: unchanged",
        ]
    )
    # Package exists in uv.lock but version is already correct
    uv_data = {"black": "23.11.0"}

    result, _changes = process_precommit_text(precommit_text, uv_data)

    # Result should be identical to input when version is already correct
    assert result == precommit_text


def test_process_precommit_text_preserves_mixed_line_endings_no_version_change() -> None:
    """Test that mixed line endings are preserved when version is already correct."""
    # Construct a precommit_text with mixed line endings (LF and CRLF)
    precommit_text = (
        "repos:\n"
        "- repo: https://github.com/psf/black-pre-commit-mirror\r\n"
        "  rev: 23.11.0\n"
        "  hooks:\r\n"
        "    - id: black\n"
        "- repo: https://github.com/unchanged/unchanged\r\n"
        "  rev: 1.2.3\n"
        "  hooks:\r\n"
        "    - id: unchanged\n"
    )
    uv_data = {"black": "23.11.0"}

    result, _changes = process_precommit_text(precommit_text, uv_data)

    # Result should be identical to input when version is already correct
    assert result == precommit_text
```
</issue_to_address>

### Comment 2
<location> `tests/test_sync.py:314` </location>
<code_context>
+    ["\n", "\r\n", "\r"],
+    ids=["LF", "CRLF", "CR"],
+)
+def test_process_precommit_text_preserves_line_endings(
+    line_ending: str,
+) -> None:
+    """Test that line endings are preserved when version is already correct."""
+    precommit_text = line_ending.join(
+        [
+            "repos:",
+            "- repo: https://github.com/psf/black-pre-commit-mirror",
+            "  rev: 23.11.0",
+            "  hooks:",
+            "    - id: black",
+            "- repo: https://github.com/unchanged/unchanged",
+            "  rev: 1.2.3",
+            "  hooks:",
+            "    - id: unchanged",
+        ]
+    )
+    # Package exists in uv.lock but version is already correct
+    uv_data = {"black": "24.0.0"}
+
+    result, _changes = process_precommit_text(precommit_text, uv_data)
+
+    # Result should be identical to input when version is already correct
+    assert result == precommit_text.replace("23.11.0", "24.0.0")
</code_context>

<issue_to_address>
**suggestion (testing):** Test description is misleading: version is changed, not just line endings preserved.

Please revise the docstring to indicate that the test updates the version, not just preserves line endings.

```suggestion
    """Test that line endings are preserved and the version is updated in the output."""
```
</issue_to_address>

### Comment 3
<location> `tests/test_cli.py:367-376` </location>
<code_context>
+def test_cli_preserves_line_endings_when_writing(
</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding a test for the scenario where no version change is needed.

Please add a test to verify that when the version is already correct, the function preserves line endings without making changes.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +311 to +334
def test_process_precommit_text_preserves_line_endings_no_version_change(
line_ending: str,
) -> None:
"""Test that line endings are preserved when version is already correct."""
precommit_text = line_ending.join(
[
"repos:",
"- repo: https://github.com/psf/black-pre-commit-mirror",
" rev: 23.11.0",
" hooks:",
" - id: black",
"- repo: https://github.com/unchanged/unchanged",
" rev: 1.2.3",
" hooks:",
" - id: unchanged",
]
)
# Package exists in uv.lock but version is already correct
uv_data = {"black": "23.11.0"}

result, _changes = process_precommit_text(precommit_text, uv_data)

# Result should be identical to input when version is already correct
assert result == precommit_text
Copy link

Choose a reason for hiding this comment

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

suggestion (testing): Consider adding a test case for mixed line endings within the same file.

Testing files with both LF and CRLF line endings will help ensure your logic handles mixed scenarios correctly.

Suggested change
def test_process_precommit_text_preserves_line_endings_no_version_change(
line_ending: str,
) -> None:
"""Test that line endings are preserved when version is already correct."""
precommit_text = line_ending.join(
[
"repos:",
"- repo: https://github.com/psf/black-pre-commit-mirror",
" rev: 23.11.0",
" hooks:",
" - id: black",
"- repo: https://github.com/unchanged/unchanged",
" rev: 1.2.3",
" hooks:",
" - id: unchanged",
]
)
# Package exists in uv.lock but version is already correct
uv_data = {"black": "23.11.0"}
result, _changes = process_precommit_text(precommit_text, uv_data)
# Result should be identical to input when version is already correct
assert result == precommit_text
def test_process_precommit_text_preserves_line_endings_no_version_change(
line_ending: str,
) -> None:
"""Test that line endings are preserved when version is already correct."""
precommit_text = line_ending.join(
[
"repos:",
"- repo: https://github.com/psf/black-pre-commit-mirror",
" rev: 23.11.0",
" hooks:",
" - id: black",
"- repo: https://github.com/unchanged/unchanged",
" rev: 1.2.3",
" hooks:",
" - id: unchanged",
]
)
# Package exists in uv.lock but version is already correct
uv_data = {"black": "23.11.0"}
result, _changes = process_precommit_text(precommit_text, uv_data)
# Result should be identical to input when version is already correct
assert result == precommit_text
def test_process_precommit_text_preserves_mixed_line_endings_no_version_change() -> None:
"""Test that mixed line endings are preserved when version is already correct."""
# Construct a precommit_text with mixed line endings (LF and CRLF)
precommit_text = (
"repos:\n"
"- repo: https://github.com/psf/black-pre-commit-mirror\r\n"
" rev: 23.11.0\n"
" hooks:\r\n"
" - id: black\n"
"- repo: https://github.com/unchanged/unchanged\r\n"
" rev: 1.2.3\n"
" hooks:\r\n"
" - id: unchanged\n"
)
uv_data = {"black": "23.11.0"}
result, _changes = process_precommit_text(precommit_text, uv_data)
# Result should be identical to input when version is already correct
assert result == precommit_text

@tsvikas tsvikas force-pushed the fix/issue-24 branch 2 times, most recently from 5c6bbd6 to 94cac6b Compare October 27, 2025 01:00
Fix handling of line endings in .pre-commit-config.yaml by reading the
file as bytes and using splitlines(keepends=True) to maintain original
line ending style (CRLF vs LF). This ensures the output file preserves
the same line ending format as the input.

Previously, the code used read_text() and split("\n"), which would
normalize all line endings to LF and potentially corrupt files that
used CRLF line endings on Windows.


Fixes #24
Fix handling of line endings in .pre-commit-config.yaml by reading the
file as bytes and using splitlines(keepends=True) to maintain original
line ending style (CRLF vs LF). This ensures the output file preserves
the same line ending format as the input.

Previously, the code used read_text() and split("\n"), which would
normalize all line endings to LF and potentially corrupt files that
used CRLF line endings on Windows.


Fixes #24
@tsvikas tsvikas merged commit 0981cad into main Oct 28, 2025
19 checks passed
@tsvikas tsvikas deleted the fix/issue-24 branch October 28, 2025 09:47
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