Skip to content

Windows support: four fixes from the windows-support branch - #50

Merged
arecarn merged 4 commits into
masterfrom
windows-support-rebased
Nov 7, 2026
Merged

arecarn merged 4 commits into
masterfrom
windows-support-rebased

Conversation

@arecarn

@arecarn arecarn commented Nov 7, 2026

Copy link
Copy Markdown
Owner

Refs #27, #13

Summary

This re-applies the four changes from the old windows-support branch on
top of current master, one commit for each. The old branch is not changed.

utils.readlink          strip the \\?\ prefix, then normalize      # new, Windows only
utils.is_same_file(s)   Path.samefile(), length check              # changed
actions                 symlink_to(target_is_directory=...)        # changed
ignore                  catch ValueError from Path.glob            # changed
  • Directory symlinks. symlink_to gets target_is_directory. On Windows
    a directory link and a file link are different, and the wrong one does not
    work. POSIX ignores the argument.
  • \\?\ prefix. readlink removes it from the raw target before the path
    is normalized, so later path comparisons work.
  • samefile(). resolve() can spell one file in different ways on
    Windows. is_same_files also checks the length, because zip() stops at the
    shorter list. For a link whose target is missing, it falls back to comparing
    resolved paths.
  • Ignore. A pattern that the file system rejects is skipped instead of
    raising ValueError.

Not taken from the old branch: testing the ignore pattern against the
source's children. It would ignore a whole directory when one child matches,
and test_recursive_ignore_keeps_parent_and_nonmatching_files forbids that.
master already handles blank lines (#3).

Two hard links to one file now count as the same file on POSIX too.

Evidence

  • Before: the new tests fail without each change: the target_is_directory
    call, the prefix strip, the hard-link comparison and the null-byte pattern.
    After: they pass on Linux (154 passed, 11 skipped).
  • The test for samefile() with different letter case and the prefix tests
    matter on Windows. The windows-latest job in this PR is the first Windows
    run for them. The drive-relative readlink test stays skipped.

Merge Danger

Door: two-way. Each change is a separate commit and can be reverted alone.

Blast Radius: readlink, is_same_file and ignore matching are used by
stow, unstow and clean on every platform. The samefile() change is the
one that can change POSIX results, for hard links.

On Windows a directory symlink and a file symlink are distinct object
types, and creating the wrong one yields a link that does not work.
Pass target_is_directory to symlink_to; it is a no-op on POSIX, so the
tests assert the call. Relates to #27, #13.
Windows returns junction and symlink targets with a verbatim prefix (the
UNC form for network paths). Left in, it defeats every path comparison.
readlink is the one place every caller goes through, and the prefix is
stripped before the absolute form is normalized. Removes the Windows skip
on test_readlink_with_absolute_target, which this should fix; that cannot
be confirmed without a Windows run. Relates to #27.
resolve() can spell one file differently on Windows (case, 8.3 short
names), so comparing resolved strings gives false negatives. Use
Path.samefile() in is_same_file and, through it, is_same_files, which keeps
its order-insensitive behaviour and gains an explicit length check.

samefile() needs both files to exist, so a dangling link falls back to the
resolved-path comparison; callers in stow/unstow/clean compare dangling
links. This acts on the TODO in the is_same_file docstring.

The POSIX test uses hard links as a stand-in for the Windows spellings; the
case-insensitivity test only runs on Windows and has not been run there.
Relates to #27.
Path.glob raises ValueError for patterns the file system rejects (some on
Windows, a null byte on POSIX). Treat them like an empty result instead of
crashing, in both should_ignore and the root glob cache.

Not taken from the original #27 commit: testing the pattern against the
source's children. That ignores a whole directory when any child matches,
so "**/*.swp" would drop the folder holding a .swp file, which
test_recursive_ignore_keeps_parent_and_nonmatching_files forbids. Master's
parent-and-root glob already covers matching the source itself, and
has_ignored_descendants handles pruning. Blank lines (#3) are already fixed
on master by filtering empty patterns. Relates to #27.
@arecarn
arecarn merged commit 2322a1d into master Nov 7, 2026
15 checks passed
@arecarn
arecarn deleted the windows-support-rebased branch November 7, 2026 04:30
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.

1 participant