Skip to content

Commit fdfabb9

Browse files
committed
refactor(url): reversible SSH-style URL patch
why: - Previously, enabling SSH-style URL detection mutated libvcs’s DEFAULT_RULES on import and lost the rule’s original metadata. - Users couldn’t cleanly disable the patch or restore upstream defaults if libvcs changed. - Implicit module-import side-effects are harder to reason about. what: - Introduce private `_orig_rule_meta` to snapshot rule’s original metadata. - Store original metadata on first enable and apply `(True, 100)`. - Restore saved metadata (or safe defaults) on disable, clearing snapshot. - Remove auto-patch on import; require explicit call. - Add `ssh_style_url_detection` context manager. - Call `enable_ssh_style_url_detection()` in `update_repo()` to maintain behavior. - Add pytest tests for enable/disable roundtrip and context manager.
1 parent 7e6bcf8 commit fdfabb9

File tree

3 files changed

+104
-15
lines changed

3 files changed

+104
-15
lines changed

src/vcspull/cli/sync.py

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from vcspull import exc
1414
from vcspull.config import filter_repos, find_config_files, load_configs
15+
from vcspull.url import enable_ssh_style_url_detection
1516

1617
if t.TYPE_CHECKING:
1718
import argparse
@@ -147,6 +148,8 @@ def update_repo(
147148
# repo_dict: Dict[str, Union[str, Dict[str, GitRemote], pathlib.Path]]
148149
) -> GitSync:
149150
"""Synchronize a single repository."""
151+
# Ensure SSH-style URLs are recognized as explicit Git URLs
152+
enable_ssh_style_url_detection()
150153
repo_dict = deepcopy(repo_dict)
151154
if "pip_url" not in repo_dict:
152155
repo_dict["pip_url"] = repo_dict.pop("url")

src/vcspull/url.py

+53-11
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@
22

33
from __future__ import annotations
44

5+
from typing import Any
6+
57
from libvcs.url.git import DEFAULT_RULES
68

9+
_orig_rule_meta: dict[str, tuple[bool, int]] = {}
10+
711

812
def enable_ssh_style_url_detection() -> None:
913
"""Enable detection of SSH-style URLs as explicit Git URLs.
1014
1115
This makes the core-git-scp rule explicit, which allows URLs like
1216
'user@hostname:path/to/repo.git' to be detected with is_explicit=True.
1317
14-
Examples:
18+
Examples
19+
--------
1520
>>> from vcspull.url import enable_ssh_style_url_detection
1621
>>> from libvcs.url.git import GitURL
1722
>>> # Without the patch
@@ -22,12 +27,14 @@ def enable_ssh_style_url_detection() -> None:
2227
>>> GitURL.is_valid('user@hostname:path/to/repo.git', is_explicit=True)
2328
True
2429
"""
30+
# Patch the core-git-scp rule, storing its original state if not already stored
2531
for rule in DEFAULT_RULES:
2632
if rule.label == "core-git-scp":
27-
# Make the rule explicit so it can be detected with is_explicit=True
33+
if rule.label not in _orig_rule_meta:
34+
_orig_rule_meta[rule.label] = (rule.is_explicit, rule.weight)
2835
rule.is_explicit = True
29-
# Increase the weight to ensure it takes precedence
3036
rule.weight = 100
37+
break
3138

3239

3340
def disable_ssh_style_url_detection() -> None:
@@ -36,8 +43,10 @@ def disable_ssh_style_url_detection() -> None:
3643
This reverts the core-git-scp rule to its original state, where URLs like
3744
'user@hostname:path/to/repo.git' are not detected with is_explicit=True.
3845
39-
Examples:
40-
>>> from vcspull.url import enable_ssh_style_url_detection, disable_ssh_style_url_detection
46+
Examples
47+
--------
48+
>>> from vcspull.url import enable_ssh_style_url_detection
49+
>>> from vcspull.url import disable_ssh_style_url_detection
4150
>>> from libvcs.url.git import GitURL
4251
>>> # Enable the patch
4352
>>> enable_ssh_style_url_detection()
@@ -48,17 +57,25 @@ def disable_ssh_style_url_detection() -> None:
4857
>>> GitURL.is_valid('user@hostname:path/to/repo.git', is_explicit=True)
4958
False
5059
"""
60+
# Restore the core-git-scp rule to its original state, if known
5161
for rule in DEFAULT_RULES:
5262
if rule.label == "core-git-scp":
53-
# Revert to original state
54-
rule.is_explicit = False
55-
rule.weight = 0
63+
orig = _orig_rule_meta.get(rule.label)
64+
if orig:
65+
rule.is_explicit, rule.weight = orig
66+
_orig_rule_meta.pop(rule.label, None)
67+
else:
68+
# Fallback to safe defaults
69+
rule.is_explicit = False
70+
rule.weight = 0
71+
break
5672

5773

5874
def is_ssh_style_url_detection_enabled() -> bool:
5975
"""Check if SSH-style URL detection is enabled.
6076
61-
Returns:
77+
Returns
78+
-------
6279
bool: True if SSH-style URL detection is enabled, False otherwise.
6380
"""
6481
for rule in DEFAULT_RULES:
@@ -67,5 +84,30 @@ def is_ssh_style_url_detection_enabled() -> bool:
6784
return False
6885

6986

70-
# Enable SSH-style URL detection by default
71-
enable_ssh_style_url_detection()
87+
"""
88+
Context manager and utility for SSH-style URL detection.
89+
"""
90+
91+
92+
class ssh_style_url_detection:
93+
"""Context manager to enable/disable SSH-style URL detection."""
94+
95+
def __init__(self, enabled: bool = True) -> None:
96+
self.enabled = enabled
97+
98+
def __enter__(self) -> None:
99+
"""Enable or disable SSH-style URL detection on context enter."""
100+
if self.enabled:
101+
enable_ssh_style_url_detection()
102+
else:
103+
disable_ssh_style_url_detection()
104+
105+
def __exit__(
106+
self,
107+
exc_type: type[BaseException] | None,
108+
exc_val: BaseException | None,
109+
exc_tb: Any,
110+
) -> None:
111+
"""Restore original SSH-style URL detection state on context exit."""
112+
# Always restore to disabled after context
113+
disable_ssh_style_url_detection()

tests/test_url.py

+48-4
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
from __future__ import annotations
44

55
import pytest
6-
from libvcs.url.git import GitURL
6+
from libvcs.url.git import DEFAULT_RULES, GitURL
77

8-
from vcspull.url import disable_ssh_style_url_detection, enable_ssh_style_url_detection
8+
from vcspull.url import (
9+
disable_ssh_style_url_detection,
10+
enable_ssh_style_url_detection,
11+
ssh_style_url_detection,
12+
)
913

1014

1115
def test_ssh_style_url_detection_toggle() -> None:
@@ -17,14 +21,18 @@ def test_ssh_style_url_detection_toggle() -> None:
1721

1822
# Without the patch, SSH-style URLs should not be detected as explicit
1923
assert GitURL.is_valid(url) # Should be valid in non-explicit mode
20-
assert not GitURL.is_valid(url, is_explicit=True) # Should not be valid in explicit mode
24+
assert not GitURL.is_valid(
25+
url, is_explicit=True
26+
) # Should not be valid in explicit mode
2127

2228
# Now enable the detection
2329
enable_ssh_style_url_detection()
2430

2531
# With the patch, SSH-style URLs should be detected as explicit
2632
assert GitURL.is_valid(url) # Should still be valid in non-explicit mode
27-
assert GitURL.is_valid(url, is_explicit=True) # Should now be valid in explicit mode
33+
assert GitURL.is_valid(
34+
url, is_explicit=True
35+
) # Should now be valid in explicit mode
2836

2937
# Verify the rule used
3038
git_url = GitURL(url)
@@ -95,3 +103,39 @@ def test_ssh_style_url_parsing(
95103
assert git_url.hostname == expected_hostname
96104
assert git_url.path == expected_path
97105
assert git_url.suffix == ".git"
106+
107+
108+
def test_enable_disable_restores_original_state() -> None:
109+
"""Original rule metadata is preserved and restored after enable/disable."""
110+
# Ensure any prior patch is cleared
111+
disable_ssh_style_url_detection()
112+
# Find the core-git-scp rule and capture its original state
113+
rule = next(r for r in DEFAULT_RULES if r.label == "core-git-scp")
114+
orig_state = (rule.is_explicit, rule.weight)
115+
116+
# Disabling without prior enable should leave original state
117+
disable_ssh_style_url_detection()
118+
assert (rule.is_explicit, rule.weight) == orig_state
119+
120+
# Enable should patch
121+
enable_ssh_style_url_detection()
122+
assert rule.is_explicit is True
123+
assert rule.weight == 100
124+
125+
# Disable should restore to original
126+
disable_ssh_style_url_detection()
127+
assert (rule.is_explicit, rule.weight) == orig_state
128+
129+
130+
def test_context_manager_restores_original_state() -> None:
131+
"""Context manager enables then restores original rule state."""
132+
rule = next(r for r in DEFAULT_RULES if r.label == "core-git-scp")
133+
orig_state = (rule.is_explicit, rule.weight)
134+
135+
# Use context manager
136+
with ssh_style_url_detection():
137+
assert rule.is_explicit is True
138+
assert rule.weight == 100
139+
140+
# After context, state should be back to original
141+
assert (rule.is_explicit, rule.weight) == orig_state

0 commit comments

Comments
 (0)