From efa481c21a8c4b48c978b35b7a5d97561f37db11 Mon Sep 17 00:00:00 2001 From: Keegan Strohm Date: Wed, 24 Jul 2024 14:14:25 -0400 Subject: [PATCH 1/6] Take sterlinds work and merge with latest 0.30.0 --- LibGit2Sharp.Tests/RefdbBackendFixture.cs | 237 +++++++ LibGit2Sharp/Core/GitRefdbBackend.cs | 175 ++++++ LibGit2Sharp/Core/Handles/Objects.cs | 24 +- LibGit2Sharp/Core/Handles/Objects.tt | 6 +- LibGit2Sharp/Core/NativeMethods.cs | 31 +- LibGit2Sharp/Core/Opaques.cs | 4 +- LibGit2Sharp/Core/Proxy.cs | 38 +- LibGit2Sharp/RefdbBackend.cs | 723 ++++++++++++++++++++++ LibGit2Sharp/ReferenceCollection.cs | 19 +- 9 files changed, 1248 insertions(+), 9 deletions(-) create mode 100644 LibGit2Sharp.Tests/RefdbBackendFixture.cs create mode 100644 LibGit2Sharp/Core/GitRefdbBackend.cs create mode 100644 LibGit2Sharp/RefdbBackend.cs diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs new file mode 100644 index 000000000..d4c13b375 --- /dev/null +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -0,0 +1,237 @@ +using LibGit2Sharp.Tests.TestHelpers; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Xunit; + +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() + { + string path = SandboxStandardTestRepo(); + using (var 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")); + + 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); + } + } + + [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); + } + } + + [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)); + } + } + + [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 (var 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" })); + } + } + + [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"; + + var 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) + { + } + + public SortedDictionary Refs { get; } = new SortedDictionary(); + + public override bool Exists(string refName) + { + return Refs.ContainsKey(refName); + } + + public override IEnumerable Iterate(string 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) + { + return Refs.TryGetValue(refName, out data); + } + + public override void Delete(ReferenceData refData) + { + if (!this.Refs.Remove(refData.RefName)) + { + throw RefdbBackendException.NotFound(refData.RefName); + } + } + + public override void Write(ReferenceData newRef, ReferenceData oldRef, bool force, Signature signature, string message) + { + ReferenceData 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 ((oldRef != null && !existingRef.Equals(oldRef))) + { + throw RefdbBackendException.Conflict(newRef.RefName); + } + + throw RefdbBackendException.Exists(newRef.RefName); + } + + 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; + } + } + } +} \ No newline at end of file diff --git a/LibGit2Sharp/Core/GitRefdbBackend.cs b/LibGit2Sharp/Core/GitRefdbBackend.cs new file mode 100644 index 000000000..3c0425ec0 --- /dev/null +++ b/LibGit2Sharp/Core/GitRefdbBackend.cs @@ -0,0 +1,175 @@ +using System; +using System.Runtime.InteropServices; + +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 IntPtr referencePtr, + IntPtr iterator); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int next_name_callback( + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] out string refName, + IntPtr iterator); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void 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 compress_callback 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 lock_callback Lock; + public unlock_callback 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( + [MarshalAs(UnmanagedType.Bool)] ref bool exists, + IntPtr backend, + [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(LaxUtf8NoCleanupMarshaler))] string refName); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int iterator_callback( + out IntPtr iteratorPtr, + IntPtr backend, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] 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(LaxUtf8NoCleanupMarshaler))] string message, + IntPtr oid, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string oldTarget); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int rename_callback( + 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, + [MarshalAs(UnmanagedType.Bool)] bool force, + git_signature* who, + [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(LaxUtf8NoCleanupMarshaler))] string refName, + 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, + [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(LaxUtf8NoCleanupMarshaler))] 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(LaxUtf8NoCleanupMarshaler))] 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(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(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); + } +} \ No newline at end of file diff --git a/LibGit2Sharp/Core/Handles/Objects.cs b/LibGit2Sharp/Core/Handles/Objects.cs index 5f8db722e..173d40e54 100644 --- a/LibGit2Sharp/Core/Handles/Objects.cs +++ b/LibGit2Sharp/Core/Handles/Objects.cs @@ -579,4 +579,26 @@ 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; + } + } +} \ No newline at end of file diff --git a/LibGit2Sharp/Core/Handles/Objects.tt b/LibGit2Sharp/Core/Handles/Objects.tt index a6d1fa251..97fc4c9af 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++) @@ -96,4 +98,4 @@ for (var i = 0; i < cNames.Length; i++) <# } #> -} +} \ No newline at end of file diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index e8e59843e..72dff926c 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -2108,5 +2108,34 @@ 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); } -} +} \ No newline at end of file diff --git a/LibGit2Sharp/Core/Opaques.cs b/LibGit2Sharp/Core/Opaques.cs index f5613a276..e72db10c3 100644 --- a/LibGit2Sharp/Core/Opaques.cs +++ b/LibGit2Sharp/Core/Opaques.cs @@ -28,5 +28,5 @@ internal struct git_object {} internal struct git_rebase {} internal struct git_odb_stream {} internal struct git_worktree { } -} - + internal struct git_refdb { }; +} \ No newline at end of file diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 18e952e68..6d9b2e349 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -1874,6 +1874,29 @@ public static unsafe void git_rebase_finish( #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)); + } + +#endregion + #region git_reference_ public static unsafe ReferenceHandle git_reference_create( @@ -1892,6 +1915,19 @@ public static unsafe ReferenceHandle git_reference_create( return new ReferenceHandle(handle, true); } + 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 IntPtr(referencePtr); + } + + public static unsafe IntPtr git_reference__alloc_symbolic(string name, string target) + { + var referencePtr = NativeMethods.git_reference__alloc_symbolic(name, target); + return new IntPtr(referencePtr); + } + public static unsafe ReferenceHandle git_reference_symbolic_create( RepositoryHandle repo, string name, @@ -3866,4 +3902,4 @@ public static long ConvertToLong(this UIntPtr input) } } } -// ReSharper restore InconsistentNaming +// ReSharper restore InconsistentNaming \ No newline at end of file diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs new file mode 100644 index 000000000..654c0a997 --- /dev/null +++ b/LibGit2Sharp/RefdbBackend.cs @@ -0,0 +1,723 @@ +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Text; + +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); + + /// + /// 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 + { + if (IntPtr.Zero == nativePointer) + { + var nativeBackend = new GitRefdbBackend() + { + Version = 1, + Compress = null, + Lock = null, + Unlock = null, + 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; + } + } + + /// + /// Frees the backend pointer, if one has been allocated. + /// + internal void Free() + { + if (IntPtr.Zero == nativePointer) + { + return; + } + + GCHandle.FromIntPtr(Marshal.ReadIntPtr(nativePointer, GitRefdbBackend.GCHandleOffset)).Free(); + Marshal.FreeHGlobal(nativePointer); + nativePointer = IntPtr.Zero; + } + + /// + /// Backend's representation of a reference. + /// + public sealed 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; + this.IsSymbolic = false; + this.ObjectId = directTarget; + this.SymbolicTarget = null; + } + + /// + /// Initializes a symbolic reference. + /// + public ReferenceData(string refName, string symbolicTarget) + { + this.RefName = refName; + this.IsSymbolic = true; + this.ObjectId = null; + 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; + } + } + + /// + /// Allocates a native git_reference for the and returns a pointer. + /// + internal IntPtr MarshalToPtr() + { + if (IsSymbolic) + { + return Proxy.git_reference__alloc_symbolic(RefName, SymbolicTarget); + } + else + { + return Proxy.git_reference__alloc(RefName, ObjectId.Oid); + } + } + + /// + /// Marshals a git_reference into a managed + /// + 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)); + } + } + } + + /// + /// 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) + { + Code = code; + } + + /// + /// Git error code to return on exception. + /// + 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)); + } + + /// + /// 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); + var backendException = ex as RefdbBackendException; + if (backendException == null) + { + return (int)GitErrorCode.Error; + } + + return (int)backendException.Code; + } + } + + /// + /// Wrapper to hold the state of the enumerator. + /// + 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; + } + } + + /// + /// Static entrypoints that trampoline into the iterator. + /// + 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) + { + return RefdbBackendException.GetCode(ex); + } + + if (data == null) + { + return (int)GitErrorCode.IterOver; + } + + referencePtr = data.MarshalToPtr(); + 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) + { + return RefdbBackendException.GetCode(ex); + } + + 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, GitRefdbIterator.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; + } + } + + /// + /// Static entry points that trampoline into the custom backend's implementation. + /// + 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) + { + return RefdbBackendException.GetCode(ex); + } + + return (int)GitErrorCode.Ok; + } + + 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.MarshalToPtr(); + } + catch (Exception ex) + { + return RefdbBackendException.GetCode(ex); + } + + return (int)GitErrorCode.Ok; + } + + 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 + { + var enumerator = backend.Iterate(glob).GetEnumerator(); + iterator = new RefIterator(enumerator); + } + catch (Exception ex) + { + return RefdbBackendException.GetCode(ex); + } + + 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, + IntPtr old, + string oldTarget) + { + 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. + backend.Write(newRef, oldRef, force, signature, message); + } + catch (Exception ex) + { + return RefdbBackendException.GetCode(ex); + } + + return (int)GitErrorCode.Ok; + } + + public static int Rename( + out IntPtr reference, + IntPtr backendPtr, + string oldName, + string newName, + bool force, + git_signature* who, + string message) + { + reference = IntPtr.Zero; + var backend = PtrToBackend(backendPtr); + if (backend == null) + { + 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( + IntPtr backendPtr, + string refName, + IntPtr oldId, + string oldTarget) + { + 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 + { + backend.Delete(existingRef); + } + catch (Exception ex) + { + return RefdbBackendException.GetCode(ex); + } + + return (int)GitErrorCode.Ok; + } + + 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) + { + PtrToBackend(backend).Free(); + } + + 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; + } + } + } +} \ No newline at end of file diff --git a/LibGit2Sharp/ReferenceCollection.cs b/LibGit2Sharp/ReferenceCollection.cs index 456b1a043..77186917e 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); } /// @@ -404,7 +407,7 @@ public virtual Reference Rename(string currentName, string newName, if (reference == null) { - throw new LibGit2SharpException("Reference '{0}' doesn't exist. One cannot move a non existing reference.", + throw new LibGit2SharpException("Reference '{0}' doesn't exist. One cannot move a non existing reference.", currentName); } @@ -842,6 +845,18 @@ 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"); + + // Refdb takes ownership of the backend pointer, so don't free it! + Proxy.git_refdb_set_backend(refdbHandle, backend.RefdbBackendPointer); + } + /// /// Ensure that a reflog exists for the given canonical name /// @@ -851,4 +866,4 @@ internal void EnsureHasLog(string canonicalName) Proxy.git_reference_ensure_log(repo.Handle, canonicalName); } } -} +} \ No newline at end of file From ea0ed0259019e3525f71ed45109157f659669870 Mon Sep 17 00:00:00 2001 From: Keegan Strohm Date: Wed, 24 Jul 2024 18:00:08 -0400 Subject: [PATCH 2/6] In memory fixes --- LibGit2Sharp/Core/NativeMethods.cs | 6 ++++++ LibGit2Sharp/Core/Proxy.cs | 14 ++++++++++++++ LibGit2Sharp/ObjectDatabase.cs | 16 +++++++++++++--- LibGit2Sharp/RefdbBackend.cs | 3 --- LibGit2Sharp/Repository.cs | 9 +++++---- 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 72dff926c..d29c85ddd 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -1582,6 +1582,12 @@ internal static extern unsafe int git_repository_message( internal static extern unsafe int git_repository_new( out git_repository* repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_repository_set_odb(git_repository* repo, git_odb* odb); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_odb_new(out git_odb* odb); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern unsafe int git_repository_odb(out git_odb* odb, git_repository* repo); diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 6d9b2e349..380bf6de6 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -2573,6 +2573,20 @@ public static unsafe string git_repository_message(RepositoryHandle repo) } } + public static unsafe void git_repository_set_odb(RepositoryHandle repo, ObjectDatabaseHandle objectDatabase) + { + NativeMethods.git_repository_set_odb(repo, objectDatabase); + } + + public static unsafe ObjectDatabaseHandle git_odb_new() + { + git_odb* handle; + var res = NativeMethods.git_odb_new(out handle); + Ensure.ZeroResult(res); + + return new ObjectDatabaseHandle(handle, true); + } + public static unsafe ObjectDatabaseHandle git_repository_odb(RepositoryHandle repo) { git_odb* handle; diff --git a/LibGit2Sharp/ObjectDatabase.cs b/LibGit2Sharp/ObjectDatabase.cs index 42b65d7d0..9ea6c1b55 100644 --- a/LibGit2Sharp/ObjectDatabase.cs +++ b/LibGit2Sharp/ObjectDatabase.cs @@ -25,10 +25,20 @@ public class ObjectDatabase : IEnumerable protected ObjectDatabase() { } - internal ObjectDatabase(Repository repo) + internal ObjectDatabase(Repository repo, bool isInMemory) { this.repo = repo; - handle = Proxy.git_repository_odb(repo.Handle); + + if (isInMemory) + { + handle = Proxy.git_odb_new(); + + Proxy.git_repository_set_odb(repo.Handle, handle); + } + else + { + handle = Proxy.git_repository_odb(repo.Handle); + } repo.RegisterForCleanup(handle); } @@ -1089,4 +1099,4 @@ public virtual MergeTreeResult RevertCommit(Commit revertCommit, Commit revertOn } } } -} +} \ No newline at end of file diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index 654c0a997..c956db66b 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -1,11 +1,8 @@ using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Handles; using System; -using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Runtime.InteropServices; -using System.Text; namespace LibGit2Sharp { diff --git a/LibGit2Sharp/Repository.cs b/LibGit2Sharp/Repository.cs index e23c9cd3b..555065f19 100644 --- a/LibGit2Sharp/Repository.cs +++ b/LibGit2Sharp/Repository.cs @@ -125,7 +125,7 @@ internal Repository(WorktreeHandle worktreeHandle) configurationGlobalFilePath, configurationXDGFilePath, configurationSystemFilePath))); - odb = new Lazy(() => new ObjectDatabase(this)); + odb = new Lazy(() => new ObjectDatabase(this, true)); diff = new Diff(this); notes = new NoteCollection(this); ignore = new Ignore(this); @@ -164,7 +164,8 @@ private Repository(string path, RepositoryOptions options, RepositoryRequiredPar /* TODO: bug in libgit2, update when fixed by * https://github.com/libgit2/libgit2/pull/2970 */ - if (path == null) + var isInMemory = path == null; + if (isInMemory) { isBare = true; } @@ -222,7 +223,7 @@ private Repository(string path, RepositoryOptions options, RepositoryRequiredPar configurationGlobalFilePath, configurationXDGFilePath, configurationSystemFilePath))); - odb = new Lazy(() => new ObjectDatabase(this)); + odb = new Lazy(() => new ObjectDatabase(this, isInMemory)); diff = new Diff(this); notes = new NoteCollection(this); ignore = new Ignore(this); @@ -1797,4 +1798,4 @@ private string DebuggerDisplay } } } -} +} \ No newline at end of file From 5b85aeca202def1c9db2a56c6d3fdd05897ee957 Mon Sep 17 00:00:00 2001 From: Keegan Strohm Date: Thu, 25 Jul 2024 10:46:16 -0400 Subject: [PATCH 3/6] Remove macos 11 --- .github/workflows/ci.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35f0241ef..3f048ef84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,13 +38,9 @@ jobs: strategy: matrix: arch: [ x64 ] - os: [ windows-2019, windows-2022, macos-11, macos-12, macos-13 ] + os: [ windows-2019, windows-2022, macos-12, macos-13 ] tfm: [ net472, net6.0, net8.0 ] exclude: - - os: macos-11 - tfm: net472 - - os: macos-11 - tfm: net8.0 - os: macos-12 tfm: net472 - os: macos-13 @@ -112,5 +108,4 @@ jobs: run: | git_command="git config --global --add safe.directory /app" test_command="dotnet test LibGit2Sharp.sln --configuration Release -p:TargetFrameworks=${{ matrix.tfm }} --logger "GitHubActions" -p:ExtraDefine=LEAKS_IDENTIFYING" - docker run -t --rm --platform linux/${{ matrix.arch }} -v "$PWD:/app" gittools/build-images:${{ matrix.distro }}-sdk-${{ matrix.sdk }} sh -c "$git_command && $test_command" - + docker run -t --rm --platform linux/${{ matrix.arch }} -v "$PWD:/app" gittools/build-images:${{ matrix.distro }}-sdk-${{ matrix.sdk }} sh -c "$git_command && $test_command" \ No newline at end of file From 801d009e7fe5dbe860e374f059271dcef2e88f57 Mon Sep 17 00:00:00 2001 From: Keegan Strohm Date: Thu, 25 Jul 2024 15:13:41 -0400 Subject: [PATCH 4/6] Update nuget package project and description urls to match --- LibGit2Sharp/LibGit2Sharp.csproj | 6 +++--- README.md | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 28404d948..aa0dcfded 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -8,7 +8,7 @@ LibGit2Sharp contributors Copyright © LibGit2Sharp contributors libgit2 git - https://github.com/libgit2/libgit2sharp/ + https://github.com/kstrohminfor/libgit2sharp/ LibGit2Sharp contributors true true @@ -49,7 +49,7 @@ - https://github.com/libgit2/libgit2sharp/blob/$(SourceRevisionId)/CHANGES.md + https://github.com/kstrohminfor/libgit2sharp/blob/$(SourceRevisionId)/CHANGES.md @@ -59,4 +59,4 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index 3aafdceb1..ede4d12e7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # LibGit2Sharp -[![CI](https://github.com/libgit2/libgit2sharp/actions/workflows/ci.yml/badge.svg)](https://github.com/libgit2/libgit2sharp/actions/workflows/ci.yml) +[![CI](https://github.com/kstrohminfor/libgit2sharp/actions/workflows/ci.yml/badge.svg)](https://github.com/kstrohminfor/libgit2sharp/actions/workflows/ci.yml) [![NuGet version (LibGit2Sharp)](https://img.shields.io/nuget/v/LibGit2Sharp.svg)](https://www.nuget.org/packages/LibGit2Sharp/) **LibGit2Sharp brings all the might and speed of [libgit2](http://libgit2.github.com/), a native Git implementation, to the managed world of .NET** @@ -8,12 +8,12 @@ ## Online resources - [NuGet package](http://nuget.org/List/Packages/LibGit2Sharp) -- [Source code](https://github.com/libgit2/libgit2sharp/) +- [Source code](https://github.com/kstrohminfor/libgit2sharp/) ## Troubleshooting and support - Usage or programming related question? Post it on [StackOverflow](http://stackoverflow.com/questions/tagged/libgit2sharp) using the tag *libgit2sharp* -- Found a bug or missing a feature? Feed the [issue tracker](https://github.com/libgit2/libgit2sharp/issues) +- Found a bug or missing a feature? Feed the [issue tracker](https://github.com/kstrohminfor/libgit2sharp/issues) - Announcements and related miscellanea through Twitter ([@libgit2sharp](http://twitter.com/libgit2sharp)) ## Quick contributing guide @@ -22,7 +22,7 @@ - Create a topic specific branch. Add some nice feature. Do not forget the tests ;-) - Send a Pull Request to spread the fun! -More thorough information is available in the [wiki](https://github.com/libgit2/libgit2sharp/wiki). +More thorough information is available in the [wiki](https://github.com/kstrohminfor/libgit2sharp/wiki). ## Optimizing unit testing @@ -38,9 +38,9 @@ You can do a few things to optimize running unit tests on Windows: ## Authors -- **Code:** The LibGit2Sharp [contributors](https://github.com/libgit2/libgit2sharp/contributors) +- **Code:** The LibGit2Sharp [contributors](https://github.com/kstrohminfor/libgit2sharp/contributors) - **Logo:** [Jason "blackant" Long](https://github.com/jasonlong) ## License -The MIT license (Refer to the [LICENSE.md](https://github.com/libgit2/libgit2sharp/blob/master/LICENSE.md) file) +The MIT license (Refer to the [LICENSE.md](https://github.com/kstrohminfor/libgit2sharp/blob/master/LICENSE.md) file) \ No newline at end of file From d45a70ad7fb91a43417a3c1dfc0edd6661a2b202 Mon Sep 17 00:00:00 2001 From: Keegan Strohm Date: Thu, 25 Jul 2024 15:31:59 -0400 Subject: [PATCH 5/6] Update LibGit2Sharp.NativeBinaries to use custom NuGet package --- LibGit2Sharp/LibGit2Sharp.csproj | 2 +- nuget.config | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index aa0dcfded..7517438ca 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -39,7 +39,7 @@ - + diff --git a/nuget.config b/nuget.config index 35696f810..653b43677 100644 --- a/nuget.config +++ b/nuget.config @@ -2,5 +2,14 @@ + - + + + + + + + + + \ No newline at end of file From a974f8c0332ff7a38b8cd5335437abf7bfa3700b Mon Sep 17 00:00:00 2001 From: Keegan Strohm Date: Thu, 25 Jul 2024 20:12:22 -0400 Subject: [PATCH 6/6] Fix gc collected callbacks maybe? --- LibGit2Sharp.Tests/RefdbBackendFixture.cs | 32 +-- LibGit2Sharp/Core/GitRefdbBackend.cs | 8 +- LibGit2Sharp/RefdbBackend.cs | 335 +--------------------- LibGit2Sharp/RefdbBackendException.cs | 76 +++++ LibGit2Sharp/RefdbIterator.cs | 175 +++++++++++ LibGit2Sharp/ReferenceData.cs | 128 +++++++++ 6 files changed, 406 insertions(+), 348 deletions(-) create mode 100644 LibGit2Sharp/RefdbBackendException.cs create mode 100644 LibGit2Sharp/RefdbIterator.cs create mode 100644 LibGit2Sharp/ReferenceData.cs diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs index d4c13b375..50da90f30 100644 --- a/LibGit2Sharp.Tests/RefdbBackendFixture.cs +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -17,7 +17,7 @@ public void CanWriteToRefdbBackend() 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"))); + Assert.Equal(backend.Refs["refs/heads/newref"], new ReferenceData("refs/heads/newref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"))); } } @@ -29,8 +29,8 @@ public void CanReadFromRefdbBackend() { 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["HEAD"] = new ReferenceData("HEAD", "refs/heads/testref"); + backend.Refs["refs/heads/testref"] = new ReferenceData("refs/heads/testref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); Assert.Equal("refs/heads/testref", repo.Refs["HEAD"].TargetIdentifier); Assert.Equal("be3563ae3f795b2b4353bcce3a527ad0a4f7f644", repo.Refs["HEAD"].ResolveToDirectReference().TargetIdentifier); @@ -46,8 +46,8 @@ public void CanDeleteFromRefdbBackend() { 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["HEAD"] = new ReferenceData("HEAD", "refs/heads/testref"); + backend.Refs["refs/heads/testref"] = new ReferenceData("refs/heads/testref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); repo.Refs.Remove("refs/heads/testref"); @@ -82,9 +82,9 @@ public void CanIterateRefdbBackend() 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"); + backend.Refs["HEAD"] = new ReferenceData("HEAD", "refs/heads/testref"); + backend.Refs["refs/heads/testref"] = new ReferenceData("refs/heads/testref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + backend.Refs["refs/heads/othersymbolic"] = new ReferenceData("refs/heads/othersymbolic", "refs/heads/testref"); Assert.True(repo.Refs.Select(r => r.CanonicalName).SequenceEqual(backend.Refs.Keys)); } @@ -103,11 +103,11 @@ public void CanIterateTagsInRefdbBackend() // 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/broken1"] = new ReferenceData("refs/tags/broken1", "tags/shouldnt/be/symbolic"); + // backend.Refs["refs/tags/broken2"] = new ReferenceData("refs/tags/broken2", "but/are/here/for/testing"); + // backend.Refs["refs/tags/broken3"] = new ReferenceData("refs/tags/broken3", "the/type/filtering"); - backend.Refs["refs/tags/correct1"] = new RefdbBackend.ReferenceData("refs/tags/correct1", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + backend.Refs["refs/tags/correct1"] = new ReferenceData("refs/tags/correct1", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); var tagNames = repo.Tags.Select(r => r.CanonicalName); Assert.True(tagNames.SequenceEqual(new List { "refs/tags/correct1" })); @@ -123,9 +123,9 @@ public void CanIterateRefdbBackendWithGlob() 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"); + backend.Refs["HEAD"] = new ReferenceData("HEAD", "refs/heads/testref"); + backend.Refs["refs/heads/testref"] = new ReferenceData("refs/heads/testref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + backend.Refs["refs/heads/othersymbolic"] = new 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" })); @@ -140,7 +140,7 @@ public void RefdbBackendCanRenameAReferenceToADeeperReferenceHierarchy() { var backend = new MockRefdbBackend(repo); repo.Refs.SetBackend(backend); - backend.Refs["refs/tags/test"] = new RefdbBackend.ReferenceData("refs/tags/test", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + backend.Refs["refs/tags/test"] = new ReferenceData("refs/tags/test", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); const string newName = "refs/tags/test/deep"; var renamed = repo.Refs.Rename("refs/tags/test", newName); diff --git a/LibGit2Sharp/Core/GitRefdbBackend.cs b/LibGit2Sharp/Core/GitRefdbBackend.cs index 3c0425ec0..ceb1d1093 100644 --- a/LibGit2Sharp/Core/GitRefdbBackend.cs +++ b/LibGit2Sharp/Core/GitRefdbBackend.cs @@ -4,7 +4,7 @@ namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] - internal unsafe struct GitRefdbIterator + internal struct GitRefdbIterator { static GitRefdbIterator() { @@ -27,16 +27,16 @@ static GitRefdbIterator() [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int next_callback( out IntPtr referencePtr, - IntPtr iterator); + IntPtr iteratorPtr); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int next_name_callback( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] out string refName, - IntPtr iterator); + IntPtr iteratorPtr); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void free_callback( - IntPtr iterator); + IntPtr iteratorPtr); } [StructLayout(LayoutKind.Sequential)] diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index c956db66b..e847a8228 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -1,7 +1,6 @@ using LibGit2Sharp.Core; using System; using System.Collections.Generic; -using System.Globalization; using System.Runtime.InteropServices; namespace LibGit2Sharp @@ -31,13 +30,13 @@ protected RefdbBackend(Repository repository) /// /// Checks to see if a reference exists. /// - public abstract bool Exists(string refName); + public abstract bool Exists(string referenceName); /// /// Attempts to look up a reference. /// /// False if the reference doesn't exist. - public abstract bool Lookup(string refName, out ReferenceData data); + public abstract bool Lookup(string referenceName, out ReferenceData data); /// /// Iterates all references (if glob is null) or only references matching glob (if not null.) @@ -113,7 +112,7 @@ internal IntPtr RefdbBackendPointer /// /// Frees the backend pointer, if one has been allocated. /// - internal void Free() + private void Free() { if (IntPtr.Zero == nativePointer) { @@ -125,320 +124,10 @@ internal void Free() nativePointer = IntPtr.Zero; } - /// - /// Backend's representation of a reference. - /// - public sealed 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; - this.IsSymbolic = false; - this.ObjectId = directTarget; - this.SymbolicTarget = null; - } - - /// - /// Initializes a symbolic reference. - /// - public ReferenceData(string refName, string symbolicTarget) - { - this.RefName = refName; - this.IsSymbolic = true; - this.ObjectId = null; - 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; - } - } - - /// - /// Allocates a native git_reference for the and returns a pointer. - /// - internal IntPtr MarshalToPtr() - { - if (IsSymbolic) - { - return Proxy.git_reference__alloc_symbolic(RefName, SymbolicTarget); - } - else - { - return Proxy.git_reference__alloc(RefName, ObjectId.Oid); - } - } - - /// - /// Marshals a git_reference into a managed - /// - 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)); - } - } - } - - /// - /// 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) - { - Code = code; - } - - /// - /// Git error code to return on exception. - /// - 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)); - } - - /// - /// 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); - var backendException = ex as RefdbBackendException; - if (backendException == null) - { - return (int)GitErrorCode.Error; - } - - return (int)backendException.Code; - } - } - - /// - /// Wrapper to hold the state of the enumerator. - /// - 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; - } - } - - /// - /// Static entrypoints that trampoline into the iterator. - /// - 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) - { - return RefdbBackendException.GetCode(ex); - } - - if (data == null) - { - return (int)GitErrorCode.IterOver; - } - - referencePtr = data.MarshalToPtr(); - 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) - { - return RefdbBackendException.GetCode(ex); - } - - 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, GitRefdbIterator.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; - } - } - /// /// Static entry points that trampoline into the custom backend's implementation. /// - private unsafe static class BackendEntryPoints + private static unsafe class BackendEntryPoints { public static readonly GitRefdbBackend.exists_callback ExistsCallback = Exists; public static readonly GitRefdbBackend.lookup_callback LookupCallback = Lookup; @@ -519,28 +208,18 @@ public static int Iterator( return (int)GitErrorCode.Error; } - RefIterator iterator; + RefdbIterator iterator; try { var enumerator = backend.Iterate(glob).GetEnumerator(); - iterator = new RefIterator(enumerator); + iterator = new RefdbIterator(enumerator); } catch (Exception ex) { return RefdbBackendException.GetCode(ex); } - 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); + iteratorPtr = iterator.RefdbIteratorPtr; return (int)GitErrorCode.Ok; } diff --git a/LibGit2Sharp/RefdbBackendException.cs b/LibGit2Sharp/RefdbBackendException.cs new file mode 100644 index 000000000..853d90f45 --- /dev/null +++ b/LibGit2Sharp/RefdbBackendException.cs @@ -0,0 +1,76 @@ +using System; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp; + +/// +/// 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 readonly GitErrorCode code; + + private RefdbBackendException(GitErrorCode code, string message) + : base(message, code, GitErrorCategory.Reference) + { + this.code = code; + } + + /// + /// Reference was not found. + /// + public static RefdbBackendException NotFound(string referenceName) + { + return new RefdbBackendException(GitErrorCode.NotFound, $"could not resolve reference '{referenceName}'"); + } + + /// + /// Reference by this name already exists. + /// + public static RefdbBackendException Exists(string referenceName) + { + return new RefdbBackendException(GitErrorCode.Exists, $"will not overwrite reference '{referenceName}' without match or force"); + } + + /// + /// Conflict between an expected reference value and the reference's actual value. + /// + public static RefdbBackendException Conflict(string referenceName) + { + return new RefdbBackendException(GitErrorCode.Conflict, $"conflict occurred while writing reference '{referenceName}'"); + } + + /// + /// 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, $"operation '{operation}' is unsupported by this refdb backend."); + } + + /// + /// 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); + var backendException = ex as RefdbBackendException; + if (backendException == null) + { + return (int)GitErrorCode.Error; + } + + return (int)backendException.code; + } +} \ No newline at end of file diff --git a/LibGit2Sharp/RefdbIterator.cs b/LibGit2Sharp/RefdbIterator.cs new file mode 100644 index 000000000..79650072a --- /dev/null +++ b/LibGit2Sharp/RefdbIterator.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + public class RefdbIterator : IDisposable + { + private IEnumerator enumerator; + + public RefdbIterator(IEnumerator enumerator) + { + this.enumerator = enumerator; + } + + public ReferenceData GetNext() + { + if (this.enumerator.MoveNext()) + { + return this.enumerator.Current; + } + + return null; + } + + public void Dispose() + { + if (this.enumerator != null) + { + this.enumerator.Dispose(); + this.enumerator = null; + } + } + + private IntPtr nativePointer; + + internal IntPtr RefdbIteratorPtr + { + get + { + if (IntPtr.Zero == nativePointer) + { + var nativeIterator = new GitRefdbIterator(); + + nativeIterator.Next = ReferenceIteratorEntryPoints.NextCallback; + nativeIterator.NextName = ReferenceIteratorEntryPoints.NextNameCallback; + nativeIterator.Free = ReferenceIteratorEntryPoints.FreeCallback; + + nativeIterator.GCHandle = GCHandle.ToIntPtr(GCHandle.Alloc(this)); + nativePointer = Marshal.AllocHGlobal(Marshal.SizeOf(nativeIterator)); + Marshal.StructureToPtr(nativeIterator, nativePointer, false); + } + + return nativePointer; + } + } + + /// + /// Frees the iterator pointer, if one has been allocated. + /// + private void Free() + { + if (IntPtr.Zero == nativePointer) + { + return; + } + + GCHandle.FromIntPtr(Marshal.ReadIntPtr(nativePointer, GitRefdbIterator.GCHandleOffset)).Free(); + Marshal.FreeHGlobal(nativePointer); + nativePointer = IntPtr.Zero; + } + + private static class ReferenceIteratorEntryPoints + { + 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 iteratorPtr) + { + referencePtr = IntPtr.Zero; + var iterator = PtrToIterator(iteratorPtr); + if (iterator == null) + { + return (int)GitErrorCode.Error; + } + + ReferenceData data; + try + { + data = iterator.GetNext(); + } + catch (Exception ex) + { + return RefdbBackendException.GetCode(ex); + } + + if (data == null) + { + return (int)GitErrorCode.IterOver; + } + + referencePtr = data.MarshalToPtr(); + return (int)GitErrorCode.Ok; + } + + public static int NextName( + out string refNamePtr, + IntPtr iteratorPtr) + { + refNamePtr = null; + var iterator = PtrToIterator(iteratorPtr); + if (iterator == null) + { + return (int)GitErrorCode.Error; + } + + ReferenceData data; + try + { + data = iterator.GetNext(); + } + catch (Exception ex) + { + return RefdbBackendException.GetCode(ex); + } + + if (data == null) + { + return (int)GitErrorCode.IterOver; + } + + refNamePtr = data.RefName; + return (int)GitErrorCode.Ok; + } + + public static void Free(IntPtr iteratorPtr) + { + var iterator = PtrToIterator(iteratorPtr); + if (iterator == null) + { + return; + } + + try + { + iterator.Free(); + + iterator.Dispose(); + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Reference, ex); + } + } + + private static RefdbIterator PtrToIterator(IntPtr pointer) + { + var intPtr = Marshal.ReadIntPtr(pointer, GitRefdbIterator.GCHandleOffset); + var interator = GCHandle.FromIntPtr(intPtr).Target as RefdbIterator; + + if (interator == null) + { + Proxy.git_error_set_str(GitErrorCategory.Reference, "Cannot retrieve the managed RefdbIterator"); + return null; + } + + return interator; + } + } + } +} \ No newline at end of file diff --git a/LibGit2Sharp/ReferenceData.cs b/LibGit2Sharp/ReferenceData.cs new file mode 100644 index 000000000..a674d40e7 --- /dev/null +++ b/LibGit2Sharp/ReferenceData.cs @@ -0,0 +1,128 @@ +using System; +using System.Globalization; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp; + +/// +/// Backend's representation of a reference. +/// +public sealed 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; + this.IsSymbolic = false; + this.ObjectId = directTarget; + this.SymbolicTarget = null; + } + + /// + /// Initializes a symbolic reference. + /// + public ReferenceData(string refName, string symbolicTarget) + { + this.RefName = refName; + this.IsSymbolic = true; + this.ObjectId = null; + 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; + } + } + + /// + /// Allocates a native git_reference for the and returns a pointer. + /// + internal IntPtr MarshalToPtr() + { + if (IsSymbolic) + { + return Proxy.git_reference__alloc_symbolic(RefName, SymbolicTarget); + } + else + { + return Proxy.git_reference__alloc(RefName, ObjectId.Oid); + } + } + + /// + /// Marshals a git_reference into a managed + /// + 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)); + } + } +} \ No newline at end of file