Skip to content

Commit fc2f02c

Browse files
authored
Merge pull request #2197 from Cyrus580529/shared-symlink-guard
Skip tests that need symlink privileges instead of erroring
2 parents 9a8f6fe + b10e250 commit fc2f02c

6 files changed

Lines changed: 36 additions & 23 deletions

File tree

test/lib/helper.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
"GIT_REPO",
2020
"GIT_DAEMON_PORT",
2121
"xfail_if_raises",
22+
"symlinks_supported",
23+
"requires_symlinks",
2224
]
2325

2426
import contextlib
@@ -29,6 +31,7 @@
2931
import logging
3032
import os
3133
import os.path as osp
34+
from stat import S_ISLNK, ST_MODE
3235
import subprocess
3336
import sys
3437
import tempfile
@@ -491,6 +494,28 @@ def _executable(self, basename):
491494
raise RuntimeError(f"no regular file or symlink {path!r}")
492495

493496

497+
def symlinks_supported() -> bool:
498+
"""Check whether this process can actually create a symlink.
499+
500+
On Windows the platform alone doesn't decide it: creating a symlink needs either
501+
Developer Mode or SeCreateSymbolicLinkPrivilege, and an unprivileged process gets
502+
OSError (WinError 1314) instead.
503+
"""
504+
with tempfile.TemporaryDirectory(prefix="gitpython-symlink-check-") as temp_dir:
505+
link_path = osp.join(temp_dir, "link")
506+
try:
507+
os.symlink("missing-target", link_path)
508+
except (NotImplementedError, OSError):
509+
return False
510+
return S_ISLNK(os.lstat(link_path)[ST_MODE])
511+
512+
513+
requires_symlinks = pytest.mark.skipif(
514+
not symlinks_supported(),
515+
reason="symlinks are unavailable, or need privileges this process doesn't have",
516+
)
517+
518+
494519
@contextlib.contextmanager
495520
def xfail_if_raises(
496521
condition: bool,

test/test_index.py

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
from git.util import Actor, cwd, hex_to_bin, rmtree
4141

4242
from test.lib import TestBase, VirtualEnvironment, fixture, fixture_path, with_rw_directory, with_rw_repo, PathLikeMock
43-
from test.lib.helper import xfail_if_raises
43+
from test.lib.helper import symlinks_supported, xfail_if_raises
4444

4545
HOOKS_SHEBANG = "#!/usr/bin/env sh\n"
4646

@@ -175,19 +175,6 @@ def _decode(stdout):
175175
_win_bash_status = WinBashStatus.check()
176176

177177

178-
def _windows_supports_symlinks():
179-
if sys.platform != "win32":
180-
return False
181-
182-
with tempfile.TemporaryDirectory(prefix="gitpython-symlink-check-") as temp_dir:
183-
link_path = osp.join(temp_dir, "link")
184-
try:
185-
os.symlink("missing-target", link_path)
186-
except (NotImplementedError, OSError):
187-
return False
188-
return S_ISLNK(os.lstat(link_path)[ST_MODE])
189-
190-
191178
def _make_hook(git_dir, name, content, make_exec=True):
192179
"""A helper to create a hook"""
193180
hp = hook_path(name, git_dir)
@@ -655,7 +642,7 @@ def _count_existing(self, repo, files):
655642
@with_rw_repo("0.1.6")
656643
def test_index_mutation(self, rw_repo):
657644
with xfail_if_raises(
658-
sys.platform == "win32" and (Git().config("core.symlinks") == "true" or _windows_supports_symlinks()),
645+
sys.platform == "win32" and (Git().config("core.symlinks") == "true" or symlinks_supported()),
659646
raises=(FileNotFoundError, GitCommandError),
660647
reason="Assumes symlinks are not created on Windows and opens a symlink to a nonexistent target.",
661648
):

test/test_installation.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
import os
77
import subprocess
88

9-
from test.lib import TestBase, VirtualEnvironment, with_rw_directory
9+
from test.lib import TestBase, VirtualEnvironment, requires_symlinks, with_rw_directory
1010

1111

1212
class TestInstallation(TestBase):
13+
@requires_symlinks
1314
@with_rw_directory
1415
def test_installation(self, rw_dir):
1516
venv, run = self._set_up_venv(rw_dir)

test/test_refs.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import git.refs as refs
2929
from git.util import Actor
3030

31-
from test.lib import TestBase, with_rw_repo, PathLikeMock
31+
from test.lib import TestBase, requires_symlinks, with_rw_repo, PathLikeMock
3232

3333

3434
class TestRefs(TestBase):
@@ -780,6 +780,7 @@ def test_symbolic_reference_log_append_rejects_path_traversal(self):
780780
)
781781
assert not outside_path.exists()
782782

783+
@requires_symlinks
783784
def test_symbolic_reference_set_reference_rejects_symlink_escape(self):
784785
with tempfile.TemporaryDirectory() as tmp_dir:
785786
base_dir = Path(tmp_dir)
@@ -791,10 +792,7 @@ def test_symbolic_reference_set_reference_rejects_symlink_escape(self):
791792
refs_heads_dir = Path(repo.common_dir) / "refs" / "heads"
792793
refs_heads_dir.mkdir(parents=True, exist_ok=True)
793794
symlink_path = refs_heads_dir / "link_out"
794-
try:
795-
symlink_path.symlink_to(outside_dir, target_is_directory=True)
796-
except (OSError, NotImplementedError) as ex:
797-
self.skipTest("symlinks unavailable on this platform: %s" % ex)
795+
symlink_path.symlink_to(outside_dir, target_is_directory=True)
798796
if osp.realpath(symlink_path / "escaped") == osp.abspath(symlink_path / "escaped"):
799797
self.skipTest("realpath does not resolve directory symlinks on this platform")
800798

test/test_repo.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
from git.repo.fun import touch
4444
from git.util import bin_to_hex, cwd, cygpath, join_path_native, rmfile, rmtree
4545

46-
from test.lib import TestBase, fixture, with_rw_directory, with_rw_repo, PathLikeMock
46+
from test.lib import TestBase, fixture, requires_symlinks, with_rw_directory, with_rw_repo, PathLikeMock
4747

4848

4949
def iter_flatten(lol):
@@ -1433,6 +1433,7 @@ def test_ignored_items_reported(self):
14331433
["included_file.txt", "ignored_file.txt", "included_dir/file.txt", "ignored_dir/file.txt"]
14341434
) == ["ignored_file.txt", "ignored_dir/file.txt"]
14351435

1436+
@requires_symlinks
14361437
def test_ignored_raises_error_w_symlink(self):
14371438
with tempfile.TemporaryDirectory() as tdir:
14381439
tmp_dir = pathlib.Path(tdir)

test/test_util.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
rmtree,
4141
)
4242

43-
from test.lib import TestBase, with_rw_repo
43+
from test.lib import TestBase, requires_symlinks, with_rw_repo
4444

4545

4646
@pytest.fixture
@@ -113,6 +113,7 @@ def test_deletes_dir_with_readonly_files(self, tmp_path):
113113
sys.platform == "cygwin",
114114
reason="Cygwin can't set the permissions that make the test meaningful.",
115115
)
116+
@requires_symlinks
116117
def test_avoids_changing_permissions_outside_tree(self, tmp_path, request):
117118
# Automatically works on Windows, but on Unix requires either special handling
118119
# or refraining from attempting to fix PermissionError by making chmod calls.

0 commit comments

Comments
 (0)