Skip to content

Commit 596cd0c

Browse files
authored
Merge pull request #461 from mjcheetham/wsl
Add support for using WSL Git for configuration
2 parents 488a595 + bdbf7c3 commit 596cd0c

File tree

11 files changed

+484
-45
lines changed

11 files changed

+484
-45
lines changed

README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,6 @@ There are two flavors of standalone installation on Windows:
152152

153153
To install, double-click the desired installation package and follow the instructions presented.
154154

155-
#### Git Credential Manager for Windows
156-
157-
GCM Core installs side-by-side any existing Git Credential Manager for Windows installation and will take precedence over it and use any existing credentials so you shouldn't need to re-authenticate.
158-
159155
#### Uninstall (Windows 10)
160156

161157
To uninstall, open the Settings app and navigate to the Apps section. Select "Git Credential Manager Core" and click "Uninstall".
@@ -164,6 +160,14 @@ To uninstall, open the Settings app and navigate to the Apps section. Select "Gi
164160

165161
To uninstall, open Control Panel and navigate to the Programs and Features screen. Select "Git Credential Manager Core" and click "Remove".
166162

163+
#### Windows Subsystem for Linux (WSL)
164+
165+
Git Credential Manager Core can be used with the [Windows Subsystem for Linux
166+
(WSL)](https://aka.ms/wsl) to enable secure authentication of your remote Git
167+
repositories from inside of WSL.
168+
169+
[Please see the GCM Core on WSL docs](docs/wsl.md) for more information.
170+
167171
## How to use
168172

169173
Once it's installed and configured, Git Credential Manager Core is called implicitly by Git.

docs/wsl.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Windows Subsystem for Linux (WSL)
2+
3+
GCM Core can be used with the
4+
[Windows Subsystem for Linux (WSL)](https://aka.ms/wsl), both WSL1 and WSL2, by
5+
following these instructions.
6+
7+
In order to use GCM with WSL you must be on Windows 10 Version 1903 or later.
8+
This is the first version of Windows that includes the required `wsl.exe` tool
9+
that GCM uses to interoperate with Git in your WSL distributions.
10+
11+
It is highly recommended that you install Git for Windows to both install GCM
12+
and enable the best experience sharing credentials & settings between WSL and
13+
the Windows host. Alternatively, you must be using GCM version 2.0.XXX or later
14+
and configure the `WSLENV` environment variable as
15+
[described below](#configuring-wsl-without-git-for-windows).
16+
17+
## Configuring WSL with Git for Windows (recommended)
18+
19+
Start by installing the [latest Git for Windows ⬇️](https://github.com/git-for-windows/git/releases/latest)
20+
21+
_Inside your WSL installation_, run the following command to set GCM as the Git
22+
credential helper:
23+
24+
```shell
25+
git config --global credential.helper /mnt/c/Program\ Files/Git/mingw64/libexec/git-core/git-credential-manager-core.exe
26+
```
27+
28+
If you intend to use Azure DevOps you must _also_ set the following Git
29+
configuration _inside of your WSL installation_.
30+
31+
```shell
32+
git config --global credential.https://dev.azure.com.useHttpPath true
33+
```
34+
35+
## Configuring WSL without Git for Windows
36+
37+
If you wish to use GCM Core inside of WSL _without installing Git for Windows_
38+
you must complete additional configuration so that GCM can callback to Git
39+
inside of your WSL installation.
40+
41+
Start by installing the [latest GCM ⬇️](https://aka.ms/gcmcore-latest)
42+
43+
_Inside your WSL installation_, run the following command to set GCM as the Git
44+
credential helper:
45+
46+
```shell
47+
git config --global credential.helper /mnt/c/Program\ Files/Git/mingw64/libexec/git-core/git-credential-manager-core.exe
48+
49+
# For Azure DevOps support only
50+
git config --global credential.https://dev.azure.com.useHttpPath true
51+
```
52+
53+
In **_Windows_** you need to update the `WSLENV` environment variable to include
54+
the value `GIT_EXEC_PATH/wp`. From an _Administrator_ Command Prompt run the
55+
following:
56+
57+
```batch
58+
SETX WSLENV=%WSLENV%:GIT_EXEC_PATH/wp
59+
```
60+
61+
After updating the `WSLENV` environment variable, restart your WSL installation.
62+
63+
## How it works
64+
65+
GCM leverages the built-in interoperability between Windows and WSL, provided by
66+
Microsoft. You can read more about Windows/WSL interop [here](https://docs.microsoft.com/en-us/windows/wsl/interop).
67+
68+
Git inside of a WSL installation can launch the GCM _Windows_ application
69+
transparently to acquire credentials. Running GCM as a Windows application
70+
allows it to take full advantage of the host operating system for storing
71+
credentials securely, and presenting GUI prompts for authentication.
72+
73+
Using the host operating system (Windows) to store credentials also means that
74+
your Windows applications and WSL distributions can all share those credentials,
75+
removing the need to sign-in multiple times.
76+
77+
## Shared configuration
78+
79+
Using GCM as a credential helper for a WSL Git installation means that any
80+
configuration set in WSL Git is NOT respected by GCM (by default). This is
81+
because GCM is running as a Windows application, and therefore will use the Git
82+
for Windows installation to query configuration.
83+
84+
This means things like proxy settings for GCM need to be set in Git for Windows
85+
as well as WSL Git as they are stored in different files
86+
(`%USERPROFILE%\.gitconfig` vs `\\wsl$\distro\home\$USER\.gitconfig`).
87+
88+
You can configure WSL such that GCM will use the WSL Git configuration following
89+
the [instructions above](#configuring-wsl-without-git-for-windows). However,
90+
this then means that things like proxy settings are unique to the specific WSL
91+
installation, and not shared with others or the Windows host.
92+
93+
## Can I install Git Credential Manager directly inside of WSL?
94+
95+
Yes. Rather than install GCM as a Windows application (and have WSL Git invoke
96+
the Windows GCM), can you install GCM as a Linux application instead.
97+
98+
To do this, simply follow the [GCM installation instructions for Linux](../README.md#linux-install-instructions).
99+
100+
**Note:** In this scenario, because GCM is running as a Linux application
101+
it cannot utilize authentication or credential storage features of the host
102+
Windows operating system.

src/shared/Microsoft.Git.CredentialManager.Tests/GitConfigurationTests.cs

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ public void GitProcess_GetConfiguration_ReturnsConfiguration()
4747
{
4848
string gitPath = GetGitPath();
4949
var trace = new NullTrace();
50-
var git = new GitProcess(trace, gitPath);
50+
var env = new TestEnvironment();
51+
var git = new GitProcess(trace, env, gitPath);
5152
var config = git.GetConfiguration();
5253
Assert.NotNull(config);
5354
}
@@ -69,7 +70,8 @@ public void GitConfiguration_Enumerate_CallbackReturnsTrue_InvokesCallbackForEac
6970

7071
string gitPath = GetGitPath();
7172
var trace = new NullTrace();
72-
var git = new GitProcess(trace, gitPath, repoPath);
73+
var env = new TestEnvironment();
74+
var git = new GitProcess(trace, env, gitPath, repoPath);
7375
IGitConfiguration config = git.GetConfiguration();
7476

7577
var actualVisitedEntries = new List<(string name, string value)>();
@@ -106,7 +108,8 @@ public void GitConfiguration_Enumerate_CallbackReturnsFalse_InvokesCallbackForEa
106108

107109
string gitPath = GetGitPath();
108110
var trace = new NullTrace();
109-
var git = new GitProcess(trace, gitPath, repoPath);
111+
var env = new TestEnvironment();
112+
var git = new GitProcess(trace, env, gitPath, repoPath);
110113
IGitConfiguration config = git.GetConfiguration();
111114

112115
var actualVisitedEntries = new List<(string name, string value)>();
@@ -135,7 +138,8 @@ public void GitConfiguration_TryGet_Name_Exists_ReturnsTrueOutString()
135138

136139
string gitPath = GetGitPath();
137140
var trace = new NullTrace();
138-
var git = new GitProcess(trace, gitPath, repoPath);
141+
var env = new TestEnvironment();
142+
var git = new GitProcess(trace, env, gitPath, repoPath);
139143
IGitConfiguration config = git.GetConfiguration();
140144

141145
bool result = config.TryGet("user.name", false, out string value);
@@ -151,7 +155,8 @@ public void GitConfiguration_TryGet_Name_DoesNotExists_ReturnsFalse()
151155

152156
string gitPath = GetGitPath();
153157
var trace = new NullTrace();
154-
var git = new GitProcess(trace, gitPath, repoPath);
158+
var env = new TestEnvironment();
159+
var git = new GitProcess(trace, env, gitPath, repoPath);
155160
IGitConfiguration config = git.GetConfiguration();
156161

157162
string randomName = $"{Guid.NewGuid():N}.{Guid.NewGuid():N}";
@@ -169,7 +174,8 @@ public void GitConfiguration_TryGet_IsPath_True_ReturnsCanonicalPath()
169174
string homeDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
170175
string gitPath = GetGitPath();
171176
var trace = new NullTrace();
172-
var git = new GitProcess(trace, gitPath, repoPath);
177+
var env = new TestEnvironment();
178+
var git = new GitProcess(trace, env, gitPath, repoPath);
173179
IGitConfiguration config = git.GetConfiguration();
174180

175181
bool result = config.TryGet("example.path", true, out string value);
@@ -186,7 +192,8 @@ public void GitConfiguration_TryGet_IsPath_False_ReturnsRawConfig()
186192

187193
string gitPath = GetGitPath();
188194
var trace = new NullTrace();
189-
var git = new GitProcess(trace, gitPath, repoPath);
195+
var env = new TestEnvironment();
196+
var git = new GitProcess(trace, env, gitPath, repoPath);
190197
IGitConfiguration config = git.GetConfiguration();
191198

192199
bool result = config.TryGet("example.path", false, out string value);
@@ -203,7 +210,8 @@ public void GitConfiguration_TryGet_BoolType_ReturnsCanonicalBool()
203210

204211
string gitPath = GetGitPath();
205212
var trace = new NullTrace();
206-
var git = new GitProcess(trace, gitPath, repoPath);
213+
var env = new TestEnvironment();
214+
var git = new GitProcess(trace, env, gitPath, repoPath);
207215
IGitConfiguration config = git.GetConfiguration();
208216

209217
bool result = config.TryGet(GitConfigurationLevel.Local, GitConfigurationType.Bool,
@@ -221,7 +229,8 @@ public void GitConfiguration_TryGet_BoolWithoutType_ReturnsRawConfig()
221229

222230
string gitPath = GetGitPath();
223231
var trace = new NullTrace();
224-
var git = new GitProcess(trace, gitPath, repoPath);
232+
var env = new TestEnvironment();
233+
var git = new GitProcess(trace, env, gitPath, repoPath);
225234
IGitConfiguration config = git.GetConfiguration();
226235

227236
bool result = config.TryGet(GitConfigurationLevel.Local, GitConfigurationType.Raw,
@@ -239,7 +248,8 @@ public void GitConfiguration_Get_Name_Exists_ReturnsString()
239248

240249
string gitPath = GetGitPath();
241250
var trace = new NullTrace();
242-
var git = new GitProcess(trace, gitPath, repoPath);
251+
var env = new TestEnvironment();
252+
var git = new GitProcess(trace, env, gitPath, repoPath);
243253
IGitConfiguration config = git.GetConfiguration();
244254

245255
string value = config.Get("user.name");
@@ -254,7 +264,8 @@ public void GitConfiguration_Get_Name_DoesNotExists_ThrowsException()
254264

255265
string gitPath = GetGitPath();
256266
var trace = new NullTrace();
257-
var git = new GitProcess(trace, gitPath, repoPath);
267+
var env = new TestEnvironment();
268+
var git = new GitProcess(trace, env, gitPath, repoPath);
258269
IGitConfiguration config = git.GetConfiguration();
259270

260271
string randomName = $"{Guid.NewGuid():N}.{Guid.NewGuid():N}";
@@ -268,7 +279,8 @@ public void GitConfiguration_Set_Local_SetsLocalConfig()
268279

269280
string gitPath = GetGitPath();
270281
var trace = new NullTrace();
271-
var git = new GitProcess(trace, gitPath, repoPath);
282+
var env = new TestEnvironment();
283+
var git = new GitProcess(trace, env, gitPath, repoPath);
272284
IGitConfiguration config = git.GetConfiguration();
273285

274286
config.Set(GitConfigurationLevel.Local, "core.foobar", "foo123");
@@ -285,7 +297,8 @@ public void GitConfiguration_Set_All_ThrowsException()
285297

286298
string gitPath = GetGitPath();
287299
var trace = new NullTrace();
288-
var git = new GitProcess(trace, gitPath, repoPath);
300+
var env = new TestEnvironment();
301+
var git = new GitProcess(trace, env, gitPath, repoPath);
289302
IGitConfiguration config = git.GetConfiguration();
290303

291304
Assert.Throws<InvalidOperationException>(() => config.Set(GitConfigurationLevel.All, "core.foobar", "test123"));
@@ -303,7 +316,8 @@ public void GitConfiguration_Unset_Global_UnsetsGlobalConfig()
303316

304317
string gitPath = GetGitPath();
305318
var trace = new NullTrace();
306-
var git = new GitProcess(trace, gitPath, repoPath);
319+
var env = new TestEnvironment();
320+
var git = new GitProcess(trace, env, gitPath, repoPath);
307321
IGitConfiguration config = git.GetConfiguration();
308322

309323
config.Unset(GitConfigurationLevel.Global, "core.foobar");
@@ -333,7 +347,8 @@ public void GitConfiguration_Unset_Local_UnsetsLocalConfig()
333347

334348
string gitPath = GetGitPath();
335349
var trace = new NullTrace();
336-
var git = new GitProcess(trace, gitPath, repoPath);
350+
var env = new TestEnvironment();
351+
var git = new GitProcess(trace, env, gitPath, repoPath);
337352
IGitConfiguration config = git.GetConfiguration();
338353

339354
config.Unset(GitConfigurationLevel.Local, "core.foobar");
@@ -358,7 +373,8 @@ public void GitConfiguration_Unset_All_ThrowsException()
358373

359374
string gitPath = GetGitPath();
360375
var trace = new NullTrace();
361-
var git = new GitProcess(trace, gitPath, repoPath);
376+
var env = new TestEnvironment();
377+
var git = new GitProcess(trace, env, gitPath, repoPath);
362378
IGitConfiguration config = git.GetConfiguration();
363379

364380
Assert.Throws<InvalidOperationException>(() => config.Unset(GitConfigurationLevel.All, "core.foobar"));
@@ -374,7 +390,8 @@ public void GitConfiguration_UnsetAll_UnsetsAllConfig()
374390

375391
string gitPath = GetGitPath();
376392
var trace = new NullTrace();
377-
var git = new GitProcess(trace, gitPath, repoPath);
393+
var env = new TestEnvironment();
394+
var git = new GitProcess(trace, env, gitPath, repoPath);
378395
IGitConfiguration config = git.GetConfiguration();
379396

380397
config.UnsetAll(GitConfigurationLevel.Local, "core.foobar", "foo*");
@@ -391,7 +408,8 @@ public void GitConfiguration_UnsetAll_All_ThrowsException()
391408

392409
string gitPath = GetGitPath();
393410
var trace = new NullTrace();
394-
var git = new GitProcess(trace, gitPath, repoPath);
411+
var env = new TestEnvironment();
412+
var git = new GitProcess(trace, env, gitPath, repoPath);
395413
IGitConfiguration config = git.GetConfiguration();
396414

397415
Assert.Throws<InvalidOperationException>(() => config.UnsetAll(GitConfigurationLevel.All, "core.foobar", Constants.RegexPatterns.Any));

0 commit comments

Comments
 (0)