Skip to content

Commit e5a7bee

Browse files
committed
fix(integration): detect Windows symlinks in status
1 parent d7815a0 commit e5a7bee

2 files changed

Lines changed: 52 additions & 1 deletion

File tree

src/specify_cli/integration_status.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,13 @@ def _manifest_file_status(
219219
valid.append(rel)
220220
modified.append(rel)
221221
continue
222-
if stat.S_ISLNK(path_stat.st_mode):
222+
is_symlink = stat.S_ISLNK(path_stat.st_mode)
223+
if not is_symlink:
224+
try:
225+
is_symlink = path.is_symlink()
226+
except OSError:
227+
is_symlink = False
228+
if is_symlink:
223229
symlink_status = _tracked_symlink_manifest_status(
224230
path,
225231
project_root_resolved,

tests/integrations/test_integration_subcommand.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,51 @@ def test_status_treats_dangling_symlink_as_missing(self, copilot_project):
623623
assert first_rel in payload["manifests"]["copilot"]["missing_files"]
624624
assert first_rel not in payload["manifests"]["copilot"]["modified_files"]
625625

626+
def test_status_treats_windows_style_dangling_symlink_as_missing(self, tmp_path, monkeypatch):
627+
from specify_cli.integration_status import _manifest_file_status
628+
from specify_cli.integrations.manifest import IntegrationManifest
629+
630+
project = tmp_path / "proj"
631+
project.mkdir()
632+
tracked = project / "tracked.md"
633+
tracked.write_text("content\n", encoding="utf-8")
634+
regular_stat = tracked.lstat()
635+
636+
manifest = IntegrationManifest("test", project, version="test")
637+
manifest.record_existing("tracked.md")
638+
639+
tracked.unlink()
640+
try:
641+
tracked.symlink_to(project / "missing-target")
642+
except OSError as exc:
643+
pytest.skip(f"symlinks unavailable: {exc}")
644+
645+
original_lstat = Path.lstat
646+
original_is_symlink = Path.is_symlink
647+
648+
def windows_style_lstat(self):
649+
if self == tracked:
650+
return regular_stat
651+
return original_lstat(self)
652+
653+
def windows_style_is_symlink(self):
654+
if self == tracked:
655+
return True
656+
return original_is_symlink(self)
657+
658+
monkeypatch.setattr(Path, "lstat", windows_style_lstat)
659+
monkeypatch.setattr(Path, "is_symlink", windows_style_is_symlink)
660+
661+
missing, modified, invalid, valid = _manifest_file_status(
662+
manifest,
663+
project.resolve(),
664+
)
665+
666+
assert missing == ["tracked.md"]
667+
assert modified == []
668+
assert invalid == []
669+
assert valid == ["tracked.md"]
670+
626671
def test_status_reports_unsafe_manifest_paths_without_hashing_them(self, tmp_path, copilot_project):
627672
outside = tmp_path / "outside"
628673
outside.mkdir()

0 commit comments

Comments
 (0)