-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
wsl: support calling Git inside of WSL
Add support for calling back to the Git executable in a Windows Subsystem for Linux (WSL).
- Loading branch information
1 parent
98551cd
commit 77ca2d4
Showing
4 changed files
with
234 additions
and
2 deletions.
There are no files selected for viewing
111 changes: 111 additions & 0 deletions
111
src/shared/Microsoft.Git.CredentialManager.Tests/WslUtilsTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
using System; | ||
using System.Diagnostics; | ||
using Xunit; | ||
|
||
namespace Microsoft.Git.CredentialManager.Tests | ||
{ | ||
public class WslUtilsTests | ||
{ | ||
[PlatformTheory(Platforms.Windows)] | ||
[InlineData(null, false)] | ||
[InlineData(@"", false)] | ||
[InlineData(@" ", false)] | ||
[InlineData(@"\", false)] | ||
[InlineData(@"wsl", false)] | ||
[InlineData(@"\wsl\ubuntu\home", false)] | ||
[InlineData(@"\\wsl\ubuntu\home", false)] | ||
[InlineData(@"\wsl$\ubuntu\home", false)] | ||
[InlineData(@"wsl$\ubuntu\home", false)] | ||
[InlineData(@"//wsl$/ubuntu/home", false)] | ||
[InlineData(@"\\wsl", false)] | ||
[InlineData(@"\\wsl$", false)] | ||
[InlineData(@"\\wsl$\", false)] | ||
[InlineData(@"\\wsl$\ubuntu", true)] | ||
[InlineData(@"\\wsl$\ubuntu\", true)] | ||
[InlineData(@"\\wsl$\ubuntu\home", true)] | ||
[InlineData(@"\\WSL$\UBUNTU\home", true)] | ||
[InlineData(@"\\wsl$\ubuntu\home\", true)] | ||
[InlineData(@"\\wsl$\openSUSE-42\home", true)] | ||
public void WslUtils_IsWslPath(string path, bool expected) | ||
{ | ||
bool actual = WslUtils.IsWslPath(path); | ||
Assert.Equal(expected, actual); | ||
} | ||
|
||
[Theory] | ||
[InlineData(@"\\wsl$\ubuntu", "ubuntu", "/")] | ||
[InlineData(@"\\wsl$\ubuntu\", "ubuntu", "/")] | ||
[InlineData(@"\\wsl$\ubuntu\home", "ubuntu", "/home")] | ||
[InlineData(@"\\wsl$\ubuntu\HOME", "ubuntu", "/HOME")] | ||
[InlineData(@"\\wsl$\UBUNTU\home", "UBUNTU", "/home")] | ||
[InlineData(@"\\wsl$\ubuntu\home\", "ubuntu", "/home/")] | ||
[InlineData(@"\\wsl$\openSUSE-42\home", "openSUSE-42", "/home")] | ||
public void WslUtils_ConvertToDistroPath(string path, string expectedDistro, string expectedPath) | ||
{ | ||
string actualPath = WslUtils.ConvertToDistroPath(path, out string actualDistro); | ||
Assert.Equal(expectedPath, actualPath); | ||
Assert.Equal(expectedDistro, actualDistro); | ||
} | ||
|
||
[PlatformTheory(Platforms.Windows)] | ||
[InlineData(null)] | ||
[InlineData(@"")] | ||
[InlineData(@" ")] | ||
[InlineData(@"\")] | ||
[InlineData(@"wsl")] | ||
[InlineData(@"\wsl\ubuntu\home")] | ||
[InlineData(@"\\wsl\ubuntu\home")] | ||
[InlineData(@"\wsl$\ubuntu\home")] | ||
[InlineData(@"wsl$\ubuntu\home")] | ||
[InlineData(@"//wsl$/ubuntu/home")] | ||
[InlineData(@"\\wsl")] | ||
[InlineData(@"\\wsl$")] | ||
[InlineData(@"\\wsl$\")] | ||
public void WslUtils_ConvertToDistroPath_Invalid_ThrowsException(string path) | ||
{ | ||
Assert.Throws<ArgumentException>(() => WslUtils.ConvertToDistroPath(path, out _)); | ||
} | ||
|
||
[PlatformFact(Platforms.Windows)] | ||
public void WslUtils_CreateWslProcess() | ||
{ | ||
const string distribution = "ubuntu"; | ||
const string command = "/usr/lib/git-core/git version"; | ||
|
||
string expectedFileName = WslUtils.GetWslPath(); | ||
string expectedArgs = $"--distribution {distribution} --exec {command}"; | ||
|
||
Process process = WslUtils.CreateWslProcess(distribution, command); | ||
|
||
Assert.NotNull(process); | ||
Assert.Equal(expectedArgs, process.StartInfo.Arguments); | ||
Assert.Equal(expectedFileName, process.StartInfo.FileName); | ||
Assert.True(process.StartInfo.RedirectStandardInput); | ||
Assert.True(process.StartInfo.RedirectStandardOutput); | ||
Assert.True(process.StartInfo.RedirectStandardError); | ||
Assert.False(process.StartInfo.UseShellExecute); | ||
} | ||
|
||
[PlatformFact(Platforms.Windows)] | ||
public void WslUtils_CreateWslProcess_WorkingDirectory() | ||
{ | ||
const string distribution = "ubuntu"; | ||
const string command = "/usr/lib/git-core/git version"; | ||
const string expectedWorkingDirectory = @"C:\Projects\"; | ||
|
||
string expectedFileName = WslUtils.GetWslPath(); | ||
string expectedArgs = $"--distribution {distribution} --exec {command}"; | ||
|
||
Process process = WslUtils.CreateWslProcess(distribution, command, expectedWorkingDirectory); | ||
|
||
Assert.NotNull(process); | ||
Assert.Equal(expectedArgs, process.StartInfo.Arguments); | ||
Assert.Equal(expectedFileName, process.StartInfo.FileName); | ||
Assert.True(process.StartInfo.RedirectStandardInput); | ||
Assert.True(process.StartInfo.RedirectStandardOutput); | ||
Assert.True(process.StartInfo.RedirectStandardError); | ||
Assert.False(process.StartInfo.UseShellExecute); | ||
Assert.Equal(expectedWorkingDirectory, process.StartInfo.WorkingDirectory); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
using System; | ||
using System.Diagnostics; | ||
using System.IO; | ||
using System.Text; | ||
|
||
namespace Microsoft.Git.CredentialManager | ||
{ | ||
public static class WslUtils | ||
{ | ||
private const string WslUncPrefix = @"\\wsl$\"; | ||
private const string WslCommandName = "wsl.exe"; | ||
|
||
/// <summary> | ||
/// Test if a file path points to a location in a Windows Subsystem for Linux distribution. | ||
/// </summary> | ||
/// <param name="path">Path to test.</param> | ||
/// <returns>True if <paramref name="path"/> is a WSL path, false otherwise.</returns> | ||
public static bool IsWslPath(string path) | ||
{ | ||
if (string.IsNullOrWhiteSpace(path)) return false; | ||
|
||
return path.StartsWith(WslUncPrefix, StringComparison.OrdinalIgnoreCase) && | ||
path.Length > WslUncPrefix.Length; | ||
} | ||
|
||
/// <summary> | ||
/// Create a command to be executed in a Windows Subsystem for Linux distribution. | ||
/// </summary> | ||
/// <param name="distribution">WSL distribution name.</param> | ||
/// <param name="command">Command to execute.</param> | ||
/// <param name="workingDirectory">Optional working directory.</param> | ||
/// <returns><see cref="Process"/> object ready to start.</returns> | ||
public static Process CreateWslProcess(string distribution, string command, string workingDirectory = null) | ||
{ | ||
var args = new StringBuilder(); | ||
args.AppendFormat("--distribution {0} ", distribution); | ||
args.AppendFormat("--exec {0}", command); | ||
|
||
string wslExePath = GetWslPath(); | ||
|
||
var psi = new ProcessStartInfo(wslExePath, args.ToString()) | ||
{ | ||
RedirectStandardInput = true, | ||
RedirectStandardOutput = true, | ||
RedirectStandardError = true, | ||
UseShellExecute = false, | ||
WorkingDirectory = workingDirectory ?? string.Empty | ||
}; | ||
|
||
return new Process { StartInfo = psi }; | ||
} | ||
|
||
public static string ConvertToDistroPath(string path, out string distribution) | ||
{ | ||
if (!IsWslPath(path)) throw new ArgumentException("Must provide a WSL path", nameof(path)); | ||
|
||
int distroStart = WslUncPrefix.Length; | ||
int distroEnd = path.IndexOf('\\', distroStart); | ||
|
||
if (distroEnd < 0) distroEnd = path.Length; | ||
|
||
distribution = path.Substring(distroStart, distroEnd - distroStart); | ||
|
||
if (path.Length > distroEnd) | ||
{ | ||
return path.Substring(distroEnd).Replace('\\', '/'); | ||
} | ||
|
||
return "/"; | ||
} | ||
|
||
internal /*for testing purposes*/ static string GetWslPath() | ||
{ | ||
// WSL is only supported on 64-bit operating systems | ||
if (!Environment.Is64BitOperatingSystem) | ||
{ | ||
throw new Exception("WSL is not supported on 32-bit operating systems"); | ||
} | ||
|
||
// | ||
// When running as a 32-bit application on a 64-bit operating system, we cannot access the real | ||
// C:\Windows\System32 directory because the OS will redirect us transparently to the | ||
// C:\Windows\SysWOW64 directory (containing 32-bit executables). | ||
// | ||
// In order to access the real 64-bit System32 directory, we must access via the pseudo directory | ||
// C:\Windows\SysNative that does **not** experience any redirection for 32-bit applications. | ||
// | ||
// HOWEVER, if we're running as a 64-bit application on a 64-bit operating system, the SysNative | ||
// directory does not exist! This means if running as a 32-bit application on a 64-bit OS we must | ||
// use the System32 directory name directly. | ||
// | ||
var sysDir = Environment.ExpandEnvironmentVariables( | ||
Environment.Is64BitProcess | ||
? @"%WINDIR%\System32" | ||
: @"%WINDIR%\SysNative" | ||
); | ||
|
||
return Path.Combine(sysDir, WslCommandName); | ||
} | ||
} | ||
} |