Skip to content

expand_dir assertion fails on a relative workspace root under a symlinked cwd #561

Description

@tony

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions