Skip to content

Commit e12fd6a

Browse files
committed
settings: pull generic 'get' methods up to interface
Pull the general `TryGetSetting` and `GetSettingValues` methods up from the `Settings` class to the `ISettings` interface for all providers to use for their own settings.
1 parent bac4884 commit e12fd6a

File tree

3 files changed

+211
-146
lines changed

3 files changed

+211
-146
lines changed

src/shared/Microsoft.Git.CredentialManager/Settings.cs

Lines changed: 139 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,27 @@ namespace Microsoft.Git.CredentialManager
1515
/// </summary>
1616
public interface ISettings : IDisposable
1717
{
18+
/// <summary>
19+
/// Try and get the value of a specified setting as specified in the environment and Git configuration,
20+
/// with the environment taking precedence over Git.
21+
/// </summary>
22+
/// <param name="envarName">Optional environment variable name.</param>
23+
/// <param name="section">Optional Git configuration section name.</param>
24+
/// <param name="property">Git configuration property name. Required if <paramref name="section"/> is set, optional otherwise.</param>
25+
/// <param name="value">Value of the requested setting.</param>
26+
/// <returns>True if a setting value was found, false otherwise.</returns>
27+
bool TryGetSetting(string envarName, string section, string property, out string value);
28+
29+
/// <summary>
30+
/// Try and get the all values of a specified setting as specified in the environment and Git configuration,
31+
/// in the correct order or precedence.
32+
/// </summary>
33+
/// <param name="envarName">Optional environment variable name.</param>
34+
/// <param name="section">Optional Git configuration section name.</param>
35+
/// <param name="property">Git configuration property name. Required if <paramref name="section"/> is set, optional otherwise.</param>
36+
/// <returns>All values for the specified setting, in order of precedence, or an empty collection if no such values are set.</returns>
37+
IEnumerable<string> GetSettingValues(string envarName, string section, string property);
38+
1839
/// <summary>
1940
/// Git repository that local configuration lookup is scoped to, or null if this instance is not scoped to a repository.
2041
/// </summary>
@@ -112,6 +133,124 @@ public Settings(IEnvironment environment, IGit git, string repositoryPath = null
112133
RepositoryPath = repositoryPath;
113134
}
114135

136+
public bool TryGetSetting(string envarName, string section, string property, out string value)
137+
{
138+
IEnumerable<string> allValues = GetSettingValues(envarName, section, property);
139+
140+
value = allValues.FirstOrDefault();
141+
142+
return value != null;
143+
}
144+
145+
public IEnumerable<string> GetSettingValues(string envarName, string section, string property)
146+
{
147+
string value;
148+
149+
if (envarName != null)
150+
{
151+
if (_environment.Variables.TryGetValue(envarName, out value))
152+
{
153+
yield return value;
154+
}
155+
}
156+
157+
if (section != null && property != null)
158+
{
159+
IGitConfiguration config = GetGitConfiguration();
160+
161+
if (RemoteUri != null)
162+
{
163+
/*
164+
* Look for URL scoped "section" configuration entries, starting from the most specific
165+
* down to the least specific (stopping before the TLD).
166+
*
167+
* In a divergence from standard Git configuration rules, we also consider matching URL scopes
168+
* without a scheme ("protocol://").
169+
*
170+
* For each level of scope, we look for an entry with the scheme included (the default), and then
171+
* also one without it specified. This allows you to have one configuration scope for both "http" and
172+
* "https" without needing to repeat yourself, for example.
173+
*
174+
* For example, starting with "https://foo.example.com/bar/buzz" we have:
175+
*
176+
* 1a. [section "https://foo.example.com/bar/buzz"]
177+
* property = value
178+
*
179+
* 1b. [section "foo.example.com/bar/buzz"]
180+
* property = value
181+
*
182+
* 2a. [section "https://foo.example.com/bar"]
183+
* property = value
184+
*
185+
* 2b. [section "foo.example.com/bar"]
186+
* property = value
187+
*
188+
* 3a. [section "https://foo.example.com"]
189+
* property = value
190+
*
191+
* 3b. [section "foo.example.com"]
192+
* property = value
193+
*
194+
* 4a. [section "https://example.com"]
195+
* property = value
196+
*
197+
* 4b. [section "example.com"]
198+
* property = value
199+
*
200+
*/
201+
202+
// Enumerate all configuration entries with the correct section and property name
203+
// and make a local copy of them here to avoid needing to call `TryGetValue` on the
204+
// IGitConfiguration object multiple times in a loop below.
205+
var configEntries = new Dictionary<string, string>();
206+
config.Enumerate((entryName, entryValue) =>
207+
{
208+
string entrySection = entryName.TruncateFromIndexOf('.');
209+
string entryProperty = entryName.TrimUntilLastIndexOf('.');
210+
211+
if (StringComparer.OrdinalIgnoreCase.Equals(entrySection, section) &&
212+
StringComparer.OrdinalIgnoreCase.Equals(entryProperty, property))
213+
{
214+
configEntries[entryName] = entryValue;
215+
}
216+
217+
// Continue the enumeration
218+
return true;
219+
});
220+
221+
foreach (string scope in RemoteUri.GetGitConfigurationScopes())
222+
{
223+
string queryName = $"{section}.{scope}.{property}";
224+
// Look for a scoped entry that includes the scheme "protocol://example.com" first as this is more specific
225+
if (configEntries.TryGetValue(queryName, out value))
226+
{
227+
yield return value;
228+
}
229+
230+
// Now look for a scoped entry that omits the scheme "example.com" second as this is less specific
231+
string scopeWithoutScheme = scope.TrimUntilIndexOf(Uri.SchemeDelimiter);
232+
string queryWithSchemeName = $"{section}.{scopeWithoutScheme}.{property}";
233+
if (configEntries.TryGetValue(queryWithSchemeName, out value))
234+
{
235+
yield return value;
236+
}
237+
}
238+
}
239+
240+
/*
241+
* Try to look for an un-scoped "section" property setting:
242+
*
243+
* [section]
244+
* property = value
245+
*
246+
*/
247+
if (config.TryGetValue($"{section}.{property}", out value))
248+
{
249+
yield return value;
250+
}
251+
}
252+
}
253+
115254
public string RepositoryPath { get; }
116255

117256
public Uri RemoteUri { get; set; }
@@ -279,141 +418,6 @@ bool TryGetUriSetting(string envarName, string section, string property, out Uri
279418

280419
public string ParentWindowId => _environment.Variables.TryGetValue(Constants.EnvironmentVariables.GcmParentWindow, out string parentWindowId) ? parentWindowId : null;
281420

282-
/// <summary>
283-
/// Try and get the value of a specified setting as specified in the environment and Git configuration,
284-
/// with the environment taking precedence over Git.
285-
/// </summary>
286-
/// <param name="envarName">Optional environment variable name.</param>
287-
/// <param name="section">Optional Git configuration section name.</param>
288-
/// <param name="property">Git configuration property name. Required if <paramref name="section"/> is set, optional otherwise.</param>
289-
/// <param name="value">Value of the requested setting.</param>
290-
/// <returns>True if a setting value was found, false otherwise.</returns>
291-
public bool TryGetSetting(string envarName, string section, string property, out string value)
292-
{
293-
IEnumerable<string> allValues = GetSettingValues(envarName, section, property);
294-
295-
value = allValues.FirstOrDefault();
296-
297-
return value != null;
298-
}
299-
300-
/// <summary>
301-
/// Try and get the all values of a specified setting as specified in the environment and Git configuration,
302-
/// in the correct order or precedence.
303-
/// </summary>
304-
/// <param name="envarName">Optional environment variable name.</param>
305-
/// <param name="section">Optional Git configuration section name.</param>
306-
/// <param name="property">Git configuration property name. Required if <paramref name="section"/> is set, optional otherwise.</param>
307-
/// <returns>All values for the specified setting, in order of precedence, or an empty collection if no such values are set.</returns>
308-
public IEnumerable<string> GetSettingValues(string envarName, string section, string property)
309-
{
310-
string value;
311-
312-
if (envarName != null)
313-
{
314-
if (_environment.Variables.TryGetValue(envarName, out value))
315-
{
316-
yield return value;
317-
}
318-
}
319-
320-
if (section != null && property != null)
321-
{
322-
IGitConfiguration config = GetGitConfiguration();
323-
324-
if (RemoteUri != null)
325-
{
326-
/*
327-
* Look for URL scoped "section" configuration entries, starting from the most specific
328-
* down to the least specific (stopping before the TLD).
329-
*
330-
* In a divergence from standard Git configuration rules, we also consider matching URL scopes
331-
* without a scheme ("protocol://").
332-
*
333-
* For each level of scope, we look for an entry with the scheme included (the default), and then
334-
* also one without it specified. This allows you to have one configuration scope for both "http" and
335-
* "https" without needing to repeat yourself, for example.
336-
*
337-
* For example, starting with "https://foo.example.com/bar/buzz" we have:
338-
*
339-
* 1a. [section "https://foo.example.com/bar/buzz"]
340-
* property = value
341-
*
342-
* 1b. [section "foo.example.com/bar/buzz"]
343-
* property = value
344-
*
345-
* 2a. [section "https://foo.example.com/bar"]
346-
* property = value
347-
*
348-
* 2b. [section "foo.example.com/bar"]
349-
* property = value
350-
*
351-
* 3a. [section "https://foo.example.com"]
352-
* property = value
353-
*
354-
* 3b. [section "foo.example.com"]
355-
* property = value
356-
*
357-
* 4a. [section "https://example.com"]
358-
* property = value
359-
*
360-
* 4b. [section "example.com"]
361-
* property = value
362-
*
363-
*/
364-
365-
// Enumerate all configuration entries with the correct section and property name
366-
// and make a local copy of them here to avoid needing to call `TryGetValue` on the
367-
// IGitConfiguration object multiple times in a loop below.
368-
var configEntries = new Dictionary<string, string>();
369-
config.Enumerate((entryName, entryValue) =>
370-
{
371-
string entrySection = entryName.TruncateFromIndexOf('.');
372-
string entryProperty = entryName.TrimUntilLastIndexOf('.');
373-
374-
if (StringComparer.OrdinalIgnoreCase.Equals(entrySection, section) &&
375-
StringComparer.OrdinalIgnoreCase.Equals(entryProperty, property))
376-
{
377-
configEntries[entryName] = entryValue;
378-
}
379-
380-
// Continue the enumeration
381-
return true;
382-
});
383-
384-
foreach (string scope in RemoteUri.GetGitConfigurationScopes())
385-
{
386-
string queryName = $"{section}.{scope}.{property}";
387-
// Look for a scoped entry that includes the scheme "protocol://example.com" first as this is more specific
388-
if (configEntries.TryGetValue(queryName, out value))
389-
{
390-
yield return value;
391-
}
392-
393-
// Now look for a scoped entry that omits the scheme "example.com" second as this is less specific
394-
string scopeWithoutScheme = scope.TrimUntilIndexOf(Uri.SchemeDelimiter);
395-
string queryWithSchemeName = $"{section}.{scopeWithoutScheme}.{property}";
396-
if (configEntries.TryGetValue(queryWithSchemeName, out value))
397-
{
398-
yield return value;
399-
}
400-
}
401-
}
402-
403-
/*
404-
* Try to look for an un-scoped "section" property setting:
405-
*
406-
* [section]
407-
* property = value
408-
*
409-
*/
410-
if (config.TryGetValue($"{section}.{property}", out value))
411-
{
412-
yield return value;
413-
}
414-
}
415-
}
416-
417421
private IGitConfiguration GetGitConfiguration() => _gitConfig ?? (_gitConfig = _git.GetConfiguration(RepositoryPath));
418422

419423
#region IDisposable

src/shared/TestInfrastructure/Objects/TestCommandContext.cs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,33 @@ namespace Microsoft.Git.CredentialManager.Tests.Objects
1010
{
1111
public class TestCommandContext : ICommandContext
1212
{
13-
public TestSettings Settings { get; set; } = new TestSettings();
14-
public TestStandardStreams Streams { get; set; } = new TestStandardStreams();
15-
public TestTerminal Terminal { get; set; } = new TestTerminal();
16-
public bool IsDesktopSession { get; set; } = true;
17-
public ITrace Trace { get; set; } = new NullTrace();
18-
public TestFileSystem FileSystem { get; set; } = new TestFileSystem();
19-
public TestCredentialStore CredentialStore { get; set; } = new TestCredentialStore();
20-
public TestHttpClientFactory HttpClientFactory { get; set; } = new TestHttpClientFactory();
21-
public TestGit Git { get; set; } = new TestGit();
22-
public TestEnvironment Environment { get; set; } = new TestEnvironment();
23-
public TestSystemPrompts SystemPrompts { get; set; } = new TestSystemPrompts();
13+
public TestCommandContext()
14+
{
15+
Streams = new TestStandardStreams();
16+
Terminal = new TestTerminal();
17+
IsDesktopSession = true;
18+
Trace = new NullTrace();
19+
FileSystem = new TestFileSystem();
20+
CredentialStore = new TestCredentialStore();
21+
HttpClientFactory = new TestHttpClientFactory();
22+
Git = new TestGit();
23+
Environment = new TestEnvironment();
24+
SystemPrompts = new TestSystemPrompts();
25+
26+
Settings = new TestSettings {Environment = Environment, GitConfiguration = Git.GlobalConfiguration};
27+
}
28+
29+
public TestSettings Settings { get; set; }
30+
public TestStandardStreams Streams { get; set; }
31+
public TestTerminal Terminal { get; set; }
32+
public bool IsDesktopSession { get; set; }
33+
public ITrace Trace { get; set; }
34+
public TestFileSystem FileSystem { get; set; }
35+
public TestCredentialStore CredentialStore { get; set; }
36+
public TestHttpClientFactory HttpClientFactory { get; set; }
37+
public TestGit Git { get; set; }
38+
public TestEnvironment Environment { get; set; }
39+
public TestSystemPrompts SystemPrompts { get; set; }
2440

2541
#region ICommandContext
2642

0 commit comments

Comments
 (0)