Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ repos:
args: ["--write-changes", "--skip=pdm.lock"]

- repo: https://github.com/tox-dev/tox-ini-fmt
rev: "1.4.1"
rev: "1.7.1"
hooks:
- id: tox-ini-fmt

Expand All @@ -46,7 +46,7 @@ repos:
verbose: true
files: ^db\.py|README\.md$

- rev: v0.14.1
- rev: v0.15.0
repo: https://github.com/astral-sh/ruff-pre-commit
hooks:
- id: ruff
Expand Down
396 changes: 239 additions & 157 deletions pdm.lock

Large diffs are not rendered by default.

18 changes: 17 additions & 1 deletion src/sync_pre_commit_lock/pre_commit_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ def update_pre_commit_repo_versions(self, new_versions: dict[PreCommitRepo, PreC
original_lines = self.original_file_lines
updated_lines = original_lines[:]

for repo_rev in self.yaml["repos"]:
repos_list = self.yaml["repos"]
for repo_idx, repo_rev in enumerate(repos_list):
if "rev" not in repo_rev:
continue

Expand All @@ -179,6 +180,12 @@ def update_pre_commit_repo_versions(self, new_versions: dict[PreCommitRepo, PreC
original_rev_line: str = updated_lines[rev_line_idx]
updated_lines[rev_line_idx] = original_rev_line.replace(str(rev), updated_repo.rev)

if repo_idx + 1 < len(repos_list):
next_repo_start_line = repos_list[repo_idx + 1]["repo"].start_line + self.document_start_offset
repo_end_idx = next_repo_start_line - 2
else:
repo_end_idx = len(updated_lines) - 1

for src_hook, old_hook, new_hook in zip(hooks, normalized_repo.hooks, updated_repo.hooks):
if new_hook == old_hook:
continue
Expand All @@ -191,6 +198,15 @@ def update_pre_commit_repo_versions(self, new_versions: dict[PreCommitRepo, PreC
continue
dep_line_number: int = src_dep.end_line + self.document_start_offset
dep_line_idx: int = dep_line_number - 1
old_dep_str = str(old_dep)
if dep_line_idx >= len(updated_lines) or old_dep_str not in updated_lines[dep_line_idx]:
search_start = rev_line_idx
search_end = min(repo_end_idx + 1, len(updated_lines))
candidates = [
idx for idx in range(search_start, search_end) if old_dep_str in updated_lines[idx]
Copy link

Choose a reason for hiding this comment

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

Substring search in fallback matches wrong dependency line

Medium Severity

The fallback candidate search uses old_dep_str in updated_lines[idx] (substring containment) to find the correct line for a dependency. When one dependency name is a prefix of another in the same hook (e.g., types-requests and types-requests-oauthlib), this matches multiple lines. The closest-to-dep_line_idx heuristic then picks the wrong line (the one nearest the ] closing bracket), and the subsequent replace call corrupts that unrelated dependency line. Before this change, the code would silently no-op on the wrong line; now it actively corrupts a different dependency.

Fix in Cursor Fix in Web

]
if candidates:
dep_line_idx = min(candidates, key=lambda idx: abs(idx - dep_line_idx))
Copy link

Choose a reason for hiding this comment

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

Fallback search crash when no candidates found

Low Severity

When the fallback search at line 202 triggers because dep_line_idx >= len(updated_lines), but candidates is empty (no matching line found in the search range), dep_line_idx retains its original out-of-bounds value. Execution then falls through to updated_lines[dep_line_idx] at line 210, which raises an IndexError. The if candidates: guard on line 208 updates dep_line_idx only on success, leaving the invalid index untouched on failure.

Fix in Cursor Fix in Web

original_dep_line: str = updated_lines[dep_line_idx]
updated_lines[dep_line_idx] = original_dep_line.replace(str(src_dep), new_dep)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

# Many unused lines before document separator

---
default_language_version:
python: python3.11

repos:

- repo: https://github.com/pre-commit/mirrors-mypy
# Some comment
rev: v1.5.0
hooks:
- id: mypy
additional_dependencies: [
types-PyYAML==1.2.4,
types-requests==3.4.5,
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

# Many unused lines before document separator

---
default_language_version:
python: python3.11

repos:

- repo: https://github.com/pre-commit/mirrors-mypy
# Some comment
rev: v1.0.0
hooks:
- id: mypy
additional_dependencies: [
types-PyYAML==1.2.4,
types-requests,
]
5 changes: 4 additions & 1 deletion tests/test_pre_commit_config_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ def test_update_versions() -> None:
assert config.pre_commit_config_file_path.open.call_count == 1


@pytest.mark.parametrize("base", ["only-deps", "with-deps", "with-one-liner-deps", "without-new-deps"])
@pytest.mark.parametrize(
"base",
["only-deps", "with-deps", "with-one-liner-deps", "without-new-deps", "flow-multiline-deps"],
)
def test_update_additional_dependencies_versions(base: str) -> None:
config = PreCommitHookConfig.from_yaml_file(FIXTURES / f"pre-commit-config-{base}.yaml")
mock_file = config.pre_commit_config_file_path = MagicMock()
Expand Down
Loading