From cc965c4b79b86c981e5e8f045f4592614ed6232b Mon Sep 17 00:00:00 2001 From: Christoph Reiter Date: Fri, 31 Jan 2025 15:45:27 +0100 Subject: [PATCH] build: delete all junctions before calling "git clean" git clean can't deal with junctions and in case there is a loop it follows them forever (or until stack overflow). https://github.com/git-for-windows/git/issues/5320 To work around this try to delete all junctions in the clean re-try code path. Fixes #108 --- msys2_autobuild/build.py | 16 ++++++++++++++++ tests/main_test.py | 17 ++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/msys2_autobuild/build.py b/msys2_autobuild/build.py index d633f3b..8f6e3f1 100644 --- a/msys2_autobuild/build.py +++ b/msys2_autobuild/build.py @@ -144,6 +144,21 @@ def chmod(p: PathLike) -> None: chmod(fpath) +def remove_junctions(topdir: PathLike) -> None: + # work around a git issue where it can't handle junctions + # https://github.com/git-for-windows/git/issues/5320 + if not hasattr(os.path, 'isjunction'): # Python 3.12 only + return + for root, dirs, _ in os.walk(topdir): + no_junctions = [] + for d in dirs: + if not os.path.isjunction(os.path.join(root, d)): + no_junctions.append(d) + else: + os.remove(os.path.join(root, d)) + dirs[:] = no_junctions + + def reset_git_repo(path: PathLike): def clean(): @@ -160,6 +175,7 @@ def clean(): if not made_writable: print("Trying to make files writable") make_tree_writable(path) + remove_junctions(path) made_writable = True except OSError as e: print("Making files writable failed", e) diff --git a/tests/main_test.py b/tests/main_test.py index 7bab414..90e8434 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -7,7 +7,7 @@ from msys2_autobuild.utils import parse_optional_deps from msys2_autobuild.queue import parse_buildqueue, get_cycles -from msys2_autobuild.build import make_tree_writable +from msys2_autobuild.build import make_tree_writable, remove_junctions def test_make_tree_writable(): @@ -37,6 +37,21 @@ def test_make_tree_writable(): assert os.access(nested_junction, os.W_OK) and os.access(nested_junction, os.R_OK) +def test_remove_junctions(): + with tempfile.TemporaryDirectory() as tempdir: + nested_dir = Path(tempdir) / "nested" + nested_junction = nested_dir / "junction" + nested_dir.mkdir() + + # Create a junction loop if possible, to make sure we ignore it + if hasattr(os.path, 'isjunction') and os.name == 'nt': + import _winapi + _winapi.CreateJunction(str(nested_dir), str(nested_junction)) + + remove_junctions(tempdir) + assert not nested_junction.exists() + + def test_parse_optional_deps(): assert parse_optional_deps("a:b,c:d,a:x") == {'a': ['b', 'x'], 'c': ['d']}