Skip to content

Workaround libgit2 safe.directory case mismatch bug#1899

Open
tyrielv wants to merge 2 commits intomicrosoft:masterfrom
tyrielv:tyrielv/safedirectory-caseinsensitive
Open

Workaround libgit2 safe.directory case mismatch bug#1899
tyrielv wants to merge 2 commits intomicrosoft:masterfrom
tyrielv:tyrielv/safedirectory-caseinsensitive

Conversation

@tyrielv
Copy link
Contributor

@tyrielv tyrielv commented Jan 30, 2026

Libgit2 has a bug where it is case sensitive for safe.directory (especially the drive letter) when git.exe isn't. Until a fix can be made and propagated, work around it by matching the repo path requested to the configured safe directory.

See libgit2/libgit2#7037

@tyrielv tyrielv force-pushed the tyrielv/safedirectory-caseinsensitive branch from 9f64938 to ba58df0 Compare January 30, 2026 17:41
@tyrielv tyrielv marked this pull request as draft January 30, 2026 18:01
@tyrielv tyrielv force-pushed the tyrielv/safedirectory-caseinsensitive branch from 61345a8 to a5e262e Compare January 30, 2026 18:30
@tyrielv tyrielv marked this pull request as ready for review February 11, 2026 19:35
using System.Security.AccessControl;
using System.Security.Principal;

namespace GVFS.FunctionalTests.Tests.EnlistmentPerTestCase
Copy link
Member

Choose a reason for hiding this comment

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

That's a full-blown integration test, and it is heavy: it P/Invokes AdjustTokenPrivileges to enable SeRestorePrivilege, changes actual directory ownership to BUILTIN\Users, manipulates global git config, and cleans it all up. That requires elevation and is fragile in CI, as well as hard to reproduce locally in case of CI failures.

A lighter approach for CI would be to split into two layers:

  • Add unit tests specifically for NormalizePath(): C:\Repos\Foo matches c:/repos/foo, trailing slashes are trimmed, mixed separators work, empty/null is safe. This is pure string manipulation, no native calls, runs anywhere.

  • Unit test the control flow with a mock. LibGit2Repo already has a protected LibGit2Repo() constructor and virtual methods for mocking. The PR could make CheckSafeDirectoryConfigForCaseSensitivityIssue (or at least the config-reading part) virtual, so a test subclass can feed fake safe.directory entries without touching global git config or real libgit2. Then you test: "if Open() fails with the ownership message and config contains a case-variant match, retry with corrected casing" vs. "if config has no match, throw as before".

Then you could drop entirely the directory-ownership manipulation. The actual libgit2 bug is well-established (issue libgit2/libgit2#7037). The question VFS for Git needs to test is not "does libgit2 have this bug?" but "does our workaround correctly detect the situation and retry with the right path?" That can be tested without triggering the real ownership mismatch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've refactored to allow the needed mocks and converted the functional tests to unit tests. Thanks for the feedback!

Replace the heavyweight SafeDirectoryOwnershipTests functional test
(which required elevation, P/Invoke for SeRestorePrivilege, directory
ownership changes, and global git config manipulation) with two layers
of lightweight unit tests:

Layer 1: NormalizePathForSafeDirectoryComparison - pure string tests
covering backslash/forward-slash conversion, case normalization,
trailing slash trimming, and null/empty safety.

Layer 2: Constructor control-flow tests using a mock subclass that
overrides the native calls (InitNative, TryOpenRepo, GetLastNativeError,
GetSafeDirectoryConfigEntries) to verify the safe.directory case-
sensitivity workaround logic without touching libgit2 or real config.

To support testability, extract virtual methods from LibGit2Repo for
native interactions and make CheckSafeDirectoryConfigForCaseSensitivity-
Issue protected. The NormalizePath helper is renamed to
NormalizePathForSafeDirectoryComparison and scoped as internal.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

2 participants