Skip to content

Commit d5f00b8

Browse files
committed
feat: use junction instead of symlink for Windows
1 parent 9d73d95 commit d5f00b8

4 files changed

Lines changed: 59 additions & 25 deletions

File tree

src/uvlink/project.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ def get_uvlink_dir(*subpaths: str | Path) -> Path:
3333
Returns:
3434
Path: Absolute path to the uvlink data directory or provided subpath.
3535
"""
36-
base = Path(os.environ.get("XDG_DATA_HOME", "~/.local/share")).expanduser()
36+
default_path = Path.home() / ".local" / "share"
37+
base = Path(os.environ.get("XDG_DATA_HOME", default_path)).expanduser()
3738
root = base / "uvlink"
3839
for sp in subpaths:
3940
root /= Path(sp)

tests/test_cli.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,7 @@ def test_ls(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
113113
assert "Is Linked" in result.stdout
114114

115115
# Verify cache location message
116-
assert "Cache Location:" in result.stdout
117-
assert "uvlink/cache" in result.stdout
116+
assert f"Cache Location: {cache_dir}" in result.stdout
118117

119118
# Verify both projects appear and have correct linked status
120119
output_lines = result.stdout.split("\n")

tests/test_path_utils.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from pathlib import Path
2+
3+
import pytest
4+
5+
from uvlink.path_utils import create_symlink, create_windows_junction, is_windows
6+
7+
8+
def test_create_symlink(tmp_path: Path):
9+
target_dir = tmp_path / "target"
10+
symlink_dir = tmp_path / "symlink"
11+
create_symlink(symlink=symlink_dir, target=target_dir)
12+
13+
assert symlink_dir.exists()
14+
assert symlink_dir.is_symlink() or (is_windows() and symlink_dir.is_junction())
15+
assert symlink_dir.resolve() == target_dir.resolve()
16+
17+
18+
@pytest.mark.skipif(
19+
not is_windows(), reason="Windows junctions are only applicable on Windows."
20+
)
21+
def test_create_windows_junction(tmp_path: Path):
22+
target_dir = tmp_path
23+
symlink_dir = tmp_path / "symlink"
24+
create_windows_junction(symlink=symlink_dir, target=target_dir)
25+
26+
assert symlink_dir.exists()
27+
assert symlink_dir.is_junction()
28+
assert symlink_dir.resolve() == target_dir.resolve()
29+
30+
31+
def test_create_windows_junction_invalid_target(tmp_path: Path):
32+
with pytest.raises(ValueError):
33+
create_windows_junction(symlink=Path("any"), target=tmp_path / "nonexistent")

tests/test_project.py

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,42 @@
44

55
import pytest
66

7-
from uvlink.path_utils import create_symlink
7+
from uvlink.path_utils import create_symlink, is_windows
88
from uvlink.project import Project
99

1010

1111
class TestProject:
1212
def test_hash_path(self):
13-
assert Project.hash_path("/") == "8a5edab28263"
13+
assert (
14+
Project.hash_path("/") == "8a5edab28263"
15+
if not is_windows()
16+
else "c1dfd96eea9"
17+
)
18+
19+
def test_project_init_path_resolution_with_tilde(self) -> None:
20+
"""Test with tilde expansion and parent directory (e.g., "~/../xxx")."""
21+
user_home_path = Path.home()
22+
test_dir = user_home_path / "test_project"
23+
24+
# Test ~/test_project resolves correctly
25+
p2 = Project(project_dir="~/test_project")
26+
assert p2.project_dir == test_dir.resolve()
27+
assert p2.project_dir.is_absolute()
28+
29+
# Test ~/.. resolves to parent of HOME
30+
p3 = Project(project_dir="~/..")
31+
assert p3.project_dir == user_home_path.parent.resolve()
32+
assert p3.project_dir.is_absolute()
1433

1534
def test_project_init(self, tmp_path: Path) -> None:
1635
p = Project(project_dir=tmp_path)
1736
# Project.resolve() normalizes the path, so compare with resolved tmp_path
1837
assert p.project_dir == tmp_path.resolve()
1938
assert p.project_name == tmp_path.name
2039
assert p.venv_type == ".venv"
21-
assert "uvlink/cache" in str(p.project_cache_dir)
40+
assert "uvlink/cache" in p.project_cache_dir.as_posix()
2241

23-
def test_project_init_path_resolution(
24-
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
25-
) -> None:
42+
def test_project_init_path_resolution(self, tmp_path: Path) -> None:
2643
"""Test that Project resolves relative paths and parent directory references."""
2744
# Create a subdirectory to test relative paths with parent directory references
2845
subdir = tmp_path / "subdir" / "nested"
@@ -37,22 +54,6 @@ def test_project_init_path_resolution(
3754
assert p1.project_dir.is_absolute()
3855
assert p1.project_name == tmp_path.name
3956

40-
# Test with tilde expansion and parent directory (e.g., "~/../xxx")
41-
# Mock HOME to be tmp_path for reliable testing
42-
monkeypatch.setenv("HOME", str(tmp_path))
43-
test_dir = tmp_path / "test_project"
44-
test_dir.mkdir(exist_ok=True)
45-
46-
# Test ~/test_project resolves correctly
47-
p2 = Project(project_dir="~/test_project")
48-
assert p2.project_dir == test_dir.resolve()
49-
assert p2.project_dir.is_absolute()
50-
51-
# Test ~/.. resolves to parent of HOME
52-
p3 = Project(project_dir="~/..")
53-
assert p3.project_dir == tmp_path.parent.resolve()
54-
assert p3.project_dir.is_absolute()
55-
5657
# Test symlink resolution
5758
# Create a target directory and a symlink pointing to it
5859
target_dir = tmp_path / "target"

0 commit comments

Comments
 (0)