Skip to content

Commit 01b4c5e

Browse files
committed
Merge pull request #1042 from libgit2/ntk/config_frompaths
Should we allow Configuration to parse a local repository config?
2 parents 7202df7 + ca4d15e commit 01b4c5e

File tree

4 files changed

+182
-47
lines changed

4 files changed

+182
-47
lines changed

LibGit2Sharp.Tests/ConfigurationFixture.cs

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.IO;
34
using System.Linq;
45
using LibGit2Sharp.Tests.TestHelpers;
56
using Xunit;
7+
using Xunit.Extensions;
68

79
namespace LibGit2Sharp.Tests
810
{
@@ -14,35 +16,6 @@ private static void AssertValueInLocalConfigFile(string repoPath, string regex)
1416
AssertValueInConfigFile(configFilePath, regex);
1517
}
1618

17-
private static string RetrieveGlobalConfigLocation()
18-
{
19-
string[] variables = { "HOME", "USERPROFILE", };
20-
21-
foreach (string variable in variables)
22-
{
23-
string potentialLocation = Environment.GetEnvironmentVariable(variable);
24-
if (string.IsNullOrEmpty(potentialLocation))
25-
{
26-
continue;
27-
}
28-
29-
string potentialPath = Path.Combine(potentialLocation, ".gitconfig");
30-
31-
if (File.Exists(potentialPath))
32-
{
33-
return potentialPath;
34-
}
35-
}
36-
37-
throw new InvalidOperationException("Unable to determine the location of '.gitconfig' file.");
38-
}
39-
40-
private static void AssertValueInGlobalConfigFile(string regex)
41-
{
42-
string configFilePath = RetrieveGlobalConfigLocation();
43-
AssertValueInConfigFile(configFilePath, regex);
44-
}
45-
4619
[Fact]
4720
public void CanUnsetAnEntryFromTheLocalConfiguration()
4821
{
@@ -256,7 +229,7 @@ public void CanSetBooleanValue()
256229
[Fact]
257230
public void SettingLocalConfigurationOutsideAReposThrows()
258231
{
259-
using (var config = new Configuration(null, null, null))
232+
using (var config = Configuration.BuildFrom(null, null, null, null))
260233
{
261234
Assert.Throws<LibGit2SharpException>(() => config.Set("unittests.intsetting", 3));
262235
}
@@ -395,5 +368,46 @@ public void CanTellIfASpecificStoreContainsAKey()
395368
Assert.Null(repo.Config.Get<string>("MCHammer.You-cant-touch-this", ConfigurationLevel.System));
396369
}
397370
}
371+
372+
public static IEnumerable<object[]> ConfigAccessors
373+
{
374+
get
375+
{
376+
return new List<object[]>
377+
{
378+
new[] { new Func<string, string>(p => Path.Combine(p, ".git", "config")) },
379+
new[] { new Func<string, string>(p => Path.Combine(p, ".git")) },
380+
new[] { new Func<string, string>(p => p) },
381+
};
382+
}
383+
}
384+
385+
[Theory, PropertyData("ConfigAccessors")]
386+
public void CanAccessConfigurationWithoutARepository(Func<string, string> localConfigurationPathProvider)
387+
{
388+
var path = SandboxStandardTestRepoGitDir();
389+
390+
string globalConfigPath = CreateConfigurationWithDummyUser(Constants.Signature);
391+
var options = new RepositoryOptions { GlobalConfigurationLocation = globalConfigPath };
392+
393+
using (var repo = new Repository(path, options))
394+
{
395+
repo.Config.Set("my.key", "local");
396+
repo.Config.Set("my.key", "mouse", ConfigurationLevel.Global);
397+
}
398+
399+
using (var config = Configuration.BuildFrom(localConfigurationPathProvider(path), globalConfigPath))
400+
{
401+
Assert.Equal("local", config.Get<string>("my.key").Value);
402+
Assert.Equal("mouse", config.Get<string>("my.key", ConfigurationLevel.Global).Value);
403+
}
404+
}
405+
406+
[Fact]
407+
public void PassingANonExistingLocalConfigurationFileToBuildFromthrowss()
408+
{
409+
Assert.Throws<FileNotFoundException>(() => Configuration.BuildFrom(
410+
Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())));
411+
}
398412
}
399413
}

LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -336,19 +336,19 @@ protected string CreateConfigurationWithDummyUser(Signature signature)
336336
protected string CreateConfigurationWithDummyUser(string name, string email)
337337
{
338338
SelfCleaningDirectory scd = BuildSelfCleaningDirectory();
339-
Directory.CreateDirectory(scd.DirectoryPath);
340-
string configFilePath = Path.Combine(scd.DirectoryPath, "global-config");
341339

342-
using (Configuration config = new Configuration(configFilePath))
340+
string configFilePath = Touch(scd.DirectoryPath, "fake-config");
341+
342+
using (Configuration config = Configuration.BuildFrom(configFilePath))
343343
{
344344
if (name != null)
345345
{
346-
config.Set("user.name", name, ConfigurationLevel.Global);
346+
config.Set("user.name", name);
347347
}
348348

349349
if (email != null)
350350
{
351-
config.Set("user.email", email, ConfigurationLevel.Global);
351+
config.Set("user.email", email);
352352
}
353353
}
354354

LibGit2Sharp/Configuration.cs

Lines changed: 132 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@ namespace LibGit2Sharp
1515
public class Configuration : IDisposable,
1616
IEnumerable<ConfigurationEntry<string>>
1717
{
18+
private readonly FilePath repoConfigPath;
1819
private readonly FilePath globalConfigPath;
1920
private readonly FilePath xdgConfigPath;
2021
private readonly FilePath systemConfigPath;
2122

22-
private readonly Repository repository;
23-
2423
private ConfigurationSafeHandle configHandle;
2524

2625
/// <summary>
@@ -29,19 +28,22 @@ public class Configuration : IDisposable,
2928
protected Configuration()
3029
{ }
3130

32-
internal Configuration(Repository repository, string globalConfigurationFileLocation,
31+
internal Configuration(Repository repository, string repositoryConfigurationFileLocation, string globalConfigurationFileLocation,
3332
string xdgConfigurationFileLocation, string systemConfigurationFileLocation)
3433
{
35-
this.repository = repository;
34+
if (repositoryConfigurationFileLocation != null)
35+
{
36+
repoConfigPath = NormalizeConfigPath(repositoryConfigurationFileLocation);
37+
}
3638

3739
globalConfigPath = globalConfigurationFileLocation ?? Proxy.git_config_find_global();
3840
xdgConfigPath = xdgConfigurationFileLocation ?? Proxy.git_config_find_xdg();
3941
systemConfigPath = systemConfigurationFileLocation ?? Proxy.git_config_find_system();
4042

41-
Init();
43+
Init(repository);
4244
}
4345

44-
private void Init()
46+
private void Init(Repository repository)
4547
{
4648
configHandle = Proxy.git_config_new();
4749

@@ -56,6 +58,10 @@ private void Init()
5658

5759
Proxy.git_repository_set_config(repository.Handle, configHandle);
5860
}
61+
else if (repoConfigPath != null)
62+
{
63+
Proxy.git_config_add_file_ondisk(configHandle, repoConfigPath, ConfigurationLevel.Local);
64+
}
5965

6066
if (globalConfigPath != null)
6167
{
@@ -73,21 +79,136 @@ private void Init()
7379
}
7480
}
7581

82+
private FilePath NormalizeConfigPath(FilePath path)
83+
{
84+
if (File.Exists(path.Native))
85+
{
86+
return path;
87+
}
88+
89+
if (!Directory.Exists(path.Native))
90+
{
91+
throw new FileNotFoundException("Cannot find repository configuration file", path.Native);
92+
}
93+
94+
var configPath = Path.Combine(path.Native, "config");
95+
96+
if (File.Exists(configPath))
97+
{
98+
return configPath;
99+
}
100+
101+
var gitConfigPath = Path.Combine(path.Native, ".git", "config");
102+
103+
if (File.Exists(gitConfigPath))
104+
{
105+
return gitConfigPath;
106+
}
107+
108+
throw new FileNotFoundException("Cannot find repository configuration file", path.Native);
109+
}
110+
111+
/// <summary>
112+
/// Access configuration values without a repository.
113+
/// <para>
114+
/// Generally you want to access configuration via an instance of <see cref="Repository"/> instead.
115+
/// </para>
116+
/// <para>
117+
/// <paramref name="repositoryConfigurationFileLocation"/> can either contains a path to a file or a directory. In the latter case,
118+
/// this can be the working directory, the .git directory or the directory containing a bare repository.
119+
/// </para>
120+
/// </summary>
121+
/// <param name="repositoryConfigurationFileLocation">Path to an existing Repository configuration file.</param>
122+
/// <returns>An instance of <see cref="Configuration"/>.</returns>
123+
public static Configuration BuildFrom(
124+
string repositoryConfigurationFileLocation)
125+
{
126+
return BuildFrom(repositoryConfigurationFileLocation, null, null, null);
127+
}
128+
129+
/// <summary>
130+
/// Access configuration values without a repository.
131+
/// <para>
132+
/// Generally you want to access configuration via an instance of <see cref="Repository"/> instead.
133+
/// </para>
134+
/// <para>
135+
/// <paramref name="repositoryConfigurationFileLocation"/> can either contains a path to a file or a directory. In the latter case,
136+
/// this can be the working directory, the .git directory or the directory containing a bare repository.
137+
/// </para>
138+
/// </summary>
139+
/// <param name="repositoryConfigurationFileLocation">Path to an existing Repository configuration file.</param>
140+
/// <param name="globalConfigurationFileLocation">Path to a Global configuration file. If null, the default path for a Global configuration file will be probed.</param>
141+
/// <returns>An instance of <see cref="Configuration"/>.</returns>
142+
public static Configuration BuildFrom(
143+
string repositoryConfigurationFileLocation,
144+
string globalConfigurationFileLocation)
145+
{
146+
return BuildFrom(repositoryConfigurationFileLocation, globalConfigurationFileLocation, null, null);
147+
}
148+
149+
/// <summary>
150+
/// Access configuration values without a repository.
151+
/// <para>
152+
/// Generally you want to access configuration via an instance of <see cref="Repository"/> instead.
153+
/// </para>
154+
/// <para>
155+
/// <paramref name="repositoryConfigurationFileLocation"/> can either contains a path to a file or a directory. In the latter case,
156+
/// this can be the working directory, the .git directory or the directory containing a bare repository.
157+
/// </para>
158+
/// </summary>
159+
/// <param name="repositoryConfigurationFileLocation">Path to an existing Repository configuration file.</param>
160+
/// <param name="globalConfigurationFileLocation">Path to a Global configuration file. If null, the default path for a Global configuration file will be probed.</param>
161+
/// <param name="xdgConfigurationFileLocation">Path to a XDG configuration file. If null, the default path for a XDG configuration file will be probed.</param>
162+
/// <returns>An instance of <see cref="Configuration"/>.</returns>
163+
public static Configuration BuildFrom(
164+
string repositoryConfigurationFileLocation,
165+
string globalConfigurationFileLocation,
166+
string xdgConfigurationFileLocation)
167+
{
168+
return BuildFrom(repositoryConfigurationFileLocation, globalConfigurationFileLocation, xdgConfigurationFileLocation, null);
169+
}
170+
171+
/// <summary>
172+
/// Access configuration values without a repository.
173+
/// <para>
174+
/// Generally you want to access configuration via an instance of <see cref="Repository"/> instead.
175+
/// </para>
176+
/// <para>
177+
/// <paramref name="repositoryConfigurationFileLocation"/> can either contains a path to a file or a directory. In the latter case,
178+
/// this can be the working directory, the .git directory or the directory containing a bare repository.
179+
/// </para>
180+
/// </summary>
181+
/// <param name="repositoryConfigurationFileLocation">Path to an existing Repository configuration file.</param>
182+
/// <param name="globalConfigurationFileLocation">Path to a Global configuration file. If null, the default path for a Global configuration file will be probed.</param>
183+
/// <param name="xdgConfigurationFileLocation">Path to a XDG configuration file. If null, the default path for a XDG configuration file will be probed.</param>
184+
/// <param name="systemConfigurationFileLocation">Path to a System configuration file. If null, the default path for a System configuration file will be probed.</param>
185+
/// <returns>An instance of <see cref="Configuration"/>.</returns>
186+
public static Configuration BuildFrom(
187+
string repositoryConfigurationFileLocation,
188+
string globalConfigurationFileLocation,
189+
string xdgConfigurationFileLocation,
190+
string systemConfigurationFileLocation)
191+
{
192+
return new Configuration(null, repositoryConfigurationFileLocation, globalConfigurationFileLocation, xdgConfigurationFileLocation, systemConfigurationFileLocation);
193+
}
194+
76195
/// <summary>
77196
/// Access configuration values without a repository. Generally you want to access configuration via an instance of <see cref="Repository"/> instead.
78197
/// </summary>
79198
/// <param name="globalConfigurationFileLocation">Path to a Global configuration file. If null, the default path for a global configuration file will be probed.</param>
199+
[Obsolete("This method will be removed in the next release. Please use Configuration.BuildFrom(string, string) instead.")]
80200
public Configuration(string globalConfigurationFileLocation)
81-
: this(null, globalConfigurationFileLocation, null, null)
201+
: this(null, null, globalConfigurationFileLocation, null, null)
82202
{ }
83203

84204
/// <summary>
85205
/// Access configuration values without a repository. Generally you want to access configuration via an instance of <see cref="Repository"/> instead.
86206
/// </summary>
87207
/// <param name="globalConfigurationFileLocation">Path to a Global configuration file. If null, the default path for a global configuration file will be probed.</param>
88208
/// <param name="xdgConfigurationFileLocation">Path to a XDG configuration file. If null, the default path for a XDG configuration file will be probed.</param>
209+
[Obsolete("This method will be removed in the next release. Please use Configuration.BuildFrom(string, string, string) instead.")]
89210
public Configuration(string globalConfigurationFileLocation, string xdgConfigurationFileLocation)
90-
: this(null, globalConfigurationFileLocation, xdgConfigurationFileLocation, null)
211+
: this(null, null, globalConfigurationFileLocation, xdgConfigurationFileLocation, null)
91212
{ }
92213

93214
/// <summary>
@@ -96,10 +217,10 @@ public Configuration(string globalConfigurationFileLocation, string xdgConfigura
96217
/// <param name="globalConfigurationFileLocation">Path to a Global configuration file. If null, the default path for a global configuration file will be probed.</param>
97218
/// <param name="xdgConfigurationFileLocation">Path to a XDG configuration file. If null, the default path for a XDG configuration file will be probed.</param>
98219
/// <param name="systemConfigurationFileLocation">Path to a System configuration file. If null, the default path for a system configuration file will be probed.</param>
220+
[Obsolete("This method will be removed in the next release. Please use Configuration.BuildFrom(string, string, string, string) instead.")]
99221
public Configuration(string globalConfigurationFileLocation, string xdgConfigurationFileLocation, string systemConfigurationFileLocation)
100-
: this(null, globalConfigurationFileLocation, xdgConfigurationFileLocation, systemConfigurationFileLocation)
101-
{
102-
}
222+
: this(null, null, globalConfigurationFileLocation, xdgConfigurationFileLocation, systemConfigurationFileLocation)
223+
{ }
103224

104225
/// <summary>
105226
/// Determines which configuration file has been found.

LibGit2Sharp/Repository.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ public Repository(string path, RepositoryOptions options)
125125
config =
126126
new Lazy<Configuration>(
127127
() =>
128-
RegisterForCleanup(new Configuration(this, configurationGlobalFilePath, configurationXDGFilePath,
128+
RegisterForCleanup(new Configuration(this, null, configurationGlobalFilePath, configurationXDGFilePath,
129129
configurationSystemFilePath)));
130130
odb = new Lazy<ObjectDatabase>(() => new ObjectDatabase(this));
131131
diff = new Diff(this);

0 commit comments

Comments
 (0)