Skip to content

Fix: absolute glob-paths silently match no files in publish command - #83

Merged
Malcolmnixon merged 8 commits into
mainfrom
copilot/fix-absolute-glob-paths
May 8, 2026
Merged

Malcolmnixon merged 8 commits into
mainfrom
copilot/fix-absolute-glob-paths

Conversation

Copilot AI commented May 8, 2026 •

Copy link
Copy Markdown
Contributor

Microsoft.Extensions.FileSystemGlobbing.Matcher.AddInclude() only accepts relative patterns — passing absolute paths (common in CI where env vars expand to absolute paths) caused silent zero-match results.

Description

The fix introduces a new Utilities subsystem with two units — GlobMatcher and PathHelpers — that encapsulate general-purpose file-system helpers. Program.cs delegates to GlobMatcher.FindMatchingFiles() for the publish command. PathHelpers has been moved from the SelfTest subsystem into Utilities as it is a general-purpose utility.

GlobMatcher unit (src/DemaConsulting.VersionMark/Utilities/GlobMatcher.cs)

  • FindMatchingFiles — detects absolute patterns via Path.IsPathRooted(); routes them through a per-pattern matcher rooted at the correct directory, while all relative patterns continue to be matched against cwd as before. Results are deduplicated via a HashSet (using a file-system-appropriate comparer) before the final sorted return.
  • SplitAbsolutePattern (internal helper) — locates the last directory separator before the first wildcard (*, ?, [) to split an absolute pattern into a root directory and a relative glob pattern; handles edge cases:
    • No wildcards → Path.GetDirectoryName / Path.GetFileName
    • Forward-slash root patterns like /*.json → root is the platform path root (/ on Unix, \ on Windows)
    • Windows drive-root patterns like C:\*.json → root normalised to C:\ (not C:)

PathHelpers unit (src/DemaConsulting.VersionMark/Utilities/PathHelpers.cs)

Moved from SelfTest to Utilities. No logic changes — only the namespace (DemaConsulting.VersionMark.Utilities) and documentation updated to reflect its new general-purpose home.

Tests

  • test/.../Utilities/GlobMatcherTests.cs — 10 unit tests covering: relative pattern matching, absolute path pattern matching, single-file absolute path (no wildcard), mixed absolute and relative patterns, empty pattern array, no-match patterns, forward-slash root pattern (/*.json, all platforms asserting the platform-specific root), and Windows drive-root pattern (C:\*.json, Windows only).
  • test/.../Utilities/PathHelpersTests.cs — moved from SelfTest/; all 10 existing tests unchanged.
  • Publishing_Run_WithAbsoluteGlobPattern_ReadsMatchingFiles — sets cwd to a different directory to confirm matching is independent of cwd when an absolute pattern is supplied.

Documentation and traceability

  • Requirements: docs/reqstream/version-mark/utilities.yaml (+ VersionMark-Utilities-SafePath) + docs/reqstream/version-mark/utilities/glob-matcher.yaml + docs/reqstream/version-mark/utilities/path-helpers.yaml
  • Design docs: docs/design/version-mark/utilities.md + docs/design/version-mark/utilities/glob-matcher.md + docs/design/version-mark/utilities/path-helpers.md; docs/design/definition.yaml updated to include Utilities chapters and remove stale self-test/path-helpers.md entry
  • Verification docs: docs/verification/version-mark/utilities.md + docs/verification/version-mark/utilities/glob-matcher.md + docs/verification/version-mark/utilities/path-helpers.md; docs/verification/definition.yaml updated similarly
  • Updated requirements.yaml, docs/design/introduction.md, docs/design/version-mark.md, docs/design/version-mark/self-test.md, docs/verification/version-mark.md, docs/verification/version-mark/self-test.md, and .reviewmark.yaml

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code quality improvement

Related Issues

Pre-Submission Checklist

Before submitting this pull request, ensure you have completed the following:

Build and Test

  • Code builds successfully and all tests pass: pwsh ./build.ps1
  • Code produces zero warnings

Code Quality

  • New code has appropriate XML documentation comments
  • Static analyzer warnings have been addressed

Quality Checks

Please run the following checks before submitting:

  • All linters pass: pwsh ./lint.ps1

Testing

  • Added unit tests for new functionality
  • Updated existing tests if behavior changed
  • All tests follow the AAA (Arrange, Act, Assert) pattern
  • Test coverage is maintained or improved

Documentation

  • Updated README.md (if applicable)
  • Updated docs/ documentation (if applicable)
  • Added code examples for new features (if applicable)
  • Updated requirements.yaml (if applicable)

Additional Notes

Relative patterns are unchanged in behaviour. Mixed invocations (some absolute, some relative) are handled correctly. The GlobMatcher and PathHelpers units are self-contained within the Utilities subsystem and have no dependency on Program or Context, making them independently testable.

The /*.json forward-slash root pattern test (GlobMatcher_SplitAbsolutePattern_ForwardSlashRootPattern_SplitsToRootAndRelative) runs on all platforms without a skip guard. On Unix, Path.GetPathRoot("/*.json") returns "/" and on Windows it returns "\" (rooted to the current drive's root); the test asserts the platform-specific expected value, covering the empty-rootDir fallback branch on both. The only platform-conditional test is the Windows drive-letter case (C:\*.json), which is genuinely not applicable on Unix.

FindMatchingFiles selects the deduplication HashSet comparer based on file-system case-sensitivity: OrdinalIgnoreCase on Windows (case-insensitive file system) and Ordinal on Linux/macOS (case-sensitive file system), ensuring distinct files like a.json and A.json are never incorrectly merged on case-sensitive platforms.

Copilot AI linked an issue May 8, 2026 that may be closed by this pull request
3 tasks
Copilot AI and others added 2 commits May 8, 2026 17:39
Refactored FindMatchingFiles to detect absolute path patterns via
Path.IsPathRooted() and split them into a root directory plus a
relative pattern (SplitAbsolutePattern). The Matcher now executes
against the correct root directory instead of silently finding nothing.

Also added test Publishing_Run_WithAbsoluteGlobPattern_ReadsMatchingFiles
which verifies that absolute patterns work independently of the cwd.

Agent-Logs-Url: https://github.com/demaconsulting/VersionMark/sessions/75c92412-4558-4f71-b78b-611acc0f2b57

Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix absolute glob-paths not working in CI builds Fix: absolute glob-paths silently match no files in publish command May 8, 2026
Copilot AI requested a review from Malcolmnixon May 8, 2026 17:43
@Malcolmnixon

Copy link
Copy Markdown
Member

@copilot That pattern-matching is a lot to bolt into Program.cs - I think it warrants being another software-unit possibly under a Utilities subsystem. Please move the glob-matching logic there and remember to:

  • Add new requirements for the subsystem and unit
  • Add design documentation for the subsystem and unit
  • Add verification documentation for the subsystem and unit
  • Add tests for the subsystem and unit (including absolute and relative patterns, and single-file)

- Add Utilities subsystem with GlobMatcher unit
  (src/DemaConsulting.VersionMark/Utilities/GlobMatcher.cs)
- Update Program.cs to delegate to GlobMatcher.FindMatchingFiles
- Add 8 unit tests in test/.../Utilities/GlobMatcherTests.cs
- Add requirements: docs/reqstream/version-mark/utilities.yaml
  and docs/reqstream/version-mark/utilities/glob-matcher.yaml
- Add design docs: docs/design/version-mark/utilities.md
  and docs/design/version-mark/utilities/glob-matcher.md
- Add verification docs: docs/verification/version-mark/utilities.md
  and docs/verification/version-mark/utilities/glob-matcher.md
- Update requirements.yaml with new includes
- Update docs/design/introduction.md software structure tree
- Update docs/design/version-mark.md publish mode interaction
- Update docs/verification/version-mark.md subsystems list
- Add VersionMark-Utilities review sets to .reviewmark.yaml

Agent-Logs-Url: https://github.com/demaconsulting/VersionMark/sessions/201cbd9d-683b-48b4-a394-9ce761661f2c

Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com>

Copilot AI commented May 8, 2026

Copy link
Copy Markdown
Contributor Author

@copilot That pattern-matching is a lot to bolt into Program.cs - I think it warrants being another software-unit possibly under a Utilities subsystem. Please move the glob-matching logic there and re...

Done in commit d20c0f3. The glob-matching logic has been moved to a new Utilities subsystem with a GlobMatcher unit (src/DemaConsulting.VersionMark/Utilities/GlobMatcher.cs). Program.cs now delegates to GlobMatcher.FindMatchingFiles.

Added alongside:

  • 8 unit tests in test/.../Utilities/GlobMatcherTests.cs (relative, absolute, single-file, mixed, empty, no-match)
  • Requirements: docs/reqstream/version-mark/utilities.yaml + docs/reqstream/version-mark/utilities/glob-matcher.yaml
  • Design docs: docs/design/version-mark/utilities.md + docs/design/version-mark/utilities/glob-matcher.md
  • Verification docs: docs/verification/version-mark/utilities.md + docs/verification/version-mark/utilities/glob-matcher.md
  • Updated requirements.yaml, docs/design/introduction.md, docs/design/version-mark.md, docs/verification/version-mark.md, and .reviewmark.yaml

@Malcolmnixon

Copy link
Copy Markdown
Member

@copilot now that there's a Utilities subsystem, should PathHelpers be moved to it?

- Move src/.../SelfTest/PathHelpers.cs → src/.../Utilities/PathHelpers.cs
  (namespace: DemaConsulting.VersionMark.Utilities)
- Move test/.../SelfTest/PathHelpersTests.cs → test/.../Utilities/PathHelpersTests.cs
- Update Validation.cs and 5 test files to use new namespace
- Move docs/reqstream/version-mark/self-test/path-helpers.yaml
  → docs/reqstream/version-mark/utilities/path-helpers.yaml
- Add VersionMark-Utilities-SafePath requirement to utilities.yaml
- Update requirements.yaml include path
- Update design/self-test.md, design/utilities.md, design/path-helpers.md
- Update verification/self-test.md, verification/utilities.md, verification/path-helpers.md
- Update introduction.md (tree, folder layout, scope)
- Update .reviewmark.yaml (new VersionMark-Utilities-PathHelpers set)

Agent-Logs-Url: https://github.com/demaconsulting/VersionMark/sessions/136dcc6b-0dbe-4bbd-a8bd-9ce44abfa395

Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com>

Copilot AI commented May 8, 2026

Copy link
Copy Markdown
Contributor Author

@copilot now that there's a Utilities subsystem, should PathHelpers be moved to it?

Yes — done in commit ede720a. PathHelpers is now part of the Utilities subsystem alongside GlobMatcher. Updated: source file (new namespace), Validation.cs and 5 test files (using statement), requirements/design/verification docs, introduction.md, and .reviewmark.yaml.

@Malcolmnixon
Malcolmnixon marked this pull request as ready for review May 8, 2026 18:52
Copilot AI review requested due to automatic review settings May 8, 2026 18:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes publish glob resolution so absolute glob patterns (common in CI) correctly match files by introducing a Utilities subsystem and routing publish-time matching through it.

Changes:

  • Added Utilities.GlobMatcher to support matching both relative and absolute glob patterns (with dedup + stable sort).
  • Moved PathHelpers from SelfTest into Utilities and updated references across source/tests/docs.
  • Added/updated tests and requirements/design/verification documentation to cover the new Utilities subsystem.

Reviewed changes

Copilot reviewed 26 out of 27 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
test/DemaConsulting.VersionMark.Tests/Utilities/PathHelpersTests.cs Updates PathHelpers tests to the new Utilities namespace.
test/DemaConsulting.VersionMark.Tests/Utilities/GlobMatcherTests.cs Adds unit tests for absolute/relative glob matching and pattern splitting.
test/DemaConsulting.VersionMark.Tests/SelfTest/SelfTestTests.cs Updates imports to use Utilities.PathHelpers.
test/DemaConsulting.VersionMark.Tests/Publishing/PublishingTests.cs Adds integration test ensuring publish works with an absolute glob pattern independent of cwd.
test/DemaConsulting.VersionMark.Tests/ProgramTests.cs Updates import to use Utilities.PathHelpers.
test/DemaConsulting.VersionMark.Tests/IntegrationTests.cs Updates import to use Utilities.PathHelpers.
test/DemaConsulting.VersionMark.Tests/Capture/CaptureTests.cs Updates import to use Utilities.PathHelpers.
src/DemaConsulting.VersionMark/Utilities/PathHelpers.cs Moves PathHelpers into Utilities namespace (no logic change).
src/DemaConsulting.VersionMark/Utilities/GlobMatcher.cs Implements absolute+relative glob matching with SplitAbsolutePattern helper.
src/DemaConsulting.VersionMark/SelfTest/Validation.cs Updates to use Utilities.PathHelpers.
src/DemaConsulting.VersionMark/Program.cs Replaces inline matcher logic with GlobMatcher.FindMatchingFiles.
requirements.yaml Adds Utilities requirements includes and removes old SelfTest PathHelpers include.
docs/verification/version-mark/utilities/path-helpers.md Updates test location reference after moving tests.
docs/verification/version-mark/utilities/glob-matcher.md Adds Utilities GlobMatcher verification chapter.
docs/verification/version-mark/utilities.md Adds Utilities subsystem verification chapter.
docs/verification/version-mark/self-test.md Updates SelfTest verification scope now that PathHelpers moved out.
docs/verification/version-mark.md Updates subsystem list to include Utilities.
docs/reqstream/version-mark/utilities/path-helpers.yaml Adds PathHelpers unit requirements under Utilities.
docs/reqstream/version-mark/utilities/glob-matcher.yaml Adds GlobMatcher unit requirements.
docs/reqstream/version-mark/utilities.yaml Adds Utilities subsystem requirements linking to GlobMatcher/PathHelpers.
docs/design/version-mark/utilities/path-helpers.md Adds Utilities PathHelpers design chapter.
docs/design/version-mark/utilities/glob-matcher.md Adds Utilities GlobMatcher design chapter.
docs/design/version-mark/utilities.md Adds Utilities subsystem design chapter.
docs/design/version-mark/self-test.md Updates SelfTest design scope now that PathHelpers moved out.
docs/design/version-mark.md Updates publish-mode flow to include Utilities glob resolution step.
docs/design/introduction.md Updates system overview to include Utilities subsystem.
.reviewmark.yaml Replaces SelfTest PathHelpers review with Utilities subsystem/unit reviews.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/design/version-mark/utilities.md
Comment thread src/DemaConsulting.VersionMark/Utilities/GlobMatcher.cs
Comment thread src/DemaConsulting.VersionMark/Utilities/GlobMatcher.cs
Comment thread docs/verification/version-mark.md Outdated
Comment thread test/DemaConsulting.VersionMark.Tests/SelfTest/SelfTestTests.cs
@Malcolmnixon

Copy link
Copy Markdown
Member

@copilot apply changes based on the comments in this thread

…s, doc fixes

- Update docs/design/definition.yaml: remove stale self-test/path-helpers.md,
  add Utilities chapters (utilities.md, glob-matcher.md, path-helpers.md)
- Update docs/verification/definition.yaml: same
- Add GlobMatcher_SplitAbsolutePattern_UnixRootPattern test (non-Windows)
- Add GlobMatcher_SplitAbsolutePattern_WindowsDriveRootPattern test (Windows-only)
- Update docs/verification/version-mark.md Utilities bullet (GlobMatcher + PathHelpers)
- Fix SelfTestTests.cs class summary (remove stale PathHelpers mention)
- Update verification docs with new test entries

Agent-Logs-Url: https://github.com/demaconsulting/VersionMark/sessions/0780fb23-d543-4557-8c6a-9f63992255a0

Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com>

Copilot AI commented May 8, 2026

Copy link
Copy Markdown
Contributor Author

@copilot apply changes based on the comments in this thread

Done in commit 1227063. Applied all review comments:

  • docs/design/definition.yaml and docs/verification/definition.yaml: removed stale self-test/path-helpers.md entries; added the three Utilities chapters (utilities.md, utilities/glob-matcher.md, utilities/path-helpers.md)
  • GlobMatcherTests.cs: added GlobMatcher_SplitAbsolutePattern_UnixRootPattern_SplitsToRootAndRelative (skips on Windows) and GlobMatcher_SplitAbsolutePattern_WindowsDriveRootPattern_SplitsToDriveRootAndRelative (skips on non-Windows)
  • docs/verification/version-mark.md: updated the Utilities bullet to mention both GlobMatcher and PathHelpers
  • SelfTestTests.cs: removed "and PathHelpers working together" from the class summary

@Malcolmnixon

Copy link
Copy Markdown
Member

@copilot Is it appropriate to skip the unix pattern on windows? Is there still a test that uses the absolute (via slash or backslash but without drive letter) pattern on windows?

… equivalent

/*.json is rooted on both Unix and Windows (.NET returns "/" as the path
root on both platforms), so no skip is needed. Rename the test method from
UnixRootPattern to ForwardSlashRootPattern to make the intent clear. Update
verification docs to match.

Agent-Logs-Url: https://github.com/demaconsulting/VersionMark/sessions/53701e75-7c7d-4d89-a994-c535fecdc7fc

Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot May 8, 2026 19:35

Copilot AI commented May 8, 2026

Copy link
Copy Markdown
Contributor Author

@copilot Is it appropriate to skip the unix pattern on windows? Is there still a test that uses the absolute (via slash or backslash but without drive letter) pattern on windows?

The skip was not appropriate — it was unnecessary. In .NET, Path.GetPathRoot("/*.json") returns "/" on both Unix and Windows (Windows treats a leading / as rooted to the current drive), and SplitAbsolutePattern produces ("/", "*.json") on both platforms via the same empty-rootDir fallback path. So the test covers the "absolute via slash without drive letter" case on Windows too.

Fixed in commit d894e84: removed the Assert.SkipUnless(!IsWindows()) and renamed the test method from _UnixRootPattern_ to _ForwardSlashRootPattern_ to make it clear the test runs everywhere. The only remaining platform-conditional test is the C:\*.json drive-letter case which genuinely cannot be parsed by Unix.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 28 out of 29 changed files in this pull request and generated 1 comment.

Comment thread src/DemaConsulting.VersionMark/Utilities/GlobMatcher.cs
@Malcolmnixon

Copy link
Copy Markdown
Member

@copilot It fails on windows with:

Failed DemaConsulting.VersionMark.Tests.Utilities.GlobMatcherTests.GlobMatcher_SplitAbsolutePattern_ForwardSlashRootPattern_SplitsToRootAndRelative [2 ms]
  Error Message:
   Assert.Equal() Failure: Strings differ
           ↓ (pos 0)
Expected: "/"
Actual:   "\\"
           ↑ (pos 0)

…t; use fs-aware comparer

On Windows, Path.GetPathRoot("/*.json") returns "\" (backslash), not "/".
Fix the ForwardSlashRootPattern test to assert the platform-specific expected
root so it passes on both Unix and Windows.

Also fix FindMatchingFiles to use a file-system-appropriate comparer for
the dedup HashSet and sort: OrdinalIgnoreCase on Windows (case-insensitive
FS), Ordinal on Linux/macOS (case-sensitive FS). This prevents incorrectly
deduplicating distinct files like a.json and A.json on Linux.

Update verification docs to document the platform-specific root behaviour.

Agent-Logs-Url: https://github.com/demaconsulting/VersionMark/sessions/c33a127e-f75e-4364-93c4-034a38823555

Co-authored-by: Malcolmnixon <1863707+Malcolmnixon@users.noreply.github.com>

Copilot AI commented May 8, 2026

Copy link
Copy Markdown
Contributor Author

@copilot It fails on windows with:

...

Fixed in commit 1b51ebc. Two changes:

  1. Test: Path.GetPathRoot("/*.json") returns "\" on Windows and "/" on Unix. The test now asserts OperatingSystem.IsWindows() ? @"\" : "/" so it passes correctly on both platforms — the empty-rootDir fallback branch of SplitAbsolutePattern is still exercised everywhere.

  2. Production code: FindMatchingFiles now selects the dedup HashSet comparer based on file-system case-sensitivity — OrdinalIgnoreCase on Windows, Ordinal on Linux/macOS — so distinct files like a.json and A.json are no longer incorrectly collapsed on case-sensitive file systems.

@Malcolmnixon
Malcolmnixon merged commit fd79b7c into main May 8, 2026
15 checks passed
@Malcolmnixon
Malcolmnixon deleted the copilot/fix-absolute-glob-paths branch May 8, 2026 21:26
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.

[Bug]: Absolute glob-paths don't work

3 participants