Skip to content

Commit f5335a3

Browse files
committed
fix(util): preserve separators before relative path tokens
The public `join_path` helper drops the slash in `join_path("parent", "/child")`, yielding `parentchild`. Strip leading slashes from later tokens before the normal joining logic so they remain relative to the accumulated prefix and receive a separator. Document this retained relative-token contract and cover string/PathLike inputs, chained tokens, roots, empty tokens and repeated leading separators. Five of eleven regression cases fail before this change. Afterward the complete utility module passes 128 tests with 39 skips on each CLI/Gix backend. Tree/reference/remote caller modules pass 230 tests with seven stalled-remote timeout failures per backend; the exact same seven failures reproduce on unchanged main. Repository pre-commit hooks pass. Tested on Linux with Python 3.12.14, Git 2.52.0 and GixPython 0.3.0; no other platform execution is claimed. Assisted-by: OpenAI Codex (model: unknown, autonomous)
1 parent cfb222f commit f5335a3

2 files changed

Lines changed: 27 additions & 5 deletions

File tree

‎git/util.py‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -235,15 +235,16 @@ def stream_copy(source: SupportsRead[AnyStr], destination: SupportsWrite[AnyStr]
235235

236236
def join_path(a: PathLike, *p: PathLike) -> PathLike:
237237
R"""Join path tokens together similar to osp.join, but always use ``/`` instead of
238-
possibly ``\`` on Windows."""
238+
possibly ``\`` on Windows.
239+
240+
Later tokens are treated as relative, even when they start with ``/``.
241+
"""
239242
path = os.fspath(a)
240243
for b in p:
241-
b = os.fspath(b)
244+
b = os.fspath(b).lstrip("/")
242245
if not b:
243246
continue
244-
if b.startswith("/"):
245-
path += b[1:]
246-
elif path == "" or path.endswith("/"):
247+
if path == "" or path.endswith("/"):
247248
path += b
248249
else:
249250
path += "/" + b

‎test/test_util.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
decygpath,
3939
get_user_id,
4040
is_cygwin_git,
41+
join_path,
4142
remove_password_if_present,
4243
rmtree,
4344
)
@@ -46,6 +47,26 @@
4647
from test.lib import TestBase, requires_symlinks, with_rw_repo
4748

4849

50+
@pytest.mark.parametrize(
51+
"parts, expected",
52+
[
53+
(("parent", "/child"), "parent/child"),
54+
(("parent", "/child", "/leaf"), "parent/child/leaf"),
55+
((pathlib.Path("parent"), "/child"), "parent/child"),
56+
(("parent/", "/child"), "parent/child"),
57+
(("parent", "child"), "parent/child"),
58+
(("", "/child"), "child"),
59+
(("/", "/child"), "/child"),
60+
(("parent", "", "child"), "parent/child"),
61+
(("parent", "//child"), "parent/child"),
62+
(("/parent/", "///child"), "/parent/child"),
63+
(("parent", "///"), "parent"),
64+
],
65+
)
66+
def test_join_path_keeps_later_tokens_relative(parts, expected):
67+
assert join_path(*parts) == expected
68+
69+
4970
@pytest.fixture
5071
def tmp_path(request):
5172
# Unreadable files and symlinks cannot be copied by native fixture tooling.

0 commit comments

Comments
 (0)