Skip to content

Commit dea7cb1

Browse files
authored
Merge pull request #600 from mjcheetham/tty-pref
Add option to disable GUI prompts/prefer terminal prompts instead
2 parents 0fb970d + 99b6d80 commit dea7cb1

File tree

10 files changed

+130
-9
lines changed

10 files changed

+130
-9
lines changed

docs/configuration.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,25 @@ git config --global credential.ghe.contoso.com.authority github
106106

107107
---
108108

109+
### credential.guiPrompt
110+
111+
Permit or disable GCM from presenting GUI prompts. If an equivalent terminal/
112+
text-based prompt is available, that will be shown instead.
113+
114+
To disable all interactivity see [credential.interactive](#credentialinteractive).
115+
116+
#### Example
117+
118+
```shell
119+
git config --global credential.guiPrompt false
120+
```
121+
122+
Defaults to enabled.
123+
124+
**Also see: [GCM_GUI_PROMPT](environment.md#GCM_GUI_PROMPT)**
125+
126+
---
127+
109128
### credential.autoDetectTimeout
110129

111130
Set the maximum length of time, in milliseconds, that GCM should wait for a

docs/environment.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,33 @@ export GCM_AUTHORITY=github
226226

227227
---
228228

229+
### GCM_GUI_PROMPT
230+
231+
Permit or disable GCM from presenting GUI prompts. If an equivalent terminal/
232+
text-based prompt is available, that will be shown instead.
233+
234+
To disable all interactivity see [GCM_INTERACTIVE](#gcm_interactive).
235+
236+
#### Example
237+
238+
##### Windows
239+
240+
```batch
241+
SET GCM_GUI_PROMPT=0
242+
```
243+
244+
##### macOS/Linux
245+
246+
```bash
247+
export GCM_GUI_PROMPT=0
248+
```
249+
250+
Defaults to enabled.
251+
252+
**Also see: [credential.guiPrompt](configuration.md#credentialguiprompt)**
253+
254+
---
255+
229256
### GCM_AUTODETECT_TIMEOUT
230257

231258
Set the maximum length of time, in milliseconds, that GCM should wait for a

docs/faq.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,30 @@ Follow the instructions in [our WSL guide](wsl.md) carefully. Especially note th
109109
### Does GCM work with multiple users? If so, how?
110110

111111
That's a fairly complicated question to answer, but in short, yes. See [our document on multiple users](multiple-users.md) for details.
112+
113+
### How can I disable GUI dialogs and prompts?
114+
115+
There are various environment variables and configuration options available to
116+
customize how GCM will prompt you (or not) for input. Please see the following:
117+
118+
- [`GCM_INTERACTIVE`](environment.md#GCM_INTERACTIVE) / [`credential.interactive`](configuration.md#credentialinteractive)
119+
- [`GCM_GUI_PROMPT`](environment.md#GCM_GUI_PROMPT) / [`credential.guiPrompt`](configuration.md#credentialguiprompt)
120+
- [`GIT_TERMINAL_PROMPT`](https://git-scm.com/docs/git#Documentation/git.txt-codeGITTERMINALPROMPTcode) (note this is a _Git setting_ that will affect Git as well as GCM)
121+
122+
### How can I extend GUI prompts/integrate prompts with my application?
123+
124+
Application developers who use Git - think Visual Studio, GitKraken, etc. - may
125+
want to replace the GCM default UI with prompts styled to look like their
126+
application. This isn't complicated (though it is a bit of work).
127+
128+
You can replace the GUI prompts of the Bitbucket and GitHub host providers
129+
specifically by using the `credential.gitHubHelper`/`credential.bitbucketHelper`
130+
settings or `GCM_GITHUB_HELPER`/`GCM_BITBUCKET_HELPER` environment variables.
131+
132+
Set these variables to the path of an external helper executable that responds
133+
to the requests as the bundled UI helpers do. See the current `--help` documents
134+
for the bundled UI helpers (`GitHub.UI`/`Atlassian.Bitbucket.UI`) for more
135+
information.
136+
137+
You may also set these variables to the empty string `""` to force terminal/
138+
text-based prompts instead.

src/shared/Atlassian.Bitbucket/BitbucketAuthentication.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ public async Task<CredentialsPromptResult> GetCredentialsAsync(Uri targetUri, st
9090
}
9191

9292
// Shell out to the UI helper and show the Bitbucket u/p prompt
93-
if (Context.SessionManager.IsDesktopSession && TryFindHelperExecutablePath(out string helperPath))
93+
if (Context.Settings.IsGuiPromptsEnabled && Context.SessionManager.IsDesktopSession &&
94+
TryFindHelperExecutablePath(out string helperPath))
9495
{
9596
var cmdArgs = new StringBuilder("userpass");
9697
if (!string.IsNullOrWhiteSpace(userName))
@@ -186,7 +187,8 @@ public async Task<bool> ShowOAuthRequiredPromptAsync()
186187
ThrowIfUserInteractionDisabled();
187188

188189
// Shell out to the UI helper and show the Bitbucket prompt
189-
if (Context.SessionManager.IsDesktopSession && TryFindHelperExecutablePath(out string helperPath))
190+
if (Context.Settings.IsGuiPromptsEnabled && Context.SessionManager.IsDesktopSession &&
191+
TryFindHelperExecutablePath(out string helperPath))
190192
{
191193
IDictionary<string, string> output = await InvokeHelperAsync(helperPath, "oauth");
192194

src/shared/Core/Authentication/AuthenticationBase.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
using System.Diagnostics;
44
using System.IO;
55
using System.Linq;
6-
using System.Reflection;
7-
using System.Text;
86
using System.Threading;
97
using System.Threading.Tasks;
108

@@ -92,6 +90,16 @@ protected void ThrowIfUserInteractionDisabled()
9290
}
9391
}
9492

93+
protected void ThrowIfGuiPromptsDisabled()
94+
{
95+
if (!Context.Settings.IsGuiPromptsEnabled)
96+
{
97+
Context.Trace.WriteLine($"{Constants.EnvironmentVariables.GitTerminalPrompts} is 0; GUI prompts have been disabled.");
98+
99+
throw new InvalidOperationException("Cannot show prompt because GUI prompts have been disabled.");
100+
}
101+
}
102+
95103
protected void ThrowIfTerminalPromptsDisabled()
96104
{
97105
if (!Context.Settings.IsTerminalPromptsEnabled)

src/shared/Core/Authentication/BasicAuthentication.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ public ICredential GetCredentials(string resource, string userName)
3232
ThrowIfUserInteractionDisabled();
3333

3434
// TODO: we only support system GUI prompts on Windows currently
35-
if (Context.SessionManager.IsDesktopSession && PlatformUtils.IsWindows())
35+
if (Context.Settings.IsGuiPromptsEnabled && Context.SessionManager.IsDesktopSession &&
36+
PlatformUtils.IsWindows())
3637
{
3738
return GetCredentialsByUi(resource, userName);
3839
}

src/shared/Core/Constants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ public static class EnvironmentVariables
8686
public const string GitExecutablePath = "GIT_EXEC_PATH";
8787
public const string GpgExecutablePath = "GCM_GPG_PATH";
8888
public const string GcmAutoDetectTimeout = "GCM_AUTODETECT_TIMEOUT";
89+
public const string GcmGuiPromptsEnabled = "GCM_GUI_PROMPT";
8990
}
9091

9192
public static class Http
@@ -120,6 +121,7 @@ public static class Credential
120121
public const string DpapiStorePath = "dpapiStorePath";
121122
public const string UserName = "username";
122123
public const string AutoDetectTimeout = "autoDetectTimeout";
124+
public const string GuiPromptsEnabled = "guiPrompt";
123125
}
124126

125127
public static class Http

src/shared/Core/Settings.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ public interface ISettings : IDisposable
6565
/// </summary>
6666
bool IsTerminalPromptsEnabled { get; }
6767

68+
/// <summary>
69+
/// True if GUI prompts are enabled, false otherwise.
70+
/// </summary>
71+
/// <remarks>
72+
/// If GUI prompts are disabled but an equivalent terminal prompt is available, the latter will be used instead.
73+
/// </remarks>
74+
bool IsGuiPromptsEnabled { get; }
75+
6876
/// <summary>
6977
/// True if it is permitted to interact with the user, false otherwise.
7078
/// </summary>
@@ -437,6 +445,25 @@ protected virtual bool TryGetExternalDefault(string section, string property, ou
437445

438446
public bool IsTerminalPromptsEnabled => _environment.Variables.GetBooleanyOrDefault(KnownEnvars.GitTerminalPrompts, true);
439447

448+
public bool IsGuiPromptsEnabled
449+
{
450+
get
451+
{
452+
const bool defaultValue = true;
453+
454+
if (TryGetSetting(
455+
KnownEnvars.GcmGuiPromptsEnabled,
456+
KnownGitCfg.Credential.SectionName,
457+
KnownGitCfg.Credential.GuiPromptsEnabled,
458+
out string str))
459+
{
460+
return str.ToBooleanyOrDefault(defaultValue);
461+
}
462+
463+
return defaultValue;
464+
}
465+
}
466+
440467
public bool IsInteractionAllowed
441468
{
442469
get

src/shared/GitHub/GitHubAuthentication.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,13 @@ public async Task<AuthenticationPromptResult> GetAuthenticationAsync(Uri targetU
8181
if (modes == AuthenticationModes.Browser ||
8282
modes == AuthenticationModes.Device)
8383
{
84-
return new AuthenticationPromptResult(modes);
84+
return new AuthenticationPromptResult(modes);
8585
}
8686

8787
ThrowIfUserInteractionDisabled();
88-
if (Context.SessionManager.IsDesktopSession && TryFindHelperExecutablePath(out string helperPath))
88+
89+
if (Context.Settings.IsGuiPromptsEnabled && Context.SessionManager.IsDesktopSession &&
90+
TryFindHelperExecutablePath(out string helperPath))
8991
{
9092
var promptArgs = new StringBuilder("prompt");
9193
if (modes == AuthenticationModes.All)
@@ -213,7 +215,8 @@ public async Task<string> GetTwoFactorCodeAsync(Uri targetUri, bool isSms)
213215
{
214216
ThrowIfUserInteractionDisabled();
215217

216-
if (Context.SessionManager.IsDesktopSession && TryFindHelperExecutablePath(out string helperPath))
218+
if (Context.Settings.IsGuiPromptsEnabled && Context.SessionManager.IsDesktopSession &&
219+
TryFindHelperExecutablePath(out string helperPath))
217220
{
218221
var args = new StringBuilder("2fa");
219222
if (isSms) args.Append(" --sms");
@@ -282,7 +285,8 @@ public async Task<OAuth2TokenResult> GetOAuthTokenViaDeviceCodeAsync(Uri targetU
282285
OAuth2DeviceCodeResult dcr = await oauthClient.GetDeviceCodeAsync(scopes, CancellationToken.None);
283286

284287
// If we have a desktop session show the device code in a dialog
285-
if (Context.SessionManager.IsDesktopSession && TryFindHelperExecutablePath(out string helperPath))
288+
if (Context.Settings.IsGuiPromptsEnabled && Context.SessionManager.IsDesktopSession &&
289+
TryFindHelperExecutablePath(out string helperPath))
286290
{
287291
var args = new StringBuilder("device");
288292
args.AppendFormat(" --code {0} ", QuoteCmdArg(dcr.UserCode));

src/shared/TestInfrastructure/Objects/TestSettings.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ public class TestSettings : ISettings
1313

1414
public bool IsTerminalPromptsEnabled { get; set; } = true;
1515

16+
public bool IsGuiPromptsEnabled { get; set; } = true;
17+
1618
public bool IsInteractionAllowed { get; set; } = true;
1719

1820
public string Trace { get; set; }
@@ -97,6 +99,8 @@ public IEnumerable<string> GetSettingValues(string envarName, string section, st
9799

98100
bool ISettings.IsTerminalPromptsEnabled => IsTerminalPromptsEnabled;
99101

102+
bool ISettings.IsGuiPromptsEnabled => IsGuiPromptsEnabled;
103+
100104
bool ISettings.IsInteractionAllowed => IsInteractionAllowed;
101105

102106
bool ISettings.GetTracingEnabled(out string value)

0 commit comments

Comments
 (0)