Skip to content

[Windows port] 2/8: Make the worktree safety gate cross-platform#20

Open
wmaciel wants to merge 3 commits into
microsoft:mainfrom
wmaciel:wmaciel/phase2-xplat-paths
Open

[Windows port] 2/8: Make the worktree safety gate cross-platform#20
wmaciel wants to merge 3 commits into
microsoft:mainfrom
wmaciel:wmaciel/phase2-xplat-paths

Conversation

@wmaciel

@wmaciel wmaciel commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Part of #7. Closes #9. Phase 2 of 8 of the Windows port.

Why

Phase 1/1b pinned UTF-8 everywhere. This phase fixes the other Windows-specific class of bug: path separators. Two coherent halves, both "make \-Windows behave like /-POSIX":

  • The rm-rf safety gate (_worktree_safety.py) fail-closes on Windows: it treated "absolute" as "starts with /", so a real C:\… worktree was rejected and dream-cleanup could never remove it. That's the core of [Windows port] 2/8: Make the worktree safety gate cross-platform #9.
  • Shadow-artifact path normalization: the shadow index and the file-walk recorded OS-native \, where the shadow format requires /.

Both keep POSIX accept/reject behaviour unchanged (only Windows behaviour changes), so Linux CI stays green.

What changed (3 commits)

  1. refactor(dream): rename _FORBIDDEN_BASES to _UNIX_FORBIDDEN_BASES — pure rename (5 sites), no behaviour change. The constant only ever held POSIX roots; the name now says so, making room for the Windows equivalent.
  2. fix(dream): make _worktree_safety gate cross-platform — the gate's three portability rules:
    • Rule 2 (absolute check): p.startswith("/") -> os.path.isabs(p), so C:\… counts as absolute.
    • Rule 3 (traversal check): substring ".." -> ".." in PurePath(p).parts, which catches .. under either separator without false-positives on names like ..foo.
    • Rule 4 (sensitive-root check): added Windows forbidden roots (env-derived at runtime) and a case-insensitive (normcase) compare, plus a filesystem-root guard. POSIX roots stay a static constant; Windows roots come from a function because they're environment-derived — _get_forbidden_bases() selects the platform-appropriate source.
  3. fix: record forward-slash paths in shadow index and file walk (Windows)_walk_files and rebuild_top_index now emit .as_posix(), so shadow paths are POSIX on every OS.

Testing

Verified on Windows (Python 3.13):

  • test_worktree_safety.py — 54 passed / 26 skipped / 0 failed (skips are the POSIX-literal-path cases, platform-guarded — see note below)
  • test_dream_reconcile.py — 187 passed / 0 failed
  • test_shadow_init.py — 241 passed (the one remaining failure is a pre-existing permission-model carve-out unrelated to this change, tracked for Phase 3)
  • worktree cleanup tests — 4 passed

Note on the platform guards

On Windows Python 3.13+, os.path.isabs("/tmp") is False (a leading slash is drive-relative, not absolute). The existing POSIX-literal-path tests therefore can't run on Windows and are guarded with skipif(os.name == "nt"); a new TestWindowsPaths class covers the C:\… equivalents. Net gate behaviour on Linux is unchanged.

One test assertion changed as a direct consequence of the .as_posix() fix: test_walk_files_lists_all_non_excluded asserted os.path.join("sub", "b.txt") (which is sub\b.txt on Windows). Since the code now guarantees / on every OS, the assertion is pinned to the canonical sub/b.txt.


Co-authored by Copilot 🤖

wmaciel and others added 3 commits July 10, 2026 13:41
The forbidden-base set holds only POSIX sensitive roots; the name now states the platform, pairing with the Windows-specific forbidden dirs added in the next commit. Pure rename, no behaviour change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The rm-rf safety gate hard-coded POSIX semantics and fail-closed on
Windows: it treated "absolute" as startswith("/") (rejecting real C:\
worktrees, so dream-cleanup could never remove them), split on "/" to
find ".." (missing backslash-delimited traversal), and only knew the
POSIX forbidden roots.

- Rule 2: os.path.isabs instead of startswith("/") (True for C:\ and
  UNC shares, False for a bare leading slash on Windows).
- Rule 3: PurePath(p).parts to detect ".." with either separator.
- Rule 4: split into a filesystem-root check plus a normcase-based match
  against the platform-appropriate forbidden set (static POSIX
  _UNIX_FORBIDDEN_BASES, or env-derived Windows roots on nt).

Tests: guard the POSIX-literal cases with skipif(nt) (a leading slash is
not absolute on Windows) and add Windows-native cases (drive/UNC roots,
%SystemRoot%, backslash traversal, a real C:\ worktree, relative base).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Shadow artifacts (the top-level index and file discovery) are a
cross-platform, git-committed format that always uses forward slashes,
but on Windows the path operations returned OS-native "\", producing
"src\main.py" instead of "src/main.py".

- shadow-init.py _walk_files: emit Path.as_posix() instead of str().
- dream-reconcile.py rebuild_top_index: emit .as_posix() (adds a pathlib
  import) so the recovered source path uses "/".

The sibling relpath in dream-reconcile (feeding only a startswith("_")
skip check, never written to disk) is left as-is. test_shadow_init's
test_walk_files_lists_all_non_excluded asserted os.path.join(...) and is
updated to the canonical "/".

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@wmaciel
wmaciel requested a review from chisingh July 10, 2026 23:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Windows port] 2/8: Make the worktree safety gate cross-platform

1 participant