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); } } }