using System;
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 globalConfigPath;
private readonly FilePath xdgConfigPath;
private readonly FilePath systemConfigPath;
private readonly Repository repository;
private ConfigurationSafeHandle configHandle;
///
/// Needed for mocking purposes.
///
protected Configuration()
{ }
internal Configuration(Repository repository, string globalConfigurationFileLocation,
string xdgConfigurationFileLocation, string systemConfigurationFileLocation)
{
this.repository = repository;
globalConfigPath = globalConfigurationFileLocation ?? Proxy.git_config_find_global();
xdgConfigPath = xdgConfigurationFileLocation ?? Proxy.git_config_find_xdg();
systemConfigPath = systemConfigurationFileLocation ?? Proxy.git_config_find_system();
Init();
}
private void Init()
{
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);
}
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);
}
}
///
/// 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.
public Configuration(string globalConfigurationFileLocation = null, string xdgConfigurationFileLocation = null, string systemConfigurationFileLocation = null)
: this(null, globalConfigurationFileLocation, xdgConfigurationFileLocation, systemConfigurationFileLocation)
{
}
///
/// Determines which configuration file has been found.
///
public virtual bool HasConfig(ConfigurationLevel level)
{
using (ConfigurationSafeHandle handle = RetrieveConfigurationHandle(level, false))
{
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).
///
/// 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 = ConfigurationLevel.Local)
{
Ensure.ArgumentNotNullOrEmptyString(key, "key");
using (ConfigurationSafeHandle h = RetrieveConfigurationHandle(level, true))
{
Proxy.git_config_delete(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 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");
return Proxy.git_config_get_entry(configHandle, 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 (ConfigurationSafeHandle handle = RetrieveConfigurationHandle(level, false))
{
if (handle == null)
{
return null;
}
return Proxy.git_config_get_entry(handle, key);
}
}
///
/// 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 = ConfigurationLevel.Local)
{
Ensure.ArgumentNotNullOrEmptyString(key, "key");
using (ConfigurationSafeHandle h = RetrieveConfigurationHandle(level, true))
{
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.
/// The configuration file into which the key should be searched for.
/// Matching entries.
public virtual IEnumerable> Find(string regexp,
ConfigurationLevel level = ConfigurationLevel.Local)
{
Ensure.ArgumentNotNullOrEmptyString(regexp, "regexp");
using (ConfigurationSafeHandle h = RetrieveConfigurationHandle(level, true))
{
return Proxy.git_config_iterator_glob(h, regexp, BuildConfigEntry).ToList();
}
}
private ConfigurationSafeHandle RetrieveConfigurationHandle(ConfigurationLevel level, bool throwIfStoreHasNotBeenFound)
{
ConfigurationSafeHandle handle = null;
if (configHandle != null)
{
handle = Proxy.git_config_open_level(configHandle, level);
}
if (handle == null && throwIfStoreHasNotBeenFound)
{
throw new LibGit2SharpException(
string.Format(CultureInfo.InvariantCulture, "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();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return ((IEnumerable>)this).GetEnumerator();
}
private IEnumerable> BuildConfigEntries()
{
return Proxy.git_config_foreach(configHandle, BuildConfigEntry);
}
private static ConfigurationEntry BuildConfigEntry(IntPtr entryPtr)
{
var entry = entryPtr.MarshalAs();
return new ConfigurationEntry(LaxUtf8Marshaler.FromNative(entry.namePtr),
LaxUtf8Marshaler.FromNative(entry.valuePtr),
(ConfigurationLevel)entry.level);
}
///
/// Builds a based on current configuration.
///
/// Name is populated from the user.name setting, and is "unknown" if unspecified.
/// Email is populated from the user.email setting, and is built from
/// and if unspecified.
///
///
/// 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.
public virtual Signature BuildSignature(DateTimeOffset now)
{
return BuildSignature(now, false);
}
internal Signature BuildSignature(DateTimeOffset now, bool shouldThrowIfNotFound)
{
var name = Get("user.name");
var email = Get("user.email");
if (shouldThrowIfNotFound && ((name == null) || (email == null)))
{
throw new LibGit2SharpException("Can not find Name or Email setting of the current user in Git configuration.");
}
return new Signature(
name != null ? name.Value : "unknown",
email != null ? email.Value : string.Format(
CultureInfo.InvariantCulture, "{0}@{1}", Environment.UserName, Environment.UserDomainName),
now);
}
}
}