From f560b17b7886a7c07571eab3d5b1084bf4d12971 Mon Sep 17 00:00:00 2001 From: Daniel Cazzulino Date: Fri, 27 Sep 2019 00:54:55 -0300 Subject: [PATCH 1/3] Add support for adding and clearing multi-valued configuration Implement value adding by exposing the underlying `set_multivar`, which supports only string values in the underlying libgit2, so no other typed overloads are provided at this point. The counterpart for deleting keys exposes the underlying `delete_multivar`. No regex-based overload is exposed for consistency with the existing `Set` overloads which don't expose it either. Also exposed the boolean return value from the `Unset` calls which is already present in the Proxy API. Fixes #1719. Fix --- LibGit2Sharp.Tests/ConfigurationFixture.cs | 113 +++++++++++++++++++++ LibGit2Sharp/Configuration.cs | 75 ++++++++++++-- LibGit2Sharp/Core/NativeMethods.cs | 7 ++ LibGit2Sharp/Core/Proxy.cs | 8 ++ LibGit2Sharp/RemoteUpdater.cs | 4 +- 5 files changed, 199 insertions(+), 8 deletions(-) diff --git a/LibGit2Sharp.Tests/ConfigurationFixture.cs b/LibGit2Sharp.Tests/ConfigurationFixture.cs index 5bb985b68..999aa0336 100644 --- a/LibGit2Sharp.Tests/ConfigurationFixture.cs +++ b/LibGit2Sharp.Tests/ConfigurationFixture.cs @@ -50,6 +50,119 @@ public void CanUnsetAnEntryFromTheGlobalConfiguration() } } + [Fact] + public void CanAddAndReadMultivarFromTheLocalConfiguration() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.Empty(repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin")); + + repo.Config.Add("unittests.plugin", "value1", ConfigurationLevel.Local); + repo.Config.Add("unittests.plugin", "value2", ConfigurationLevel.Local); + + Assert.Equal(new[] { "value1", "value2" }, repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin" && x.Level == ConfigurationLevel.Local) + .Select(x => x.Value) + .ToArray()); + } + } + + [Fact] + public void CanAddAndReadMultivarFromTheGlobalConfiguration() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + Assert.True(repo.Config.HasConfig(ConfigurationLevel.Global)); + Assert.Empty(repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin")); + + repo.Config.Add("unittests.plugin", "value1", ConfigurationLevel.Global); + repo.Config.Add("unittests.plugin", "value2", ConfigurationLevel.Global); + + Assert.Equal(new[] { "value1", "value2" }, repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin") + .Select(x => x.Value) + .ToArray()); + } + } + + [Fact] + public void CanUnsetAllFromTheGlobalConfiguration() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + Assert.True(repo.Config.HasConfig(ConfigurationLevel.Global)); + Assert.Empty(repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin") + .Select(x => x.Value) + .ToArray()); + + repo.Config.Add("unittests.plugin", "value1", ConfigurationLevel.Global); + repo.Config.Add("unittests.plugin", "value2", ConfigurationLevel.Global); + + Assert.Equal(2, repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin" && x.Level == ConfigurationLevel.Global) + .Select(x => x.Value) + .Count()); + + repo.Config.UnsetAll("unittests.plugin"); + + Assert.Equal(2, repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin" && x.Level == ConfigurationLevel.Global) + .Select(x => x.Value) + .Count()); + + repo.Config.UnsetAll("unittests.plugin", ConfigurationLevel.Global); + + Assert.Empty(repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin") + .Select(x => x.Value) + .ToArray()); + } + } + + [Fact] + public void CanUnsetAllFromTheLocalConfiguration() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.True(repo.Config.HasConfig(ConfigurationLevel.Global)); + Assert.Empty(repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin") + .Select(x => x.Value) + .ToArray()); + + repo.Config.Add("unittests.plugin", "value1"); + repo.Config.Add("unittests.plugin", "value2"); + + Assert.Equal(2, repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin" && x.Level == ConfigurationLevel.Local) + .Select(x => x.Value) + .Count()); + + repo.Config.UnsetAll("unittests.plugin"); + + Assert.Empty(repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin")); + } + } + [Fact] public void CanReadBooleanValue() { diff --git a/LibGit2Sharp/Configuration.cs b/LibGit2Sharp/Configuration.cs index 9297a5c37..3c8f32d76 100644 --- a/LibGit2Sharp/Configuration.cs +++ b/LibGit2Sharp/Configuration.cs @@ -233,9 +233,9 @@ public void Dispose() /// Unset a configuration variable (key and value) in the local configuration. /// /// The key to unset. - public virtual void Unset(string key) + public virtual bool Unset(string key) { - Unset(key, ConfigurationLevel.Local); + return Unset(key, ConfigurationLevel.Local); } /// @@ -243,23 +243,37 @@ public virtual void Unset(string key) /// /// 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) + public virtual bool Unset(string key, ConfigurationLevel level) { Ensure.ArgumentNotNullOrEmptyString(key, "key"); using (ConfigurationHandle h = RetrieveConfigurationHandle(level, true, configHandle)) { - Proxy.git_config_delete(h, key); + return Proxy.git_config_delete(h, key); } } - internal void UnsetMultivar(string key, ConfigurationLevel level) + /// + /// Unset a configuration values in a multivar variable (key and value) in the local configuration. + /// + /// The key to unset. + public virtual bool UnsetAll(string key) + { + return UnsetAll(key, ConfigurationLevel.Local); + } + + /// + /// Unset all configuration values in a multivar variable (key and value). + /// + /// The key to unset. + /// The configuration file which should be considered as the target of this operation + public virtual bool UnsetAll(string key, ConfigurationLevel level) { Ensure.ArgumentNotNullOrEmptyString(key, "key"); using (ConfigurationHandle h = RetrieveConfigurationHandle(level, true, configHandle)) { - Proxy.git_config_delete_multivar(h, key); + return Proxy.git_config_delete_multivar(h, key); } } @@ -634,6 +648,55 @@ public virtual void Set(string key, T value, ConfigurationLevel level) } } + /// + /// Adds a configuration value for a multivalue key in the local configuration. Keys are in the form 'section.name'. + /// + /// For example in order to add the value for this in a .git\config file: + /// + /// [test] + /// plugin = first + /// + /// You would call: + /// + /// repo.Config.Add("test.plugin", "first"); + /// + /// + /// The configuration value type + /// The key parts + /// The value + public virtual void Add(string key, string value) + { + Add(key, value, ConfigurationLevel.Local); + } + + /// + /// Adds a configuration value for a multivalue key. Keys are in the form 'section.name'. + /// + /// For example in order to add the value for this in a .git\config file: + /// + /// [test] + /// plugin = first + /// + /// You would call: + /// + /// repo.Config.Add("test.plugin", "first"); + /// + /// + /// 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 Add(string key, string value, ConfigurationLevel level) + { + Ensure.ArgumentNotNull(value, "value"); + Ensure.ArgumentNotNullOrEmptyString(key, "key"); + + using (ConfigurationHandle h = RetrieveConfigurationHandle(level, true, configHandle)) + { + Proxy.git_config_add_string(h, key, value); + } + } + /// /// Find configuration entries matching . /// diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index d237832be..7079ecdc4 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -497,6 +497,13 @@ internal static extern unsafe int git_config_delete_multivar( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string regexp); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_set_multivar( + git_config* cfg, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string regexp, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string value); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_config_find_global(GitBuf global_config_path); diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index de31b4a1e..c3a53b95e 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -601,6 +601,14 @@ public static unsafe void git_config_set_string(ConfigurationHandle config, stri Ensure.ZeroResult(res); } + static readonly string non_existing_regex = Guid.NewGuid().ToString(); + + public static unsafe void git_config_add_string(ConfigurationHandle config, string name, string value) + { + int res = NativeMethods.git_config_set_multivar(config, name, non_existing_regex, value); + Ensure.ZeroResult(res); + } + public static unsafe ICollection git_config_foreach( ConfigurationHandle config, Func resultSelector) diff --git a/LibGit2Sharp/RemoteUpdater.cs b/LibGit2Sharp/RemoteUpdater.cs index ec8b08bcd..53fd33a4b 100644 --- a/LibGit2Sharp/RemoteUpdater.cs +++ b/LibGit2Sharp/RemoteUpdater.cs @@ -56,7 +56,7 @@ private IEnumerable GetFetchRefSpecs() private void SetFetchRefSpecs(IEnumerable value) { - repo.Config.UnsetMultivar(string.Format("remote.{0}.fetch", remoteName), ConfigurationLevel.Local); + repo.Config.UnsetAll(string.Format("remote.{0}.fetch", remoteName), ConfigurationLevel.Local); foreach (var url in value) { @@ -74,7 +74,7 @@ private IEnumerable GetPushRefSpecs() private void SetPushRefSpecs(IEnumerable value) { - repo.Config.UnsetMultivar(string.Format("remote.{0}.push", remoteName), ConfigurationLevel.Local); + repo.Config.UnsetAll(string.Format("remote.{0}.push", remoteName), ConfigurationLevel.Local); foreach (var url in value) { From 6a8463e097ec2b76c7935923ed3d4b7c51cdb3f9 Mon Sep 17 00:00:00 2001 From: Daniel Cazzulino Date: Fri, 27 Sep 2019 22:12:39 -0300 Subject: [PATCH 2/3] Update to latest LTS stable version for .NET Core 2.1 This should fix travis failures in CI --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 573c44408..cc1e3c35a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: csharp dist: xenial -dotnet: 2.1.506 +dotnet: 2.1.802 mono: none osx_image: xcode8.3 From 4081885645970e440c0495140a72cb810a0255d1 Mon Sep 17 00:00:00 2001 From: Daniel Cazzulino Date: Wed, 9 Oct 2019 14:44:21 -0300 Subject: [PATCH 3/3] Update LibGit2Sharp/Configuration.cs Co-Authored-By: Brandon Ording --- LibGit2Sharp/Configuration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LibGit2Sharp/Configuration.cs b/LibGit2Sharp/Configuration.cs index 3c8f32d76..cb9bc6815 100644 --- a/LibGit2Sharp/Configuration.cs +++ b/LibGit2Sharp/Configuration.cs @@ -254,7 +254,7 @@ public virtual bool Unset(string key, ConfigurationLevel level) } /// - /// Unset a configuration values in a multivar variable (key and value) in the local configuration. + /// Unset all configuration values in a multivar variable (key and value) in the local configuration. /// /// The key to unset. public virtual bool UnsetAll(string key)