mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(scripts): find Git Bash under a PortableGit mingw64 layout (#7214)
* fix(scripts): find Git Bash under a PortableGit mingw64 layout `scripts/run-with-git-bash.cmd` derives `bash.exe` from `git.exe`'s parent, which only holds for the Git for Windows `<root>/cmd` and `<root>/bin` layouts. PortableGit and GitHub Desktop ship `git.exe` in `<root>/mingw64/bin`, so the wrapper looked for `<root>/mingw64/bin/bash.exe` and reported "Could not locate Git for Windows Bash" even though `<root>/bin/bash.exe` exists. Every Makefile recipe that runs a repository shell script failed on those hosts. Resolve the shell under the Git installation root instead, and mirror the rule in `backend/tests/support/shell.py` (documented as mirroring the wrapper) so the shell-script suites stop skipping on the same hosts. * test(backend): pin both Git layouts in one shell-discovery suite Review follow-up for the PortableGit probe: - `_git_roots` now probes `git.exe`'s parent and grandparent unconditionally — exactly the two directories the Makefile wrapper checks — so the batch probe and the Python mirror cannot drift; the docstring records that the first `mingw64` candidate normally misses and is kept only to mirror it. - fold the candidate-layout cases and the Windows-only wrapper case into `tests/test_support_shell.py`, which already owns this module's unit tests, instead of a second file with a duplicate `_touch` helper. - report the wrapper's stdout in the failure message: its diagnostic goes to stdout, so `stderr` was empty on the discovery-failure path.
This commit is contained in:
parent
619132da5b
commit
dede3faed4
@ -19,13 +19,26 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
|
||||
def _git_roots(git: str) -> list[Path]:
|
||||
"""Return the two directories the Makefile wrapper probes for the Git root.
|
||||
|
||||
``scripts/run-with-git-bash.cmd`` checks ``bin\\bash.exe`` under ``git.exe``'s
|
||||
directory and then one level above it. Git for Windows keeps ``git.exe`` in
|
||||
``<root>/cmd`` (or ``<root>/bin``), so the first entry is the installation
|
||||
root. PortableGit and GitHub Desktop keep it in ``<root>/mingw64/bin``, where
|
||||
only the second entry is; the first is ``<root>/mingw64`` and typically holds
|
||||
no ``bin/bash.exe`` in stock trees. Bash and sh themselves stay at
|
||||
``<root>/bin`` and ``<root>/usr/bin`` in both layouts.
|
||||
"""
|
||||
git_dir = Path(git).resolve().parent
|
||||
return [git_dir.parent, git_dir.parent.parent]
|
||||
|
||||
|
||||
def _script_bash_candidates(git: str | None, program_files: str | None, path_bash: str | None) -> list[Path]:
|
||||
"""Build the ordered bash candidate list for Windows hosts."""
|
||||
candidates: list[Path] = []
|
||||
if git is not None:
|
||||
# Git for Windows layout: <root>/cmd/git.exe (or <root>/bin/git.exe)
|
||||
# both resolve to <root>/bin/bash.exe two levels up from git's parent.
|
||||
candidates.append(Path(git).resolve().parent.parent / "bin" / "bash.exe")
|
||||
candidates.extend(root / "bin" / "bash.exe" for root in _git_roots(git))
|
||||
if program_files:
|
||||
candidates.append(Path(program_files) / "Git" / "bin" / "bash.exe")
|
||||
if path_bash is not None:
|
||||
@ -42,9 +55,9 @@ def _script_sh_candidates(git: str | None, program_files: str | None) -> list[Pa
|
||||
"""
|
||||
candidates: list[Path] = []
|
||||
if git is not None:
|
||||
git_root = Path(git).resolve().parent.parent
|
||||
candidates.append(git_root / "bin" / "sh.exe")
|
||||
candidates.append(git_root / "usr" / "bin" / "sh.exe")
|
||||
for root in _git_roots(git):
|
||||
candidates.append(root / "bin" / "sh.exe")
|
||||
candidates.append(root / "usr" / "bin" / "sh.exe")
|
||||
if program_files:
|
||||
program_files_git = Path(program_files) / "Git"
|
||||
candidates.append(program_files_git / "bin" / "sh.exe")
|
||||
|
||||
@ -11,6 +11,8 @@ is exercised on every CI leg.
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@ -100,7 +102,11 @@ def test_git_derived_bash_candidate_comes_first(fake_windows: Path):
|
||||
candidates = shell._script_bash_candidates(git=git_exe, program_files=None, path_bash=path_bash)
|
||||
|
||||
git_root = (fake_windows / "git" / "cmd" / "git.exe").resolve().parent.parent
|
||||
assert candidates == [git_root / "bin" / "bash.exe", Path(path_bash)]
|
||||
assert candidates == [
|
||||
git_root / "bin" / "bash.exe",
|
||||
git_root.parent / "bin" / "bash.exe",
|
||||
Path(path_bash),
|
||||
]
|
||||
|
||||
|
||||
def test_program_files_bash_used_when_git_absent(fake_windows: Path):
|
||||
@ -120,6 +126,8 @@ def test_posix_sh_prefers_git_sh_exe(fake_windows: Path):
|
||||
assert candidates == [
|
||||
git_root / "bin" / "sh.exe",
|
||||
git_root / "usr" / "bin" / "sh.exe",
|
||||
git_root.parent / "bin" / "sh.exe",
|
||||
git_root.parent / "usr" / "bin" / "sh.exe",
|
||||
]
|
||||
|
||||
|
||||
@ -134,6 +142,55 @@ def test_posix_sh_falls_back_to_program_files(fake_windows: Path):
|
||||
]
|
||||
|
||||
|
||||
def _portablegit_git(fake_windows: Path) -> tuple[Path, str]:
|
||||
"""Create a PortableGit layout (``<root>/mingw64/bin/git.exe``) and return it."""
|
||||
git_exe = _touch(fake_windows / "PortableGit" / "mingw64" / "bin" / "git.exe")
|
||||
return (fake_windows / "PortableGit").resolve(), str(git_exe)
|
||||
|
||||
|
||||
def test_portablegit_bash_candidate_reaches_the_git_root(fake_windows: Path):
|
||||
git_root, git_exe = _portablegit_git(fake_windows)
|
||||
|
||||
candidates = shell._script_bash_candidates(git=git_exe, program_files=None, path_bash=None)
|
||||
|
||||
assert candidates == [git_root / "mingw64" / "bin" / "bash.exe", git_root / "bin" / "bash.exe"]
|
||||
|
||||
|
||||
def test_portablegit_posix_sh_candidate_reaches_the_git_root(fake_windows: Path):
|
||||
git_root, git_exe = _portablegit_git(fake_windows)
|
||||
|
||||
candidates = shell._script_sh_candidates(git=git_exe, program_files=None)
|
||||
|
||||
assert candidates == [
|
||||
git_root / "mingw64" / "bin" / "sh.exe",
|
||||
git_root / "mingw64" / "usr" / "bin" / "sh.exe",
|
||||
git_root / "bin" / "sh.exe",
|
||||
git_root / "usr" / "bin" / "sh.exe",
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "nt", reason="the Makefile shell wrapper is a cmd.exe script")
|
||||
def test_makefile_shell_wrapper_runs_portablegit_bash(tmp_path: Path):
|
||||
"""`make` reaches Git Bash when `where git` reports the mingw64 layout.
|
||||
|
||||
A bare ``cmd.exe`` copy stands in for ``bash.exe``: the wrapper only has to
|
||||
locate and launch it, and ``cmd /c echo`` keeps the assertion offline.
|
||||
"""
|
||||
git_root = tmp_path / "PortableGit"
|
||||
_touch(git_root / "mingw64" / "bin" / "git.exe")
|
||||
bash = git_root / "bin" / "bash.exe"
|
||||
system_root = Path(os.environ.get("SystemRoot", r"C:\Windows"))
|
||||
bash.parent.mkdir()
|
||||
shutil.copy(system_root / "System32" / "cmd.exe", bash)
|
||||
wrapper = Path(__file__).resolve().parents[2] / "scripts" / "run-with-git-bash.cmd"
|
||||
|
||||
env = {**os.environ, "PATH": os.pathsep.join([str(git_root / "mingw64" / "bin"), str(system_root / "System32")])}
|
||||
result = subprocess.run(["cmd", "/c", str(wrapper), "/c", "echo", "BASH_OK"], capture_output=True, text=True, env=env, timeout=60)
|
||||
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
assert "BASH_OK" in result.stdout
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "nt", reason="real Windows environment reads")
|
||||
def test_find_script_bash_never_returns_wsl_or_store_stubs():
|
||||
"""On a real Windows host the result must not be a stub launcher."""
|
||||
|
||||
@ -208,6 +208,13 @@ likewise prefix the target with `bash`. This keeps documented `make` commands
|
||||
working when a source archive, `core.fileMode=false`, or a non-POSIX filesystem
|
||||
does not preserve executable bits.
|
||||
|
||||
The Windows wrapper resolves `bash.exe` under the Git installation root, which
|
||||
is the parent of `cmd/`, `bin/`, or `mingw64/bin/` depending on the distribution:
|
||||
Git for Windows puts `git.exe` in `cmd/`, while PortableGit and GitHub Desktop
|
||||
put it in `mingw64/bin/`. Bash and sh stay at `<root>/bin` and `<root>/usr/bin`
|
||||
in both layouts. `backend/tests/support/shell.py` mirrors the same rule for the
|
||||
shell-script suites.
|
||||
|
||||
`make clean` deletes `backend/.deer-flow` (database, users, threads, uploads,
|
||||
secrets), which both compose stacks mount into `deer-flow-gateway`. Its recipe
|
||||
runs `check-data-not-in-use.sh` before `make stop` (which would stop a live
|
||||
|
||||
@ -8,9 +8,13 @@ for /f "delims=" %%I in ('where git 2^>NUL') do (
|
||||
set "bash_exe=%%~dpI..\bin\bash.exe"
|
||||
goto :found_bash
|
||||
)
|
||||
if exist "%%~dpI..\..\bin\bash.exe" (
|
||||
set "bash_exe=%%~dpI..\..\bin\bash.exe"
|
||||
goto :found_bash
|
||||
)
|
||||
)
|
||||
|
||||
echo Could not locate Git for Windows Bash ("..\bin\bash.exe" relative to git on PATH). Ensure Git for Windows is installed and that git and bash.exe are available on PATH.
|
||||
echo Could not locate Git for Windows Bash ("bin\bash.exe" under the git root). Ensure Git for Windows is installed and that git and bash.exe are available on PATH.
|
||||
exit /b 1
|
||||
|
||||
:found_bash
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user