Skip to content

fix(origin): accept drive-letter file URIs and run the suite on Windows - #5

Merged
HuiJun merged 6 commits into
developfrom
fix/windows
Oct 30, 2026
Merged

HuiJun merged 6 commits into
developfrom
fix/windows

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Running the Go gate on windows-latest (first done on #4) showed the suite had never run on Windows; this makes it pass there and adds Windows to the CI gate so it stays that way. #4 (release line) ships a Windows binary and depends on this.

  • internal/origin: url.Parse("file://C:/Users/…") makes C: the host, so every repository on a Windows machine was refused with "a repository on this machine is git+file:///absolute/path". The file transport now accepts git+file:///C:/path (RFC 8089) and the git+file://C:/path spelling, stores Path as C:/path, and the local-path errors name the form for this machine (FileURIPath):
    // before: u.Host == "C:" → refused
    // after:  rest "C:/x" → "/C:/x" → url.Parse → Path "/C:/x" → o.Path "C:/x", Source `C:\x` (svn: file:///C:/x)
    The same drive handling applies to plain file:// resources (.kpar, project directories).
  • tests/e2e: the harness builds syspi.exe on Windows (exec refused the suffix-less file).
  • ci.yml: windows-latest joins the gate matrix with hg and svn from chocolatey (hg, sliksvn) on GITHUB_PATH, so SYSPI_REQUIRE_VCS=1 holds on all three systems; gate steps run under bash on every runner.
  • .gitattributes pins LF: a Windows checkout turned every file CRLF and gofmt -l . listed the whole tree.

The Windows-only expectations in origin_test.go are derived from filepath.Abs, so the test file is one source for all three systems.

Verified: full gate on Linux (go build, go vet, gofmt -l, go test ./..., SYSPI_REQUIRE_VCS=1) green; the Windows job on this PR is the first real Windows run, so further runner-specific findings may follow in this PR.


Devin Review

devin-ai-integration Bot and others added 2 commits October 30, 2026 01:05
url.Parse made C: the host of git+file://C:/path, so every repository on a Windows
machine was refused; the file transport now takes file:///C:/path (RFC 8089) and the
file://C:/path spelling, and the local-path errors name the form for this machine.
The e2e harness builds syspi.exe on Windows. CI adds windows-latest to the gate with
hg and svn from chocolatey, and .gitattributes keeps LF so gofmt holds there.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

devin-ai-integration Bot and others added 4 commits October 30, 2026 01:12
…s-safe

A Windows git with core.autocrlf=true rewrote every line ending in the checkout,
so the tree digest and the .meta.json checksums no longer matched the other VCSs;
syspi now clones with core.autocrlf=false. svn file URLs carry the drive as
file:///C:/path. Test fixtures: svn URLs through fileURL, no ':' in directory
names, the credential helper path in slash form, the lock-source rewrite escapes
backslashes once, and the read-only-directory rollback test skips on Windows.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…ead of prompting

On Windows hg takes the NUL stdin for a terminal, prompts for the user name and
dies with "response expected", which hid the authorization hint. The svn netrc
test derives the repository path from the fixture root rather than the file URL.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
SlikSvn 1.14 on Windows takes a bare LF as part of the password (CRLF and no
newline both work); svn on Linux and macOS reads to EOF, so the newline goes.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration
devin-ai-integration Bot marked this pull request as ready for review October 30, 2026 01:46
@HuiJun
HuiJun merged commit a873e5a into develop Oct 30, 2026
4 checks passed

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment thread internal/origin/origin.go
Comment on lines +422 to +423
if drive.MatchString(rest[1:]) {
return rest[1:], true

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Absolute local paths become relative

On POSIX, file:///C:/tools.kpar becomes C:/tools.kpar in fileURIPath. The archive loader resolves it under the project instead of /C:/tools.kpar, so an existing archive fails to install.

Learn more

A file URI with a first directory named C: is a valid absolute path on POSIX. The new drive check removes its leading slash without checking the host OS. resolveLocalArchive then joins the resulting relative path with the consuming project's root; parseVCSURL also removes the slash for repositories.

Example: On Linux, a real archive at /C:/tools.kpar is referenced as file:///C:/tools.kpar. Parsing yields C:/tools.kpar, so install looks for <project>/C:/tools.kpar and fails.

Recommended fix: Apply the drive normalization only on Windows in both fileURIPath and parseVCSURL; leave POSIX paths starting /C:/ absolute. Add a POSIX regression test for both local files and VCS sources.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed: on POSIX a directory named C: is legal and fileURIPath dropped its leading slash. #6 makes the drive check Windows-only (isDrivePath = windowsPaths && drive.MatchString) and adds TestDriveLetterFileURIs, which exercises both machines from one test file (file:///C:/tools.kpar → /C:/tools.kpar on POSIX, file://C:/x refused there).

Comment on lines +553 to +555
if runtime.GOOS == "windows" {
t.Skip("a read-only directory does not refuse new files on Windows")
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Windows rollback loses its test

On Windows, TestPublishRollsBackWhenTheLockCannotBeWritten skips instead of testing rollback. The Windows gate cannot catch a failed lock write leaving lib/ inconsistent with the lock.

Learn more

The rollback test simulates a lock write failure by changing directory permissions. Windows does not enforce that permission change in the same way, so the new skip removes rollback coverage precisely on the newly gated platform. Run publishes packages before writing the lock and invokes rollback if writing fails.

Example: On Windows, a regression in lock-write failure handling can leave lib/ containing version 1.1.0 while the on-disk lock still names 1.0.0; the CI test exits as skipped.

Recommended fix: Replace the permissions-based failure setup on Windows with a deterministic lock-write failure that still lets lib/ be updated, then assert the old lock and installed tree remain intact. Retain the existing POSIX setup if needed.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. #6 removes the skip: Windows ignores a read-only directory but refuses to replace a read-only file, so there the test makes .project.lock.json itself 0444; lock.Write's final os.Rename then fails after lib/ was published and the same rollback path is asserted (message "Access is denied" instead of "permission denied"). The Windows gate on #6 is the proof.

Comment thread internal/origin/origin.go
Comment on lines +304 to +308
if transport == "file" {
// file:///C:/path is the RFC 8089 form; file://C:/path is accepted as input.
if drive.MatchString(rest) {
rest = "/" + rest
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Windows file URI contract remains undocumented

file://C:/path becomes accepted input, but the documented resource grammar lacks the Windows forms. CONTRIBUTING.md requires documentation updates when a format or contract changes.

Learn more

Example: A Windows user reading the resource grammar finds <vcs>+file:///… but no explanation of the drive-letter spelling accepted by the parser.

Recommended fix: Document the accepted Windows file:///C:/path and file://C:/path forms and their canonical identity in README.md or docs/packages.md, including plain local file:// resources.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in #6: docs/packages.md and the README now name <vcs>+file:///C:/path as the canonical Windows form, <vcs>+file://C:/path as accepted input normalised to it, the same two forms for a local .kpar or project directory, and that /C:/path is an ordinary directory on other systems.

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