Skip to content

Should we allow Configuration to parse a local repository config? #1042

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 6, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 44 additions & 30 deletions LibGit2Sharp.Tests/ConfigurationFixture.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using LibGit2Sharp.Tests.TestHelpers;
using Xunit;
using Xunit.Extensions;

namespace LibGit2Sharp.Tests
{
Expand All @@ -14,35 +16,6 @@ private static void AssertValueInLocalConfigFile(string repoPath, string regex)
AssertValueInConfigFile(configFilePath, regex);
}

private static string RetrieveGlobalConfigLocation()
{
string[] variables = { "HOME", "USERPROFILE", };

foreach (string variable in variables)
{
string potentialLocation = Environment.GetEnvironmentVariable(variable);
if (string.IsNullOrEmpty(potentialLocation))
{
continue;
}

string potentialPath = Path.Combine(potentialLocation, ".gitconfig");

if (File.Exists(potentialPath))
{
return potentialPath;
}
}

throw new InvalidOperationException("Unable to determine the location of '.gitconfig' file.");
}

private static void AssertValueInGlobalConfigFile(string regex)
{
string configFilePath = RetrieveGlobalConfigLocation();
AssertValueInConfigFile(configFilePath, regex);
}

[Fact]
public void CanUnsetAnEntryFromTheLocalConfiguration()
{
Expand Down Expand Up @@ -256,7 +229,7 @@ public void CanSetBooleanValue()
[Fact]
public void SettingLocalConfigurationOutsideAReposThrows()
{
using (var config = new Configuration(null, null, null))
using (var config = Configuration.BuildFrom(null, null, null, null))
{
Assert.Throws<LibGit2SharpException>(() => config.Set("unittests.intsetting", 3));
}
Expand Down Expand Up @@ -395,5 +368,46 @@ public void CanTellIfASpecificStoreContainsAKey()
Assert.Null(repo.Config.Get<string>("MCHammer.You-cant-touch-this", ConfigurationLevel.System));
}
}

public static IEnumerable<object[]> ConfigAccessors
{
get
{
return new List<object[]>
{
new[] { new Func<string, string>(p => Path.Combine(p, ".git", "config")) },
new[] { new Func<string, string>(p => Path.Combine(p, ".git")) },
new[] { new Func<string, string>(p => p) },
};
}
}

[Theory, PropertyData("ConfigAccessors")]
public void CanAccessConfigurationWithoutARepository(Func<string, string> localConfigurationPathProvider)
{
var path = SandboxStandardTestRepoGitDir();

string globalConfigPath = CreateConfigurationWithDummyUser(Constants.Signature);
var options = new RepositoryOptions { GlobalConfigurationLocation = globalConfigPath };

using (var repo = new Repository(path, options))
{
repo.Config.Set("my.key", "local");
repo.Config.Set("my.key", "mouse", ConfigurationLevel.Global);
}

using (var config = Configuration.BuildFrom(localConfigurationPathProvider(path), globalConfigPath))
{
Assert.Equal("local", config.Get<string>("my.key").Value);
Assert.Equal("mouse", config.Get<string>("my.key", ConfigurationLevel.Global).Value);
}
}

[Fact]
public void PassingANonExistingLocalConfigurationFileToBuildFromthrowss()
{
Assert.Throws<FileNotFoundException>(() => Configuration.BuildFrom(
Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())));
}
}
}
10 changes: 5 additions & 5 deletions LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -336,19 +336,19 @@ protected string CreateConfigurationWithDummyUser(Signature signature)
protected string CreateConfigurationWithDummyUser(string name, string email)
{
SelfCleaningDirectory scd = BuildSelfCleaningDirectory();
Directory.CreateDirectory(scd.DirectoryPath);
string configFilePath = Path.Combine(scd.DirectoryPath, "global-config");

using (Configuration config = new Configuration(configFilePath))
string configFilePath = Touch(scd.DirectoryPath, "fake-config");

using (Configuration config = Configuration.BuildFrom(configFilePath))
{
if (name != null)
{
config.Set("user.name", name, ConfigurationLevel.Global);
config.Set("user.name", name);
}

if (email != null)
{
config.Set("user.email", email, ConfigurationLevel.Global);
config.Set("user.email", email);
}
}

Expand Down
143 changes: 132 additions & 11 deletions LibGit2Sharp/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@ namespace LibGit2Sharp
public class Configuration : IDisposable,
IEnumerable<ConfigurationEntry<string>>
{
private readonly FilePath repoConfigPath;
private readonly FilePath globalConfigPath;
private readonly FilePath xdgConfigPath;
private readonly FilePath systemConfigPath;

private readonly Repository repository;

private ConfigurationSafeHandle configHandle;

/// <summary>
Expand All @@ -29,19 +28,22 @@ public class Configuration : IDisposable,
protected Configuration()
{ }

internal Configuration(Repository repository, string globalConfigurationFileLocation,
internal Configuration(Repository repository, string repositoryConfigurationFileLocation, string globalConfigurationFileLocation,
string xdgConfigurationFileLocation, string systemConfigurationFileLocation)
{
this.repository = repository;
if (repositoryConfigurationFileLocation != null)
{
repoConfigPath = NormalizeConfigPath(repositoryConfigurationFileLocation);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when does this have to be normalized? Could we have a single expected format for the incoming config file location?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probing was added because @ethomson mentioned once that it might be interesting to not being compelled to pass the full path to the config.

As such, it would accept a working directory, a bare repository folder or a full path to a known config file.

If this is no longer required, I can easily remove it.

}

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

Init();
Init(repository);
}

private void Init()
private void Init(Repository repository)
{
configHandle = Proxy.git_config_new();

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

Proxy.git_repository_set_config(repository.Handle, configHandle);
}
else if (repoConfigPath != null)
{
Proxy.git_config_add_file_ondisk(configHandle, repoConfigPath, ConfigurationLevel.Local);
}

if (globalConfigPath != null)
{
Expand All @@ -73,21 +79,136 @@ private void Init()
}
}

private FilePath NormalizeConfigPath(FilePath path)
{
if (File.Exists(path.Native))
{
return path;
}

if (!Directory.Exists(path.Native))
{
throw new FileNotFoundException("Cannot find repository configuration file", path.Native);
}

var configPath = Path.Combine(path.Native, "config");

if (File.Exists(configPath))
{
return configPath;
}

var gitConfigPath = Path.Combine(path.Native, ".git", "config");

if (File.Exists(gitConfigPath))
{
return gitConfigPath;
}

throw new FileNotFoundException("Cannot find repository configuration file", path.Native);
}

/// <summary>
/// Access configuration values without a repository.
/// <para>
/// Generally you want to access configuration via an instance of <see cref="Repository"/> instead.
/// </para>
/// <para>
/// <paramref name="repositoryConfigurationFileLocation"/> can either contains a path to a file or a directory. In the latter case,
/// this can be the working directory, the .git directory or the directory containing a bare repository.
/// </para>
/// </summary>
/// <param name="repositoryConfigurationFileLocation">Path to an existing Repository configuration file.</param>
/// <returns>An instance of <see cref="Configuration"/>.</returns>
public static Configuration BuildFrom(
string repositoryConfigurationFileLocation)
{
return BuildFrom(repositoryConfigurationFileLocation, null, null, null);
}

/// <summary>
/// Access configuration values without a repository.
/// <para>
/// Generally you want to access configuration via an instance of <see cref="Repository"/> instead.
/// </para>
/// <para>
/// <paramref name="repositoryConfigurationFileLocation"/> can either contains a path to a file or a directory. In the latter case,
/// this can be the working directory, the .git directory or the directory containing a bare repository.
/// </para>
/// </summary>
/// <param name="repositoryConfigurationFileLocation">Path to an existing Repository configuration file.</param>
/// <param name="globalConfigurationFileLocation">Path to a Global configuration file. If null, the default path for a Global configuration file will be probed.</param>
/// <returns>An instance of <see cref="Configuration"/>.</returns>
public static Configuration BuildFrom(
string repositoryConfigurationFileLocation,
string globalConfigurationFileLocation)
{
return BuildFrom(repositoryConfigurationFileLocation, globalConfigurationFileLocation, null, null);
}

/// <summary>
/// Access configuration values without a repository.
/// <para>
/// Generally you want to access configuration via an instance of <see cref="Repository"/> instead.
/// </para>
/// <para>
/// <paramref name="repositoryConfigurationFileLocation"/> can either contains a path to a file or a directory. In the latter case,
/// this can be the working directory, the .git directory or the directory containing a bare repository.
/// </para>
/// </summary>
/// <param name="repositoryConfigurationFileLocation">Path to an existing Repository configuration file.</param>
/// <param name="globalConfigurationFileLocation">Path to a Global configuration file. If null, the default path for a Global configuration file will be probed.</param>
/// <param name="xdgConfigurationFileLocation">Path to a XDG configuration file. If null, the default path for a XDG configuration file will be probed.</param>
/// <returns>An instance of <see cref="Configuration"/>.</returns>
public static Configuration BuildFrom(
string repositoryConfigurationFileLocation,
string globalConfigurationFileLocation,
string xdgConfigurationFileLocation)
{
return BuildFrom(repositoryConfigurationFileLocation, globalConfigurationFileLocation, xdgConfigurationFileLocation, null);
}

/// <summary>
/// Access configuration values without a repository.
/// <para>
/// Generally you want to access configuration via an instance of <see cref="Repository"/> instead.
/// </para>
/// <para>
/// <paramref name="repositoryConfigurationFileLocation"/> can either contains a path to a file or a directory. In the latter case,
/// this can be the working directory, the .git directory or the directory containing a bare repository.
/// </para>
/// </summary>
/// <param name="repositoryConfigurationFileLocation">Path to an existing Repository configuration file.</param>
/// <param name="globalConfigurationFileLocation">Path to a Global configuration file. If null, the default path for a Global configuration file will be probed.</param>
/// <param name="xdgConfigurationFileLocation">Path to a XDG configuration file. If null, the default path for a XDG configuration file will be probed.</param>
/// <param name="systemConfigurationFileLocation">Path to a System configuration file. If null, the default path for a System configuration file will be probed.</param>
/// <returns>An instance of <see cref="Configuration"/>.</returns>
public static Configuration BuildFrom(
string repositoryConfigurationFileLocation,
string globalConfigurationFileLocation,
string xdgConfigurationFileLocation,
string systemConfigurationFileLocation)
{
return new Configuration(null, repositoryConfigurationFileLocation, globalConfigurationFileLocation, xdgConfigurationFileLocation, systemConfigurationFileLocation);
}

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

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

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

/// <summary>
/// Determines which configuration file has been found.
Expand Down
2 changes: 1 addition & 1 deletion LibGit2Sharp/Repository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public Repository(string path, RepositoryOptions options)
config =
new Lazy<Configuration>(
() =>
RegisterForCleanup(new Configuration(this, configurationGlobalFilePath, configurationXDGFilePath,
RegisterForCleanup(new Configuration(this, null, configurationGlobalFilePath, configurationXDGFilePath,
configurationSystemFilePath)));
odb = new Lazy<ObjectDatabase>(() => new ObjectDatabase(this));
diff = new Diff(this);
Expand Down