From 16c27274d0b34ee187e351ff9cd57d0e9f36879f Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Mon, 11 Nov 2019 18:44:14 -0800 Subject: [PATCH 01/26] Specified the libgit2 contract in P/Invoke. --- LibGit2Sharp/Core/GitRefdbBackend.cs | 145 +++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 LibGit2Sharp/Core/GitRefdbBackend.cs diff --git a/LibGit2Sharp/Core/GitRefdbBackend.cs b/LibGit2Sharp/Core/GitRefdbBackend.cs new file mode 100644 index 000000000..e441140f8 --- /dev/null +++ b/LibGit2Sharp/Core/GitRefdbBackend.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct GitRefdbIterator + { + public IntPtr Refdb; + public next_callback Next; + public next_name_callback NextName; + public free_callback Free; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int next_callback( + out git_reference* referencePtr, + IntPtr iterator); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int next_name_callback( + out IntPtr refNamePtr, + IntPtr iterator); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int free_callback( + IntPtr iterator); + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct GitRefdbBackend + { + static GitRefdbBackend() + { + GCHandleOffset = Marshal.OffsetOf(nameof(GCHandle)).ToInt32(); + } + + public uint Version; + public exists_callback Exists; + public lookup_callback Lookup; + public iterator_callback Iterator; + public write_callback Write; + public rename_callback Rename; + public del_callback Del; + public IntPtr Compress; + public has_log_callback HasLog; + public ensure_log_callback EnsureLog; + public free_callback Free; + public reflog_read_callback ReflogRead; + public reflog_write_callback ReflogWrite; + public reflog_rename_callback ReflogRename; + public reflog_delete_callback ReflogDelete; + public IntPtr Lock; + public IntPtr Unlock; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int exists_callback( + ref IntPtr exists, + IntPtr backend, + IntPtr refNamePtr); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int lookup_callback( + out IntPtr referencePtr, + IntPtr backend, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int iterator_callback( + out IntPtr iteratorPtr, + IntPtr backend, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string glob); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int write_callback( + IntPtr backend, + git_reference* reference, + [MarshalAs(UnmanagedType.Bool)] bool force, + git_signature* who, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message, + ref git_oid oid, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string oldTarget); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int rename_callback( + git_reference* reference, + IntPtr backend, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string oldName, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string newName, + [MarshalAs(UnmanagedType.Bool)] bool force, + git_signature* who, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int del_callback( + IntPtr backend, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName, + ref git_oid oldId, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string oldTarget); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int has_log_callback( + IntPtr backend, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int ensure_log_callback( + IntPtr backend, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void free_callback(IntPtr backend); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int reflog_read_callback( + out git_reflog* reflog, + IntPtr backend, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int reflog_write_callback( + IntPtr backend, + git_reflog* reflog); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int reflog_rename_callback( + IntPtr backend, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string oldName, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string newName); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int reflog_delete_callback( + IntPtr backend, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); + + /* The libgit2 structure definition ends here. Subsequent fields are for libgit2sharp bookkeeping. */ + + public IntPtr GCHandle; + + /* The following static fields are not part of the structure definition. */ + + public static int GCHandleOffset; + } +} From f77b2fd1885069a1f3f3154490dfcb85f440974a Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Mon, 11 Nov 2019 23:29:31 -0800 Subject: [PATCH 02/26] Initial draft of minimum testable implementation. --- LibGit2Sharp/Core/GitRefdbBackend.cs | 39 ++- LibGit2Sharp/Core/Handles/Objects.cs | 21 ++ LibGit2Sharp/Core/NativeMethods.cs | 29 ++ LibGit2Sharp/Core/Opaques.cs | 1 + LibGit2Sharp/Core/Proxy.cs | 41 ++- LibGit2Sharp/RefdbBackend.cs | 392 +++++++++++++++++++++++++++ LibGit2Sharp/ReferenceCollection.cs | 11 + 7 files changed, 519 insertions(+), 15 deletions(-) create mode 100644 LibGit2Sharp/RefdbBackend.cs diff --git a/LibGit2Sharp/Core/GitRefdbBackend.cs b/LibGit2Sharp/Core/GitRefdbBackend.cs index e441140f8..3df51586d 100644 --- a/LibGit2Sharp/Core/GitRefdbBackend.cs +++ b/LibGit2Sharp/Core/GitRefdbBackend.cs @@ -8,23 +8,36 @@ namespace LibGit2Sharp.Core [StructLayout(LayoutKind.Sequential)] internal unsafe struct GitRefdbIterator { + static GitRefdbIterator() + { + GCHandleOffset = Marshal.OffsetOf(nameof(GCHandle)).ToInt32(); + } + public IntPtr Refdb; public next_callback Next; public next_name_callback NextName; public free_callback Free; + /* The libgit2 structure definition ends here. Subsequent fields are for libgit2sharp bookkeeping. */ + + public IntPtr GCHandle; + + /* The following static fields are not part of the structure definition. */ + + public static int GCHandleOffset; + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int next_callback( - out git_reference* referencePtr, + out IntPtr referencePtr, IntPtr iterator); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int next_name_callback( - out IntPtr refNamePtr, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] out string refName, IntPtr iterator); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int free_callback( + public delegate void free_callback( IntPtr iterator); } @@ -54,11 +67,19 @@ static GitRefdbBackend() public IntPtr Lock; public IntPtr Unlock; + /* The libgit2 structure definition ends here. Subsequent fields are for libgit2sharp bookkeeping. */ + + public IntPtr GCHandle; + + /* The following static fields are not part of the structure definition. */ + + public static int GCHandleOffset; + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int exists_callback( - ref IntPtr exists, + [MarshalAs(UnmanagedType.Bool)] ref bool exists, IntPtr backend, - IntPtr refNamePtr); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refNamePtr); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int lookup_callback( @@ -133,13 +154,5 @@ public delegate int reflog_rename_callback( public delegate int reflog_delete_callback( IntPtr backend, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); - - /* The libgit2 structure definition ends here. Subsequent fields are for libgit2sharp bookkeeping. */ - - public IntPtr GCHandle; - - /* The following static fields are not part of the structure definition. */ - - public static int GCHandleOffset; } } diff --git a/LibGit2Sharp/Core/Handles/Objects.cs b/LibGit2Sharp/Core/Handles/Objects.cs index 5f8db722e..3d4bdfdd0 100644 --- a/LibGit2Sharp/Core/Handles/Objects.cs +++ b/LibGit2Sharp/Core/Handles/Objects.cs @@ -441,6 +441,27 @@ public override void Free() } } + internal unsafe class ReferenceDatabaseHandle : Libgit2Object + { + public ReferenceDatabaseHandle(void* handle, bool owned) : base(handle, owned) + { + } + + public ReferenceDatabaseHandle(IntPtr ptr, bool owned) : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_refdb_free((git_refdb*)ptr); + } + + public static implicit operator git_refdb*(ReferenceDatabaseHandle handle) + { + return (git_refdb*)handle.Handle; + } + } + internal unsafe class RevWalkerHandle : Libgit2Object { internal RevWalkerHandle(git_revwalk *ptr, bool owned) diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 60bd0a915..ca00311b5 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -2100,6 +2100,35 @@ internal static extern unsafe int git_worktree_add ( internal static extern unsafe int git_worktree_prune( git_worktree* worktree, git_worktree_prune_options options); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_refdb( + out git_refdb* refdb, + git_repository* repo); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_refdb_new( + out git_refdb* refdb, + git_repository* repo); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_refdb_set_backend( + git_refdb* refdb, + IntPtr refdbBackend); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_refdb_free(git_refdb* refdb); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_reference* git_reference__alloc( + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, + ref GitOid oid, + IntPtr peel); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_reference* git_reference__alloc_symbolic( + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string target); } } // ReSharper restore InconsistentNaming diff --git a/LibGit2Sharp/Core/Opaques.cs b/LibGit2Sharp/Core/Opaques.cs index f5613a276..138155730 100644 --- a/LibGit2Sharp/Core/Opaques.cs +++ b/LibGit2Sharp/Core/Opaques.cs @@ -28,5 +28,6 @@ internal struct git_object {} internal struct git_rebase {} internal struct git_odb_stream {} internal struct git_worktree { } + internal struct git_refdb { }; } diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index c3a53b95e..0de07c2fc 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Security.Cryptography; using System.Text; using LibGit2Sharp.Core.Handles; using LibGit2Sharp.Handlers; @@ -1868,9 +1869,32 @@ public static unsafe void git_rebase_finish( } } -#endregion + #endregion + + #region git_refdb_ + + public static unsafe ReferenceDatabaseHandle git_repository_refdb(RepositoryHandle repo) + { + git_refdb* refdb; + Ensure.ZeroResult(NativeMethods.git_repository_refdb(out refdb, repo)); + return new ReferenceDatabaseHandle(refdb, true); + } + + public static unsafe ReferenceDatabaseHandle git_refdb_new(RepositoryHandle repo) + { + git_refdb* refdb; + Ensure.ZeroResult(NativeMethods.git_refdb_new(out refdb, repo)); + return new ReferenceDatabaseHandle(refdb, true); + } + + public static unsafe void git_refdb_set_backend(ReferenceDatabaseHandle refdb, IntPtr backend) + { + Ensure.ZeroResult(NativeMethods.git_refdb_set_backend(refdb, backend)); + } -#region git_reference_ + #endregion + + #region git_reference_ public static unsafe ReferenceHandle git_reference_create( RepositoryHandle repo, @@ -2018,6 +2042,19 @@ public static unsafe void git_reference_ensure_log(RepositoryHandle repo, string Ensure.ZeroResult(res); } + public static unsafe ReferenceHandle git_reference__alloc(string name, ObjectId objectId) + { + var oid = objectId.Oid; + var referencePtr = NativeMethods.git_reference__alloc(name, ref oid, IntPtr.Zero); + return new ReferenceHandle(referencePtr, true); + } + + public static unsafe ReferenceHandle git_reference__alloc_symbolic(string name, string target) + { + var referencePtr = NativeMethods.git_reference__alloc_symbolic(name, target); + return new ReferenceHandle(referencePtr, true); + } + #endregion #region git_reflog_ diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs new file mode 100644 index 000000000..6f4807ff7 --- /dev/null +++ b/LibGit2Sharp/RefdbBackend.cs @@ -0,0 +1,392 @@ +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace LibGit2Sharp +{ + public abstract class RefdbBackend + { + public class ReferenceData + { + public string RefName { get; private set; } + public bool IsSymbolic { get; private set; } + public ObjectId ObjectId { get; private set; } + public string SymbolicTarget { get; private set; } + + public ReferenceData(string refName, ObjectId directTarget) + { + this.RefName = refName; + this.IsSymbolic = false; + this.ObjectId = directTarget; + this.SymbolicTarget = null; + } + + public ReferenceData(string refName, string symbolicTarget) + { + this.RefName = refName; + this.IsSymbolic = true; + this.ObjectId = null; + this.SymbolicTarget = symbolicTarget; + } + + internal IntPtr GetPtr() + { + if (IsSymbolic) + { + return Proxy.git_reference__alloc_symbolic(RefName, SymbolicTarget).AsIntPtr(); + } + else + { + return Proxy.git_reference__alloc(RefName, ObjectId.Oid).AsIntPtr(); + } + } + } + + private IntPtr nativePointer; + + protected Repository Repository { get; private set; } + + protected RefdbBackend(Repository repository) + { + Ensure.ArgumentNotNull(repository, "repository"); + this.Repository = repository; + } + + public abstract bool Exists(string refName); + + public abstract bool Lookup(string refName, out ReferenceData data); + + public abstract RefIterator Iterate(string glob); + + internal IntPtr RefdbBackendPointer + { + get + { + if (IntPtr.Zero == nativePointer) + { + var nativeBackend = new GitRefdbBackend() + { + Version = 1, + Compress = IntPtr.Zero, + Lock = IntPtr.Zero, + Unlock = IntPtr.Zero, + Exists = BackendEntryPoints.ExistsCallback, + Lookup = BackendEntryPoints.LookupCallback, + Iterator = BackendEntryPoints.IteratorCallback, + Write = BackendEntryPoints.WriteCallback, + Rename = BackendEntryPoints.RenameCallback, + Del = BackendEntryPoints.DelCallback, + HasLog = BackendEntryPoints.HasLogCallback, + EnsureLog = BackendEntryPoints.EnsureLogCallback, + Free = BackendEntryPoints.FreeCallback, + ReflogRead = BackendEntryPoints.ReflogReadCallback, + ReflogWrite = BackendEntryPoints.ReflogWriteCallback, + ReflogRename = BackendEntryPoints.ReflogRenameCallback, + ReflogDelete = BackendEntryPoints.ReflogDeleteCallback, + GCHandle = GCHandle.ToIntPtr(GCHandle.Alloc(this)) + }; + + nativePointer = Marshal.AllocHGlobal(Marshal.SizeOf(nativeBackend)); + Marshal.StructureToPtr(nativeBackend, nativePointer, false); + } + + return nativePointer; + } + } + + public abstract class RefIterator + { + public abstract ReferenceData GetNext(); + } + + private unsafe static class IteratorEntryPoints + { + public static readonly GitRefdbIterator.next_callback NextCallback = Next; + public static readonly GitRefdbIterator.next_name_callback NextNameCallback = NextName; + public static readonly GitRefdbIterator.free_callback FreeCallback = Free; + + public static int Next( + out IntPtr referencePtr, + IntPtr iterator) + { + referencePtr = IntPtr.Zero; + var backend = PtrToBackend(iterator); + if (backend == null) + { + return (int)GitErrorCode.Error; + } + + ReferenceData data; + try + { + data = backend.GetNext(); + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Reference, ex); + return (int)GitErrorCode.Error; + } + + if (data == null) + { + return (int)GitErrorCode.IterOver; + } + + referencePtr = data.GetPtr(); + return (int)GitErrorCode.Ok; + } + + public static int NextName( + out string refNamePtr, + IntPtr iterator) + { + refNamePtr = null; + var backend = PtrToBackend(iterator); + if (backend == null) + { + return (int)GitErrorCode.Error; + } + + ReferenceData data; + try + { + data = backend.GetNext(); + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Reference, ex); + return (int)GitErrorCode.Error; + } + + refNamePtr = data.RefName; + return (int)GitErrorCode.Ok; + } + + public static void Free(IntPtr iterator) + { + Marshal.FreeHGlobal(iterator); + } + + private static RefIterator PtrToBackend(IntPtr pointer) + { + var intPtr = Marshal.ReadIntPtr(pointer, GitRefdbBackend.GCHandleOffset); + var backend = GCHandle.FromIntPtr(intPtr).Target as RefIterator; + + if (backend == null) + { + Proxy.git_error_set_str(GitErrorCategory.Reference, "Cannot retrieve the managed RefIterator"); + } + + return backend; + } + } + + private unsafe static class BackendEntryPoints + { + public static readonly GitRefdbBackend.exists_callback ExistsCallback = Exists; + public static readonly GitRefdbBackend.lookup_callback LookupCallback = Lookup; + public static readonly GitRefdbBackend.iterator_callback IteratorCallback = Iterator; + public static readonly GitRefdbBackend.write_callback WriteCallback = Write; + public static readonly GitRefdbBackend.rename_callback RenameCallback = Rename; + public static readonly GitRefdbBackend.del_callback DelCallback = Del; + public static readonly GitRefdbBackend.has_log_callback HasLogCallback = HasLog; + public static readonly GitRefdbBackend.ensure_log_callback EnsureLogCallback = EnsureLog; + public static readonly GitRefdbBackend.free_callback FreeCallback = Free; + public static readonly GitRefdbBackend.reflog_read_callback ReflogReadCallback = ReflogRead; + public static readonly GitRefdbBackend.reflog_write_callback ReflogWriteCallback = ReflogWrite; + public static readonly GitRefdbBackend.reflog_rename_callback ReflogRenameCallback = ReflogRename; + public static readonly GitRefdbBackend.reflog_delete_callback ReflogDeleteCallback = ReflogDelete; + + public static int Exists( + ref bool exists, + IntPtr backendPtr, + string refName) + { + var backend = PtrToBackend(backendPtr); + if (backend == null) + { + return (int)GitErrorCode.Error; + } + + try + { + exists = backend.Exists(refName); + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Reference, ex); + return (int)GitErrorCode.Error; + } + + return 0; + } + + public static int Lookup( + out IntPtr referencePtr, + IntPtr backendPtr, + string refName) + { + referencePtr = IntPtr.Zero; + var backend = PtrToBackend(backendPtr); + if (backend == null) + { + return (int)GitErrorCode.Error; + } + + try + { + ReferenceData data; + if (!backend.Lookup(refName, out data)) + { + return (int)GitErrorCode.NotFound; + } + + referencePtr = data.GetPtr(); + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Reference, ex); + return (int)GitErrorCode.Error; + } + + return 0; + } + + public static int Iterator( + out IntPtr iteratorPtr, + IntPtr backendPtr, + string glob) + { + iteratorPtr = IntPtr.Zero; + var backend = PtrToBackend(backendPtr); + if (backend == null) + { + return (int)GitErrorCode.Error; + } + + RefIterator iterator; + try + { + iterator = backend.Iterate(glob); + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Reference, ex); + return (int)GitErrorCode.Error; + } + + var nativeIterator = new GitRefdbIterator() + { + Refdb = backendPtr, + Next = IteratorEntryPoints.Next, + NextName = IteratorEntryPoints.NextName, + Free = IteratorEntryPoints.Free, + GCHandle = GCHandle.ToIntPtr(GCHandle.Alloc(iterator)) + }; + + iteratorPtr = Marshal.AllocHGlobal(Marshal.SizeOf(nativeIterator)); + Marshal.StructureToPtr(nativeIterator, iteratorPtr, false); + return (int)GitErrorCode.Ok; + } + + public static int Write( + IntPtr backendPtr, + git_reference* reference, + bool force, + git_signature* who, + string message, + ref git_oid oid, + string oldTarget) + { + return (int)GitErrorCode.Error; + } + + public static int Rename( + git_reference* reference, + IntPtr backendPtr, + string oldName, + string newName, + bool force, + git_signature* who, + string message) + { + return (int)GitErrorCode.Error; + } + + public static int Del( + IntPtr backendPtr, + string refName, + ref git_oid oldId, + string oldTarget) + { + return (int)GitErrorCode.Error; + } + + public static int HasLog( + IntPtr backend, + string refName) + { + return (int)GitErrorCode.Error; + } + + public static int EnsureLog( + IntPtr backend, + string refName) + { + return (int)GitErrorCode.Error; + } + + public static void Free(IntPtr backend) + { + Marshal.FreeHGlobal(backend); + } + + public static int ReflogRead( + out git_reflog* reflog, + IntPtr backend, + string name) + { + reflog = null; + return (int)GitErrorCode.Error; + } + + public static int ReflogWrite( + IntPtr backend, + git_reflog* reflog) + { + return (int)GitErrorCode.Error; + } + + public static int ReflogRename( + IntPtr backend, + string oldName, + string newName) + { + return (int)GitErrorCode.Error; + } + + public static int ReflogDelete( + IntPtr backend, + string name) + { + return (int)GitErrorCode.Error; + } + + private static RefdbBackend PtrToBackend(IntPtr pointer) + { + var intPtr = Marshal.ReadIntPtr(pointer, GitRefdbBackend.GCHandleOffset); + var backend = GCHandle.FromIntPtr(intPtr).Target as RefdbBackend; + + if (backend == null) + { + Proxy.git_error_set_str(GitErrorCategory.Reference, "Cannot retrieve the managed RefdbBackend"); + } + + return backend; + } + } + } +} diff --git a/LibGit2Sharp/ReferenceCollection.cs b/LibGit2Sharp/ReferenceCollection.cs index 602a20f17..d28c35c87 100644 --- a/LibGit2Sharp/ReferenceCollection.cs +++ b/LibGit2Sharp/ReferenceCollection.cs @@ -16,6 +16,7 @@ namespace LibGit2Sharp public class ReferenceCollection : IEnumerable { internal readonly Repository repo; + internal readonly ReferenceDatabaseHandle refdbHandle; /// /// Needed for mocking purposes. @@ -30,6 +31,8 @@ protected ReferenceCollection() internal ReferenceCollection(Repository repo) { this.repo = repo; + refdbHandle = Proxy.git_repository_refdb(repo.Handle); + repo.RegisterForCleanup(refdbHandle); } /// @@ -842,6 +845,14 @@ public virtual void RewriteHistory(RewriteHistoryOptions options, IEnumerable /// Ensure that a reflog exists for the given canonical name /// From 09c2db0498c9d2f65598e2f6c6b365df5f15a1ea Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Mon, 11 Nov 2019 23:40:55 -0800 Subject: [PATCH 03/26] Added initial unit test, copied from zoxiv's prior PR. --- LibGit2Sharp.Tests/RefdbBackendFixture.cs | 76 +++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 LibGit2Sharp.Tests/RefdbBackendFixture.cs diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs new file mode 100644 index 000000000..ed67ffa92 --- /dev/null +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -0,0 +1,76 @@ +using LibGit2Sharp.Tests.TestHelpers; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class RefdbBackendFixture : BaseFixture + { + [Fact] + public void CanReadFromRefdbBackend() + { + var scd = new SelfCleaningDirectory(this); + Repository.Init(scd.RootedDirectoryPath); + using (var repo = new Repository(scd.RootedDirectoryPath)) + { + var backend = new MockRefdbBackend(repo); + repo.Refs.SetBackend(backend); + backend.Refs["HEAD"] = new RefdbBackend.ReferenceData("HEAD", "refs/heads/testref"); + backend.Refs["refs/heads/testref"] = new RefdbBackend.ReferenceData("refs/heads/testref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + + Assert.Equal("refs/heads/testref", repo.Refs["HEAD"].TargetIdentifier); + Assert.Equal("be3563ae3f795b2b4353bcce3a527ad0a4f7f644", repo.Refs["HEAD"].ResolveToDirectReference().TargetIdentifier); + Assert.Equal("refs/heads/testref", repo.Head.CanonicalName); + } + } + + private class MockRefdbBackend : RefdbBackend + { + public MockRefdbBackend(Repository repository) : base(repository) + { + } + + public SortedDictionary Refs { get; } = new SortedDictionary(); + + public override bool Exists(string refName) + { + return Refs.ContainsKey(refName); + } + + public override RefIterator Iterate(string glob) + { + return new MockRefIterator(this); + } + + public override bool Lookup(string refName, out ReferenceData data) + { + return Refs.TryGetValue(refName, out data); + } + + private class MockRefIterator : RefIterator + { + private readonly IEnumerator enumerator; + + public MockRefIterator(MockRefdbBackend parent) + { + this.enumerator = parent.Refs.Values.GetEnumerator(); + } + + public override ReferenceData GetNext() + { + if (this.enumerator.MoveNext()) + { + return this.enumerator.Current; + } + + return null; + } + } + } + } +} From 98ce93f691aa74bdd95a1ca5e8aa1959d89e60af Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Tue, 12 Nov 2019 00:35:00 -0800 Subject: [PATCH 04/26] First test pass. --- LibGit2Sharp/Core/GitRefdbBackend.cs | 36 ++++++++++++++-------------- LibGit2Sharp/Core/Proxy.cs | 4 ++-- LibGit2Sharp/RefdbBackend.cs | 14 ++++++++++- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/LibGit2Sharp/Core/GitRefdbBackend.cs b/LibGit2Sharp/Core/GitRefdbBackend.cs index 3df51586d..ae51278d3 100644 --- a/LibGit2Sharp/Core/GitRefdbBackend.cs +++ b/LibGit2Sharp/Core/GitRefdbBackend.cs @@ -33,7 +33,7 @@ public delegate int next_callback( [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int next_name_callback( - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] out string refName, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] out string refName, IntPtr iterator); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] @@ -46,7 +46,7 @@ internal unsafe struct GitRefdbBackend { static GitRefdbBackend() { - GCHandleOffset = Marshal.OffsetOf(nameof(GCHandle)).ToInt32(); + GCHandleOffset = Marshal.OffsetOf(nameof(GCHandle)).ToInt32(); } public uint Version; @@ -79,19 +79,19 @@ static GitRefdbBackend() public delegate int exists_callback( [MarshalAs(UnmanagedType.Bool)] ref bool exists, IntPtr backend, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refNamePtr); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string refNamePtr); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int lookup_callback( out IntPtr referencePtr, IntPtr backend, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string refName); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int iterator_callback( out IntPtr iteratorPtr, IntPtr backend, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string glob); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string glob); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int write_callback( @@ -99,36 +99,36 @@ public delegate int write_callback( git_reference* reference, [MarshalAs(UnmanagedType.Bool)] bool force, git_signature* who, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string message, ref git_oid oid, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string oldTarget); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string oldTarget); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int rename_callback( git_reference* reference, IntPtr backend, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string oldName, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string newName, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string oldName, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string newName, [MarshalAs(UnmanagedType.Bool)] bool force, git_signature* who, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string message); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int del_callback( IntPtr backend, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string refName, ref git_oid oldId, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string oldTarget); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string oldTarget); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int has_log_callback( IntPtr backend, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string refName); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int ensure_log_callback( IntPtr backend, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string refName); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void free_callback(IntPtr backend); @@ -137,7 +137,7 @@ public delegate int ensure_log_callback( public delegate int reflog_read_callback( out git_reflog* reflog, IntPtr backend, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string name); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int reflog_write_callback( @@ -147,12 +147,12 @@ public delegate int reflog_write_callback( [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int reflog_rename_callback( IntPtr backend, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string oldName, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string newName); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string oldName, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string newName); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int reflog_delete_callback( IntPtr backend, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string name); } } diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 0de07c2fc..71ef7888c 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -2046,13 +2046,13 @@ public static unsafe ReferenceHandle git_reference__alloc(string name, ObjectId { var oid = objectId.Oid; var referencePtr = NativeMethods.git_reference__alloc(name, ref oid, IntPtr.Zero); - return new ReferenceHandle(referencePtr, true); + return new ReferenceHandle(referencePtr, false); } public static unsafe ReferenceHandle git_reference__alloc_symbolic(string name, string target) { var referencePtr = NativeMethods.git_reference__alloc_symbolic(name, target); - return new ReferenceHandle(referencePtr, true); + return new ReferenceHandle(referencePtr, false); } #endregion diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index 6f4807ff7..a97df5755 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -97,6 +97,18 @@ internal IntPtr RefdbBackendPointer } } + internal void Free() + { + if (IntPtr.Zero == nativePointer) + { + return; + } + + GCHandle.FromIntPtr(Marshal.ReadIntPtr(nativePointer, GitRefdbBackend.GCHandleOffset)).Free(); + Marshal.FreeHGlobal(nativePointer); + nativePointer = IntPtr.Zero; + } + public abstract class RefIterator { public abstract ReferenceData GetNext(); @@ -341,7 +353,7 @@ public static int EnsureLog( public static void Free(IntPtr backend) { - Marshal.FreeHGlobal(backend); + PtrToBackend(backend).Free(); } public static int ReflogRead( From c2eafd554f6cfb56651d30bfd5df0a2b7c9786b7 Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Tue, 12 Nov 2019 00:43:34 -0800 Subject: [PATCH 05/26] More work on contract/implementation. --- LibGit2Sharp/Core/GitRefdbBackend.cs | 4 ++-- LibGit2Sharp/RefdbBackend.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LibGit2Sharp/Core/GitRefdbBackend.cs b/LibGit2Sharp/Core/GitRefdbBackend.cs index ae51278d3..e508a1972 100644 --- a/LibGit2Sharp/Core/GitRefdbBackend.cs +++ b/LibGit2Sharp/Core/GitRefdbBackend.cs @@ -100,7 +100,7 @@ public delegate int write_callback( [MarshalAs(UnmanagedType.Bool)] bool force, git_signature* who, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string message, - ref git_oid oid, + ref GitOid oid, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string oldTarget); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] @@ -117,7 +117,7 @@ public delegate int rename_callback( public delegate int del_callback( IntPtr backend, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string refName, - ref git_oid oldId, + ref GitOid oldId, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string oldTarget); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index a97df5755..2416a14e5 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -310,7 +310,7 @@ public static int Write( bool force, git_signature* who, string message, - ref git_oid oid, + ref GitOid oid, string oldTarget) { return (int)GitErrorCode.Error; @@ -331,7 +331,7 @@ public static int Rename( public static int Del( IntPtr backendPtr, string refName, - ref git_oid oldId, + ref GitOid oldId, string oldTarget) { return (int)GitErrorCode.Error; From a0b91987e8729a6cc17914d907b6aba3e58a3377 Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Tue, 12 Nov 2019 14:09:22 -0800 Subject: [PATCH 06/26] Shuffled class --- LibGit2Sharp/RefdbBackend.cs | 72 ++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index 2416a14e5..d1c8fda2d 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -9,42 +9,6 @@ namespace LibGit2Sharp { public abstract class RefdbBackend { - public class ReferenceData - { - public string RefName { get; private set; } - public bool IsSymbolic { get; private set; } - public ObjectId ObjectId { get; private set; } - public string SymbolicTarget { get; private set; } - - public ReferenceData(string refName, ObjectId directTarget) - { - this.RefName = refName; - this.IsSymbolic = false; - this.ObjectId = directTarget; - this.SymbolicTarget = null; - } - - public ReferenceData(string refName, string symbolicTarget) - { - this.RefName = refName; - this.IsSymbolic = true; - this.ObjectId = null; - this.SymbolicTarget = symbolicTarget; - } - - internal IntPtr GetPtr() - { - if (IsSymbolic) - { - return Proxy.git_reference__alloc_symbolic(RefName, SymbolicTarget).AsIntPtr(); - } - else - { - return Proxy.git_reference__alloc(RefName, ObjectId.Oid).AsIntPtr(); - } - } - } - private IntPtr nativePointer; protected Repository Repository { get; private set; } @@ -109,6 +73,42 @@ internal void Free() nativePointer = IntPtr.Zero; } + public class ReferenceData + { + public string RefName { get; private set; } + public bool IsSymbolic { get; private set; } + public ObjectId ObjectId { get; private set; } + public string SymbolicTarget { get; private set; } + + public ReferenceData(string refName, ObjectId directTarget) + { + this.RefName = refName; + this.IsSymbolic = false; + this.ObjectId = directTarget; + this.SymbolicTarget = null; + } + + public ReferenceData(string refName, string symbolicTarget) + { + this.RefName = refName; + this.IsSymbolic = true; + this.ObjectId = null; + this.SymbolicTarget = symbolicTarget; + } + + internal IntPtr GetPtr() + { + if (IsSymbolic) + { + return Proxy.git_reference__alloc_symbolic(RefName, SymbolicTarget).AsIntPtr(); + } + else + { + return Proxy.git_reference__alloc(RefName, ObjectId.Oid).AsIntPtr(); + } + } + } + public abstract class RefIterator { public abstract ReferenceData GetNext(); From 54e8f3c9b50dfb1405d30c37d38e6f5785ffa3c7 Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Tue, 12 Nov 2019 16:11:46 -0800 Subject: [PATCH 07/26] Added reference marshalling and write. --- .../Properties/launchSettings.json | 8 +++ LibGit2Sharp/Core/GitRefdbBackend.cs | 2 +- LibGit2Sharp/RefdbBackend.cs | 70 +++++++++++++++++-- 3 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 LibGit2Sharp.Tests/Properties/launchSettings.json diff --git a/LibGit2Sharp.Tests/Properties/launchSettings.json b/LibGit2Sharp.Tests/Properties/launchSettings.json new file mode 100644 index 000000000..a06f68133 --- /dev/null +++ b/LibGit2Sharp.Tests/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "LibGit2Sharp.Tests": { + "commandName": "Project", + "nativeDebugging": true + } + } +} \ No newline at end of file diff --git a/LibGit2Sharp/Core/GitRefdbBackend.cs b/LibGit2Sharp/Core/GitRefdbBackend.cs index e508a1972..91f1c18cc 100644 --- a/LibGit2Sharp/Core/GitRefdbBackend.cs +++ b/LibGit2Sharp/Core/GitRefdbBackend.cs @@ -100,7 +100,7 @@ public delegate int write_callback( [MarshalAs(UnmanagedType.Bool)] bool force, git_signature* who, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string message, - ref GitOid oid, + IntPtr oid, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string oldTarget); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index d1c8fda2d..1c1baf154 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -2,6 +2,7 @@ using LibGit2Sharp.Core.Handles; using System; using System.Collections.Generic; +using System.Globalization; using System.Runtime.InteropServices; using System.Text; @@ -25,6 +26,8 @@ protected RefdbBackend(Repository repository) public abstract RefIterator Iterate(string glob); + public abstract bool TryWrite(ReferenceData newRef, ReferenceData oldRef, bool force, Signature signature, string message); + internal IntPtr RefdbBackendPointer { get @@ -96,7 +99,7 @@ public ReferenceData(string refName, string symbolicTarget) this.SymbolicTarget = symbolicTarget; } - internal IntPtr GetPtr() + internal IntPtr MarshalToPtr() { if (IsSymbolic) { @@ -107,6 +110,27 @@ internal IntPtr GetPtr() return Proxy.git_reference__alloc(RefName, ObjectId.Oid).AsIntPtr(); } } + + internal static unsafe ReferenceData MarshalFromPtr(git_reference* ptr) + { + var name = Proxy.git_reference_name(ptr); + var type = Proxy.git_reference_type(ptr); + switch (type) + { + case GitReferenceType.Oid: + var targetOid = Proxy.git_reference_target(ptr); + return new ReferenceData(name, targetOid); + case GitReferenceType.Symbolic: + var targetName = Proxy.git_reference_symbolic_target(ptr); + return new ReferenceData(name, targetName); + default: + throw new LibGit2SharpException( + string.Format( + CultureInfo.InvariantCulture, + "Unable to build a new reference from type '{0}'", + type)); + } + } } public abstract class RefIterator @@ -147,7 +171,7 @@ public static int Next( return (int)GitErrorCode.IterOver; } - referencePtr = data.GetPtr(); + referencePtr = data.MarshalToPtr(); return (int)GitErrorCode.Ok; } @@ -256,7 +280,7 @@ public static int Lookup( return (int)GitErrorCode.NotFound; } - referencePtr = data.GetPtr(); + referencePtr = data.MarshalToPtr(); } catch (Exception ex) { @@ -310,10 +334,46 @@ public static int Write( bool force, git_signature* who, string message, - ref GitOid oid, + IntPtr old, string oldTarget) { - return (int)GitErrorCode.Error; + var backend = PtrToBackend(backendPtr); + if (backend == null) + { + return (int)GitErrorCode.Error; + } + + var signature = new Signature(who); + + // New ref data is constructed directly from the reference pointer. + var newRef = ReferenceData.MarshalFromPtr(reference); + + // Old ref value is provided as a check, so that the refdb can atomically test the old value + // and set the new value, thereby preventing write conflicts. + // If a write conflict is detected, we should return GIT_EMODIFIED. + // If the ref is brand new, the "old" oid pointer is null. + ReferenceData oldRef = null; + if (old != IntPtr.Zero) + { + oldRef = new ReferenceData(oldTarget, ObjectId.BuildFromPtr(old)); + } + + try + { + // If the user returns false, we detected a conflict and aborted the write. + if (!backend.TryWrite(newRef, oldRef, force, signature, message)) + { + Proxy.git_error_set_str(GitErrorCategory.Reference, "old reference value does not match."); + return (int)GitErrorCode.Modified; + } + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Reference, ex); + return (int)GitErrorCode.Error; + } + + return (int)GitErrorCode.Ok; } public static int Rename( From 4a10468da98d0a2cef0824615532b2909cb0ad47 Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Tue, 12 Nov 2019 16:28:52 -0800 Subject: [PATCH 08/26] Write test succeeds. --- LibGit2Sharp.Tests/RefdbBackendFixture.cs | 38 +++++++++++++++++++++-- LibGit2Sharp/RefdbBackend.cs | 34 ++++++++++++++++++++ 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs index ed67ffa92..7bfdb869b 100644 --- a/LibGit2Sharp.Tests/RefdbBackendFixture.cs +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -11,12 +11,24 @@ namespace LibGit2Sharp.Tests { public class RefdbBackendFixture : BaseFixture { + [Fact] + public void CanWriteToRefdbBackend() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var backend = new MockRefdbBackend(repo); + repo.Refs.SetBackend(backend); + repo.Refs.Add("refs/heads/newref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"), true); + Assert.Equal(backend.Refs["refs/heads/newref"], new RefdbBackend.ReferenceData("refs/heads/newref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"))); + } + } + [Fact] public void CanReadFromRefdbBackend() { - var scd = new SelfCleaningDirectory(this); - Repository.Init(scd.RootedDirectoryPath); - using (var repo = new Repository(scd.RootedDirectoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { var backend = new MockRefdbBackend(repo); repo.Refs.SetBackend(backend); @@ -52,6 +64,26 @@ public override bool Lookup(string refName, out ReferenceData data) return Refs.TryGetValue(refName, out data); } + public override bool TryWrite(ReferenceData newRef, ReferenceData oldRef, bool force, Signature signature, string message) + { + ReferenceData existingRef; + if (this.Refs.TryGetValue(newRef.RefName, out existingRef)) + { + Assert.NotNull(oldRef); + if (!existingRef.Equals(oldRef)) + { + return false; + } + } + else + { + Assert.Null(oldRef); + } + + this.Refs[newRef.RefName] = newRef; + return true; + } + private class MockRefIterator : RefIterator { private readonly IEnumerator enumerator; diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index 1c1baf154..59617ca64 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -99,6 +99,40 @@ public ReferenceData(string refName, string symbolicTarget) this.SymbolicTarget = symbolicTarget; } + public override bool Equals(object obj) + { + var other = obj as ReferenceData; + if (other == null) + { + return false; + } + + return other.RefName == this.RefName + && other.IsSymbolic == this.IsSymbolic + && other.ObjectId == this.ObjectId + && other.SymbolicTarget == this.SymbolicTarget; + } + + public override int GetHashCode() + { + unchecked + { + var accumulator = this.RefName.GetHashCode(); + accumulator = accumulator * 17 + this.IsSymbolic.GetHashCode(); + if (this.ObjectId != null) + { + accumulator = accumulator * 17 + this.ObjectId.GetHashCode(); + } + + if (this.SymbolicTarget != null) + { + accumulator = accumulator * 17 + this.SymbolicTarget.GetHashCode(); + } + + return accumulator; + } + } + internal IntPtr MarshalToPtr() { if (IsSymbolic) From 927e6ea30c62dc9930b7fbc08cd5584cf4d7c7ab Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Tue, 12 Nov 2019 17:05:58 -0800 Subject: [PATCH 09/26] Fixed write conflict stuff. --- LibGit2Sharp.Tests/RefdbBackendFixture.cs | 50 ++++++++++++++--- LibGit2Sharp/Core/GitRefdbBackend.cs | 2 +- LibGit2Sharp/RefdbBackend.cs | 66 +++++++++++++++++++++-- 3 files changed, 107 insertions(+), 11 deletions(-) diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs index 7bfdb869b..7a373f685 100644 --- a/LibGit2Sharp.Tests/RefdbBackendFixture.cs +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -41,6 +41,41 @@ public void CanReadFromRefdbBackend() } } + [Fact] + public void CanDeleteFromRefdbBackend() + { + string path = SandboxStandardTestRepo(); + using (Repository repo = new Repository(path)) + { + var backend = new MockRefdbBackend(repo); + repo.Refs.SetBackend(backend); + backend.Refs["HEAD"] = new RefdbBackend.ReferenceData("HEAD", "refs/heads/testref"); + backend.Refs["refs/heads/testref"] = new RefdbBackend.ReferenceData("refs/heads/testref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + + repo.Refs.Remove("refs/heads/testref"); + + Assert.True(!backend.Refs.ContainsKey("refs/heads/testref")); + } + } + + [Fact] + public void CannotOverwriteExistingInRefdbBackend() + { + string path = SandboxStandardTestRepo(); + using (Repository repo = new Repository(path)) + { + var backend = new MockRefdbBackend(repo); + repo.Refs.SetBackend(backend); + + repo.Refs.Add("refs/heads/newref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"), false); + + Assert.Throws(() => repo.Refs.Add("refs/heads/newref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"), false)); + + // With allowOverwrite, it should succeed: + repo.Refs.Add("refs/heads/newref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"), true); + } + } + private class MockRefdbBackend : RefdbBackend { public MockRefdbBackend(Repository repository) : base(repository) @@ -64,21 +99,24 @@ public override bool Lookup(string refName, out ReferenceData data) return Refs.TryGetValue(refName, out data); } + public override bool TryDelete(ReferenceData refData, out bool notFound, out bool hadConflict) + { + hadConflict = false; + notFound = !this.Refs.Remove(refData.RefName); + return !notFound; + } + public override bool TryWrite(ReferenceData newRef, ReferenceData oldRef, bool force, Signature signature, string message) { ReferenceData existingRef; if (this.Refs.TryGetValue(newRef.RefName, out existingRef)) { - Assert.NotNull(oldRef); - if (!existingRef.Equals(oldRef)) + // If either oldRef wasn't provided/didn't match, or force isn't enabled, reject. + if (!force || (oldRef != null && !existingRef.Equals(oldRef))) { return false; } } - else - { - Assert.Null(oldRef); - } this.Refs[newRef.RefName] = newRef; return true; diff --git a/LibGit2Sharp/Core/GitRefdbBackend.cs b/LibGit2Sharp/Core/GitRefdbBackend.cs index 91f1c18cc..d0f17b71e 100644 --- a/LibGit2Sharp/Core/GitRefdbBackend.cs +++ b/LibGit2Sharp/Core/GitRefdbBackend.cs @@ -117,7 +117,7 @@ public delegate int rename_callback( public delegate int del_callback( IntPtr backend, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string refName, - ref GitOid oldId, + IntPtr oldId, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string oldTarget); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index 59617ca64..29bdec101 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -28,6 +28,8 @@ protected RefdbBackend(Repository repository) public abstract bool TryWrite(ReferenceData newRef, ReferenceData oldRef, bool force, Signature signature, string message); + public abstract bool TryDelete(ReferenceData existingRef, out bool notFound, out bool hadConflict); + internal IntPtr RefdbBackendPointer { get @@ -397,8 +399,19 @@ public static int Write( // If the user returns false, we detected a conflict and aborted the write. if (!backend.TryWrite(newRef, oldRef, force, signature, message)) { - Proxy.git_error_set_str(GitErrorCategory.Reference, "old reference value does not match."); - return (int)GitErrorCode.Modified; + if (oldRef != null) + { + Proxy.git_error_set_str(GitErrorCategory.Reference, "old reference value does not match."); + return (int)GitErrorCode.Modified; + } + + if (!force) + { + Proxy.git_error_set_str(GitErrorCategory.Reference, "name is in conflict."); + return (int)GitErrorCode.Exists; + } + + return (int)GitErrorCode.Error; } } catch (Exception ex) @@ -419,16 +432,61 @@ public static int Rename( git_signature* who, string message) { + var backend = PtrToBackend(backendPtr); + if (backend == null) + { + return (int)GitErrorCode.Error; + } + return (int)GitErrorCode.Error; } public static int Del( IntPtr backendPtr, string refName, - ref GitOid oldId, + IntPtr oldId, string oldTarget) { - return (int)GitErrorCode.Error; + var backend = PtrToBackend(backendPtr); + if (backend == null) + { + return (int)GitErrorCode.Error; + } + + ReferenceData existingRef; + if (IntPtr.Zero == oldId) + { + existingRef = new ReferenceData(refName, oldTarget); + } + else + { + existingRef = new ReferenceData(refName, ObjectId.BuildFromPtr(oldId)); + } + + try + { + if (!backend.TryDelete(existingRef, out bool notFound, out bool hadConflict)) + { + if (notFound) + { + return (int)GitErrorCode.NotFound; + } + + if (hadConflict) + { + return (int)GitErrorCode.Conflict; + } + + return (int)GitErrorCode.Error; + } + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Reference, ex); + return (int)GitErrorCode.Error; + } + + return (int)GitErrorCode.Ok; } public static int HasLog( From 78a512e039aaa147e7078612dbdb8961acaec3ca Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Tue, 12 Nov 2019 17:08:21 -0800 Subject: [PATCH 10/26] Set up iteration test. --- LibGit2Sharp.Tests/RefdbBackendFixture.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs index 7a373f685..ea4d99861 100644 --- a/LibGit2Sharp.Tests/RefdbBackendFixture.cs +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -76,6 +76,23 @@ public void CannotOverwriteExistingInRefdbBackend() } } + [Fact] + public void CanIterateRefdbBackend() + { + string path = SandboxStandardTestRepo(); + using (Repository repo = new Repository(path)) + { + var backend = new MockRefdbBackend(repo); + repo.Refs.SetBackend(backend); + + backend.Refs["HEAD"] = new RefdbBackend.ReferenceData("HEAD", "refs/heads/testref"); + backend.Refs["refs/heads/testref"] = new RefdbBackend.ReferenceData("refs/heads/testref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + backend.Refs["refs/heads/othersymbolic"] = new RefdbBackend.ReferenceData("refs/heads/othersymbolic", "refs/heads/testref"); + + Assert.True(repo.Refs.Select(r => r.CanonicalName).SequenceEqual(backend.Refs.Keys)); + } + } + private class MockRefdbBackend : RefdbBackend { public MockRefdbBackend(Repository repository) : base(repository) From 2a1ece4d87e99ee6d88a80e79d0b1f19e43e0f2e Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Tue, 12 Nov 2019 17:15:52 -0800 Subject: [PATCH 11/26] Fixed bug in iteration. --- LibGit2Sharp/RefdbBackend.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index 29bdec101..7dd2848bf 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -233,18 +233,24 @@ public static int NextName( return (int)GitErrorCode.Error; } + if (data == null) + { + return (int)GitErrorCode.IterOver; + } + refNamePtr = data.RefName; return (int)GitErrorCode.Ok; } public static void Free(IntPtr iterator) { + GCHandle.FromIntPtr(Marshal.ReadIntPtr(iterator, GitRefdbIterator.GCHandleOffset)).Free(); Marshal.FreeHGlobal(iterator); } private static RefIterator PtrToBackend(IntPtr pointer) { - var intPtr = Marshal.ReadIntPtr(pointer, GitRefdbBackend.GCHandleOffset); + var intPtr = Marshal.ReadIntPtr(pointer, GitRefdbIterator.GCHandleOffset); var backend = GCHandle.FromIntPtr(intPtr).Target as RefIterator; if (backend == null) From 2f879e8e96ce2ff5ceb2891815fa7443df111d87 Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Tue, 12 Nov 2019 17:45:40 -0800 Subject: [PATCH 12/26] All existing tests from zoxiv's PR pass! --- LibGit2Sharp.Tests/RefdbBackendFixture.cs | 57 +++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs index ea4d99861..8ab884d2b 100644 --- a/LibGit2Sharp.Tests/RefdbBackendFixture.cs +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Xunit; @@ -93,6 +94,48 @@ public void CanIterateRefdbBackend() } } + [Fact] + public void CanIterateTagsInRefdbBackend() + { + string path = SandboxStandardTestRepo(); + using (Repository repo = new Repository(path)) + { + var backend = new MockRefdbBackend(repo); + repo.Refs.SetBackend(backend); + + // The behavior of libgit2 has changed: + // If libgit2 can't resolve any tag to an OID, then git_tag_list silently fails and returns zero tags. + // This test previously used broken refs to test type filtering, but refdb is no longer responsible for type filtering. + // The old test code is commented below: + // backend.Refs["refs/tags/broken1"] = new RefdbBackend.ReferenceData("refs/tags/broken1", "tags/shouldnt/be/symbolic"); + // backend.Refs["refs/tags/broken2"] = new RefdbBackend.ReferenceData("refs/tags/broken2", "but/are/here/for/testing"); + // backend.Refs["refs/tags/broken3"] = new RefdbBackend.ReferenceData("refs/tags/broken3", "the/type/filtering"); + + backend.Refs["refs/tags/correct1"] = new RefdbBackend.ReferenceData("refs/tags/correct1", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + + var tagNames = repo.Tags.Select(r => r.CanonicalName); + Assert.True(tagNames.SequenceEqual(new List { "refs/tags/correct1" })); + } + } + + [Fact] + public void CanIterateRefdbBackendWithGlob() + { + string path = SandboxStandardTestRepo(); + using (Repository repo = new Repository(path)) + { + var backend = new MockRefdbBackend(repo); + repo.Refs.SetBackend(backend); + + backend.Refs["HEAD"] = new RefdbBackend.ReferenceData("HEAD", "refs/heads/testref"); + backend.Refs["refs/heads/testref"] = new RefdbBackend.ReferenceData("refs/heads/testref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + backend.Refs["refs/heads/othersymbolic"] = new RefdbBackend.ReferenceData("refs/heads/othersymbolic", "refs/heads/testref"); + + Assert.True(repo.Refs.FromGlob("refs/heads/*").Select(r => r.CanonicalName).SequenceEqual(new List() { "refs/heads/othersymbolic", "refs/heads/testref" })); + Assert.True(repo.Refs.FromGlob("refs/heads/?estref").Select(r => r.CanonicalName).SequenceEqual(new List() { "refs/heads/testref" })); + } + } + private class MockRefdbBackend : RefdbBackend { public MockRefdbBackend(Repository repository) : base(repository) @@ -108,7 +151,7 @@ public override bool Exists(string refName) public override RefIterator Iterate(string glob) { - return new MockRefIterator(this); + return new MockRefIterator(this, glob); } public override bool Lookup(string refName, out ReferenceData data) @@ -143,9 +186,17 @@ private class MockRefIterator : RefIterator { private readonly IEnumerator enumerator; - public MockRefIterator(MockRefdbBackend parent) + public MockRefIterator(MockRefdbBackend parent, string glob) { - this.enumerator = parent.Refs.Values.GetEnumerator(); + if (string.IsNullOrEmpty(glob)) + { + this.enumerator = parent.Refs.Values.GetEnumerator(); + } + else + { + var globRegex = new Regex("^" + Regex.Escape(glob).Replace(@"\*", ".*").Replace(@"\?", ".") + "$"); + this.enumerator = parent.Refs.Values.Where(r => globRegex.IsMatch(r.RefName)).GetEnumerator(); + } } public override ReferenceData GetNext() From d6a8d68ecd1123f91d05b4749e94ff57d6b18529 Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Tue, 12 Nov 2019 18:08:33 -0800 Subject: [PATCH 13/26] Refactored to use exceptions. --- LibGit2Sharp.Tests/RefdbBackendFixture.cs | 20 ++--- LibGit2Sharp/RefdbBackend.cs | 99 ++++++++++++----------- 2 files changed, 62 insertions(+), 57 deletions(-) diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs index 8ab884d2b..5c6155806 100644 --- a/LibGit2Sharp.Tests/RefdbBackendFixture.cs +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -159,27 +159,29 @@ public override bool Lookup(string refName, out ReferenceData data) return Refs.TryGetValue(refName, out data); } - public override bool TryDelete(ReferenceData refData, out bool notFound, out bool hadConflict) + public override void Delete(ReferenceData refData) { - hadConflict = false; - notFound = !this.Refs.Remove(refData.RefName); - return !notFound; + if (!this.Refs.Remove(refData.RefName)) + { + throw RefdbBackendException.NotFound(refData.RefName); + } } - public override bool TryWrite(ReferenceData newRef, ReferenceData oldRef, bool force, Signature signature, string message) + public override void Write(ReferenceData newRef, ReferenceData oldRef, bool force, Signature signature, string message) { ReferenceData existingRef; - if (this.Refs.TryGetValue(newRef.RefName, out existingRef)) + if (!force && this.Refs.TryGetValue(newRef.RefName, out existingRef)) { // If either oldRef wasn't provided/didn't match, or force isn't enabled, reject. - if (!force || (oldRef != null && !existingRef.Equals(oldRef))) + if ((oldRef != null && !existingRef.Equals(oldRef))) { - return false; + throw RefdbBackendException.Conflict(newRef.RefName); } + + throw RefdbBackendException.Exists(newRef.RefName); } this.Refs[newRef.RefName] = newRef; - return true; } private class MockRefIterator : RefIterator diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index 7dd2848bf..3b766ba08 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -26,9 +26,9 @@ protected RefdbBackend(Repository repository) public abstract RefIterator Iterate(string glob); - public abstract bool TryWrite(ReferenceData newRef, ReferenceData oldRef, bool force, Signature signature, string message); + public abstract void Write(ReferenceData newRef, ReferenceData oldRef, bool force, Signature signature, string message); - public abstract bool TryDelete(ReferenceData existingRef, out bool notFound, out bool hadConflict); + public abstract void Delete(ReferenceData existingRef); internal IntPtr RefdbBackendPointer { @@ -174,6 +174,44 @@ public abstract class RefIterator public abstract ReferenceData GetNext(); } + protected sealed class RefdbBackendException : LibGit2SharpException + { + private RefdbBackendException(GitErrorCode code, string message) + : base(message, code, GitErrorCategory.Reference) + { + Code = code; + } + + internal GitErrorCode Code { get; private set; } + + public static RefdbBackendException NotFound(string refName) + { + return new RefdbBackendException(GitErrorCode.NotFound, string.Format("could not resolve reference '{0}'", refName)); + } + + public static RefdbBackendException Exists(string refName) + { + return new RefdbBackendException(GitErrorCode.Exists, string.Format("will not overwrite reference '{0}' without match or force", refName)); + } + + public static RefdbBackendException Conflict(string refName) + { + return new RefdbBackendException(GitErrorCode.Conflict, string.Format("conflict occurred while writing reference '{0}'", refName)); + } + + internal static int GetCode(Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Reference, ex); + var backendException = ex as RefdbBackendException; + if (backendException == null) + { + return (int)GitErrorCode.Error; + } + + return (int)backendException.Code; + } + } + private unsafe static class IteratorEntryPoints { public static readonly GitRefdbIterator.next_callback NextCallback = Next; @@ -198,8 +236,7 @@ public static int Next( } catch (Exception ex) { - Proxy.git_error_set_str(GitErrorCategory.Reference, ex); - return (int)GitErrorCode.Error; + return RefdbBackendException.GetCode(ex); } if (data == null) @@ -229,8 +266,7 @@ public static int NextName( } catch (Exception ex) { - Proxy.git_error_set_str(GitErrorCategory.Reference, ex); - return (int)GitErrorCode.Error; + return RefdbBackendException.GetCode(ex); } if (data == null) @@ -295,11 +331,10 @@ public static int Exists( } catch (Exception ex) { - Proxy.git_error_set_str(GitErrorCategory.Reference, ex); - return (int)GitErrorCode.Error; + return RefdbBackendException.GetCode(ex); } - return 0; + return (int)GitErrorCode.Ok; } public static int Lookup( @@ -326,11 +361,10 @@ public static int Lookup( } catch (Exception ex) { - Proxy.git_error_set_str(GitErrorCategory.Reference, ex); - return (int)GitErrorCode.Error; + return RefdbBackendException.GetCode(ex); } - return 0; + return (int)GitErrorCode.Ok; } public static int Iterator( @@ -352,8 +386,7 @@ public static int Iterator( } catch (Exception ex) { - Proxy.git_error_set_str(GitErrorCategory.Reference, ex); - return (int)GitErrorCode.Error; + return RefdbBackendException.GetCode(ex); } var nativeIterator = new GitRefdbIterator() @@ -403,27 +436,11 @@ public static int Write( try { // If the user returns false, we detected a conflict and aborted the write. - if (!backend.TryWrite(newRef, oldRef, force, signature, message)) - { - if (oldRef != null) - { - Proxy.git_error_set_str(GitErrorCategory.Reference, "old reference value does not match."); - return (int)GitErrorCode.Modified; - } - - if (!force) - { - Proxy.git_error_set_str(GitErrorCategory.Reference, "name is in conflict."); - return (int)GitErrorCode.Exists; - } - - return (int)GitErrorCode.Error; - } + backend.Write(newRef, oldRef, force, signature, message); } catch (Exception ex) { - Proxy.git_error_set_str(GitErrorCategory.Reference, ex); - return (int)GitErrorCode.Error; + return RefdbBackendException.GetCode(ex); } return (int)GitErrorCode.Ok; @@ -471,25 +488,11 @@ public static int Del( try { - if (!backend.TryDelete(existingRef, out bool notFound, out bool hadConflict)) - { - if (notFound) - { - return (int)GitErrorCode.NotFound; - } - - if (hadConflict) - { - return (int)GitErrorCode.Conflict; - } - - return (int)GitErrorCode.Error; - } + backend.Delete(existingRef); } catch (Exception ex) { - Proxy.git_error_set_str(GitErrorCategory.Reference, ex); - return (int)GitErrorCode.Error; + return RefdbBackendException.GetCode(ex); } return (int)GitErrorCode.Ok; From 9a7ca0903c8d21ef2df437f5db3e2302d9ce00cd Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Tue, 12 Nov 2019 18:42:21 -0800 Subject: [PATCH 14/26] Got rename working! --- LibGit2Sharp.Tests/RefdbBackendFixture.cs | 45 +++++++++++++++++++++++ LibGit2Sharp/Core/GitRefdbBackend.cs | 2 +- LibGit2Sharp/RefdbBackend.cs | 20 +++++++++- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs index 5c6155806..0ea666e26 100644 --- a/LibGit2Sharp.Tests/RefdbBackendFixture.cs +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -136,6 +136,23 @@ public void CanIterateRefdbBackendWithGlob() } } + [Fact] + public void RefdbBackendCanRenameAReferenceToADeeperReferenceHierarchy() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + var backend = new MockRefdbBackend(repo); + repo.Refs.SetBackend(backend); + backend.Refs["refs/tags/test"] = new RefdbBackend.ReferenceData("refs/tags/test", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + const string newName = "refs/tags/test/deep"; + + Reference renamed = repo.Refs.Rename("refs/tags/test", newName); + Assert.NotNull(renamed); + Assert.Equal(newName, renamed.CanonicalName); + } + } + private class MockRefdbBackend : RefdbBackend { public MockRefdbBackend(Repository repository) : base(repository) @@ -184,6 +201,34 @@ public override void Write(ReferenceData newRef, ReferenceData oldRef, bool forc this.Refs[newRef.RefName] = newRef; } + public override ReferenceData Rename(string oldName, string newName, bool force, Signature signature, string message) + { + ReferenceData oldValue; + if (!this.Refs.TryGetValue(oldName, out oldValue)) + { + throw RefdbBackendException.NotFound(oldName); + } + + if (!force && this.Refs.ContainsKey(newName)) + { + throw RefdbBackendException.Exists(newName); + } + + ReferenceData newRef; + if (oldValue.IsSymbolic) + { + newRef = new ReferenceData(newName, oldValue.SymbolicTarget); + } + else + { + newRef = new ReferenceData(newName, oldValue.ObjectId); + } + + this.Refs.Remove(oldName); + this.Refs[newName] = newRef; + return newRef; + } + private class MockRefIterator : RefIterator { private readonly IEnumerator enumerator; diff --git a/LibGit2Sharp/Core/GitRefdbBackend.cs b/LibGit2Sharp/Core/GitRefdbBackend.cs index d0f17b71e..d974ef051 100644 --- a/LibGit2Sharp/Core/GitRefdbBackend.cs +++ b/LibGit2Sharp/Core/GitRefdbBackend.cs @@ -105,7 +105,7 @@ public delegate int write_callback( [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int rename_callback( - git_reference* reference, + out IntPtr reference, IntPtr backend, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string oldName, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string newName, diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index 3b766ba08..404ecb5f1 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -30,6 +30,8 @@ protected RefdbBackend(Repository repository) public abstract void Delete(ReferenceData existingRef); + public abstract ReferenceData Rename(string oldName, string newName, bool force, Signature signature, string message); + internal IntPtr RefdbBackendPointer { get @@ -447,7 +449,7 @@ public static int Write( } public static int Rename( - git_reference* reference, + out IntPtr reference, IntPtr backendPtr, string oldName, string newName, @@ -455,13 +457,27 @@ public static int Rename( git_signature* who, string message) { + reference = IntPtr.Zero; var backend = PtrToBackend(backendPtr); if (backend == null) { return (int)GitErrorCode.Error; } - return (int)GitErrorCode.Error; + var signature = new Signature(who); + + ReferenceData newRef; + try + { + newRef = backend.Rename(oldName, newName, force, signature, message); + } + catch (Exception ex) + { + return RefdbBackendException.GetCode(ex); + } + + reference = newRef.MarshalToPtr(); + return (int)GitErrorCode.Ok; } public static int Del( From e747a8348d30507c5eb6dea7b8566ad0177f9d6a Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Tue, 12 Nov 2019 18:50:33 -0800 Subject: [PATCH 15/26] Refactored iterator. --- LibGit2Sharp.Tests/RefdbBackendFixture.cs | 40 +++++-------------- LibGit2Sharp/RefdbBackend.cs | 48 +++++++++++++++++++---- 2 files changed, 51 insertions(+), 37 deletions(-) diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs index 0ea666e26..687351b29 100644 --- a/LibGit2Sharp.Tests/RefdbBackendFixture.cs +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -166,9 +166,17 @@ public override bool Exists(string refName) return Refs.ContainsKey(refName); } - public override RefIterator Iterate(string glob) + public override IEnumerable Iterate(string glob) { - return new MockRefIterator(this, glob); + if (string.IsNullOrEmpty(glob)) + { + return Refs.Values; + } + else + { + var globRegex = new Regex("^" + Regex.Escape(glob).Replace(@"\*", ".*").Replace(@"\?", ".") + "$"); + return Refs.Values.Where(r => globRegex.IsMatch(r.RefName)); + } } public override bool Lookup(string refName, out ReferenceData data) @@ -228,34 +236,6 @@ public override ReferenceData Rename(string oldName, string newName, bool force, this.Refs[newName] = newRef; return newRef; } - - private class MockRefIterator : RefIterator - { - private readonly IEnumerator enumerator; - - public MockRefIterator(MockRefdbBackend parent, string glob) - { - if (string.IsNullOrEmpty(glob)) - { - this.enumerator = parent.Refs.Values.GetEnumerator(); - } - else - { - var globRegex = new Regex("^" + Regex.Escape(glob).Replace(@"\*", ".*").Replace(@"\?", ".") + "$"); - this.enumerator = parent.Refs.Values.Where(r => globRegex.IsMatch(r.RefName)).GetEnumerator(); - } - } - - public override ReferenceData GetNext() - { - if (this.enumerator.MoveNext()) - { - return this.enumerator.Current; - } - - return null; - } - } } } } diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index 404ecb5f1..93e5a4412 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -1,6 +1,7 @@ using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Runtime.InteropServices; @@ -8,23 +9,40 @@ namespace LibGit2Sharp { + /// + /// Reference database backend. + /// public abstract class RefdbBackend { private IntPtr nativePointer; + /// + /// Gets the repository. + /// protected Repository Repository { get; private set; } + /// + /// Initializes a new instance of the class. + /// + /// Repository that this refdb is attached to. protected RefdbBackend(Repository repository) { Ensure.ArgumentNotNull(repository, "repository"); this.Repository = repository; } + /// + /// Checks to see if a reference exists. + /// public abstract bool Exists(string refName); + /// + /// Attempts to look up a reference. + /// + /// False if the reference doesn't exist. public abstract bool Lookup(string refName, out ReferenceData data); - public abstract RefIterator Iterate(string glob); + public abstract IEnumerable Iterate(string glob); public abstract void Write(ReferenceData newRef, ReferenceData oldRef, bool force, Signature signature, string message); @@ -171,11 +189,6 @@ internal static unsafe ReferenceData MarshalFromPtr(git_reference* ptr) } } - public abstract class RefIterator - { - public abstract ReferenceData GetNext(); - } - protected sealed class RefdbBackendException : LibGit2SharpException { private RefdbBackendException(GitErrorCode code, string message) @@ -214,6 +227,26 @@ internal static int GetCode(Exception ex) } } + private class RefIterator + { + private readonly IEnumerator enumerator; + + public RefIterator(IEnumerator enumerator) + { + this.enumerator = enumerator; + } + + public ReferenceData GetNext() + { + if (this.enumerator.MoveNext()) + { + return this.enumerator.Current; + } + + return null; + } + } + private unsafe static class IteratorEntryPoints { public static readonly GitRefdbIterator.next_callback NextCallback = Next; @@ -384,7 +417,8 @@ public static int Iterator( RefIterator iterator; try { - iterator = backend.Iterate(glob); + var enumerator = backend.Iterate(glob).GetEnumerator(); + iterator = new RefIterator(enumerator); } catch (Exception ex) { From a86ffdc5487a086f2f843fbb8a9d4fe080b1ff9e Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Tue, 12 Nov 2019 19:29:22 -0800 Subject: [PATCH 16/26] Documentation, added supported capabilities. --- LibGit2Sharp/RefdbBackend.cs | 125 ++++++++++++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index 93e5a4412..27db9a40d 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -14,8 +14,44 @@ namespace LibGit2Sharp /// public abstract class RefdbBackend { + /// + /// Minimum supported operations. + /// + [Flags] + public enum SupportedOperations + { + /// + /// At a minimum: Exists, Lookup, Iterate, Write, Delete, Rename + /// + /// + /// libgit2 wants us to also support all the reflog operations, but unless + /// reflog is enabled on the repository, we shouldn't have any issues stubbing those out. + /// + Minimum = 0, + + /// + /// Compress operation. + /// + Compress = 1, + + /// + /// Lock and unlock (transactional) operations. + /// + LockUnlock = 2, + + /// + /// Reflog operations. + /// + Reflog = 4 + } + private IntPtr nativePointer; + /// + /// Gets the supported operations. + /// + protected abstract SupportedOperations OperationsSupported { get; } + /// /// Gets the repository. /// @@ -42,14 +78,41 @@ protected RefdbBackend(Repository repository) /// False if the reference doesn't exist. public abstract bool Lookup(string refName, out ReferenceData data); + /// + /// Iterates all references (if glob is null) or only references matching glob (if not null.) + /// public abstract IEnumerable Iterate(string glob); + /// + /// Writes a reference to the database. + /// + /// New reference to write. + /// Old reference (possibly null.) + /// True if overwrites are allowed. + /// User signature. + /// User message. public abstract void Write(ReferenceData newRef, ReferenceData oldRef, bool force, Signature signature, string message); + /// + /// Deletes a reference from the database. + /// + /// Reference to delete. public abstract void Delete(ReferenceData existingRef); + /// + /// Renames a reference. + /// + /// Old name. + /// New name. + /// Allow overwrites. + /// User signature. + /// User message. + /// New reference. public abstract ReferenceData Rename(string oldName, string newName, bool force, Signature signature, string message); + /// + /// Backend pointer. Accessing this lazily allocates a marshalled GitRefdbBackend, which is freed with Free(). + /// internal IntPtr RefdbBackendPointer { get @@ -86,6 +149,9 @@ internal IntPtr RefdbBackendPointer } } + /// + /// Frees the backend pointer, if one has been allocated. + /// internal void Free() { if (IntPtr.Zero == nativePointer) @@ -98,13 +164,34 @@ internal void Free() nativePointer = IntPtr.Zero; } + /// + /// Backend's representation of a reference. + /// public class ReferenceData { + /// + /// Reference name. + /// public string RefName { get; private set; } + + /// + /// True if symbolic; otherwise, false. + /// public bool IsSymbolic { get; private set; } + + /// + /// Object ID, if the ref isn't symbolic. + /// public ObjectId ObjectId { get; private set; } + + /// + /// Target name, if the ref is symbolic. + /// public string SymbolicTarget { get; private set; } + /// + /// Initializes a direct reference. + /// public ReferenceData(string refName, ObjectId directTarget) { this.RefName = refName; @@ -113,6 +200,9 @@ public ReferenceData(string refName, ObjectId directTarget) this.SymbolicTarget = null; } + /// + /// Initializes a symbolic reference. + /// public ReferenceData(string refName, string symbolicTarget) { this.RefName = refName; @@ -121,6 +211,7 @@ public ReferenceData(string refName, string symbolicTarget) this.SymbolicTarget = symbolicTarget; } + /// public override bool Equals(object obj) { var other = obj as ReferenceData; @@ -135,6 +226,7 @@ public override bool Equals(object obj) && other.SymbolicTarget == this.SymbolicTarget; } + /// public override int GetHashCode() { unchecked @@ -189,7 +281,11 @@ internal static unsafe ReferenceData MarshalFromPtr(git_reference* ptr) } } - protected sealed class RefdbBackendException : LibGit2SharpException + /// + /// Exception types that can be thrown from the backend. + /// Exceptions of this type will be converted to libgit2 error codes. + /// + public sealed class RefdbBackendException : LibGit2SharpException { private RefdbBackendException(GitErrorCode code, string message) : base(message, code, GitErrorCategory.Reference) @@ -199,21 +295,48 @@ private RefdbBackendException(GitErrorCode code, string message) internal GitErrorCode Code { get; private set; } + /// + /// Reference was not found. + /// public static RefdbBackendException NotFound(string refName) { return new RefdbBackendException(GitErrorCode.NotFound, string.Format("could not resolve reference '{0}'", refName)); } + /// + /// Reference by this name already exists. + /// public static RefdbBackendException Exists(string refName) { return new RefdbBackendException(GitErrorCode.Exists, string.Format("will not overwrite reference '{0}' without match or force", refName)); } + /// + /// Conflict between an expected reference value and the reference's actual value. + /// public static RefdbBackendException Conflict(string refName) { return new RefdbBackendException(GitErrorCode.Conflict, string.Format("conflict occurred while writing reference '{0}'", refName)); } + /// + /// User is not allowed to alter this reference. + /// + /// Arbitrary message. + public static RefdbBackendException NotAllowed(string message) + { + return new RefdbBackendException(GitErrorCode.Auth, message); + } + + /// + /// Operation is not implemented. + /// + /// Operation that's not implemented. + public static RefdbBackendException NotImplemented(string operation) + { + return new RefdbBackendException(GitErrorCode.User, string.Format("operation '{0}' is unsupported by this refdb backend.", operation)); + } + internal static int GetCode(Exception ex) { Proxy.git_error_set_str(GitErrorCategory.Reference, ex); From 92584287e936edf25cbfccb6950ffc7b053063d6 Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Tue, 12 Nov 2019 19:50:37 -0800 Subject: [PATCH 17/26] Plumbed lock, unlock and compress. --- LibGit2Sharp/Core/GitRefdbBackend.cs | 25 +++++- LibGit2Sharp/RefdbBackend.cs | 124 ++++++++++++++++++++++++++- 2 files changed, 143 insertions(+), 6 deletions(-) diff --git a/LibGit2Sharp/Core/GitRefdbBackend.cs b/LibGit2Sharp/Core/GitRefdbBackend.cs index d974ef051..37e50c73d 100644 --- a/LibGit2Sharp/Core/GitRefdbBackend.cs +++ b/LibGit2Sharp/Core/GitRefdbBackend.cs @@ -56,7 +56,7 @@ static GitRefdbBackend() public write_callback Write; public rename_callback Rename; public del_callback Del; - public IntPtr Compress; + public compress_callback Compress; public has_log_callback HasLog; public ensure_log_callback EnsureLog; public free_callback Free; @@ -64,8 +64,8 @@ static GitRefdbBackend() public reflog_write_callback ReflogWrite; public reflog_rename_callback ReflogRename; public reflog_delete_callback ReflogDelete; - public IntPtr Lock; - public IntPtr Unlock; + public lock_callback Lock; + public unlock_callback Unlock; /* The libgit2 structure definition ends here. Subsequent fields are for libgit2sharp bookkeeping. */ @@ -120,6 +120,9 @@ public delegate int del_callback( IntPtr oldId, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string oldTarget); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int compress_callback(IntPtr backend); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int has_log_callback( IntPtr backend, @@ -154,5 +157,21 @@ public delegate int reflog_rename_callback( public delegate int reflog_delete_callback( IntPtr backend, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string name); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int lock_callback( + out IntPtr payloadOut, + IntPtr backend, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string refName); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int unlock_callback( + IntPtr backend, + IntPtr payload, + [MarshalAs(UnmanagedType.Bool)] bool success, + [MarshalAs(UnmanagedType.Bool)] bool updateRefLog, + git_reference* reference, + git_signature* who, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string message); } } diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index 27db9a40d..486dee4fa 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -110,6 +110,30 @@ protected RefdbBackend(Repository repository) /// New reference. public abstract ReferenceData Rename(string oldName, string newName, bool force, Signature signature, string message); + /// + /// Compresses the references. + /// + public virtual void Compress() + { + throw RefdbBackendException.NotImplemented("Compress"); + } + + /// + /// Locks a single reference, returning a payload object which is passed to unlock it. + /// + public virtual object Lock(string refName) + { + throw RefdbBackendException.NotImplemented("Lock"); + } + + /// + /// Unlocks a single reference, given its payload object and operational details. + /// + public virtual object Unlock(object payload, ReferenceData reference, Signature sig, string message, bool success, bool updateReflog) + { + throw RefdbBackendException.NotImplemented("Unlock"); + } + /// /// Backend pointer. Accessing this lazily allocates a marshalled GitRefdbBackend, which is freed with Free(). /// @@ -122,9 +146,9 @@ internal IntPtr RefdbBackendPointer var nativeBackend = new GitRefdbBackend() { Version = 1, - Compress = IntPtr.Zero, - Lock = IntPtr.Zero, - Unlock = IntPtr.Zero, + Compress = null, + Lock = null, + Unlock = null, Exists = BackendEntryPoints.ExistsCallback, Lookup = BackendEntryPoints.LookupCallback, Iterator = BackendEntryPoints.IteratorCallback, @@ -141,6 +165,17 @@ internal IntPtr RefdbBackendPointer GCHandle = GCHandle.ToIntPtr(GCHandle.Alloc(this)) }; + if (OperationsSupported.HasFlag(SupportedOperations.Compress)) + { + nativeBackend.Compress = BackendEntryPoints.CompressCallback; + } + + if (OperationsSupported.HasFlag(SupportedOperations.LockUnlock)) + { + nativeBackend.Lock = BackendEntryPoints.LockCallback; + nativeBackend.Unlock = BackendEntryPoints.UnlockCallback; + } + nativePointer = Marshal.AllocHGlobal(Marshal.SizeOf(nativeBackend)); Marshal.StructureToPtr(nativeBackend, nativePointer, false); } @@ -464,6 +499,7 @@ private unsafe static class BackendEntryPoints public static readonly GitRefdbBackend.write_callback WriteCallback = Write; public static readonly GitRefdbBackend.rename_callback RenameCallback = Rename; public static readonly GitRefdbBackend.del_callback DelCallback = Del; + public static readonly GitRefdbBackend.compress_callback CompressCallback = Compress; public static readonly GitRefdbBackend.has_log_callback HasLogCallback = HasLog; public static readonly GitRefdbBackend.ensure_log_callback EnsureLogCallback = EnsureLog; public static readonly GitRefdbBackend.free_callback FreeCallback = Free; @@ -471,6 +507,8 @@ private unsafe static class BackendEntryPoints public static readonly GitRefdbBackend.reflog_write_callback ReflogWriteCallback = ReflogWrite; public static readonly GitRefdbBackend.reflog_rename_callback ReflogRenameCallback = ReflogRename; public static readonly GitRefdbBackend.reflog_delete_callback ReflogDeleteCallback = ReflogDelete; + public static readonly GitRefdbBackend.lock_callback LockCallback = Lock; + public static readonly GitRefdbBackend.unlock_callback UnlockCallback = Unlock; public static int Exists( ref bool exists, @@ -671,6 +709,26 @@ public static int Del( return (int)GitErrorCode.Ok; } + public static int Compress(IntPtr backendPtr) + { + var backend = PtrToBackend(backendPtr); + if (backend == null) + { + return (int)GitErrorCode.Error; + } + + try + { + backend.Compress(); + } + catch (Exception ex) + { + return RefdbBackendException.GetCode(ex); + } + + return (int)GitErrorCode.Ok; + } + public static int HasLog( IntPtr backend, string refName) @@ -721,6 +779,66 @@ public static int ReflogDelete( return (int)GitErrorCode.Error; } + public static int Lock( + out IntPtr payloadPtr, + IntPtr backendPtr, + string refName) + { + payloadPtr = IntPtr.Zero; + var backend = PtrToBackend(backendPtr); + if (backend == null) + { + return (int)GitErrorCode.Error; + } + + object payload; + try + { + payload = backend.Lock(refName); + } + catch (Exception ex) + { + return RefdbBackendException.GetCode(ex); + } + + payloadPtr = GCHandle.ToIntPtr(GCHandle.Alloc(payload)); + return (int)GitErrorCode.Ok; + } + + public static int Unlock( + IntPtr backendPtr, + IntPtr payloadPtr, + bool success, + bool updateReflog, + git_reference* referencePtr, + git_signature* who, + string message) + { + var backend = PtrToBackend(backendPtr); + if (backend == null) + { + return (int)GitErrorCode.Error; + } + + var reference = ReferenceData.MarshalFromPtr(referencePtr); + var signature = new Signature(who); + + var payloadGC = GCHandle.FromIntPtr(payloadPtr); + var payload = payloadGC.Target; + payloadGC.Free(); + + try + { + backend.Unlock(payload, reference, signature, message, success, updateReflog); + } + catch (Exception ex) + { + return RefdbBackendException.GetCode(ex); + } + + return (int)GitErrorCode.Ok; + } + private static RefdbBackend PtrToBackend(IntPtr pointer) { var intPtr = Marshal.ReadIntPtr(pointer, GitRefdbBackend.GCHandleOffset); From 51cd524ce5cd2d9a669e8f7946c126f41ccda484 Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Wed, 13 Nov 2019 00:13:18 -0800 Subject: [PATCH 18/26] More documentation and cleanup. --- LibGit2Sharp.Tests/RefdbBackendFixture.cs | 19 +++++++++++++++++-- LibGit2Sharp/RefdbBackend.cs | 23 ++++++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs index 687351b29..1a0286f96 100644 --- a/LibGit2Sharp.Tests/RefdbBackendFixture.cs +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -122,7 +122,7 @@ public void CanIterateTagsInRefdbBackend() public void CanIterateRefdbBackendWithGlob() { string path = SandboxStandardTestRepo(); - using (Repository repo = new Repository(path)) + using (var repo = new Repository(path)) { var backend = new MockRefdbBackend(repo); repo.Refs.SetBackend(backend); @@ -147,7 +147,7 @@ public void RefdbBackendCanRenameAReferenceToADeeperReferenceHierarchy() backend.Refs["refs/tags/test"] = new RefdbBackend.ReferenceData("refs/tags/test", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); const string newName = "refs/tags/test/deep"; - Reference renamed = repo.Refs.Rename("refs/tags/test", newName); + var renamed = repo.Refs.Rename("refs/tags/test", newName); Assert.NotNull(renamed); Assert.Equal(newName, renamed.CanonicalName); } @@ -161,6 +161,11 @@ public MockRefdbBackend(Repository repository) : base(repository) public SortedDictionary Refs { get; } = new SortedDictionary(); + protected override SupportedOperations OperationsSupported + { + get { return SupportedOperations.Minimum | SupportedOperations.LockUnlock; } + } + public override bool Exists(string refName) { return Refs.ContainsKey(refName); @@ -236,6 +241,16 @@ public override ReferenceData Rename(string oldName, string newName, bool force, this.Refs[newName] = newRef; return newRef; } + + public override object Lock(string refName) + { + return base.Lock(refName); + } + + public override void Unlock(object payload, ReferenceData reference, Signature sig, string message, bool success, bool updateReflog) + { + base.Unlock(payload, reference, sig, message, success, updateReflog); + } } } } diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index 486dee4fa..ec3acf730 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -129,7 +129,7 @@ public virtual object Lock(string refName) /// /// Unlocks a single reference, given its payload object and operational details. /// - public virtual object Unlock(object payload, ReferenceData reference, Signature sig, string message, bool success, bool updateReflog) + public virtual void Unlock(object payload, ReferenceData reference, Signature sig, string message, bool success, bool updateReflog) { throw RefdbBackendException.NotImplemented("Unlock"); } @@ -282,6 +282,9 @@ public override int GetHashCode() } } + /// + /// Allocates a native git_reference for the and returns a pointer. + /// internal IntPtr MarshalToPtr() { if (IsSymbolic) @@ -294,6 +297,9 @@ internal IntPtr MarshalToPtr() } } + /// + /// Marshals a git_reference into a managed + /// internal static unsafe ReferenceData MarshalFromPtr(git_reference* ptr) { var name = Proxy.git_reference_name(ptr); @@ -328,6 +334,9 @@ private RefdbBackendException(GitErrorCode code, string message) Code = code; } + /// + /// Git error code to return on exception. + /// internal GitErrorCode Code { get; private set; } /// @@ -372,6 +381,9 @@ public static RefdbBackendException NotImplemented(string operation) return new RefdbBackendException(GitErrorCode.User, string.Format("operation '{0}' is unsupported by this refdb backend.", operation)); } + /// + /// Transform an exception into an error code and message, which is logged. + /// internal static int GetCode(Exception ex) { Proxy.git_error_set_str(GitErrorCategory.Reference, ex); @@ -385,6 +397,9 @@ internal static int GetCode(Exception ex) } } + /// + /// Wrapper to hold the state of the enumerator. + /// private class RefIterator { private readonly IEnumerator enumerator; @@ -405,6 +420,9 @@ public ReferenceData GetNext() } } + /// + /// Static entrypoints that trampoline into the iterator. + /// private unsafe static class IteratorEntryPoints { public static readonly GitRefdbIterator.next_callback NextCallback = Next; @@ -491,6 +509,9 @@ private static RefIterator PtrToBackend(IntPtr pointer) } } + /// + /// Static entry points that trampoline into the custom backend's implementation. + /// private unsafe static class BackendEntryPoints { public static readonly GitRefdbBackend.exists_callback ExistsCallback = Exists; From 82bdacdce6e4174aa667b062f4682b17436bb652 Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Wed, 13 Nov 2019 00:35:15 -0800 Subject: [PATCH 19/26] Removed lock and compress, as libgit2sharp doesn't use them. --- LibGit2Sharp.Tests/RefdbBackendFixture.cs | 15 --- LibGit2Sharp/RefdbBackend.cs | 154 ---------------------- 2 files changed, 169 deletions(-) diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs index 1a0286f96..c309fbfa3 100644 --- a/LibGit2Sharp.Tests/RefdbBackendFixture.cs +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -161,11 +161,6 @@ public MockRefdbBackend(Repository repository) : base(repository) public SortedDictionary Refs { get; } = new SortedDictionary(); - protected override SupportedOperations OperationsSupported - { - get { return SupportedOperations.Minimum | SupportedOperations.LockUnlock; } - } - public override bool Exists(string refName) { return Refs.ContainsKey(refName); @@ -241,16 +236,6 @@ public override ReferenceData Rename(string oldName, string newName, bool force, this.Refs[newName] = newRef; return newRef; } - - public override object Lock(string refName) - { - return base.Lock(refName); - } - - public override void Unlock(object payload, ReferenceData reference, Signature sig, string message, bool success, bool updateReflog) - { - base.Unlock(payload, reference, sig, message, success, updateReflog); - } } } } diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index ec3acf730..bff7c1fff 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -14,44 +14,8 @@ namespace LibGit2Sharp /// public abstract class RefdbBackend { - /// - /// Minimum supported operations. - /// - [Flags] - public enum SupportedOperations - { - /// - /// At a minimum: Exists, Lookup, Iterate, Write, Delete, Rename - /// - /// - /// libgit2 wants us to also support all the reflog operations, but unless - /// reflog is enabled on the repository, we shouldn't have any issues stubbing those out. - /// - Minimum = 0, - - /// - /// Compress operation. - /// - Compress = 1, - - /// - /// Lock and unlock (transactional) operations. - /// - LockUnlock = 2, - - /// - /// Reflog operations. - /// - Reflog = 4 - } - private IntPtr nativePointer; - /// - /// Gets the supported operations. - /// - protected abstract SupportedOperations OperationsSupported { get; } - /// /// Gets the repository. /// @@ -110,30 +74,6 @@ protected RefdbBackend(Repository repository) /// New reference. public abstract ReferenceData Rename(string oldName, string newName, bool force, Signature signature, string message); - /// - /// Compresses the references. - /// - public virtual void Compress() - { - throw RefdbBackendException.NotImplemented("Compress"); - } - - /// - /// Locks a single reference, returning a payload object which is passed to unlock it. - /// - public virtual object Lock(string refName) - { - throw RefdbBackendException.NotImplemented("Lock"); - } - - /// - /// Unlocks a single reference, given its payload object and operational details. - /// - public virtual void Unlock(object payload, ReferenceData reference, Signature sig, string message, bool success, bool updateReflog) - { - throw RefdbBackendException.NotImplemented("Unlock"); - } - /// /// Backend pointer. Accessing this lazily allocates a marshalled GitRefdbBackend, which is freed with Free(). /// @@ -165,17 +105,6 @@ internal IntPtr RefdbBackendPointer GCHandle = GCHandle.ToIntPtr(GCHandle.Alloc(this)) }; - if (OperationsSupported.HasFlag(SupportedOperations.Compress)) - { - nativeBackend.Compress = BackendEntryPoints.CompressCallback; - } - - if (OperationsSupported.HasFlag(SupportedOperations.LockUnlock)) - { - nativeBackend.Lock = BackendEntryPoints.LockCallback; - nativeBackend.Unlock = BackendEntryPoints.UnlockCallback; - } - nativePointer = Marshal.AllocHGlobal(Marshal.SizeOf(nativeBackend)); Marshal.StructureToPtr(nativeBackend, nativePointer, false); } @@ -520,7 +449,6 @@ private unsafe static class BackendEntryPoints public static readonly GitRefdbBackend.write_callback WriteCallback = Write; public static readonly GitRefdbBackend.rename_callback RenameCallback = Rename; public static readonly GitRefdbBackend.del_callback DelCallback = Del; - public static readonly GitRefdbBackend.compress_callback CompressCallback = Compress; public static readonly GitRefdbBackend.has_log_callback HasLogCallback = HasLog; public static readonly GitRefdbBackend.ensure_log_callback EnsureLogCallback = EnsureLog; public static readonly GitRefdbBackend.free_callback FreeCallback = Free; @@ -528,8 +456,6 @@ private unsafe static class BackendEntryPoints public static readonly GitRefdbBackend.reflog_write_callback ReflogWriteCallback = ReflogWrite; public static readonly GitRefdbBackend.reflog_rename_callback ReflogRenameCallback = ReflogRename; public static readonly GitRefdbBackend.reflog_delete_callback ReflogDeleteCallback = ReflogDelete; - public static readonly GitRefdbBackend.lock_callback LockCallback = Lock; - public static readonly GitRefdbBackend.unlock_callback UnlockCallback = Unlock; public static int Exists( ref bool exists, @@ -730,26 +656,6 @@ public static int Del( return (int)GitErrorCode.Ok; } - public static int Compress(IntPtr backendPtr) - { - var backend = PtrToBackend(backendPtr); - if (backend == null) - { - return (int)GitErrorCode.Error; - } - - try - { - backend.Compress(); - } - catch (Exception ex) - { - return RefdbBackendException.GetCode(ex); - } - - return (int)GitErrorCode.Ok; - } - public static int HasLog( IntPtr backend, string refName) @@ -800,66 +706,6 @@ public static int ReflogDelete( return (int)GitErrorCode.Error; } - public static int Lock( - out IntPtr payloadPtr, - IntPtr backendPtr, - string refName) - { - payloadPtr = IntPtr.Zero; - var backend = PtrToBackend(backendPtr); - if (backend == null) - { - return (int)GitErrorCode.Error; - } - - object payload; - try - { - payload = backend.Lock(refName); - } - catch (Exception ex) - { - return RefdbBackendException.GetCode(ex); - } - - payloadPtr = GCHandle.ToIntPtr(GCHandle.Alloc(payload)); - return (int)GitErrorCode.Ok; - } - - public static int Unlock( - IntPtr backendPtr, - IntPtr payloadPtr, - bool success, - bool updateReflog, - git_reference* referencePtr, - git_signature* who, - string message) - { - var backend = PtrToBackend(backendPtr); - if (backend == null) - { - return (int)GitErrorCode.Error; - } - - var reference = ReferenceData.MarshalFromPtr(referencePtr); - var signature = new Signature(who); - - var payloadGC = GCHandle.FromIntPtr(payloadPtr); - var payload = payloadGC.Target; - payloadGC.Free(); - - try - { - backend.Unlock(payload, reference, signature, message, success, updateReflog); - } - catch (Exception ex) - { - return RefdbBackendException.GetCode(ex); - } - - return (int)GitErrorCode.Ok; - } - private static RefdbBackend PtrToBackend(IntPtr pointer) { var intPtr = Marshal.ReadIntPtr(pointer, GitRefdbBackend.GCHandleOffset); From 4c059f60e28a30ad97c5465637c07645e8465e3c Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Wed, 13 Nov 2019 00:40:29 -0800 Subject: [PATCH 20/26] Oops, forgot to delete launchSettings.json --- LibGit2Sharp.Tests/Properties/launchSettings.json | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 LibGit2Sharp.Tests/Properties/launchSettings.json diff --git a/LibGit2Sharp.Tests/Properties/launchSettings.json b/LibGit2Sharp.Tests/Properties/launchSettings.json deleted file mode 100644 index a06f68133..000000000 --- a/LibGit2Sharp.Tests/Properties/launchSettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "profiles": { - "LibGit2Sharp.Tests": { - "commandName": "Project", - "nativeDebugging": true - } - } -} \ No newline at end of file From 4f6de9ede967ca1de767ebd2dfd215b8f837fb22 Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Wed, 13 Nov 2019 00:52:07 -0800 Subject: [PATCH 21/26] Final comments. --- LibGit2Sharp/ReferenceCollection.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/LibGit2Sharp/ReferenceCollection.cs b/LibGit2Sharp/ReferenceCollection.cs index d28c35c87..201344376 100644 --- a/LibGit2Sharp/ReferenceCollection.cs +++ b/LibGit2Sharp/ReferenceCollection.cs @@ -845,6 +845,10 @@ public virtual void RewriteHistory(RewriteHistoryOptions options, IEnumerable + /// Replaces the Refdb backend with a custom backend. + /// + /// Custom backend to use. public virtual void SetBackend(RefdbBackend backend) { Ensure.ArgumentNotNull(backend, "backend"); From f5bcb8ee55879c8696fc014333730596f7072365 Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Wed, 13 Nov 2019 15:43:12 -0800 Subject: [PATCH 22/26] Sealed ReferenceData. --- LibGit2Sharp/RefdbBackend.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index bff7c1fff..f07b0e54c 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -131,7 +131,7 @@ internal void Free() /// /// Backend's representation of a reference. /// - public class ReferenceData + public sealed class ReferenceData { /// /// Reference name. From 529b61826d6d3ff8030df4a4473e8801c17555e7 Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Wed, 13 Nov 2019 19:15:37 -0800 Subject: [PATCH 23/26] Turned on leak tracking. --- LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj | 8 ++++++ LibGit2Sharp.Tests/RefdbBackendFixture.cs | 26 ++++++++++++++++++++ LibGit2Sharp/Core/Handles/Libgit2Object.cs | 4 +-- LibGit2Sharp/LibGit2Sharp.csproj | 8 ++++++ 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj index 3503801c9..795b2bfe1 100644 --- a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj +++ b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj @@ -4,6 +4,14 @@ net46;netcoreapp2.1 + + TRACE;LEAKS_IDENTIFYING + + + + TRACE;LEAKS_IDENTIFYING;LEAKS_TRACKING + + diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs index c309fbfa3..3885896fe 100644 --- a/LibGit2Sharp.Tests/RefdbBackendFixture.cs +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -2,16 +2,42 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using Xunit; +using Xunit.Abstractions; namespace LibGit2Sharp.Tests { public class RefdbBackendFixture : BaseFixture { + public RefdbBackendFixture(ITestOutputHelper output) + { + Trace.Listeners.Add(new TestOutputListener(output)); + } + + private class TestOutputListener : TraceListener + { + private readonly ITestOutputHelper output; + public TestOutputListener(ITestOutputHelper output) + { + this.output = output; + } + + public override void Write(string message) + { + output.WriteLine(message); + } + + public override void WriteLine(string message) + { + output.WriteLine(message); + } + } + [Fact] public void CanWriteToRefdbBackend() { diff --git a/LibGit2Sharp/Core/Handles/Libgit2Object.cs b/LibGit2Sharp/Core/Handles/Libgit2Object.cs index 892ebde90..b5f505b18 100644 --- a/LibGit2Sharp/Core/Handles/Libgit2Object.cs +++ b/LibGit2Sharp/Core/Handles/Libgit2Object.cs @@ -5,7 +5,7 @@ // // Uncomment the line below or add a conditional symbol to activate this mode -// #define LEAKS_IDENTIFYING +#define LEAKS_IDENTIFYING // This activates a more throrough mode which will show the stack trace of the // allocation code path for each handle that has been improperly released. @@ -15,7 +15,7 @@ // // Uncomment the line below or add a conditional symbol to activate this mode -// #define LEAKS_TRACKING +#define LEAKS_TRACKING using System; using System.Linq; diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index de205fc56..5fbc7f42a 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -16,6 +16,14 @@ ..\libgit2sharp.snk + + TRACE;LEAKS_IDENTIFYING + + + + TRACE + + From 9ebf927fab3fb4622850381751c81c1ae04b3e8e Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Wed, 13 Nov 2019 19:38:21 -0800 Subject: [PATCH 24/26] Addressed detected handle leaks by making git_reference__alloc(_symbolic) return IntPtr. This is safe, since libgit2 disposes te handles after consuming them, and allocating the handles is the last thing the callbacks do, so no exceptions can cause leaks. --- LibGit2Sharp/Core/Proxy.cs | 8 ++++---- LibGit2Sharp/RefdbBackend.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 71ef7888c..9b961d83d 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -2042,17 +2042,17 @@ public static unsafe void git_reference_ensure_log(RepositoryHandle repo, string Ensure.ZeroResult(res); } - public static unsafe ReferenceHandle git_reference__alloc(string name, ObjectId objectId) + public static unsafe IntPtr git_reference__alloc(string name, ObjectId objectId) { var oid = objectId.Oid; var referencePtr = NativeMethods.git_reference__alloc(name, ref oid, IntPtr.Zero); - return new ReferenceHandle(referencePtr, false); + return new IntPtr(referencePtr); } - public static unsafe ReferenceHandle git_reference__alloc_symbolic(string name, string target) + public static unsafe IntPtr git_reference__alloc_symbolic(string name, string target) { var referencePtr = NativeMethods.git_reference__alloc_symbolic(name, target); - return new ReferenceHandle(referencePtr, false); + return new IntPtr(referencePtr); } #endregion diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index f07b0e54c..8a2c1b0c4 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -218,11 +218,11 @@ internal IntPtr MarshalToPtr() { if (IsSymbolic) { - return Proxy.git_reference__alloc_symbolic(RefName, SymbolicTarget).AsIntPtr(); + return Proxy.git_reference__alloc_symbolic(RefName, SymbolicTarget); } else { - return Proxy.git_reference__alloc(RefName, ObjectId.Oid).AsIntPtr(); + return Proxy.git_reference__alloc(RefName, ObjectId.Oid); } } From 480cd59a798b665c07679ce7a4f3795e03de027d Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Wed, 13 Nov 2019 19:52:16 -0800 Subject: [PATCH 25/26] Revert "Turned on leak tracking." This reverts commit 529b61826d6d3ff8030df4a4473e8801c17555e7. --- LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj | 8 ------ LibGit2Sharp.Tests/RefdbBackendFixture.cs | 26 -------------------- LibGit2Sharp/Core/Handles/Libgit2Object.cs | 4 +-- LibGit2Sharp/LibGit2Sharp.csproj | 8 ------ 4 files changed, 2 insertions(+), 44 deletions(-) diff --git a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj index 795b2bfe1..3503801c9 100644 --- a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj +++ b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj @@ -4,14 +4,6 @@ net46;netcoreapp2.1 - - TRACE;LEAKS_IDENTIFYING - - - - TRACE;LEAKS_IDENTIFYING;LEAKS_TRACKING - - diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs index 3885896fe..c309fbfa3 100644 --- a/LibGit2Sharp.Tests/RefdbBackendFixture.cs +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -2,42 +2,16 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using Xunit; -using Xunit.Abstractions; namespace LibGit2Sharp.Tests { public class RefdbBackendFixture : BaseFixture { - public RefdbBackendFixture(ITestOutputHelper output) - { - Trace.Listeners.Add(new TestOutputListener(output)); - } - - private class TestOutputListener : TraceListener - { - private readonly ITestOutputHelper output; - public TestOutputListener(ITestOutputHelper output) - { - this.output = output; - } - - public override void Write(string message) - { - output.WriteLine(message); - } - - public override void WriteLine(string message) - { - output.WriteLine(message); - } - } - [Fact] public void CanWriteToRefdbBackend() { diff --git a/LibGit2Sharp/Core/Handles/Libgit2Object.cs b/LibGit2Sharp/Core/Handles/Libgit2Object.cs index b5f505b18..892ebde90 100644 --- a/LibGit2Sharp/Core/Handles/Libgit2Object.cs +++ b/LibGit2Sharp/Core/Handles/Libgit2Object.cs @@ -5,7 +5,7 @@ // // Uncomment the line below or add a conditional symbol to activate this mode -#define LEAKS_IDENTIFYING +// #define LEAKS_IDENTIFYING // This activates a more throrough mode which will show the stack trace of the // allocation code path for each handle that has been improperly released. @@ -15,7 +15,7 @@ // // Uncomment the line below or add a conditional symbol to activate this mode -#define LEAKS_TRACKING +// #define LEAKS_TRACKING using System; using System.Linq; diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 5fbc7f42a..de205fc56 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -16,14 +16,6 @@ ..\libgit2sharp.snk - - TRACE;LEAKS_IDENTIFYING - - - - TRACE - - From 7e8ba55d53f4f7128ca08c4992465e604f88d7ec Mon Sep 17 00:00:00 2001 From: Star Dorminey Date: Tue, 19 Nov 2019 00:41:12 -0800 Subject: [PATCH 26/26] Forgot to edit Objects.tt instead of Objects.cs --- LibGit2Sharp/Core/Handles/Objects.cs | 44 +++++++++++++++------------- LibGit2Sharp/Core/Handles/Objects.tt | 4 ++- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/LibGit2Sharp/Core/Handles/Objects.cs b/LibGit2Sharp/Core/Handles/Objects.cs index 3d4bdfdd0..cd54e73cd 100644 --- a/LibGit2Sharp/Core/Handles/Objects.cs +++ b/LibGit2Sharp/Core/Handles/Objects.cs @@ -441,27 +441,6 @@ public override void Free() } } - internal unsafe class ReferenceDatabaseHandle : Libgit2Object - { - public ReferenceDatabaseHandle(void* handle, bool owned) : base(handle, owned) - { - } - - public ReferenceDatabaseHandle(IntPtr ptr, bool owned) : base(ptr, owned) - { - } - - public override void Free() - { - NativeMethods.git_refdb_free((git_refdb*)ptr); - } - - public static implicit operator git_refdb*(ReferenceDatabaseHandle handle) - { - return (git_refdb*)handle.Handle; - } - } - internal unsafe class RevWalkerHandle : Libgit2Object { internal RevWalkerHandle(git_revwalk *ptr, bool owned) @@ -600,4 +579,27 @@ public override void Free() } } + internal unsafe class ReferenceDatabaseHandle : Libgit2Object + { + internal ReferenceDatabaseHandle(git_refdb *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal ReferenceDatabaseHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_refdb_free((git_refdb*) ptr); + } + + public static implicit operator git_refdb*(ReferenceDatabaseHandle handle) + { + return (git_refdb*) handle.Handle; + } + } + } diff --git a/LibGit2Sharp/Core/Handles/Objects.tt b/LibGit2Sharp/Core/Handles/Objects.tt index a6d1fa251..47866de41 100644 --- a/LibGit2Sharp/Core/Handles/Objects.tt +++ b/LibGit2Sharp/Core/Handles/Objects.tt @@ -37,6 +37,7 @@ var cNames = new[] { "git_rebase", "git_odb_stream", "git_worktree", + "git_refdb", }; var csNames = new[] { @@ -64,7 +65,8 @@ var csNames = new[] { "ObjectHandle", "RebaseHandle", "OdbStreamHandle", - "WorktreeHandle" + "WorktreeHandle", + "ReferenceDatabaseHandle", }; for (var i = 0; i < cNames.Length; i++)