From 760da5dbcc142e070864eb3cb864e5c8e526b0c6 Mon Sep 17 00:00:00 2001 From: Pigalot Date: Sat, 12 May 2018 18:25:35 +0100 Subject: [PATCH] Dry run at config test fails --- .../ConfigurationBackendFixture.cs | 98 ++++++++++++++ LibGit2Sharp/Configuration.cs | 28 +++- ...nfigBackend.cs => ConfigurationBackend.cs} | 96 +++++++++----- LibGit2Sharp/ConfigurationEntry.cs | 21 ++- LibGit2Sharp/ConfigurationIterator.cs | 125 ++++++++++++++++++ LibGit2Sharp/Core/GitConfigBackend.cs | 7 +- LibGit2Sharp/Core/GitConfigIterator.cs | 33 +++++ LibGit2Sharp/Core/NativeMethods.cs | 4 + LibGit2Sharp/Core/Proxy.cs | 5 + 9 files changed, 377 insertions(+), 40 deletions(-) create mode 100644 LibGit2Sharp.Tests/ConfigurationBackendFixture.cs rename LibGit2Sharp/{ConfigBackend.cs => ConfigurationBackend.cs} (80%) create mode 100644 LibGit2Sharp/ConfigurationIterator.cs create mode 100644 LibGit2Sharp/Core/GitConfigIterator.cs diff --git a/LibGit2Sharp.Tests/ConfigurationBackendFixture.cs b/LibGit2Sharp.Tests/ConfigurationBackendFixture.cs new file mode 100644 index 000000000..c00742e0c --- /dev/null +++ b/LibGit2Sharp.Tests/ConfigurationBackendFixture.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class ConfigurationBackendFixture : BaseFixture + { + [Fact] + public void CanCreateInMemoryRepositoryWithBackend() + { + using (var repo = new Repository()) + { + repo.Config.AddBackend(new MockConfigurationBackend(), ConfigurationLevel.Local); + + Assert.True(repo.Info.IsBare); + Assert.Null(repo.Info.Path); + Assert.Null(repo.Info.WorkingDirectory); + + Assert.Throws(() => { var idx = repo.Index; }); + } + } + + #region MockBackend + + private class MockConfigurationBackend : ConfigurationBackend + { + protected override ConfigBackendOperations SupportedOperations + { + get { return ConfigBackendOperations.Open; } + } + + public override int Open(ConfigurationLevel level) + { + return 0; + } + + public override int Get(string key, out ConfigurationEntry configurationEntry) + { + throw new NotImplementedException(); + } + + public override int Set(string key, string value) + { + throw new NotImplementedException(); + } + + public override int SetMultivar(string Name, string regexp, string value) + { + throw new NotImplementedException(); + } + + public override int Del(string key) + { + throw new NotImplementedException(); + } + + public override int DelMultivar(string Name, string regexp) + { + throw new NotImplementedException(); + } + + public override int Iterator(out MockConfigurationIterator iterator) + { + throw new NotImplementedException(); + } + + public override int Snapshot(out ConfigurationBackend configSnapshot) + { + throw new NotImplementedException(); + } + + public override int Lock() + { + throw new NotImplementedException(); + } + + public override int Unlock(out bool success) + { + throw new NotImplementedException(); + } + } + + private class MockConfigurationIterator : ConfigurationIterator + { + public override int Next(out ConfigurationEntry configurationEntry) + { + throw new NotImplementedException(); + } + } + + #endregion + } +} diff --git a/LibGit2Sharp/Configuration.cs b/LibGit2Sharp/Configuration.cs index 417fe146d..040e1a1b3 100644 --- a/LibGit2Sharp/Configuration.cs +++ b/LibGit2Sharp/Configuration.cs @@ -21,6 +21,7 @@ public class Configuration : IDisposable, private readonly FilePath systemConfigPath; private readonly FilePath programDataConfigPath; + private readonly RepositoryHandle repositoryHandle; private ConfigurationHandle configHandle; /// @@ -31,15 +32,16 @@ protected Configuration() internal Configuration(Repository repository, bool isInMemory) { + repositoryHandle = repository.Handle; if (isInMemory) { configHandle = Proxy.git_config_new(); - Proxy.git_repository_set_config(repository.Handle, configHandle); + Proxy.git_repository_set_config(repositoryHandle, configHandle); } else { - configHandle = Proxy.git_repository_config(repository.Handle); + configHandle = Proxy.git_repository_config(repositoryHandle); } repository.RegisterForCleanup(configHandle); @@ -52,6 +54,7 @@ internal Configuration( string xdgConfigurationFileLocation, string systemConfigurationFileLocation) { + repositoryHandle = repository.Handle; if (repositoryConfigurationFileLocation != null) { repoConfigPath = NormalizeConfigPath(repositoryConfigurationFileLocation); @@ -78,7 +81,7 @@ private void Init(Repository repository) 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); + Proxy.git_repository_set_config(repositoryHandle, configHandle); } else if (repoConfigPath != null) { @@ -135,6 +138,25 @@ private FilePath NormalizeConfigPath(FilePath path) throw new FileNotFoundException("Cannot find repository configuration file", path.Native); } + /// + /// Adds the provided backend to the config. + /// + /// If the provided backend implements , the + /// method will be honored and invoked upon the disposal of the repository. + /// + /// + /// + /// + /// + /// + public virtual void AddBackend(ConfigurationBackend backend, ConfigurationLevel level) + where TConfigurationValue : class where TConfigurationIterator : ConfigurationIterator + { + Ensure.ArgumentNotNull(backend, "backend"); + + Proxy.git_config_add_backend(configHandle, backend.GitConfigBackendPointer, level, repositoryHandle); + } + /// /// Access configuration values without a repository. /// diff --git a/LibGit2Sharp/ConfigBackend.cs b/LibGit2Sharp/ConfigurationBackend.cs similarity index 80% rename from LibGit2Sharp/ConfigBackend.cs rename to LibGit2Sharp/ConfigurationBackend.cs index 1e29e2e20..43d2876f0 100644 --- a/LibGit2Sharp/ConfigBackend.cs +++ b/LibGit2Sharp/ConfigurationBackend.cs @@ -1,13 +1,11 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; using System.Text; using LibGit2Sharp.Core; namespace LibGit2Sharp { - public abstract class ConfigBackend + public abstract class ConfigurationBackend where TConfigurationValue : class where TConfigurationIterator : ConfigurationIterator { /// /// Invoked by libgit2 when this backend is no longer needed. @@ -29,9 +27,9 @@ internal void Free() /// protected abstract ConfigBackendOperations SupportedOperations { get; } - public abstract int Open(LogLevel level); + public abstract int Open(ConfigurationLevel level); - public abstract int Get(string key, out ConfigurationEntry configurationEntry); + public abstract int Get(string key, out ConfigurationEntry configurationEntry); public abstract int Set(string key, string value); @@ -41,9 +39,9 @@ internal void Free() public abstract int DelMultivar(string Name, string regexp); - public abstract int Iterator(out object iterator); // ToDo: impliment iterator + public abstract int Iterator(out TConfigurationIterator iterator); - public abstract int Snapshot(out ConfigBackend configSnapshot); + public abstract int Snapshot(out ConfigurationBackend configSnapshot); public abstract int Lock(); @@ -142,24 +140,24 @@ private static class BackendEntryPoints public static readonly GitConfigBackend.unlock_callback UnlockCallback = Unlock; public static readonly GitConfigBackend.free_callback FreeCallback = Free; - private static ConfigBackend MarshalConfigBackend(IntPtr backendPtr) + private static ConfigurationBackend MarshalConfigurationBackend(IntPtr backendPtr) { var intPtr = Marshal.ReadIntPtr(backendPtr, GitConfigBackend.GCHandleOffset); - var configBackend = GCHandle.FromIntPtr(intPtr).Target as ConfigBackend; + var configBackend = GCHandle.FromIntPtr(intPtr).Target as ConfigurationBackend; if (configBackend == null) { - Proxy.giterr_set_str(GitErrorCategory.Reference, "Cannot retrieve the managed ConfigBackend."); + Proxy.giterr_set_str(GitErrorCategory.Reference, "Cannot retrieve the managed ConfigurationBackend."); return null; } return configBackend; } - private static int Open(IntPtr backend, LogLevel level) + private static int Open(IntPtr backend, uint level) { - ConfigBackend configBackend = MarshalConfigBackend(backend); + var configBackend = MarshalConfigurationBackend(backend); if (configBackend == null) { return (int)GitErrorCode.Error; @@ -167,7 +165,7 @@ private static int Open(IntPtr backend, LogLevel level) try { - int toReturn = configBackend.Open(level); + int toReturn = configBackend.Open((ConfigurationLevel)level); if (toReturn != 0) { @@ -187,7 +185,7 @@ private static unsafe int Get(IntPtr backend, string key, out GitConfigEntry ent { entry = default(GitConfigEntry); - ConfigBackend configBackend = MarshalConfigBackend(backend); + var configBackend = MarshalConfigurationBackend(backend); if (configBackend == null) { return (int)GitErrorCode.Error; @@ -195,7 +193,7 @@ private static unsafe int Get(IntPtr backend, string key, out GitConfigEntry ent try { - ConfigurationEntry configurationEntry; + ConfigurationEntry configurationEntry; int toReturn = configBackend.Get(key, out configurationEntry); if (toReturn != 0) @@ -203,11 +201,7 @@ private static unsafe int Get(IntPtr backend, string key, out GitConfigEntry ent return toReturn; } - var namePtr = EncodingMarshaler.FromManaged(Encoding.UTF8, configurationEntry.Key); - var valuePtr = EncodingMarshaler.FromManaged(Encoding.UTF8, configurationEntry.Value); - var level = (uint)configurationEntry.Level; - - entry = new GitConfigEntry { namePtr = (char*)namePtr, valuePtr = (char*)valuePtr, level = level }; + entry = configurationEntry.GetGitConfigEntry(); } catch (Exception ex) { @@ -220,7 +214,7 @@ private static unsafe int Get(IntPtr backend, string key, out GitConfigEntry ent private static int Set(IntPtr backend, string key, string value) { - ConfigBackend configBackend = MarshalConfigBackend(backend); + var configBackend = MarshalConfigurationBackend(backend); if (configBackend == null) { return (int)GitErrorCode.Error; @@ -239,7 +233,7 @@ private static int Set(IntPtr backend, string key, string value) private static int SetMultivar(IntPtr backend, string name, string regexp, string value) { - ConfigBackend configBackend = MarshalConfigBackend(backend); + var configBackend = MarshalConfigurationBackend(backend); if (configBackend == null) { return (int)GitErrorCode.Error; @@ -258,7 +252,7 @@ private static int SetMultivar(IntPtr backend, string name, string regexp, strin private static int Del(IntPtr backend, string key) { - ConfigBackend configBackend = MarshalConfigBackend(backend); + var configBackend = MarshalConfigurationBackend(backend); if (configBackend == null) { return (int)GitErrorCode.Error; @@ -277,7 +271,7 @@ private static int Del(IntPtr backend, string key) private static int DelMultivar(IntPtr backend, string name, string regexp) { - ConfigBackend configBackend = MarshalConfigBackend(backend); + var configBackend = MarshalConfigurationBackend(backend); if (configBackend == null) { return (int)GitErrorCode.Error; @@ -296,19 +290,59 @@ private static int DelMultivar(IntPtr backend, string name, string regexp) private static int Iterator(out IntPtr iterator, IntPtr backend) { - // Impliment iterator - // https://github.com/libgit2/libgit2/blob/0d723f39decff4ff77def8a4acf3b7a656af59b2/include/git2/sys/config.h line 34 - throw new NotImplementedException(); + iterator = IntPtr.Zero; + + var configBackend = MarshalConfigurationBackend(backend); + if (configBackend == null) + { + return (int)GitErrorCode.Error; + } + + try + { + TConfigurationIterator configurationIterator; + configBackend.Iterator(out configurationIterator); + + iterator = configurationIterator.GitConfigBackendPointer; + } + catch (Exception ex) + { + Proxy.giterr_set_str(GitErrorCategory.Config, ex); + return (int)GitErrorCode.Error; + } + + return (int)GitErrorCode.Ok; } private static int Snapshot(out IntPtr snapshot, IntPtr backend) { - throw new NotImplementedException(); + snapshot = IntPtr.Zero; + + var configBackend = MarshalConfigurationBackend(backend); + if (configBackend == null) + { + return (int)GitErrorCode.Error; + } + + try + { + ConfigurationBackend configurationBackend; + configBackend.Snapshot(out configurationBackend); + + snapshot = configurationBackend.GitConfigBackendPointer; + } + catch (Exception ex) + { + Proxy.giterr_set_str(GitErrorCategory.Config, ex); + return (int)GitErrorCode.Error; + } + + return (int)GitErrorCode.Ok; } private static int Lock(IntPtr backend) { - ConfigBackend configBackend = MarshalConfigBackend(backend); + var configBackend = MarshalConfigurationBackend(backend); if (configBackend == null) { return (int)GitErrorCode.Error; @@ -328,7 +362,7 @@ private static int Lock(IntPtr backend) private static int Unlock(IntPtr backend, out int success) { success = 0; - ConfigBackend configBackend = MarshalConfigBackend(backend); + var configBackend = MarshalConfigurationBackend(backend); if (configBackend == null) { return (int)GitErrorCode.Error; @@ -357,7 +391,7 @@ private static int Unlock(IntPtr backend, out int success) private static void Free(IntPtr backend) { - ConfigBackend configBackend = MarshalConfigBackend(backend); + var configBackend = MarshalConfigurationBackend(backend); if (configBackend == null) { return; diff --git a/LibGit2Sharp/ConfigurationEntry.cs b/LibGit2Sharp/ConfigurationEntry.cs index 13c153a2a..bb33d39a8 100644 --- a/LibGit2Sharp/ConfigurationEntry.cs +++ b/LibGit2Sharp/ConfigurationEntry.cs @@ -1,5 +1,8 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; using System.Globalization; +using System.Text; +using LibGit2Sharp.Core; namespace LibGit2Sharp { @@ -51,5 +54,21 @@ private string DebuggerDisplay return string.Format(CultureInfo.InvariantCulture, "{0} = \"{1}\"", Key, Value); } } + + internal unsafe GitConfigEntry GetGitConfigEntry() + { + var namePtr = EncodingMarshaler.FromManaged(Encoding.UTF8, Key); + IntPtr valuePtr = IntPtr.Zero; + + var value = Value as string; + if (value != null) + { + valuePtr = EncodingMarshaler.FromManaged(Encoding.UTF8, value); + } + + var level = (uint)Level; + + return new GitConfigEntry { namePtr = (char*)namePtr, valuePtr = (char*)valuePtr, level = level }; + } } } diff --git a/LibGit2Sharp/ConfigurationIterator.cs b/LibGit2Sharp/ConfigurationIterator.cs new file mode 100644 index 000000000..3ed2a608c --- /dev/null +++ b/LibGit2Sharp/ConfigurationIterator.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + public abstract class ConfigurationIterator where T : class + { + /// + /// Invoked by libgit2 when this backend is no longer needed. + /// + internal void Free() + { + if (nativeBackendPointer == IntPtr.Zero) + { + return; + } + + GCHandle.FromIntPtr(Marshal.ReadIntPtr(nativeBackendPointer, GitOdbBackend.GCHandleOffset)).Free(); + Marshal.FreeHGlobal(nativeBackendPointer); + nativeBackendPointer = IntPtr.Zero; + } + + /// + /// The implimentation of this method should out the current entry and advance the iterator. + /// + /// + /// + public abstract int Next(out ConfigurationEntry configurationEntry); + + private IntPtr nativeBackendPointer; + + internal IntPtr GitConfigBackendPointer + { + get + { + if (IntPtr.Zero == nativeBackendPointer) + { + var nativeBackend = new GitConfigIterator(); + + nativeBackend.Next = IteratorEntryPoints.NextCallback; + nativeBackend.Free = IteratorEntryPoints.FreeCallback; + + nativeBackend.GCHandle = GCHandle.ToIntPtr(GCHandle.Alloc(this)); + nativeBackendPointer = Marshal.AllocHGlobal(Marshal.SizeOf(nativeBackend)); + Marshal.StructureToPtr(nativeBackend, nativeBackendPointer, false); + } + + return nativeBackendPointer; + } + } + + private static class IteratorEntryPoints + { + // Because our GitOdbBackend structure exists on the managed heap only for a short time (to be marshaled + // to native memory with StructureToPtr), we need to bind to static delegates. If at construction time + // we were to bind to the methods directly, that's the same as newing up a fresh delegate every time. + // Those delegates won't be rooted in the object graph and can be collected as soon as StructureToPtr finishes. + + public static readonly GitConfigIterator.next_callback NextCallback = Next; + public static readonly GitConfigIterator.free_callback FreeCallback = Free; + + private static ConfigurationIterator MarshalConfigurationIterator(IntPtr iteratorPtr) + { + + var intPtr = Marshal.ReadIntPtr(iteratorPtr, GitConfigIterator.GCHandleOffset); + var configBackend = GCHandle.FromIntPtr(intPtr).Target as ConfigurationIterator; + + if (configBackend == null) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, "Cannot retrieve the managed ConfigurationIterator."); + return null; + } + + return configBackend; + } + + private static int Next(out GitConfigEntry configEntry, IntPtr iterator) + { + configEntry = default(GitConfigEntry); + + var configIterator = MarshalConfigurationIterator(iterator); + if (configIterator == null) + { + return (int)GitErrorCode.Error; + } + + try + { + ConfigurationEntry configurationEntry; + configIterator.Next(out configurationEntry); + + configEntry = configurationEntry.GetGitConfigEntry(); + } + catch (Exception ex) + { + Proxy.giterr_set_str(GitErrorCategory.Config, ex); + } + + return (int)GitErrorCode.Ok; + } + + private static void Free(IntPtr iterator) + { + var configIterator = MarshalConfigurationIterator(iterator); + if (configIterator == null) + { + return; + } + + try + { + configIterator.Free(); + } + catch (Exception ex) + { + Proxy.giterr_set_str(GitErrorCategory.Config, ex); + } + } + } + } +} diff --git a/LibGit2Sharp/Core/GitConfigBackend.cs b/LibGit2Sharp/Core/GitConfigBackend.cs index 9677e47d5..1fea38dd9 100644 --- a/LibGit2Sharp/Core/GitConfigBackend.cs +++ b/LibGit2Sharp/Core/GitConfigBackend.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; -using System.Text; namespace LibGit2Sharp.Core { @@ -11,7 +8,7 @@ internal struct GitConfigBackend { static GitConfigBackend() { - GCHandleOffset = Marshal.OffsetOf(typeof(GitRefDbBackend), "GCHandle").ToInt32(); + GCHandleOffset = MarshalPortable.OffsetOf(nameof(GCHandle)).ToInt32(); } public uint Version; @@ -48,7 +45,7 @@ static GitConfigBackend() public delegate int open_callback( IntPtr backend, - LogLevel level); + uint level); public delegate int get_callback( IntPtr backend, diff --git a/LibGit2Sharp/Core/GitConfigIterator.cs b/LibGit2Sharp/Core/GitConfigIterator.cs new file mode 100644 index 000000000..1b519e2da --- /dev/null +++ b/LibGit2Sharp/Core/GitConfigIterator.cs @@ -0,0 +1,33 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal struct GitConfigIterator + { + static GitConfigIterator() + { + GCHandleOffset = Marshal.OffsetOf(typeof(GitConfigIterator), "GCHandle").ToInt32(); + } + + public GitConfigBackend Backend; + public uint Flags; + + public next_callback Next; + public free_callback Free; + + /* The libgit2 structure definition ends here. Subsequent fields are for libgit2sharp bookkeeping. */ + + public IntPtr GCHandle; + + public static int GCHandleOffset; + + public delegate int next_callback( + out GitConfigEntry configEntry, + IntPtr iterator); + + public delegate void free_callback( + IntPtr iterator); + } +} diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 73e4f8680..f9a6b355b 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -383,6 +383,10 @@ private static extern unsafe int git_config_add_file_ondisk( [DllImport(libgit2)] internal static extern unsafe int git_config_new(out git_config* cfg); + [DllImport(libgit2)] + internal static extern unsafe int git_config_add_backend(git_config* cfg, IntPtr backend, uint level, git_repository* repo, + int force); + [DllImport(libgit2)] internal static extern unsafe int git_config_open_level( out git_config* cfg, diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 428879324..fe9e94d47 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -517,6 +517,11 @@ public static unsafe SignatureInfo git_commit_extract_signature(RepositoryHandle #region git_config_ + public static unsafe void git_config_add_backend(ConfigurationHandle config, IntPtr backend, ConfigurationLevel level, RepositoryHandle repo) + { + Ensure.ZeroResult(NativeMethods.git_config_add_backend(config, backend, (uint)level, repo, 1)); + } + public static unsafe void git_config_add_file_ondisk(ConfigurationHandle config, FilePath path, ConfigurationLevel level) { int res = NativeMethods.git_config_add_file_ondisk(config, path, (uint)level, true);