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