From 42ce0749c47780a09ed25e9eda94d53c35d3a88a Mon Sep 17 00:00:00 2001 From: RussKie Date: Sun, 23 Apr 2023 00:30:31 +1000 Subject: [PATCH] Intercept and convert \\wsl.localhost\ to \\wsl$\ paths Resolves #10025 Resolves #10815 --- GitCommands/Git/GitModule.cs | 2 +- GitCommands/PathUtil.cs | 40 ++++++++++++++----- GitExtensions/Program.cs | 10 ++++- .../GitCommands.Tests/Helpers/PathUtilTest.cs | 36 ++++++++++++----- 4 files changed, 66 insertions(+), 22 deletions(-) diff --git a/GitCommands/Git/GitModule.cs b/GitCommands/Git/GitModule.cs index fdbb64cc372..f0eea67d689 100644 --- a/GitCommands/Git/GitModule.cs +++ b/GitCommands/Git/GitModule.cs @@ -67,7 +67,7 @@ public sealed class GitModule : IGitModule public GitModule(string? workingDir) { - WorkingDir = (workingDir ?? "").NormalizePath().EnsureTrailingPathSeparator(); + WorkingDir = (workingDir ?? "").NormalizePath().NormalizeWslPath().EnsureTrailingPathSeparator(); WorkingDirGitDir = GitDirectoryResolverInstance.Resolve(WorkingDir); _indexLockManager = new IndexLockManager(this); _commitDataManager = new CommitDataManager(() => this); diff --git a/GitCommands/PathUtil.cs b/GitCommands/PathUtil.cs index 4836136e61a..123f8376a31 100644 --- a/GitCommands/PathUtil.cs +++ b/GitCommands/PathUtil.cs @@ -11,6 +11,7 @@ public static class PathUtil // Windows build 21354 supports wsl.localhost too, not supported for WSL Git private const string WslPrefix = @"\\wsl$\"; + private const string WslLocalhostPrefix = @"\\wsl.localhost\"; public static readonly char PosixDirectorySeparatorChar = '/'; public static readonly char NativeDirectorySeparatorChar = Path.DirectorySeparatorChar; @@ -146,6 +147,23 @@ public static string NormalizePath(this string path) } } + /// + /// Replaces \\wsl.localhost\ with \\wsl$\, if found. Else returns the untouched. + /// + /// The path to normalize. + /// The WSL normalized path. + public static string NormalizeWslPath(this string path) + { + // NOTE: path is expected to be already normalized! + + if (!IsWslLocalhostPrefixPath(path)) + { + return path; + } + + return path.Replace(WslLocalhostPrefix, WslPrefix, StringComparison.OrdinalIgnoreCase); + } + public static string Resolve(string path, string relativePath = "") { if (string.IsNullOrWhiteSpace(path)) @@ -198,23 +216,26 @@ private static string ResolveRelativePath(string path, string relativePath) /// Check if the path is any known path for WSL that may require special handling /// /// Path to check - /// true if a path is a known WSL path - public static bool IsWslPath(string path) + /// if a path is a known WSL path; otherwise, . + public static bool IsWslPath(string? path) { return !string.IsNullOrWhiteSpace(path) - && (IsWslPrefixPath(path) - || path.ToLower().StartsWith(@"\\wsl.localhost\")); + && (IsWslPrefixPath(path) || IsWslLocalhostPrefixPath(path)); } + /// + /// Check if the path starts with '\\wsl.localhost\'. + /// + /// Path to check + /// if the path starts with '\\wsl.localhost\'; otherwise, . + private static bool IsWslLocalhostPrefixPath(string path) => path.StartsWith(WslLocalhostPrefix, StringComparison.OrdinalIgnoreCase); + /// /// Check if the path is has handled specially for WSL /// /// Path to check - /// true if the path is a WSL path with internal handling - private static bool IsWslPrefixPath(string path) - { - return path.ToLower().StartsWith(WslPrefix); - } + /// if the path is a WSL path with internal handling; otherwise, . + private static bool IsWslPrefixPath(string path) => path.StartsWith(WslPrefix, StringComparison.OrdinalIgnoreCase); /// /// Get the name of the distribution (like "Ubuntu-20.04") for WSL2 paths. @@ -536,6 +557,7 @@ public static bool TryDeleteDirectory(this string? path, [NotNullWhen(returnValu internal readonly struct TestAccessor { + public static bool IsWslLocalhostPrefixPath(string path) => PathUtil.IsWslLocalhostPrefixPath(path); public static bool IsWslPrefixPath(string path) => PathUtil.IsWslPrefixPath(path); } } diff --git a/GitExtensions/Program.cs b/GitExtensions/Program.cs index f57291df406..4388d3a6f75 100644 --- a/GitExtensions/Program.cs +++ b/GitExtensions/Program.cs @@ -34,12 +34,18 @@ private static void Main() Application.SetCompatibleTextRenderingDefault(false); Application.SetHighDpiMode(HighDpiMode.SystemAware); + bool checkForIllegalCrossThreadCalls = false; +#if DEBUG + checkForIllegalCrossThreadCalls = true; +#endif if (ThisAssembly.Git.IsDirty) { - // In non official builds force to fail for cross-thread operations so we can fix those. - Control.CheckForIllegalCrossThreadCalls = true; + checkForIllegalCrossThreadCalls = true; } + // In non official builds force to fail for cross-thread operations so we can fix those. + Control.CheckForIllegalCrossThreadCalls = checkForIllegalCrossThreadCalls; + // If an error happens before we had a chance to init the environment information // the call to GetInformation() from BugReporter.ShowNBug() will fail. // There's no perf hit calling Initialise() multiple times. diff --git a/UnitTests/GitCommands.Tests/Helpers/PathUtilTest.cs b/UnitTests/GitCommands.Tests/Helpers/PathUtilTest.cs index 9dbd4525070..b159a4e48ac 100644 --- a/UnitTests/GitCommands.Tests/Helpers/PathUtilTest.cs +++ b/UnitTests/GitCommands.Tests/Helpers/PathUtilTest.cs @@ -177,16 +177,10 @@ public void GetRepositoryNameTest() } [Platform(Include = "Win")] - [TestCase(null)] - [TestCase("")] - [TestCase(" ")] - [TestCase("c:")] - public void NormalizePath(string path) - { - PathUtil.NormalizePath(path).Should().BeEmpty(); - } - - [Platform(Include = "Win")] + [TestCase(null, "")] + [TestCase("", "")] + [TestCase(" ", "")] + [TestCase("c:", "")] [TestCase("C:\\", "C:\\")] [TestCase("a:\\folder\\filename.txt", "a:\\folder\\filename.txt")] [TestCase("a:\\folder\\..\\filename.txt", "a:\\filename.txt")] @@ -206,6 +200,19 @@ public void NormalizePath(string path, string expected) PathUtil.NormalizePath(path).Should().Be(expected); } + [Platform(Include = "Win")] + [TestCase("", "")] + [TestCase(" ", " ")] + [TestCase("c:", "c:")] + [TestCase("C:\\", "C:\\")] + [TestCase(@"\\wsl$\Ubuntu\home\jack\work\", @"\\wsl$\Ubuntu\home\jack\work\")] + [TestCase(@"\\Wsl.LoCALhosT\Ubuntu\home\jack\work\", @"\\wsl$\Ubuntu\home\jack\work\")] + [TestCase(@"\\wsl.localhost\Ubuntu\home\jack\work\", @"\\wsl$\Ubuntu\home\jack\work\")] + public void NormalizeWslPath(string path, string expected) + { + PathUtil.NormalizeWslPath(path).Should().Be(expected); + } + [TestCase(@"C:\WORK\GitExtensions\", @"C:\WORK\GitExtensions\")] [TestCase(@"\\my-pc\Work\GitExtensions\", @"\\my-pc\Work\GitExtensions\")] [TestCase(@"\\wsl$\Ubuntu\home\jack\work\", @"\\wsl$\Ubuntu\home\jack\work\")] @@ -255,6 +262,15 @@ public void ResolveWsl(string input, Type expectedException) Assert.Throws(expectedException, () => PathUtil.ResolveWsl(input)); } + [TestCase(@"C:\work\..\GitExtensions\", false)] + [TestCase(@"\\Wsl$\Ubuntu\work\..\GitExtensions\", false)] + [TestCase(@"\\wsl.localhost\Ubuntu\work\..\GitExtensions\", true)] + [TestCase(@"\\wsl.localhost/Ubuntu\work\..\GitExtensions\", false)] + public void IsWslLocalhostPath(string path, bool expected) + { + PathUtil.TestAccessor.IsWslLocalhostPrefixPath(path).Should().Be(expected); + } + [TestCase(@"\\Wsl$\Ubuntu\work\..\GitExtensions\", true, true)] [TestCase(@"\\wsl$\Ubuntu\work\..\GitExtensions\", true, true)] [TestCase(@"C:\work\..\GitExtensions\", false, false)]