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 diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs new file mode 100644 index 000000000..50da90f30 --- /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 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 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); + 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 ReferenceData("HEAD", "refs/heads/testref"); + backend.Refs["refs/heads/testref"] = new 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 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)); + } + } + + [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 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 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 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" })); + } + } + + [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 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..ceb1d1093 --- /dev/null +++ b/LibGit2Sharp/Core/GitRefdbBackend.cs @@ -0,0 +1,175 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal 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 iteratorPtr); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int next_name_callback( + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] out string refName, + IntPtr iteratorPtr); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void free_callback( + IntPtr iteratorPtr); + } + + [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..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); @@ -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); } -} +} \ 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..380bf6de6 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, @@ -2537,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; @@ -3866,4 +3916,4 @@ public static long ConvertToLong(this UIntPtr input) } } } -// ReSharper restore InconsistentNaming +// ReSharper restore InconsistentNaming \ No newline at end of file diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 28404d948..7517438ca 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 @@ -39,7 +39,7 @@ - + @@ -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/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 new file mode 100644 index 000000000..e847a8228 --- /dev/null +++ b/LibGit2Sharp/RefdbBackend.cs @@ -0,0 +1,399 @@ +using LibGit2Sharp.Core; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +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 referenceName); + + /// + /// Attempts to look up a reference. + /// + /// False if the reference doesn't exist. + public abstract bool Lookup(string referenceName, 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. + /// + private void Free() + { + if (IntPtr.Zero == nativePointer) + { + return; + } + + GCHandle.FromIntPtr(Marshal.ReadIntPtr(nativePointer, GitRefdbBackend.GCHandleOffset)).Free(); + Marshal.FreeHGlobal(nativePointer); + nativePointer = IntPtr.Zero; + } + + /// + /// Static entry points that trampoline into the custom backend's implementation. + /// + private static unsafe 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; + } + + RefdbIterator iterator; + try + { + var enumerator = backend.Iterate(glob).GetEnumerator(); + iterator = new RefdbIterator(enumerator); + } + catch (Exception ex) + { + return RefdbBackendException.GetCode(ex); + } + + iteratorPtr = iterator.RefdbIteratorPtr; + 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/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/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 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 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 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 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