Summary
expand_dir() in src/vcspull/config.py asserts that a lexically-normalized relative path equals its symlink-resolved form:
if not dir_.is_absolute():
dir_ = pathlib.Path(os.path.normpath(cwd / dir_))
assert dir_ == pathlib.Path(cwd, dir_).resolve(strict=False)
os.path.normpath normalizes lexically and does not resolve symlinks; Path.resolve() does. When cwd is reached through a symlinked directory and the workspace root is relative, the two disagree and the assertion fails. Under a normal (non--O) interpreter this raises AssertionError out of extract_repos, aborting the command.
Reproduction
import os, pathlib, tempfile
with tempfile.TemporaryDirectory() as t:
t = pathlib.Path(t)
real = t / "real"; real.mkdir()
link = t / "link"; link.symlink_to(real)
cwd, dir_ = link, pathlib.Path("vendor")
normed = pathlib.Path(os.path.normpath(cwd / dir_))
resolved = pathlib.Path(cwd, dir_).resolve(strict=False)
print(normed) # .../link/vendor
print(resolved) # .../real/vendor
print(normed == resolved) # False -> assert fires
Impact
Any config that uses a relative workspace root (./vendor/, vendor/) crashes when the invocation cwd passes through a symlink — common on macOS (/tmp -> /private/tmp) and with symlinked checkout roots. It is latent today because relative roots are uncommon, but it is reachable now.
Suggested fix
The assertion encodes an assumption that no longer holds once symlinks are involved. Either drop it, or compare against the lexical form (os.path.normpath) on both sides rather than mixing normpath with resolve. Whichever is chosen, the function should not resolve symlinks silently if downstream identity depends on the unresolved path.
Summary
expand_dir()insrc/vcspull/config.pyasserts that a lexically-normalized relative path equals its symlink-resolved form:os.path.normpathnormalizes lexically and does not resolve symlinks;Path.resolve()does. Whencwdis reached through a symlinked directory and the workspace root is relative, the two disagree and the assertion fails. Under a normal (non--O) interpreter this raisesAssertionErrorout ofextract_repos, aborting the command.Reproduction
Impact
Any config that uses a relative workspace root (
./vendor/,vendor/) crashes when the invocation cwd passes through a symlink — common on macOS (/tmp->/private/tmp) and with symlinked checkout roots. It is latent today because relative roots are uncommon, but it is reachable now.Suggested fix
The assertion encodes an assumption that no longer holds once symlinks are involved. Either drop it, or compare against the lexical form (
os.path.normpath) on both sides rather than mixingnormpathwithresolve. Whichever is chosen, the function should not resolve symlinks silently if downstream identity depends on the unresolved path.