using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using LibGit2Sharp.Core;
using LibGit2Sharp.Core.Handles;
namespace LibGit2Sharp
{
///
/// Provides access to configuration variables for a repository.
///
public class Configuration : IDisposable,
IEnumerable>
{
private readonly FilePath repoConfigPath;
private readonly FilePath globalConfigPath;
private readonly FilePath xdgConfigPath;
private readonly FilePath systemConfigPath;
private ConfigurationHandle configHandle;
///
/// Needed for mocking purposes.
///
protected Configuration()
{ }
internal Configuration(
Repository repository,
string repositoryConfigurationFileLocation,
string globalConfigurationFileLocation,
string xdgConfigurationFileLocation,
string systemConfigurationFileLocation)
{
if (repositoryConfigurationFileLocation != null)
{
repoConfigPath = NormalizeConfigPath(repositoryConfigurationFileLocation);
}
globalConfigPath = globalConfigurationFileLocation ?? Proxy.git_config_find_global();
xdgConfigPath = xdgConfigurationFileLocation ?? Proxy.git_config_find_xdg();
systemConfigPath = systemConfigurationFileLocation ?? Proxy.git_config_find_system();
Init(repository);
}
private void Init(Repository repository)
{
configHandle = Proxy.git_config_new();
if (repository != null)
{
//TODO: push back this logic into libgit2.
// As stated by @carlosmn "having a helper function to load the defaults and then allowing you
// to modify it before giving it to git_repository_open_ext() would be a good addition, I think."
// -- Agreed :)
string repoConfigLocation = Path.Combine(repository.Info.Path, "config");
Proxy.git_config_add_file_ondisk(configHandle, repoConfigLocation, ConfigurationLevel.Local);
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)
{
Proxy.git_config_add_file_ondisk(configHandle, globalConfigPath, ConfigurationLevel.Global);
}
if (xdgConfigPath != null)
{
Proxy.git_config_add_file_ondisk(configHandle, xdgConfigPath, ConfigurationLevel.Xdg);
}
if (systemConfigPath != null)
{
Proxy.git_config_add_file_ondisk(configHandle, systemConfigPath, ConfigurationLevel.System);
}
}
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);
}
///
/// Access configuration values without a repository.
///
/// Generally you want to access configuration via an instance of instead.
///
///
/// 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.
///
///
/// Path to an existing Repository configuration file.
/// An instance of .
public static Configuration BuildFrom(string repositoryConfigurationFileLocation)
{
return BuildFrom(repositoryConfigurationFileLocation, null, null, null);
}
///
/// Access configuration values without a repository.
///
/// Generally you want to access configuration via an instance of instead.
///
///
/// 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.
///
///
/// Path to an existing Repository configuration file.
/// Path to a Global configuration file. If null, the default path for a Global configuration file will be probed.
/// An instance of .
public static Configuration BuildFrom(
string repositoryConfigurationFileLocation,
string globalConfigurationFileLocation)
{
return BuildFrom(repositoryConfigurationFileLocation, globalConfigurationFileLocation, null, null);
}
///
/// Access configuration values without a repository.
///
/// Generally you want to access configuration via an instance of instead.
///
///
/// 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.
///
///
/// Path to an existing Repository configuration file.
/// Path to a Global configuration file. If null, the default path for a Global configuration file will be probed.
/// Path to a XDG configuration file. If null, the default path for a XDG configuration file will be probed.
/// An instance of .
public static Configuration BuildFrom(
string repositoryConfigurationFileLocation,
string globalConfigurationFileLocation,
string xdgConfigurationFileLocation)
{
return BuildFrom(repositoryConfigurationFileLocation, globalConfigurationFileLocation, xdgConfigurationFileLocation, null);
}
///
/// Access configuration values without a repository.
///
/// Generally you want to access configuration via an instance of instead.
///
///
/// 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.
///
///
/// Path to an existing Repository configuration file.
/// Path to a Global configuration file. If null, the default path for a Global configuration file will be probed.
/// Path to a XDG configuration file. If null, the default path for a XDG configuration file will be probed.
/// Path to a System configuration file. If null, the default path for a System configuration file will be probed.
/// An instance of .
public static Configuration BuildFrom(
string repositoryConfigurationFileLocation,
string globalConfigurationFileLocation,
string xdgConfigurationFileLocation,
string systemConfigurationFileLocation)
{
return new Configuration(null, repositoryConfigurationFileLocation, globalConfigurationFileLocation, xdgConfigurationFileLocation, systemConfigurationFileLocation);
}
///
/// Access configuration values without a repository. Generally you want to access configuration via an instance of instead.
///
/// Path to a Global configuration file. If null, the default path for a global configuration file will be probed.
[Obsolete("This method will be removed in the next release. Please use Configuration.BuildFrom(string, string) instead.")]
public Configuration(string globalConfigurationFileLocation)
: this(null, null, globalConfigurationFileLocation, null, null)
{ }
///
/// Access configuration values without a repository. Generally you want to access configuration via an instance of instead.
///
/// Path to a Global configuration file. If null, the default path for a global configuration file will be probed.
/// Path to a XDG configuration file. If null, the default path for a XDG configuration file will be probed.
[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, null, globalConfigurationFileLocation, xdgConfigurationFileLocation, null)
{ }
///
/// Access configuration values without a repository. Generally you want to access configuration via an instance of instead.
///
/// Path to a Global configuration file. If null, the default path for a global configuration file will be probed.
/// Path to a XDG configuration file. If null, the default path for a XDG configuration file will be probed.
/// Path to a System configuration file. If null, the default path for a system configuration file will be probed.
[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, null, globalConfigurationFileLocation, xdgConfigurationFileLocation, systemConfigurationFileLocation)
{ }
///
/// Determines which configuration file has been found.
///
public virtual bool HasConfig(ConfigurationLevel level)
{
using (ConfigurationHandle snapshot = Snapshot())
using (ConfigurationHandle handle = RetrieveConfigurationHandle(level, false, snapshot))
{
return handle != null;
}
}
#region IDisposable Members
///
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// Saves any open configuration files.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
///
/// Unset a configuration variable (key and value) in the local configuration.
///
/// The key to unset.
public virtual void Unset(string key)
{
Unset(key, ConfigurationLevel.Local);
}
///
/// Unset a configuration variable (key and value).
///
/// The key to unset.
/// The configuration file which should be considered as the target of this operation
public virtual void Unset(string key, ConfigurationLevel level)
{
Ensure.ArgumentNotNullOrEmptyString(key, "key");
using (ConfigurationHandle h = RetrieveConfigurationHandle(level, true, configHandle))
{
Proxy.git_config_delete(h, key);
}
}
internal void UnsetMultivar(string key, ConfigurationLevel level)
{
Ensure.ArgumentNotNullOrEmptyString(key, "key");
using (ConfigurationHandle h = RetrieveConfigurationHandle(level, true, configHandle))
{
Proxy.git_config_delete_multivar(h, key);
}
}
///
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
///
protected virtual void Dispose(bool disposing)
{
configHandle.SafeDispose();
}
///
/// Get a configuration value for the given key parts.
///
/// For example in order to get the value for this in a .git\config file:
///
///
/// [core]
/// bare = true
///
///
/// You would call:
///
///
/// bool isBare = repo.Config.Get<bool>(new []{ "core", "bare" }).Value;
///
///
///
/// The configuration value type
/// The key parts
/// The , or null if not set
public virtual ConfigurationEntry Get(string[] keyParts)
{
Ensure.ArgumentNotNull(keyParts, "keyParts");
return Get(string.Join(".", keyParts));
}
///
/// Get a configuration value for the given key parts.
///
/// For example in order to get the value for this in a .git\config file:
///
///
/// [difftool "kdiff3"]
/// path = c:/Program Files/KDiff3/kdiff3.exe
///
///
/// You would call:
///
///
/// string where = repo.Config.Get<string>("difftool", "kdiff3", "path").Value;
///
///
///
/// The configuration value type
/// The first key part
/// The second key part
/// The third key part
/// The , or null if not set
public virtual ConfigurationEntry Get(string firstKeyPart, string secondKeyPart, string thirdKeyPart)
{
Ensure.ArgumentNotNullOrEmptyString(firstKeyPart, "firstKeyPart");
Ensure.ArgumentNotNullOrEmptyString(secondKeyPart, "secondKeyPart");
Ensure.ArgumentNotNullOrEmptyString(thirdKeyPart, "thirdKeyPart");
return Get(new[] { firstKeyPart, secondKeyPart, thirdKeyPart });
}
///
/// Get a configuration value for a key. Keys are in the form 'section.name'.
///
/// The same escalation logic than in git.git will be used when looking for the key in the config files:
/// - local: the Git file in the current repository
/// - global: the Git file specific to the current interactive user (usually in `$HOME/.gitconfig`)
/// - xdg: another Git file specific to the current interactive user (usually in `$HOME/.config/git/config`)
/// - system: the system-wide Git file
///
/// The first occurence of the key will be returned.
///
///
/// For example in order to get the value for this in a .git\config file:
///
///
/// [core]
/// bare = true
///
///
/// You would call:
///
///
/// bool isBare = repo.Config.Get<bool>("core.bare").Value;
///
///
///
/// The configuration value type
/// The key
/// The , or null if not set
public virtual ConfigurationEntry Get(string key)
{
Ensure.ArgumentNotNullOrEmptyString(key, "key");
using (ConfigurationHandle snapshot = Snapshot())
{
return Proxy.git_config_get_entry(snapshot, key);
}
}
///
/// Get a configuration value for a key. Keys are in the form 'section.name'.
///
/// For example in order to get the value for this in a .git\config file:
///
///
/// [core]
/// bare = true
///
///
/// You would call:
///
///
/// bool isBare = repo.Config.Get<bool>("core.bare").Value;
///
///
///
/// The configuration value type
/// The key
/// The configuration file into which the key should be searched for
/// The , or null if not set
public virtual ConfigurationEntry Get(string key, ConfigurationLevel level)
{
Ensure.ArgumentNotNullOrEmptyString(key, "key");
using (ConfigurationHandle snapshot = Snapshot())
using (ConfigurationHandle handle = RetrieveConfigurationHandle(level, false, snapshot))
{
if (handle == null)
{
return null;
}
return Proxy.git_config_get_entry(handle, key);
}
}
///
/// Get a configuration value for the given key.
///
/// The configuration value type.
/// The key
/// The configuration value, or the default value for the selected if not found
public virtual T GetValueOrDefault(string key)
{
return ValueOrDefault(Get(key), default(T));
}
///
/// Get a configuration value for the given key,
/// or if the key is not set.
///
/// The configuration value type.
/// The key
/// The default value if the key is not set.
/// The configuration value, or the default value
public virtual T GetValueOrDefault(string key, T defaultValue)
{
return ValueOrDefault(Get(key), defaultValue);
}
///
/// Get a configuration value for the given key
///
/// The configuration value type.
/// The key.
/// The configuration file into which the key should be searched for.
/// The configuration value, or the default value for if not found
public virtual T GetValueOrDefault(string key, ConfigurationLevel level)
{
return ValueOrDefault(Get(key, level), default(T));
}
///
/// Get a configuration value for the given key,
/// or if the key is not set.
///
/// The configuration value type.
/// The key.
/// The configuration file into which the key should be searched for.
/// The selector used to generate a default value if the key is not set.
/// The configuration value, or the default value.
public virtual T GetValueOrDefault(string key, ConfigurationLevel level, T defaultValue)
{
return ValueOrDefault(Get(key, level), defaultValue);
}
///
/// Get a configuration value for the given key parts
///
/// The configuration value type.
/// The key parts.
/// The configuration value, or the default value for if not found
public virtual T GetValueOrDefault(string[] keyParts)
{
return ValueOrDefault(Get(keyParts), default(T));
}
///
/// Get a configuration value for the given key parts,
/// or if the key is not set.
///
/// The configuration value type.
/// The key parts.
/// The default value if the key is not set.
/// The configuration value, or the default value.
public virtual T GetValueOrDefault(string[] keyParts, T defaultValue)
{
return ValueOrDefault(Get(keyParts), defaultValue);
}
///
/// Get a configuration value for the given key parts.
///
/// The configuration value type.
/// The first key part.
/// The second key part.
/// The third key part.
/// The configuration value, or the default value for the selected if not found
public virtual T GetValueOrDefault(string firstKeyPart, string secondKeyPart, string thirdKeyPart)
{
return ValueOrDefault(Get(firstKeyPart, secondKeyPart, thirdKeyPart), default(T));
}
///
/// Get a configuration value for the given key parts,
/// or if the key is not set.
///
/// The configuration value type.
/// The first key part.
/// The second key part.
/// The third key part.
/// The default value if the key is not set.
/// The configuration value, or the default.
public virtual T GetValueOrDefault(string firstKeyPart, string secondKeyPart, string thirdKeyPart, T defaultValue)
{
return ValueOrDefault(Get(firstKeyPart, secondKeyPart, thirdKeyPart), defaultValue);
}
///
/// Get a configuration value for the given key,
/// or a value generated by
/// if the key is not set.
///
/// The configuration value type.
/// The key
/// The selector used to generate a default value if the key is not set.
/// The configuration value, or a generated default.
public virtual T GetValueOrDefault(string key, Func defaultValueSelector)
{
return ValueOrDefault(Get(key), defaultValueSelector);
}
///
/// Get a configuration value for the given key,
/// or a value generated by
/// if the key is not set.
///
/// The configuration value type.
/// The key.
/// The configuration file into which the key should be searched for.
/// The selector used to generate a default value if the key is not set.
/// The configuration value, or a generated default.
public virtual T GetValueOrDefault(string key, ConfigurationLevel level, Func defaultValueSelector)
{
return ValueOrDefault(Get(key, level), defaultValueSelector);
}
///
/// Get a configuration value for the given key parts,
/// or a value generated by
/// if the key is not set.
///
/// The configuration value type.
/// The key parts.
/// The selector used to generate a default value if the key is not set.
/// The configuration value, or a generated default.
public virtual T GetValueOrDefault(string[] keyParts, Func defaultValueSelector)
{
return ValueOrDefault(Get(keyParts), defaultValueSelector);
}
///
/// Get a configuration value for the given key parts,
/// or a value generated by
/// if the key is not set.
///
/// The configuration value type.
/// The first key part.
/// The second key part.
/// The third key part.
/// The selector used to generate a default value if the key is not set.
/// The configuration value, or a generated default.
public virtual T GetValueOrDefault(string firstKeyPart, string secondKeyPart, string thirdKeyPart, Func defaultValueSelector)
{
return ValueOrDefault(Get(firstKeyPart, secondKeyPart, thirdKeyPart), defaultValueSelector);
}
private static T ValueOrDefault(ConfigurationEntry value, T defaultValue)
{
return value == null ? defaultValue : value.Value;
}
private static T ValueOrDefault(ConfigurationEntry value, Func defaultValueSelector)
{
Ensure.ArgumentNotNull(defaultValueSelector, "defaultValueSelector");
return value == null
? defaultValueSelector()
: value.Value;
}
///
/// Set a configuration value for a key in the local configuration. Keys are in the form 'section.name'.
///
/// For example in order to set the value for this in a .git\config file:
///
/// [test]
/// boolsetting = true
///
/// You would call:
///
/// repo.Config.Set("test.boolsetting", true);
///
///
/// The configuration value type
/// The key parts
/// The value
public virtual void Set(string key, T value)
{
Set(key, value, ConfigurationLevel.Local);
}
///
/// Set a configuration value for a key. Keys are in the form 'section.name'.
///
/// For example in order to set the value for this in a .git\config file:
///
/// [test]
/// boolsetting = true
///
/// You would call:
///
/// repo.Config.Set("test.boolsetting", true);
///
///
/// The configuration value type
/// The key parts
/// The value
/// The configuration file which should be considered as the target of this operation
public virtual void Set(string key, T value, ConfigurationLevel level)
{
Ensure.ArgumentNotNull(value, "value");
Ensure.ArgumentNotNullOrEmptyString(key, "key");
using (ConfigurationHandle h = RetrieveConfigurationHandle(level, true, configHandle))
{
if (!configurationTypedUpdater.ContainsKey(typeof(T)))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Generic Argument of type '{0}' is not supported.", typeof(T).FullName));
}
configurationTypedUpdater[typeof(T)](key, value, h);
}
}
///
/// Find configuration entries matching .
///
/// A regular expression.
/// Matching entries.
public virtual IEnumerable> Find(string regexp)
{
return Find(regexp, ConfigurationLevel.Local);
}
///
/// Find configuration entries matching .
///
/// A regular expression.
/// The configuration file into which the key should be searched for.
/// Matching entries.
public virtual IEnumerable> Find(string regexp, ConfigurationLevel level)
{
Ensure.ArgumentNotNullOrEmptyString(regexp, "regexp");
using (ConfigurationHandle snapshot = Snapshot())
using (ConfigurationHandle h = RetrieveConfigurationHandle(level, true, snapshot))
{
return Proxy.git_config_iterator_glob(h, regexp).ToList();
}
}
private ConfigurationHandle RetrieveConfigurationHandle(ConfigurationLevel level, bool throwIfStoreHasNotBeenFound, ConfigurationHandle fromHandle)
{
ConfigurationHandle handle = null;
if (fromHandle != null)
{
handle = Proxy.git_config_open_level(fromHandle, level);
}
if (handle == null && throwIfStoreHasNotBeenFound)
{
throw new LibGit2SharpException("No {0} configuration file has been found.",
Enum.GetName(typeof(ConfigurationLevel), level));
}
return handle;
}
private static Action GetUpdater(Action setter)
{
return (key, val, handle) => setter(handle, key, (T)val);
}
private readonly static IDictionary> configurationTypedUpdater = new Dictionary>
{
{ typeof(int), GetUpdater(Proxy.git_config_set_int32) },
{ typeof(long), GetUpdater(Proxy.git_config_set_int64) },
{ typeof(bool), GetUpdater(Proxy.git_config_set_bool) },
{ typeof(string), GetUpdater(Proxy.git_config_set_string) },
};
///
/// Returns an enumerator that iterates through the configuration entries.
///
/// An object that can be used to iterate through the configuration entries.
public virtual IEnumerator> GetEnumerator()
{
return BuildConfigEntries().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable>)this).GetEnumerator();
}
private IEnumerable> BuildConfigEntries()
{
return Proxy.git_config_foreach(configHandle, BuildConfigEntry);
}
internal static unsafe ConfigurationEntry BuildConfigEntry(IntPtr entryPtr)
{
var entry = (GitConfigEntry*)entryPtr.ToPointer();
return new ConfigurationEntry(LaxUtf8Marshaler.FromNative(entry->namePtr),
LaxUtf8Marshaler.FromNative(entry->valuePtr),
(ConfigurationLevel)entry->level);
}
///
/// Builds a based on current configuration. If it is not found or
/// some configuration is missing, null is returned.
///
/// The same escalation logic than in git.git will be used when looking for the key in the config files:
/// - local: the Git file in the current repository
/// - global: the Git file specific to the current interactive user (usually in `$HOME/.gitconfig`)
/// - xdg: another Git file specific to the current interactive user (usually in `$HOME/.config/git/config`)
/// - system: the system-wide Git file
///
///
/// The timestamp to use for the .
/// The signature or null if no user identity can be found in the configuration.
public virtual Signature BuildSignature(DateTimeOffset now)
{
var name = this.GetValueOrDefault("user.name");
var email = this.GetValueOrDefault("user.email");
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(email))
{
return null;
}
return new Signature(name, email, now);
}
internal Signature BuildSignatureOrThrow(DateTimeOffset now)
{
var signature = BuildSignature(now);
if (signature == null)
{
throw new LibGit2SharpException("This overload requires 'user.name' and 'user.email' to be set. " +
"Use a different overload or set those variables in the configuation");
}
return signature;
}
private ConfigurationHandle Snapshot()
{
return Proxy.git_config_snapshot(configHandle);
}
}
}