From 76d3bcaf57884ad2ab1d5d2bf83e4760004c2859 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 22 Oct 2023 08:30:33 -0500 Subject: [PATCH 1/9] tests(config-variations): Git schemes in repo URLs --- tests/test_sync.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_sync.py b/tests/test_sync.py index e7a379ed..f8fe9363 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -133,6 +133,35 @@ class ConfigVariationTest(t.NamedTuple): """, remote_list=["git_scheme_repo"], ), + ConfigVariationTest( + test_id="expanded_repo_style_with_unprefixed_remote_3", + config_tpl=""" + {tmp_path}/study/myrepo: + {CLONE_NAME}: + repo: git+file://{dir} + remotes: + git_scheme_repo: user@myhostname.de:org/repo.git + """, + remote_list=["git_scheme_repo"], + ), + ConfigVariationTest( + test_id="expanded_repo_style_with_unprefixed_repo", + config_tpl=""" + {tmp_path}/study/myrepo: + {CLONE_NAME}: + repo: user@myhostname.de:org/repo.git + """, + remote_list=["git_scheme_repo"], + ), + ConfigVariationTest( + test_id="expanded_repo_style_with_prefixed_repo_3_with_prefix", + config_tpl=""" + {tmp_path}/study/myrepo: + {CLONE_NAME}: + repo: git+ssh://user@myhostname.de:org/repo.git + """, + remote_list=["git_scheme_repo"], + ), ] From 622333f18432bb4b1118e842245a003d8a522514 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 20 Apr 2025 17:40:23 -0500 Subject: [PATCH 2/9] Add URL handling module to make SSH-style URLs explicit --- src/vcspull/url.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/vcspull/url.py diff --git a/src/vcspull/url.py b/src/vcspull/url.py new file mode 100644 index 00000000..63905200 --- /dev/null +++ b/src/vcspull/url.py @@ -0,0 +1,13 @@ +"""URL handling for vcspull.""" + +from __future__ import annotations + +from libvcs.url.git import DEFAULT_RULES + +# Find the core-git-scp rule and modify it to be explicit +for rule in DEFAULT_RULES: + if rule.label == "core-git-scp": + # Make the rule explicit so it can be detected with is_explicit=True + rule.is_explicit = True + # Increase the weight to ensure it takes precedence + rule.weight = 100 From 9befa58b45d9612ccfa27607a235a2f393d76cf7 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 20 Apr 2025 17:40:32 -0500 Subject: [PATCH 3/9] Import custom URL handling module in __init__.py --- src/vcspull/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vcspull/__init__.py b/src/vcspull/__init__.py index 5c9da904..beaee244 100644 --- a/src/vcspull/__init__.py +++ b/src/vcspull/__init__.py @@ -11,6 +11,9 @@ import logging from logging import NullHandler -from . import cli +from . import ( + cli, + url, # Import custom URL handling +) logging.getLogger(__name__).addHandler(NullHandler()) From 9e383fc620323e35af926c0e92d8509c71998767 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 20 Apr 2025 17:40:42 -0500 Subject: [PATCH 4/9] Fix typo in test case: {dir} -> {path} --- tests/test_sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_sync.py b/tests/test_sync.py index f8fe9363..f4d09ccf 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -138,7 +138,7 @@ class ConfigVariationTest(t.NamedTuple): config_tpl=""" {tmp_path}/study/myrepo: {CLONE_NAME}: - repo: git+file://{dir} + repo: git+file://{path} remotes: git_scheme_repo: user@myhostname.de:org/repo.git """, From 2c52ca3766cdc55356e0791778951c185ee4e930 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 20 Apr 2025 17:40:54 -0500 Subject: [PATCH 5/9] Add tests for SSH-style URL detection and parsing --- tests/test_url.py | 62 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tests/test_url.py diff --git a/tests/test_url.py b/tests/test_url.py new file mode 100644 index 00000000..80dfa4de --- /dev/null +++ b/tests/test_url.py @@ -0,0 +1,62 @@ +"""Tests for URL handling in vcspull.""" + +from __future__ import annotations + +import pytest +from libvcs.url.git import GitURL + + +@pytest.mark.parametrize( + "url", + [ + "user@myhostname.de:org/repo.git", + "git@github.com:vcs-python/vcspull.git", + "git@gitlab.com:vcs-python/vcspull.git", + "user@custom-host.com:path/to/repo.git", + ], +) +def test_ssh_style_url_detection(url: str) -> None: + """Test that SSH-style URLs are correctly detected.""" + assert GitURL.is_valid(url) + git_url = GitURL(url) + assert git_url.rule == "core-git-scp" + + +@pytest.mark.parametrize( + "url,expected_user,expected_hostname,expected_path", + [ + ( + "user@myhostname.de:org/repo.git", + "user", + "myhostname.de", + "org/repo", + ), + ( + "git@github.com:vcs-python/vcspull.git", + "git", + "github.com", + "vcs-python/vcspull", + ), + ( + "git@gitlab.com:vcs-python/vcspull.git", + "git", + "gitlab.com", + "vcs-python/vcspull", + ), + ( + "user@custom-host.com:path/to/repo.git", + "user", + "custom-host.com", + "path/to/repo", + ), + ], +) +def test_ssh_style_url_parsing( + url: str, expected_user: str, expected_hostname: str, expected_path: str +) -> None: + """Test that SSH-style URLs are correctly parsed.""" + git_url = GitURL(url) + assert git_url.user == expected_user + assert git_url.hostname == expected_hostname + assert git_url.path == expected_path + assert git_url.suffix == ".git" From 5c45c876bf367aa2f747d6faab61d164d1e2b814 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 20 Apr 2025 18:00:10 -0500 Subject: [PATCH 6/9] Refactor URL handling to provide toggleable functions --- src/vcspull/url.py | 75 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 7 deletions(-) diff --git a/src/vcspull/url.py b/src/vcspull/url.py index 63905200..790107c6 100644 --- a/src/vcspull/url.py +++ b/src/vcspull/url.py @@ -4,10 +4,71 @@ from libvcs.url.git import DEFAULT_RULES -# Find the core-git-scp rule and modify it to be explicit -for rule in DEFAULT_RULES: - if rule.label == "core-git-scp": - # Make the rule explicit so it can be detected with is_explicit=True - rule.is_explicit = True - # Increase the weight to ensure it takes precedence - rule.weight = 100 + +def enable_ssh_style_url_detection() -> None: + """Enable detection of SSH-style URLs as explicit Git URLs. + + This makes the core-git-scp rule explicit, which allows URLs like + 'user@hostname:path/to/repo.git' to be detected with is_explicit=True. + + Examples + -------- + >>> from vcspull.url import enable_ssh_style_url_detection + >>> from libvcs.url.git import GitURL + >>> # Without the patch + >>> GitURL.is_valid('user@hostname:path/to/repo.git', is_explicit=True) + False + >>> # With the patch + >>> enable_ssh_style_url_detection() + >>> GitURL.is_valid('user@hostname:path/to/repo.git', is_explicit=True) + True + """ + for rule in DEFAULT_RULES: + if rule.label == "core-git-scp": + # Make the rule explicit so it can be detected with is_explicit=True + rule.is_explicit = True + # Increase the weight to ensure it takes precedence + rule.weight = 100 + + +def disable_ssh_style_url_detection() -> None: + """Disable detection of SSH-style URLs as explicit Git URLs. + + This reverts the core-git-scp rule to its original state, where URLs like + 'user@hostname:path/to/repo.git' are not detected with is_explicit=True. + + Examples + -------- + >>> from vcspull.url import enable_ssh_style_url_detection, disable_ssh_style_url_detection + >>> from libvcs.url.git import GitURL + >>> # Enable the patch + >>> enable_ssh_style_url_detection() + >>> GitURL.is_valid('user@hostname:path/to/repo.git', is_explicit=True) + True + >>> # Disable the patch + >>> disable_ssh_style_url_detection() + >>> GitURL.is_valid('user@hostname:path/to/repo.git', is_explicit=True) + False + """ + for rule in DEFAULT_RULES: + if rule.label == "core-git-scp": + # Revert to original state + rule.is_explicit = False + rule.weight = 0 + + +def is_ssh_style_url_detection_enabled() -> bool: + """Check if SSH-style URL detection is enabled. + + Returns + ------- + bool: True if SSH-style URL detection is enabled, False otherwise. + """ + for rule in DEFAULT_RULES: + if rule.label == "core-git-scp": + return rule.is_explicit + return False + + +# Enable SSH-style URL detection by default +enable_ssh_style_url_detection() From d55a5869c811fdcc47e90e56455f35e67e3967a3 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 20 Apr 2025 18:00:23 -0500 Subject: [PATCH 7/9] Update __init__.py to import enable_ssh_style_url_detection function --- src/vcspull/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vcspull/__init__.py b/src/vcspull/__init__.py index beaee244..1157e4d0 100644 --- a/src/vcspull/__init__.py +++ b/src/vcspull/__init__.py @@ -11,9 +11,7 @@ import logging from logging import NullHandler -from . import ( - cli, - url, # Import custom URL handling -) +from . import cli +from .url import enable_ssh_style_url_detection # Import custom URL handling logging.getLogger(__name__).addHandler(NullHandler()) From f8375edd628dd0dc654e0c61602c5fd6b5fdda7e Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 20 Apr 2025 18:00:37 -0500 Subject: [PATCH 8/9] Update URL tests to use enable/disable functions --- tests/test_url.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/test_url.py b/tests/test_url.py index 80dfa4de..1fc81f8b 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -5,6 +5,38 @@ import pytest from libvcs.url.git import GitURL +from vcspull.url import disable_ssh_style_url_detection, enable_ssh_style_url_detection + + +def test_ssh_style_url_detection_toggle() -> None: + """Test that SSH-style URL detection can be toggled on and off.""" + url = "user@myhostname.de:org/repo.git" + + # First, disable the detection + disable_ssh_style_url_detection() + + # Without the patch, SSH-style URLs should not be detected as explicit + assert GitURL.is_valid(url) # Should be valid in non-explicit mode + assert not GitURL.is_valid( + url, is_explicit=True + ) # Should not be valid in explicit mode + + # Now enable the detection + enable_ssh_style_url_detection() + + # With the patch, SSH-style URLs should be detected as explicit + assert GitURL.is_valid(url) # Should still be valid in non-explicit mode + assert GitURL.is_valid( + url, is_explicit=True + ) # Should now be valid in explicit mode + + # Verify the rule used + git_url = GitURL(url) + assert git_url.rule == "core-git-scp" + + # Re-enable for other tests + enable_ssh_style_url_detection() + @pytest.mark.parametrize( "url", @@ -17,7 +49,11 @@ ) def test_ssh_style_url_detection(url: str) -> None: """Test that SSH-style URLs are correctly detected.""" + # Ensure detection is enabled + enable_ssh_style_url_detection() + assert GitURL.is_valid(url) + assert GitURL.is_valid(url, is_explicit=True) # Should be valid in explicit mode git_url = GitURL(url) assert git_url.rule == "core-git-scp" @@ -55,6 +91,9 @@ def test_ssh_style_url_parsing( url: str, expected_user: str, expected_hostname: str, expected_path: str ) -> None: """Test that SSH-style URLs are correctly parsed.""" + # Ensure detection is enabled + enable_ssh_style_url_detection() + git_url = GitURL(url) assert git_url.user == expected_user assert git_url.hostname == expected_hostname From 1f39d8066a1fbf50a25f98935e4f77380f02272a Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Mon, 21 Apr 2025 09:53:24 -0500 Subject: [PATCH 9/9] refactor(url): reversible SSH-style URL patch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/vcspull/cli/sync.py | 3 +++ src/vcspull/url.py | 55 +++++++++++++++++++++++++++++++++++------ tests/test_url.py | 44 +++++++++++++++++++++++++++++++-- 3 files changed, 92 insertions(+), 10 deletions(-) diff --git a/src/vcspull/cli/sync.py b/src/vcspull/cli/sync.py index 1f754887..fc49b0da 100644 --- a/src/vcspull/cli/sync.py +++ b/src/vcspull/cli/sync.py @@ -12,6 +12,7 @@ from vcspull import exc from vcspull.config import filter_repos, find_config_files, load_configs +from vcspull.url import enable_ssh_style_url_detection if t.TYPE_CHECKING: import argparse @@ -147,6 +148,8 @@ def update_repo( # repo_dict: Dict[str, Union[str, Dict[str, GitRemote], pathlib.Path]] ) -> GitSync: """Synchronize a single repository.""" + # Ensure SSH-style URLs are recognized as explicit Git URLs + enable_ssh_style_url_detection() repo_dict = deepcopy(repo_dict) if "pip_url" not in repo_dict: repo_dict["pip_url"] = repo_dict.pop("url") diff --git a/src/vcspull/url.py b/src/vcspull/url.py index 790107c6..2812bfc9 100644 --- a/src/vcspull/url.py +++ b/src/vcspull/url.py @@ -2,8 +2,12 @@ from __future__ import annotations +from typing import Any + from libvcs.url.git import DEFAULT_RULES +_orig_rule_meta: dict[str, tuple[bool, int]] = {} + def enable_ssh_style_url_detection() -> None: """Enable detection of SSH-style URLs as explicit Git URLs. @@ -23,12 +27,14 @@ def enable_ssh_style_url_detection() -> None: >>> GitURL.is_valid('user@hostname:path/to/repo.git', is_explicit=True) True """ + # Patch the core-git-scp rule, storing its original state if not already stored for rule in DEFAULT_RULES: if rule.label == "core-git-scp": - # Make the rule explicit so it can be detected with is_explicit=True + if rule.label not in _orig_rule_meta: + _orig_rule_meta[rule.label] = (rule.is_explicit, rule.weight) rule.is_explicit = True - # Increase the weight to ensure it takes precedence rule.weight = 100 + break def disable_ssh_style_url_detection() -> None: @@ -39,7 +45,8 @@ def disable_ssh_style_url_detection() -> None: Examples -------- - >>> from vcspull.url import enable_ssh_style_url_detection, disable_ssh_style_url_detection + >>> from vcspull.url import enable_ssh_style_url_detection + >>> from vcspull.url import disable_ssh_style_url_detection >>> from libvcs.url.git import GitURL >>> # Enable the patch >>> enable_ssh_style_url_detection() @@ -50,11 +57,18 @@ def disable_ssh_style_url_detection() -> None: >>> GitURL.is_valid('user@hostname:path/to/repo.git', is_explicit=True) False """ + # Restore the core-git-scp rule to its original state, if known for rule in DEFAULT_RULES: if rule.label == "core-git-scp": - # Revert to original state - rule.is_explicit = False - rule.weight = 0 + orig = _orig_rule_meta.get(rule.label) + if orig: + rule.is_explicit, rule.weight = orig + _orig_rule_meta.pop(rule.label, None) + else: + # Fallback to safe defaults + rule.is_explicit = False + rule.weight = 0 + break def is_ssh_style_url_detection_enabled() -> bool: @@ -70,5 +84,30 @@ def is_ssh_style_url_detection_enabled() -> bool: return False -# Enable SSH-style URL detection by default -enable_ssh_style_url_detection() +""" +Context manager and utility for SSH-style URL detection. +""" + + +class ssh_style_url_detection: + """Context manager to enable/disable SSH-style URL detection.""" + + def __init__(self, enabled: bool = True) -> None: + self.enabled = enabled + + def __enter__(self) -> None: + """Enable or disable SSH-style URL detection on context enter.""" + if self.enabled: + enable_ssh_style_url_detection() + else: + disable_ssh_style_url_detection() + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: Any, + ) -> None: + """Restore original SSH-style URL detection state on context exit.""" + # Always restore to disabled after context + disable_ssh_style_url_detection() diff --git a/tests/test_url.py b/tests/test_url.py index 1fc81f8b..854ed4b5 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -3,9 +3,13 @@ from __future__ import annotations import pytest -from libvcs.url.git import GitURL +from libvcs.url.git import DEFAULT_RULES, GitURL -from vcspull.url import disable_ssh_style_url_detection, enable_ssh_style_url_detection +from vcspull.url import ( + disable_ssh_style_url_detection, + enable_ssh_style_url_detection, + ssh_style_url_detection, +) def test_ssh_style_url_detection_toggle() -> None: @@ -99,3 +103,39 @@ def test_ssh_style_url_parsing( assert git_url.hostname == expected_hostname assert git_url.path == expected_path assert git_url.suffix == ".git" + + +def test_enable_disable_restores_original_state() -> None: + """Original rule metadata is preserved and restored after enable/disable.""" + # Ensure any prior patch is cleared + disable_ssh_style_url_detection() + # Find the core-git-scp rule and capture its original state + rule = next(r for r in DEFAULT_RULES if r.label == "core-git-scp") + orig_state = (rule.is_explicit, rule.weight) + + # Disabling without prior enable should leave original state + disable_ssh_style_url_detection() + assert (rule.is_explicit, rule.weight) == orig_state + + # Enable should patch + enable_ssh_style_url_detection() + assert rule.is_explicit is True + assert rule.weight == 100 + + # Disable should restore to original + disable_ssh_style_url_detection() + assert (rule.is_explicit, rule.weight) == orig_state + + +def test_context_manager_restores_original_state() -> None: + """Context manager enables then restores original rule state.""" + rule = next(r for r in DEFAULT_RULES if r.label == "core-git-scp") + orig_state = (rule.is_explicit, rule.weight) + + # Use context manager + with ssh_style_url_detection(): + assert rule.is_explicit is True + assert rule.weight == 100 + + # After context, state should be back to original + assert (rule.is_explicit, rule.weight) == orig_state