From c393bbf0a65d4469caa308c639d6786387fe3a42 Mon Sep 17 00:00:00 2001 From: Kenneth Cochran Date: Thu, 14 Mar 2024 00:34:39 -0500 Subject: [PATCH] Fix support for custom object database and expose support for custom ref database --- LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj | 2 +- LibGit2Sharp.Tests/OdbBackendFixture.cs | 15 + LibGit2Sharp.Tests/RefdbBackendFixture.cs | 241 ++++++ LibGit2Sharp.Tests/RepositoryFixture.cs | 2 + LibGit2Sharp/Core/GitRefdbBackend.cs | 215 ++++++ LibGit2Sharp/Core/Handles/Objects.cs | 23 + LibGit2Sharp/Core/Handles/Objects.tt | 4 +- LibGit2Sharp/Core/NativeMethods.cs | 35 + LibGit2Sharp/Core/Opaques.cs | 1 + LibGit2Sharp/Core/Proxy.cs | 48 ++ LibGit2Sharp/LibGit2Sharp.csproj | 2 +- LibGit2Sharp/ObjectDatabase.cs | 14 +- LibGit2Sharp/RefdbBackend.cs | 729 ++++++++++++++++++ LibGit2Sharp/ReferenceCollection.cs | 17 +- LibGit2Sharp/Repository.cs | 7 +- .../x64/NativeLibraryLoadTestApp.x64.csproj | 2 +- .../x86/NativeLibraryLoadTestApp.x86.csproj | 2 +- 17 files changed, 1348 insertions(+), 11 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/LibGit2Sharp.Tests.csproj b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj index c5cbb5f24..89e016dbd 100644 --- a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj +++ b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj @@ -1,7 +1,7 @@  - net472;net6.0;net7.0 + net8.0 diff --git a/LibGit2Sharp.Tests/OdbBackendFixture.cs b/LibGit2Sharp.Tests/OdbBackendFixture.cs index 975d0e88c..032a2c94a 100644 --- a/LibGit2Sharp.Tests/OdbBackendFixture.cs +++ b/LibGit2Sharp.Tests/OdbBackendFixture.cs @@ -251,6 +251,21 @@ public void ADisposableOdbBackendGetsDisposedUponRepositoryDisposal() Assert.Equal(1, nbOfDisposeCalls); } + [Fact] + public void CanCreateInMemoryRepositoryWithBackend() + { + using (var repo = new Repository()) + { + repo.ObjectDatabase.AddBackend(new MockOdbBackend(), int.MaxValue); + + Assert.True(repo.Info.IsBare); + Assert.Null(repo.Info.Path); + Assert.Null(repo.Info.WorkingDirectory); + + Assert.Throws(() => { var idx = repo.Index; }); + } + } + #region MockOdbBackend private class MockOdbBackend : OdbBackend, IDisposable diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs new file mode 100644 index 000000000..5c8e2eb67 --- /dev/null +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -0,0 +1,241 @@ +using LibGit2Sharp.Tests.TestHelpers; + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Text.RegularExpressions; + using System.Threading.Tasks; + 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; + } + } + } + } diff --git a/LibGit2Sharp.Tests/RepositoryFixture.cs b/LibGit2Sharp.Tests/RepositoryFixture.cs index bf27b6091..d77a28f22 100644 --- a/LibGit2Sharp.Tests/RepositoryFixture.cs +++ b/LibGit2Sharp.Tests/RepositoryFixture.cs @@ -682,6 +682,8 @@ public void CanCreateInMemoryRepository() { using (var repo = new Repository()) { + Assert.NotNull(repo.ObjectDatabase); + Assert.True(repo.Info.IsBare); Assert.Null(repo.Info.Path); Assert.Null(repo.Info.WorkingDirectory); diff --git a/LibGit2Sharp/Core/GitRefdbBackend.cs b/LibGit2Sharp/Core/GitRefdbBackend.cs new file mode 100644 index 000000000..2efe5bc2f --- /dev/null +++ b/LibGit2Sharp/Core/GitRefdbBackend.cs @@ -0,0 +1,215 @@ +using System; + using System.Collections.Generic; + using System.Runtime.InteropServices; + using System.Text; + + 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); + } + } diff --git a/LibGit2Sharp/Core/Handles/Objects.cs b/LibGit2Sharp/Core/Handles/Objects.cs index 5f8db722e..cd54e73cd 100644 --- a/LibGit2Sharp/Core/Handles/Objects.cs +++ b/LibGit2Sharp/Core/Handles/Objects.cs @@ -579,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++) diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index e20d755ba..7159410fa 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, IntPtr 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); @@ -2108,5 +2114,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); } } 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 50cefc0df..ac7066326 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( @@ -2021,7 +2044,18 @@ public static unsafe void git_reference_ensure_log(RepositoryHandle repo, string int res = NativeMethods.git_reference_ensure_log(repo, refname); Ensure.ZeroResult(res); } + 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); + } #endregion #region git_reflog_ @@ -2537,6 +2571,20 @@ public static unsafe string git_repository_message(RepositoryHandle repo) } } + public static unsafe void git_repository_set_odb(RepositoryHandle repo, IntPtr gitOdbBackendPointer) + { + NativeMethods.git_repository_set_odb(repo, gitOdbBackendPointer); + } + + 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/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 4d0876c4d..7fe8741c8 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -1,7 +1,7 @@  - net472;net6.0 + net8.0 10.0 true LibGit2Sharp brings all the might and speed of libgit2, a native Git implementation, to the managed world of .NET diff --git a/LibGit2Sharp/ObjectDatabase.cs b/LibGit2Sharp/ObjectDatabase.cs index b48c72a99..4f6c4cc9f 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.AsIntPtr()); + } + else + { + handle = Proxy.git_repository_odb(repo.Handle); + } repo.RegisterForCleanup(handle); } diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs new file mode 100644 index 000000000..fc3f8f55e --- /dev/null +++ b/LibGit2Sharp/RefdbBackend.cs @@ -0,0 +1,729 @@ +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; + } + } + } + } diff --git a/LibGit2Sharp/ReferenceCollection.cs b/LibGit2Sharp/ReferenceCollection.cs index 602a20f17..34b01b360 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 /// diff --git a/LibGit2Sharp/Repository.cs b/LibGit2Sharp/Repository.cs index 73f560c3c..24b9baaf7 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, false)); 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); diff --git a/NativeLibraryLoadTestApp/x64/NativeLibraryLoadTestApp.x64.csproj b/NativeLibraryLoadTestApp/x64/NativeLibraryLoadTestApp.x64.csproj index 3bca18b34..c9682009f 100644 --- a/NativeLibraryLoadTestApp/x64/NativeLibraryLoadTestApp.x64.csproj +++ b/NativeLibraryLoadTestApp/x64/NativeLibraryLoadTestApp.x64.csproj @@ -2,7 +2,7 @@ Exe - net472 + net8.0 x64 diff --git a/NativeLibraryLoadTestApp/x86/NativeLibraryLoadTestApp.x86.csproj b/NativeLibraryLoadTestApp/x86/NativeLibraryLoadTestApp.x86.csproj index 0596f203c..841181bc1 100644 --- a/NativeLibraryLoadTestApp/x86/NativeLibraryLoadTestApp.x86.csproj +++ b/NativeLibraryLoadTestApp/x86/NativeLibraryLoadTestApp.x86.csproj @@ -2,7 +2,7 @@ Exe - net472 + net8.0 x86