From 3832e6715670d16f92194656edd40b77f8047e8d Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 3 Apr 2013 18:16:53 -0500 Subject: [PATCH 01/12] Expose pluggable reference database backend --- LibGit2Sharp.Tests/RefdbBackendFixture.cs | 372 ++++++++++++++++++ LibGit2Sharp/Core/GitRefdbBackend.cs | 134 +++++++ LibGit2Sharp/Core/GitReferenceType.cs | 4 +- LibGit2Sharp/Core/Handles/Objects.cs | 24 ++ LibGit2Sharp/Core/NativeMethods.cs | 23 ++ LibGit2Sharp/Core/Opaques.cs | 2 + LibGit2Sharp/Core/Proxy.cs | 71 ++++ LibGit2Sharp/RefdbBackend.cs | 450 ++++++++++++++++++++++ LibGit2Sharp/ReferenceCollection.cs | 15 + 9 files changed, 1092 insertions(+), 3 deletions(-) create mode 100644 LibGit2Sharp.Tests/RefdbBackendFixture.cs create mode 100644 LibGit2Sharp/Core/GitRefdbBackend.cs create mode 100644 LibGit2Sharp/RefdbBackend.cs diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs new file mode 100644 index 000000000..244df45d8 --- /dev/null +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -0,0 +1,372 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class RefdbBackendFixture : BaseFixture + { + [Fact] + public void CanWriteToRefdbBackend() + { + string path = SandboxStandardTestRepo(); + + using (var repository = new Repository(path)) + { + MockRefdbBackend backend = SetupBackend(repository); + + repository.Refs.Add("refs/heads/newref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"), true); + + Assert.Equal(backend.References["refs/heads/newref"], new MockRefdbReference(new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"))); + } + } + + [Fact] + public void CanReadFromRefdbBackend() + { + var scd = new SelfCleaningDirectory(this); + var path = Repository.Init(scd.RootedDirectoryPath); + + using (Repository repository = new Repository(path)) + { + MockRefdbBackend backend = SetupBackend(repository); + + backend.References["HEAD"] = new MockRefdbReference("refs/heads/testref"); + backend.References["refs/heads/testref"] = new MockRefdbReference(new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + + Assert.True(repository.Refs["HEAD"].TargetIdentifier.Equals("refs/heads/testref")); + Assert.True(repository.Refs["HEAD"].ResolveToDirectReference().TargetIdentifier.Equals("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + + Branch branch = repository.Head; + + Assert.True(branch.CanonicalName.Equals("refs/heads/testref")); + } + } + + [Fact] + public void CanDeleteFromRefdbBackend() + { + var scd = new SelfCleaningDirectory(this); + var path = Repository.Init(scd.RootedDirectoryPath); + + using (Repository repository = new Repository(path)) + { + MockRefdbBackend backend = SetupBackend(repository); + + backend.References["HEAD"] = new MockRefdbReference("refs/heads/testref"); + backend.References["refs/heads/testref"] = new MockRefdbReference(new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + + repository.Refs.Remove("refs/heads/testref"); + + Assert.True(!backend.References.ContainsKey("refs/heads/testref")); + } + } + + [Fact] + public void CannotOverwriteExistingInRefdbBackend() + { + string path = SandboxStandardTestRepo(); + using (var repository = new Repository(path)) + { + SetupBackend(repository); + + repository.Refs.Add("refs/heads/newref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"), false); + + Assert.Throws(() => repository.Refs.Add("refs/heads/newref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"), false)); + } + } + + [Fact] + public void CanIterateRefdbBackend() + { + var scd = new SelfCleaningDirectory(this); + var path = Repository.Init(scd.RootedDirectoryPath); + + using (Repository repository = new Repository(path)) + { + MockRefdbBackend backend = SetupBackend(repository); + + backend.References["HEAD"] = new MockRefdbReference("refs/heads/testref"); + backend.References["refs/heads/testref"] = new MockRefdbReference(new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + backend.References["refs/heads/othersymbolic"] = new MockRefdbReference("refs/heads/testref"); + + Assert.True(repository.Refs.Select(r => r.CanonicalName).SequenceEqual(backend.References.Keys)); + } + } + + [Fact] + public void CanIterateTypesInRefdbBackend() + { + var scd = new SelfCleaningDirectory(this); + var path = Repository.Init(scd.RootedDirectoryPath); + + using (Repository repository = new Repository(path)) + { + MockRefdbBackend backend = SetupBackend(repository); + + backend.References["refs/tags/broken1"] = new MockRefdbReference("tags/shouldnt/be/symbolic"); + backend.References["refs/tags/broken2"] = new MockRefdbReference("but/are/here/for/testing"); + backend.References["refs/tags/broken3"] = new MockRefdbReference("the/type/filtering"); + backend.References["refs/tags/correct1"] = new MockRefdbReference(new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + + Assert.True(repository.Tags.Select(r => r.CanonicalName).SequenceEqual(new List { "refs/tags/correct1" })); + } + } + + [Fact] + public void CanIterateRefdbBackendWithGlob() + { + var scd = new SelfCleaningDirectory(this); + var path = Repository.Init(scd.RootedDirectoryPath); + + using (Repository repository = new Repository(path)) + { + MockRefdbBackend backend = SetupBackend(repository); + + backend.References["HEAD"] = new MockRefdbReference("refs/heads/testref"); + backend.References["refs/heads/testref"] = new MockRefdbReference(new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + backend.References["refs/heads/othersymbolic"] = new MockRefdbReference("refs/heads/testref"); + + Assert.True(repository.Refs.FromGlob("refs/heads/*").Select(r => r.CanonicalName).SequenceEqual(new List() { "refs/heads/othersymbolic", "refs/heads/testref" })); + Assert.True(repository.Refs.FromGlob("refs/heads/?estref").Select(r => r.CanonicalName).SequenceEqual(new List() { "refs/heads/testref" })); + } + } + + #region MockRefdbBackend + + + /// + /// Kind type of a + /// + private enum ReferenceType + { + /// + /// A direct reference, the target is an object ID. + /// + Oid = 1, + + /// + /// A symbolic reference, the target is another reference. + /// + Symbolic = 2, + } + + private class MockRefdbReference + { + public MockRefdbReference(string target) + { + Type = ReferenceType.Symbolic; + Symbolic = target; + } + + public MockRefdbReference(ObjectId target) + { + Type = ReferenceType.Oid; + Oid = target; + } + + public ReferenceType Type + { + get; + private set; + } + + public ObjectId Oid + { + get; + private set; + } + + public string Symbolic + { + get; + private set; + } + + public override int GetHashCode() + { + int result = 17; + + result = 37 * result + (int)Type; + + if (Type == ReferenceType.Symbolic) + { + result = 37 * result + Symbolic.GetHashCode(); + } + else + { + result = 37 * result + Oid.GetHashCode(); + } + + return result; + } + + public override bool Equals(object obj) + { + var other = obj as MockRefdbReference; + + if (other == null || Type != other.Type) + { + return false; + } + + if (Type == ReferenceType.Symbolic) + { + return Symbolic.Equals(other.Symbolic); + } + + return Oid.Equals(other.Oid); + } + } + + private class MockRefdbBackend : RefdbBackend + { + private readonly Repository repository; + + private readonly SortedDictionary references = + new SortedDictionary(); + + public MockRefdbBackend(Repository repository) + { + this.repository = repository; + } + + protected override Repository Repository + { + get { return repository; } + } + + public SortedDictionary References + { + get { return references; } + } + + public bool Compressed { get; private set; } + + protected override RefdbBackendOperations SupportedOperations + { + get + { + return RefdbBackendOperations.Compress | RefdbBackendOperations.ForeachGlob; + } + } + + public override bool Exists(string referenceName) + { + return references.ContainsKey(referenceName); + } + + public override bool Lookup(string referenceName, out bool isSymbolic, out ObjectId oid, out string symbolic) + { + MockRefdbReference reference = references[referenceName]; + + if (reference == null) + { + isSymbolic = false; + oid = null; + symbolic = null; + return false; + } + + isSymbolic = reference.Type == ReferenceType.Symbolic; + oid = reference.Oid; + symbolic = reference.Symbolic; + return true; + } + + public override int Foreach(ForeachCallback callback, bool includeSymbolicRefs, bool includeDirectRefs) + { + int result = 0; + + foreach (KeyValuePair kvp in references) + { + var referenceType = kvp.Value.Type; + + if ((referenceType == ReferenceType.Symbolic && !includeSymbolicRefs) || + (referenceType == ReferenceType.Oid && !includeDirectRefs)) + { + continue; + } + + if ((result = callback(kvp.Key)) != 0) + { + return result; + } + } + + return result; + } + + public override int ForeachGlob(string glob, ForeachCallback callback, bool includeSymbolicRefs, bool includeDirectRefs) + { + int result = 0; + + var globRegex = new Regex("^" + + Regex.Escape(glob).Replace(@"\*", ".*").Replace(@"\?", ".") + + "$"); + + foreach (KeyValuePair kvp in references) + { + var referenceType = kvp.Value.Type; + + if ((referenceType == ReferenceType.Symbolic && !includeSymbolicRefs) || + (referenceType == ReferenceType.Oid && !includeDirectRefs)) + { + continue; + } + + if (!globRegex.IsMatch(kvp.Key)) + { + continue; + } + + if ((result = callback(kvp.Key)) != 0) + { + return result; + } + } + + return result; + } + + public override void WriteDirectReference(string referenceCanonicalName, ObjectId target) + { + var storage = new MockRefdbReference(target); + references.Add(referenceCanonicalName, storage); + } + + public override void WriteSymbolicReference(string referenceCanonicalName, string targetCanonicalName) + { + var storage = new MockRefdbReference(targetCanonicalName); + references.Add(referenceCanonicalName, storage); + } + + public override void Delete(string referenceCanonicalName) + { + references.Remove(referenceCanonicalName); + } + + public override void Compress() + { + Compressed = true; + } + + public override void Free() + { + references.Clear(); + } + } + + #endregion + + private static MockRefdbBackend SetupBackend(Repository repository) + { + var backend = new MockRefdbBackend(repository); + repository.Refs.SetBackend(backend); + + return backend; + } + } +} diff --git a/LibGit2Sharp/Core/GitRefdbBackend.cs b/LibGit2Sharp/Core/GitRefdbBackend.cs new file mode 100644 index 000000000..0c7b96ca1 --- /dev/null +++ b/LibGit2Sharp/Core/GitRefdbBackend.cs @@ -0,0 +1,134 @@ +using System; +using System.Runtime.InteropServices; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal struct GitRefdbBackend + { + static GitRefdbBackend() + { + GCHandleOffset = Marshal.OffsetOf(typeof(GitRefdbBackend), "GCHandle").ToInt32(); + } + + public uint Version; + + public exists_callback Exists; + public lookup_callback Lookup; + public foreach_callback Foreach; + public foreach_glob_callback ForeachGlob; + public write_callback Write; + public delete_callback Delete; + public compress_callback Compress; + 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; + + /// + /// Queries the backend to determine if the given referenceName + /// exists. + /// + /// [out] If the call is successful, the backend will set this to 1 if the reference exists, 0 otherwise. + /// [in] A pointer to the backend which is being queried. + /// [in] The reference name to look up. + /// 0 if successful; an error code otherwise. + public delegate int exists_callback( + out IntPtr exists, + IntPtr backend, + IntPtr referenceName); + + /// + /// Queries the backend for the given reference. + /// + /// [out] If the call is successful, the backend will set this to the reference. + /// [in] A pointer to the backend which is being queried. + /// [in] The reference name to look up. + /// 0 if successful; GIT_EEXISTS or an error code otherwise. + public delegate int lookup_callback( + out IntPtr reference, + IntPtr backend, + IntPtr referenceName); + + /// + /// Iterates each reference that matches list_flags, calling back to the given callback. + /// + /// [in] A pointer to the backend to query. + /// [in] The references to list. + /// [in] The callback function to invoke. + /// [in] An arbitrary parameter to pass through to the callback + /// 0 if successful; GIT_EUSER or an error code otherwise. + public delegate int foreach_callback( + IntPtr backend, + GitReferenceType list_flags, + foreach_callback_callback cb, + IntPtr data); + + /// + /// Iterates each reference that matches the glob pattern and the list_flags, calling back to the given callback. + /// + /// [in] A pointer to the backend to query. + /// [in] A glob pattern. + /// [in] The references to list. + /// [in] The callback function to invoke. + /// [in] An arbitrary parameter to pass through to the callback + /// 0 if successful; GIT_EUSER or an error code otherwise. + public delegate int foreach_glob_callback( + IntPtr backend, + IntPtr glob, + GitReferenceType list_flags, + foreach_callback_callback cb, + IntPtr data); + + /// + /// Writes the given reference. + /// + /// [in] A pointer to the backend to write to. + /// [in] The reference to write. + /// 0 if successful; an error code otherwise. + public delegate int write_callback( + IntPtr backend, + IntPtr referencePtr); + + /// + /// Deletes the given reference. + /// + /// [in] A pointer to the backend to delete. + /// [in] The reference to delete. + /// 0 if successful; an error code otherwise. + public delegate int delete_callback( + IntPtr backend, + IntPtr referencePtr); + + /// + /// Compresses the contained references, if possible. The backend is free to implement this in any implementation-defined way; or not at all. + /// + /// [in] A pointer to the backend to compress. + /// 0 if successful; an error code otherwise. + public delegate int compress_callback( + IntPtr backend); + + /// + /// The owner of this backend is finished with it. The backend is asked to clean up and shut down. + /// + /// [in] A pointer to the backend which is being freed. + public delegate void free_callback( + IntPtr backend); + + /// + /// A callback for the backend's implementation of foreach. + /// + /// The reference name. + /// Pointer to payload data passed to the caller. + /// A zero result indicates the enumeration should continue. Otherwise, the enumeration should stop. + public delegate int foreach_callback_callback( + IntPtr referenceName, + IntPtr data); + } +} diff --git a/LibGit2Sharp/Core/GitReferenceType.cs b/LibGit2Sharp/Core/GitReferenceType.cs index 04eaaa219..3b1f25eb8 100644 --- a/LibGit2Sharp/Core/GitReferenceType.cs +++ b/LibGit2Sharp/Core/GitReferenceType.cs @@ -8,8 +8,6 @@ internal enum GitReferenceType Invalid = 0, Oid = 1, Symbolic = 2, - Packed = 4, - Peel = 8, - ListAll = Oid | Symbolic | Packed + ListAll = Oid | Symbolic } } diff --git a/LibGit2Sharp/Core/Handles/Objects.cs b/LibGit2Sharp/Core/Handles/Objects.cs index f904b75b9..b94a6ed20 100644 --- a/LibGit2Sharp/Core/Handles/Objects.cs +++ b/LibGit2Sharp/Core/Handles/Objects.cs @@ -556,4 +556,28 @@ public override void Free() } } + internal unsafe class RefDatabaseHandle : Libgit2Object + + { + internal RefDatabaseHandle(git_refdb *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal RefDatabaseHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_refdb_free((git_refdb*) ptr); + } + + public static implicit operator git_refdb*(RefDatabaseHandle handle) + { + return (git_refdb*) handle.Handle; + } + } + } diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index b5aa8097e..d0c1245f5 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -1035,6 +1035,26 @@ internal static extern unsafe int git_packbuilder_write( [DllImport(libgit2)] internal static extern unsafe UIntPtr git_packbuilder_written(git_packbuilder* packbuilder); + [DllImport(libgit2)] + internal static extern unsafe int git_refdb_set_backend(git_refdb* refdb, IntPtr backend); + + [DllImport(libgit2)] + internal static extern unsafe int git_refdb_open(out git_refdb* refdb, git_repository* repo); + + [DllImport(libgit2)] + internal static extern unsafe void git_refdb_free(git_refdb* refdb); + + [DllImport(libgit2)] + internal static extern unsafe IntPtr git_reference__alloc( + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof (StrictUtf8Marshaler))] string name, + IntPtr oid, + IntPtr peel); + + [DllImport(libgit2)] + internal static extern unsafe IntPtr git_reference__alloc_symbolic( + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string target); + [DllImport(libgit2)] internal static extern unsafe int git_reference_create( out git_reference* reference, @@ -1440,6 +1460,9 @@ internal static extern unsafe int git_repository_open_ext( [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxFilePathNoCleanupMarshaler))] internal static extern unsafe FilePath git_repository_path(git_repository* repository); + [DllImport(libgit2)] + internal static extern unsafe int git_repository_refdb(out git_refdb* refdb, git_repository* repo); + [DllImport(libgit2)] internal static extern unsafe void git_repository_set_config( git_repository* repository, diff --git a/LibGit2Sharp/Core/Opaques.cs b/LibGit2Sharp/Core/Opaques.cs index 0d0bb55f0..722aacfba 100644 --- a/LibGit2Sharp/Core/Opaques.cs +++ b/LibGit2Sharp/Core/Opaques.cs @@ -27,5 +27,7 @@ internal struct git_remote {} internal struct git_object {} internal struct git_rebase {} internal struct git_odb_stream {} + internal struct git_refdb { } + internal struct git_refdb_backend { } } diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 9c1d1218f..52b141a90 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -1862,6 +1862,15 @@ public static unsafe void git_rebase_finish( #endregion +#region git_refdb_ + + public static unsafe void git_refdb_set_backend(RefDatabaseHandle refdb, IntPtr backend) + { + Ensure.ZeroResult(NativeMethods.git_refdb_set_backend(refdb, backend)); + } + + #endregion + #region git_reference_ public static unsafe ReferenceHandle git_reference_create( @@ -1879,6 +1888,39 @@ public static unsafe ReferenceHandle git_reference_create( return new ReferenceHandle(handle, true); } + + public static unsafe IntPtr git_reference__alloc(string name, ObjectId oid) + { + // GitOid is not nullable, do the IntPtr marshalling ourselves + IntPtr oidPtr; + + if (oid == null) + { + oidPtr = IntPtr.Zero; + } + else + { + oidPtr = Marshal.AllocHGlobal(20); + Marshal.Copy(oid.Oid.Id, 0, oidPtr, 20); + } + + try + { + return NativeMethods.git_reference__alloc(name, oidPtr, IntPtr.Zero); + } + finally + { + if (oidPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(oidPtr); + } + } + } + + public static IntPtr git_reference__alloc_symbolic(string name, string target) + { + return NativeMethods.git_reference__alloc_symbolic(name, target); + } public static unsafe ReferenceHandle git_reference_symbolic_create( RepositoryHandle repo, @@ -1948,6 +1990,11 @@ public static unsafe string git_reference_name(git_reference* reference) return NativeMethods.git_reference_name(reference); } + public static unsafe string git_reference_name(ReferenceHandle reference) + { + return NativeMethods.git_reference_name(reference); + } + public static unsafe void git_reference_remove(RepositoryHandle repo, string name) { int res = NativeMethods.git_reference_remove(repo, name); @@ -1959,6 +2006,11 @@ public static unsafe ObjectId git_reference_target(git_reference* reference) return ObjectId.BuildFromPtr(NativeMethods.git_reference_target(reference)); } + public static unsafe ObjectId git_reference_target(ReferenceHandle reference) + { + return ObjectId.BuildFromPtr(NativeMethods.git_reference_target(reference)); + } + public static unsafe ReferenceHandle git_reference_rename( ReferenceHandle reference, string newName, @@ -1999,11 +2051,21 @@ public static unsafe string git_reference_symbolic_target(git_reference* referen return NativeMethods.git_reference_symbolic_target(reference); } + public static unsafe string git_reference_symbolic_target(ReferenceHandle reference) + { + return NativeMethods.git_reference_symbolic_target(reference); + } + public static unsafe GitReferenceType git_reference_type(git_reference* reference) { return NativeMethods.git_reference_type(reference); } + public static unsafe GitReferenceType git_reference_type(ReferenceHandle reference) + { + return NativeMethods.git_reference_type(reference); + } + public static unsafe void git_reference_ensure_log(RepositoryHandle repo, string refname) { int res = NativeMethods.git_reference_ensure_log(repo, refname); @@ -2581,6 +2643,15 @@ public static unsafe FilePath git_repository_path(RepositoryHandle repo) return NativeMethods.git_repository_path(repo); } + public static unsafe RefDatabaseHandle git_repository_refdb(RepositoryHandle repo) + { + git_refdb* refdb; + int res = NativeMethods.git_repository_refdb(out refdb, repo); + Ensure.ZeroResult(res); + + return new RefDatabaseHandle(refdb, true); + } + public static unsafe void git_repository_set_config(RepositoryHandle repo, ConfigurationHandle config) { NativeMethods.git_repository_set_config(repo, config); diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs new file mode 100644 index 000000000..fe961e4d2 --- /dev/null +++ b/LibGit2Sharp/RefdbBackend.cs @@ -0,0 +1,450 @@ +using System; +using System.Globalization; +using System.Runtime.InteropServices; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// Base class for all custom managed backends for the libgit2 reference database. + /// + public abstract class RefdbBackend + { + /// + /// Requests the repository configured for this backend. + /// + protected abstract Repository Repository + { + get; + } + + /// + /// Requests that the backend provide all optional operations that are supported. + /// + protected abstract RefdbBackendOperations SupportedOperations + { + get; + } + + /// + /// Queries the backend for whether a reference exists. + /// + /// Name of the reference to query + /// True if the reference exists in the backend, false otherwise. + public abstract bool Exists(string referenceName); + + /// + /// Queries the backend for the given reference + /// + /// Name of the reference to query + /// + /// True if the returned reference is symbolic. False if the returned reference is direct. + /// + /// Object ID of the returned reference. Valued when is false. + /// Target of the returned reference. Valued when is false + /// True if the reference exists, false otherwise + public abstract bool Lookup(string referenceName, out bool isSymbolic, out ObjectId oid, out string symbolic); + + /// + /// Iterates the references in this backend. + /// + /// The callback to execute for each reference + /// True is symbolic references should be enumerated. + /// True is symbolic references should be enumerated. + /// The return code from the callback + public abstract int Foreach(ForeachCallback callback, bool includeSymbolicRefs, bool includeDirectRefs); + + /// + /// Iterates the references in this backend. + /// + /// The glob pattern reference names must match + /// The callback to execute for each reference + /// True is symbolic references should be enumerated. + /// True is symbolic references should be enumerated. + /// The return code from the callback + public abstract int ForeachGlob(string glob, ForeachCallback callback, bool includeSymbolicRefs, bool includeDirectRefs); + + /// + /// The signature of the callback method provided to the reference iterators. + /// + /// The name of the reference in the backend + /// 0 if enumeration should continue, any other value on error + public delegate int ForeachCallback(string referenceCanonicalName); + + /// + /// Write the given direct reference to the backend. + /// + /// The reference to write + /// The of the target . + public abstract void WriteDirectReference(string referenceCanonicalName, ObjectId target); + + /// + /// Write the given symbolic reference to the backend. + /// + /// The reference to write + /// The target of the symbolic reference + public abstract void WriteSymbolicReference(string referenceCanonicalName, string targetCanonicalName); + + /// + /// Delete the given reference from the backend. + /// + /// The reference to delete + public abstract void Delete(string referenceCanonicalName); + + /// + /// Compress the backend in an implementation-specific way. + /// + public abstract void Compress(); + + /// + /// Free any data associated with this backend. + /// + public abstract void Free(); + + private IntPtr nativeBackendPointer; + + internal IntPtr GitRefdbBackendPointer + { + get + { + if (IntPtr.Zero == nativeBackendPointer) + { + var nativeBackend = new GitRefdbBackend(); + nativeBackend.Version = 1; + + // The "free" entry point is always provided. + nativeBackend.Exists = BackendEntryPoints.ExistsCallback; + nativeBackend.Lookup = BackendEntryPoints.LookupCallback; + nativeBackend.Foreach = BackendEntryPoints.ForeachCallback; + nativeBackend.Write = BackendEntryPoints.WriteCallback; + nativeBackend.Delete = BackendEntryPoints.DeleteCallback; + nativeBackend.Free = BackendEntryPoints.FreeCallback; + + var supportedOperations = this.SupportedOperations; + + if ((supportedOperations & RefdbBackendOperations.ForeachGlob) != 0) + { + nativeBackend.ForeachGlob = BackendEntryPoints.ForeachGlobCallback; + } + + if ((supportedOperations & RefdbBackendOperations.Compress) != 0) + { + nativeBackend.Compress = BackendEntryPoints.CompressCallback; + } + + nativeBackend.GCHandle = GCHandle.ToIntPtr(GCHandle.Alloc(this)); + nativeBackendPointer = Marshal.AllocHGlobal(Marshal.SizeOf(nativeBackend)); + Marshal.StructureToPtr(nativeBackend, nativeBackendPointer, false); + } + + return nativeBackendPointer; + } + } + + private static class BackendEntryPoints + { + // Because our GitOdbBackend structure exists on the managed heap only for a short time (to be marshaled + // to native memory with StructureToPtr), we need to bind to static delegates. If at construction time + // we were to bind to the methods directly, that's the same as newing up a fresh delegate every time. + // Those delegates won't be rooted in the object graph and can be collected as soon as StructureToPtr finishes. + public static readonly GitRefdbBackend.exists_callback ExistsCallback = Exists; + public static readonly GitRefdbBackend.lookup_callback LookupCallback = Lookup; + public static readonly GitRefdbBackend.foreach_callback ForeachCallback = Foreach; + public static readonly GitRefdbBackend.foreach_glob_callback ForeachGlobCallback = ForeachGlob; + public static readonly GitRefdbBackend.write_callback WriteCallback = Write; + public static readonly GitRefdbBackend.delete_callback DeleteCallback = Delete; + public static readonly GitRefdbBackend.compress_callback CompressCallback = Compress; + public static readonly GitRefdbBackend.free_callback FreeCallback = Free; + + private static bool TryMarshalRefdbBackend(out RefdbBackend refdbBackend, IntPtr backend) + { + refdbBackend = null; + + var intPtr = Marshal.ReadIntPtr(backend, GitRefdbBackend.GCHandleOffset); + var handle = GCHandle.FromIntPtr(intPtr).Target as RefdbBackend; + + if (handle == null) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, "Cannot retrieve the RefdbBackend handle."); + return false; + } + + refdbBackend = handle; + return true; + } + + private static int Exists( + out IntPtr exists, + IntPtr backend, + IntPtr namePtr) + { + exists = IntPtr.Zero; + + RefdbBackend refdbBackend; + if (!TryMarshalRefdbBackend(out refdbBackend, backend)) + { + return (int)GitErrorCode.Error; + } + + string referenceName = LaxUtf8Marshaler.FromNative(namePtr); + + try + { + if (refdbBackend.Exists(referenceName)) + { + exists = (IntPtr)1; + } + } + catch (Exception ex) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, ex); + return (int)GitErrorCode.Error; + } + + return (int)GitErrorCode.Ok; + } + + private static int Lookup( + out IntPtr referencePtr, + IntPtr backend, + IntPtr namePtr) + { + referencePtr = IntPtr.Zero; + + RefdbBackend refdbBackend; + if (!TryMarshalRefdbBackend(out refdbBackend, backend)) + { + return (int)GitErrorCode.Error; + } + + string referenceName = LaxUtf8Marshaler.FromNative(namePtr); + + try + { + bool isSymbolic; + ObjectId oid; + string symbolic; + + if (!refdbBackend.Lookup(referenceName, out isSymbolic, out oid, out symbolic)) + { + return (int)GitErrorCode.NotFound; + } + + referencePtr = isSymbolic ? + Proxy.git_reference__alloc_symbolic(referenceName, symbolic) : + Proxy.git_reference__alloc(referenceName, oid); + } + catch (Exception ex) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, ex); + return (int)GitErrorCode.Error; + } + + return referencePtr != IntPtr.Zero ? + (int) GitErrorCode.Ok : (int) GitErrorCode.Error; + } + + private static int Foreach( + IntPtr backend, + GitReferenceType list_flags, + GitRefdbBackend.foreach_callback_callback callback, + IntPtr data) + { + RefdbBackend refdbBackend; + if (!TryMarshalRefdbBackend(out refdbBackend, backend)) + { + return (int)GitErrorCode.Error; + } + + try + { + bool includeSymbolicRefs = list_flags.HasFlag(GitReferenceType.Symbolic); + bool includeDirectRefs = list_flags.HasFlag(GitReferenceType.Oid); + + return refdbBackend.Foreach( + new ForeachState(callback, data).ManagedCallback, + includeSymbolicRefs, + includeDirectRefs); + } + catch (Exception ex) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, ex); + return (int)GitErrorCode.Error; + } + } + + private static int ForeachGlob( + IntPtr backend, + IntPtr globPtr, + GitReferenceType list_flags, + GitRefdbBackend.foreach_callback_callback callback, + IntPtr data) + { + RefdbBackend refdbBackend; + if (!TryMarshalRefdbBackend(out refdbBackend, backend)) + { + return (int)GitErrorCode.Error; + } + + string glob = LaxUtf8Marshaler.FromNative(globPtr); + + try + { + bool includeSymbolicRefs = list_flags.HasFlag(GitReferenceType.Symbolic); + bool includeDirectRefs = list_flags.HasFlag(GitReferenceType.Oid); + + return refdbBackend.ForeachGlob( + glob, + new ForeachState(callback, data).ManagedCallback, includeSymbolicRefs, includeDirectRefs); + } + catch (Exception ex) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, ex); + return (int)GitErrorCode.Error; + } + } + + private static int Write( + IntPtr backend, + IntPtr referencePtr) + { + RefdbBackend refdbBackend; + if (!TryMarshalRefdbBackend(out refdbBackend, backend)) + { + return (int)GitErrorCode.Error; + } + + var referenceHandle = new ReferenceHandle(referencePtr, false); + string name = Proxy.git_reference_name(referenceHandle); + GitReferenceType type = Proxy.git_reference_type(referenceHandle); + + try + { + switch (type) + { + case GitReferenceType.Oid: + ObjectId targetOid = Proxy.git_reference_target(referenceHandle); + refdbBackend.WriteDirectReference(name, targetOid); + break; + + case GitReferenceType.Symbolic: + string targetIdentifier = Proxy.git_reference_symbolic_target(referenceHandle); + refdbBackend.WriteSymbolicReference(name, targetIdentifier); + break; + + default: + throw new LibGit2SharpException( + String.Format(CultureInfo.InvariantCulture, + "Unable to build a new reference from a type '{0}'.", type)); + } + } + catch (Exception ex) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, ex); + return (int)GitErrorCode.Error; + } + + return (int)GitErrorCode.Ok; + } + + private static int Delete( + IntPtr backend, + IntPtr referencePtr) + { + RefdbBackend refdbBackend; + if (!TryMarshalRefdbBackend(out refdbBackend, backend)) + { + return (int)GitErrorCode.Error; + } + + var referenceHandle = new ReferenceHandle(referencePtr, false); + string name = Proxy.git_reference_name(referenceHandle); + + try + { + refdbBackend.Delete(name); + } + catch (Exception ex) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, ex); + return (int)GitErrorCode.Error; + } + + return (int)GitErrorCode.Ok; + } + + private static int Compress(IntPtr backend) + { + RefdbBackend refdbBackend; + if (!TryMarshalRefdbBackend(out refdbBackend, backend)) + { + return (int)GitErrorCode.Error; + } + + try + { + refdbBackend.Compress(); + } + catch (Exception ex) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, ex); + return (int)GitErrorCode.Error; + } + + return (int)GitErrorCode.Ok; + } + + private static void Free(IntPtr backend) + { + RefdbBackend refdbBackend; + if (!TryMarshalRefdbBackend(out refdbBackend, backend)) + { + // Really? Looks weird. + return; + } + + refdbBackend.Free(); + } + + private class ForeachState + { + public ForeachState(GitRefdbBackend.foreach_callback_callback cb, IntPtr data) + { + this.cb = cb; + this.data = data; + this.ManagedCallback = CallbackMethod; + } + + private int CallbackMethod(string name) + { + IntPtr namePtr = StrictUtf8Marshaler.FromManaged(name); + + return cb(namePtr, data); + } + + public readonly ForeachCallback ManagedCallback; + + private readonly GitRefdbBackend.foreach_callback_callback cb; + private readonly IntPtr data; + } + } + + /// + /// Flags used by subclasses of RefdbBackend to indicate which operations they support. + /// + [Flags] + protected enum RefdbBackendOperations + { + /// + /// This RefdbBackend declares that it supports the Compress method. + /// + Compress = 1, + + /// + /// This RefdbBackend declares that it supports the ForeachGlob method. + /// + ForeachGlob = 2, + } + } +} diff --git a/LibGit2Sharp/ReferenceCollection.cs b/LibGit2Sharp/ReferenceCollection.cs index 602a20f17..5fa7f63ce 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 RefDatabaseHandle refDbHandle; /// /// Needed for mocking purposes. @@ -30,6 +31,9 @@ protected ReferenceCollection() internal ReferenceCollection(Repository repo) { this.repo = repo; + refDbHandle = Proxy.git_repository_refdb(repo.Handle); + + repo.RegisterForCleanup(refDbHandle); } /// @@ -807,6 +811,17 @@ public virtual ReflogCollection Log(Reference reference) return new ReflogCollection(repo, reference.CanonicalName); } + /// + /// Sets the provided backend to be the reference database provider. + /// + /// The backend to add + public virtual void SetBackend(RefdbBackend backend) + { + Ensure.ArgumentNotNull(backend, "backend"); + + Proxy.git_refdb_set_backend(refDbHandle, backend.GitRefdbBackendPointer); + } + /// /// Rewrite some of the commits in the repository and all the references that can reach them. /// From f0e658ef788232f7f1cffa89084205dba33c6697 Mon Sep 17 00:00:00 2001 From: Jameson Miller Date: Mon, 1 Jun 2015 23:56:56 -0400 Subject: [PATCH 02/12] Introduce ref DB backend support --- LibGit2Sharp.Tests/RefTransactionFixture.cs | 175 +++++++ LibGit2Sharp.Tests/RefdbBackendFixture.cs | 538 ++++++++++++++++--- LibGit2Sharp/Core/GitRefdbBackend.cs | 213 ++++---- LibGit2Sharp/Core/GitRefdbIterator.cs | 39 ++ LibGit2Sharp/Core/Handles/Objects.cs | 25 +- LibGit2Sharp/Core/NativeMethods.cs | 61 ++- LibGit2Sharp/Core/Opaques.cs | 1 + LibGit2Sharp/Core/Proxy.cs | 86 +++ LibGit2Sharp/RefTransaction.cs | 119 +++++ LibGit2Sharp/RefdbBackend.cs | 546 +++++++++++++------- LibGit2Sharp/RefdbIterator.cs | 150 ++++++ LibGit2Sharp/ReferenceCollection.cs | 9 + 12 files changed, 1621 insertions(+), 341 deletions(-) create mode 100644 LibGit2Sharp.Tests/RefTransactionFixture.cs create mode 100644 LibGit2Sharp/Core/GitRefdbIterator.cs create mode 100644 LibGit2Sharp/RefTransaction.cs create mode 100644 LibGit2Sharp/RefdbIterator.cs diff --git a/LibGit2Sharp.Tests/RefTransactionFixture.cs b/LibGit2Sharp.Tests/RefTransactionFixture.cs new file mode 100644 index 000000000..6dd201021 --- /dev/null +++ b/LibGit2Sharp.Tests/RefTransactionFixture.cs @@ -0,0 +1,175 @@ +using LibGit2Sharp.Tests.TestHelpers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class RefTransactionFixture : BaseFixture + { + public static ObjectId oid1 = new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + public static ObjectId oid2 = new ObjectId("580c2111be43802dab11328176d94c391f1deae9"); + + [Fact] + public void CanCreateTransaction() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + using (repo.Refs.NewRefTransaction()) + { + } + } + } + + #region Shared transaction tests + + [Fact] + public void ReferenceIsNotRemovedWhenTransactionIsNotCommited() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var myRef = repo.Refs.Add("refs/heads/myref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + + using (var tx = repo.Refs.NewRefTransaction()) + { + tx.LockReference(myRef); + tx.RemoveReference(myRef); + } + + Assert.NotNull(repo.Refs[myRef.CanonicalName]); + } + } + + [Fact] + public void ReferenceIsNotModifiedWhenTransactionIsNotCommitted() + { + + } + + [Fact] + public void CanUpdateReferenceAfterTransactionIsAbandonded() + { + + } + + [Fact] + public void CanRemoveReferenceInTransaction() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var myRef = repo.Refs.Add("refs/heads/myref", oid1); + + using (var tx = repo.Refs.NewRefTransaction()) + { + tx.LockReference(myRef); + tx.RemoveReference(myRef); + tx.Commit(); + } + + Assert.Null(repo.Refs[myRef.CanonicalName]); + } + } + + [Fact] + public void CanUpdateDirectReferenceInTransaction() + { + string path = SandboxStandardTestRepo(); + string myRefName = "refs/heads/myref"; + + using (var repo = new Repository(path)) + { + var myRef = repo.Refs.Add(myRefName, oid1); + + using (var tx = repo.Refs.NewRefTransaction()) + { + tx.LockReference(myRef); + tx.UpdateTarget(myRef, oid2, "updated by me"); + tx.Commit(); + } + + var updatedRef = repo.Refs[myRefName]; + Assert.NotNull(updatedRef); + Assert.Equal(updatedRef.TargetIdentifier, oid2.Sha); + } + } + + [Fact] + public void CanUpdateSymbolicReferenceInTransaction() + { + string path = SandboxStandardTestRepo(); + string mySymRefName = "refs/heads/symRef"; + string refTargetName = "refs/heads/myref"; + string refTarget2Name = "refs/heads/myref2"; + + using (var repo = new Repository(path)) + { + var refTarget1 = repo.Refs.Add(refTargetName, oid1); + var refTarget2 = repo.Refs.Add(refTarget2Name, oid2); + var mySymRef = repo.Refs.Add(mySymRefName, refTargetName, null, true); + + using (var tx = repo.Refs.NewRefTransaction()) + { + tx.LockReference(mySymRef); + tx.UpdateTarget(mySymRef, refTarget2, null); + tx.Commit(); + } + + var updatedRef = repo.Refs[mySymRefName]; + Assert.NotNull(updatedRef); + Assert.Equal(updatedRef.TargetIdentifier, refTarget2Name); + } + } + + [SkippableFact(Skip = "Unsure of intended behavior")] + public void CanCreateNewDirectReferenceInTransaction() + { + + } + + [Fact] + public void LockingNonExistingReferenceThrows() + { + string path = SandboxStandardTestRepo(); + string myRefName = "refs/heads/myref"; + + using (var repo = new Repository(path)) + { + var myRef = repo.Refs.Add(myRefName, oid1); + repo.Refs.Remove(myRef); + + using (var tx = repo.Refs.NewRefTransaction()) + { + // Should this throw (reference no longer exists...) + Assert.Throws( + () => tx.LockReference(myRef)); + } + } + } + + [Fact] + public void LockingAlreadyLockedReferenceThrows() + { + string path = SandboxStandardTestRepo(); + string myRefName = "refs/heads/myref"; + + using (var repo = new Repository(path)) + { + var myRef = repo.Refs.Add(myRefName, oid1); + + using (var tx = repo.Refs.NewRefTransaction()) + using (var tx2 = repo.Refs.NewRefTransaction()) + { + tx.LockReference(myRef); + Assert.Throws(() => tx2.LockReference(myRef)); + } + } + } + + #endregion + } +} diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs index 244df45d8..759213757 100644 --- a/LibGit2Sharp.Tests/RefdbBackendFixture.cs +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using LibGit2Sharp.Tests.TestHelpers; @@ -65,7 +66,7 @@ public void CanDeleteFromRefdbBackend() } [Fact] - public void CannotOverwriteExistingInRefdbBackend() + public void CannotOverwriteExistingDirectReferenceInRefdbBackend() { string path = SandboxStandardTestRepo(); using (var repository = new Repository(path)) @@ -78,6 +79,54 @@ public void CannotOverwriteExistingInRefdbBackend() } } + [Fact] + public void CannotOverwriteExistingSymbolicReferenceInRefdbBackend() + { + string path = SandboxStandardTestRepo(); + using (var repository = new Repository(path)) + { + SetupBackend(repository); + + repository.Refs.Add("refs/heads/directRef", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"), false); + repository.Refs.Add("refs/heads/directRef2", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"), false); + repository.Refs.Add("refs/heads/newref", "refs/heads/directRef", false); + + Assert.Throws(() => repository.Refs.Add("refs/heads/newref", "refs/heads/directRef2", false)); + } + } + + [Fact] + public void CanForcefullyOverwriteExistingDirectReferenceInRefdbBackend() + { + string path = SandboxStandardTestRepo(); + using (var repository = new Repository(path)) + { + SetupBackend(repository); + + repository.Refs.Add("refs/heads/newref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"), false); + repository.Refs.Add("refs/heads/newref", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"), true); + } + } + + [Fact] + public void CanForcefullyOverwriteExistingSymbolicReferenceInRefdbBackend() + { + string path = SandboxStandardTestRepo(); + using (var repository = new Repository(path)) + { + SetupBackend(repository); + + repository.Refs.Add("refs/heads/directRef", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"), false); + repository.Refs.Add("refs/heads/directRef2", new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"), false); + repository.Refs.Add("refs/heads/newref", "refs/heads/directRef", false); + + repository.Refs.Add("refs/heads/newref", "refs/heads/directRef2", true); + + var symRef = repository.Refs["refs/heads/newref"]; + Assert.Equal("refs/heads/directRef2", symRef.TargetIdentifier); + } + } + [Fact] public void CanIterateRefdbBackend() { @@ -97,7 +146,7 @@ public void CanIterateRefdbBackend() } [Fact] - public void CanIterateTypesInRefdbBackend() + public void CanIterateBrokenTypesInRefdbBackend() { var scd = new SelfCleaningDirectory(this); var path = Repository.Init(scd.RootedDirectoryPath); @@ -111,7 +160,30 @@ public void CanIterateTypesInRefdbBackend() backend.References["refs/tags/broken3"] = new MockRefdbReference("the/type/filtering"); backend.References["refs/tags/correct1"] = new MockRefdbReference(new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); - Assert.True(repository.Tags.Select(r => r.CanonicalName).SequenceEqual(new List { "refs/tags/correct1" })); + List tags = repository.Tags.Select(r => r.CanonicalName).ToList(); + Assert.True(tags.SequenceEqual(new List { "refs/tags/correct1" })); + } + } + + [Fact] + public void CanIterateTypesInRefdbBackend() + { + var scd = new SelfCleaningDirectory(this); + var path = Repository.Init(scd.RootedDirectoryPath); + + using (Repository repository = new Repository(path)) + { + MockRefdbBackend backend = SetupBackend(repository); + + backend.References["refs/heads/testref"] = new MockRefdbReference(new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + backend.References["refs/heads/othersymbolic"] = new MockRefdbReference("refs/heads/testref"); + backend.References["refs/tags/correct"] = new MockRefdbReference(new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + + IEnumerable tags = repository.Tags.Select(r => r.CanonicalName); + Assert.True(tags.SequenceEqual(new List { "refs/tags/correct" })); + + IEnumerable branches = repository.Branches.Select(r => r.CanonicalName); + Assert.True(branches.SequenceEqual(new List { "refs/heads/othersymbolic", "refs/heads/testref" })); } } @@ -129,13 +201,267 @@ public void CanIterateRefdbBackendWithGlob() backend.References["refs/heads/testref"] = new MockRefdbReference(new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); backend.References["refs/heads/othersymbolic"] = new MockRefdbReference("refs/heads/testref"); - Assert.True(repository.Refs.FromGlob("refs/heads/*").Select(r => r.CanonicalName).SequenceEqual(new List() { "refs/heads/othersymbolic", "refs/heads/testref" })); - Assert.True(repository.Refs.FromGlob("refs/heads/?estref").Select(r => r.CanonicalName).SequenceEqual(new List() { "refs/heads/testref" })); + Assert.Equal(repository.Refs.FromGlob("refs/heads/*").Select(r => r.CanonicalName), new string[] { "refs/heads/othersymbolic", "refs/heads/testref" }); + Assert.Equal(repository.Refs.FromGlob("refs/heads/?estref").Select(r => r.CanonicalName), new string[] { "refs/heads/testref" }); } } - #region MockRefdbBackend + [Fact] + public void CanRenameFromRefDbBackend() + { + var scd = new SelfCleaningDirectory(this); + var path = Repository.Init(scd.RootedDirectoryPath); + + string originalRefName = "refs/heads/testref"; + string renamedRefName = "refs/heads/testref2"; + + using (Repository repository = new Repository(path)) + { + MockRefdbBackend backend = SetupBackend(repository); + + backend.References["HEAD"] = new MockRefdbReference("refs/heads/testref"); + backend.References[originalRefName] = new MockRefdbReference(new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + Reference myRef = repository.Refs[originalRefName]; + Reference renamedRef = repository.Refs.Rename(myRef, renamedRefName); + + // original ref name should not be found + Assert.Null(repository.Refs[originalRefName]); + Assert.NotNull(repository.Refs[renamedRefName]); + } + } + + [Fact] + public void CannotRenameOverExistingRef() + { + var scd = new SelfCleaningDirectory(this); + var path = Repository.Init(scd.RootedDirectoryPath); + + string originalRefName = "refs/heads/testref"; + string renamedRefName = "refs/heads/testref2"; + + using (Repository repository = new Repository(path)) + { + MockRefdbBackend backend = SetupBackend(repository); + + backend.References["HEAD"] = new MockRefdbReference("refs/heads/testref"); + backend.References[originalRefName] = new MockRefdbReference(new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + backend.References[renamedRefName] = new MockRefdbReference(new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + + Reference myRef = repository.Refs[originalRefName]; + Assert.Throws(() => + repository.Refs.Rename(myRef, renamedRefName)); + + // original ref name should be found + Assert.NotNull(repository.Refs[originalRefName]); + } + } + + [Fact] + public void CanForcefullyRenameOverExistingRef() + { + // TODO: test with a different target ref + + var scd = new SelfCleaningDirectory(this); + var path = Repository.Init(scd.RootedDirectoryPath); + + string originalRefName = "refs/heads/testref"; + string renamedRefName = "refs/heads/testref2"; + + using (Repository repository = new Repository(path)) + { + MockRefdbBackend backend = SetupBackend(repository); + + backend.References["HEAD"] = new MockRefdbReference("refs/heads/testref"); + backend.References[originalRefName] = new MockRefdbReference(new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + backend.References[renamedRefName] = new MockRefdbReference(new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + + Reference myRef = repository.Refs[originalRefName]; + Reference renamedRef = repository.Refs.Rename(myRef, renamedRefName, true); + + // original ref name should not be found + Assert.Null(repository.Refs[originalRefName]); + Assert.NotNull(repository.Refs[renamedRefName]); + } + } + + [Fact] + public void RenamingNonexistentRefThrows() + { + var scd = new SelfCleaningDirectory(this); + var path = Repository.Init(scd.RootedDirectoryPath); + + string originalRefName = "refs/heads/testref"; + string renamedRefName = "refs/heads/testref2"; + + using (Repository repository = new Repository(path)) + { + MockRefdbBackend backend = SetupBackend(repository); + + backend.References["HEAD"] = new MockRefdbReference("refs/heads/testref"); + backend.References[originalRefName] = new MockRefdbReference(new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); + + Reference myRef = repository.Refs[originalRefName]; + + repository.Refs.Remove(myRef); + + Assert.Throws(() => + repository.Refs.Rename(myRef, renamedRefName)); + } + } + + [Fact] + public void CanCompressFromRefDbBackend() + { + } + + [Fact] + public void CanLockAndUnlockFromRefDbBackend() + { + + } + + #region Shared transaction tests + + public static ObjectId oid1 = new ObjectId("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + public static ObjectId oid2 = new ObjectId("580c2111be43802dab11328176d94c391f1deae9"); + + [Fact] + public void ReferenceIsNotRemovedWhenTransactionIsNotCommittedRefDb() + { + var scd = new SelfCleaningDirectory(this); + var path = Repository.Init(scd.RootedDirectoryPath); + + string refName = "refs/heads/myref"; + + using (var repo = new Repository(path)) + { + MockRefdbBackend backend = SetupBackend(repo); + backend.References[refName] = new MockRefdbReference(oid1); + + var myRef = repo.Refs[refName]; + + using (var tx = repo.Refs.NewRefTransaction()) + { + tx.LockReference(myRef); + tx.RemoveReference(myRef); + } + + Assert.NotNull(repo.Refs[myRef.CanonicalName]); + } + } + + [Fact] + public void CanRemoveReferenceInTransactionRefDb() + { + var scd = new SelfCleaningDirectory(this); + var path = Repository.Init(scd.RootedDirectoryPath); + + string refName = "refs/heads/myref"; + + using (var repo = new Repository(path)) + { + MockRefdbBackend backend = SetupBackend(repo); + backend.References[refName] = new MockRefdbReference(oid1); + + var myRef = repo.Refs[refName]; + + using (var tx = repo.Refs.NewRefTransaction()) + { + tx.LockReference(myRef); + tx.RemoveReference(myRef); + tx.Commit(); + } + + Assert.Null(repo.Refs[myRef.CanonicalName]); + } + } + + [Fact] + public void CanUpdateDirectReferenceInTransactionRefDb() + { + var scd = new SelfCleaningDirectory(this); + var path = Repository.Init(scd.RootedDirectoryPath); + + string refName = "refs/heads/myref"; + + using (var repo = new Repository(path)) + { + MockRefdbBackend backend = SetupBackend(repo); + + backend.References[refName] = new MockRefdbReference(oid1); + + var myRef = repo.Refs[refName]; + + using (var tx = repo.Refs.NewRefTransaction()) + { + tx.LockReference(myRef); + tx.UpdateTarget(myRef, oid2, "updated by me"); + tx.Commit(); + } + + var updatedRef = repo.Refs[refName]; + Assert.NotNull(updatedRef); + Assert.Equal(updatedRef.TargetIdentifier, oid2.Sha); + } + } + + [Fact] + public void CanUpdateSymbolicReferenceInTransactionRefDb() + { + var scd = new SelfCleaningDirectory(this); + var path = Repository.Init(scd.RootedDirectoryPath); + + string mySymRefName = "refs/heads/symRef"; + string refTargetName = "refs/heads/myref"; + string refTarget2Name = "refs/heads/myref2"; + + using (var repo = new Repository(path)) + { + MockRefdbBackend backend = SetupBackend(repo); + + var refTarget1 = repo.Refs.Add(refTargetName, oid1); + var refTarget2 = repo.Refs.Add(refTarget2Name, oid2); + var mySymRef = repo.Refs.Add(mySymRefName, refTargetName, null, true); + + using (var tx = repo.Refs.NewRefTransaction()) + { + tx.LockReference(mySymRef); + tx.UpdateTarget(mySymRef, refTarget2, null); + tx.Commit(); + } + + var updatedRef = repo.Refs[mySymRefName]; + Assert.NotNull(updatedRef); + Assert.Equal(updatedRef.TargetIdentifier, refTarget2Name); + } + } + + [Fact] + public void LockingAlreadyLockedReferenceThrowsRefDb() + { + var scd = new SelfCleaningDirectory(this); + var path = Repository.Init(scd.RootedDirectoryPath); + + string myRefName = "refs/heads/myref"; + + using (var repo = new Repository(path)) + { + MockRefdbBackend backend = SetupBackend(repo); + var myRef = repo.Refs.Add(myRefName, oid1); + + using (var tx = repo.Refs.NewRefTransaction()) + using (var tx2 = repo.Refs.NewRefTransaction()) + { + tx.LockReference(myRef); + Assert.Throws(() => tx2.LockReference(myRef)); + } + } + } + + #endregion + + #region MockRefdbBackend /// /// Kind type of a @@ -173,6 +499,8 @@ public ReferenceType Type private set; } + public bool IsLocked { get; set; } + public ObjectId Oid { get; @@ -249,7 +577,7 @@ protected override RefdbBackendOperations SupportedOperations { get { - return RefdbBackendOperations.Compress | RefdbBackendOperations.ForeachGlob; + return RefdbBackendOperations.Compress; } } @@ -260,9 +588,9 @@ public override bool Exists(string referenceName) public override bool Lookup(string referenceName, out bool isSymbolic, out ObjectId oid, out string symbolic) { - MockRefdbReference reference = references[referenceName]; - - if (reference == null) + MockRefdbReference reference; + + if (!references.TryGetValue(referenceName, out reference)) { isSymbolic = false; oid = null; @@ -276,71 +604,51 @@ public override bool Lookup(string referenceName, out bool isSymbolic, out Objec return true; } - public override int Foreach(ForeachCallback callback, bool includeSymbolicRefs, bool includeDirectRefs) + public override void WriteDirectReference(string referenceCanonicalName, ObjectId target, bool force) { - int result = 0; - - foreach (KeyValuePair kvp in references) + var storage = new MockRefdbReference(target); + if (references.ContainsKey(referenceCanonicalName) && !force) { - var referenceType = kvp.Value.Type; - - if ((referenceType == ReferenceType.Symbolic && !includeSymbolicRefs) || - (referenceType == ReferenceType.Oid && !includeDirectRefs)) - { - continue; - } - - if ((result = callback(kvp.Key)) != 0) - { - return result; - } + throw new NameConflictException("A reference with this name already exists."); } - return result; + references[referenceCanonicalName] = storage; } - public override int ForeachGlob(string glob, ForeachCallback callback, bool includeSymbolicRefs, bool includeDirectRefs) + public override void RenameReference(string referenceName, string newReferenceName, bool force, + out bool isSymbolic, out ObjectId oid, out string symbolic) { - int result = 0; - - var globRegex = new Regex("^" + - Regex.Escape(glob).Replace(@"\*", ".*").Replace(@"\?", ".") + - "$"); - - foreach (KeyValuePair kvp in references) + if (references.ContainsKey(referenceName)) { - var referenceType = kvp.Value.Type; - if ((referenceType == ReferenceType.Symbolic && !includeSymbolicRefs) || - (referenceType == ReferenceType.Oid && !includeDirectRefs)) + if (references.ContainsKey(newReferenceName) && !force) { - continue; + throw new NameConflictException("error"); } - if (!globRegex.IsMatch(kvp.Key)) - { - continue; - } + var refToRename = references[referenceName]; + references[newReferenceName] = refToRename; + references.Remove(referenceName); - if ((result = callback(kvp.Key)) != 0) - { - return result; - } + isSymbolic = refToRename.Type == ReferenceType.Symbolic; + oid = refToRename.Oid; + symbolic = refToRename.Symbolic; + } + else + { + throw new Exception("error"); } - - return result; - } - - public override void WriteDirectReference(string referenceCanonicalName, ObjectId target) - { - var storage = new MockRefdbReference(target); - references.Add(referenceCanonicalName, storage); } - public override void WriteSymbolicReference(string referenceCanonicalName, string targetCanonicalName) + public override void WriteSymbolicReference(string referenceCanonicalName, string targetCanonicalName, bool force) { var storage = new MockRefdbReference(targetCanonicalName); - references.Add(referenceCanonicalName, storage); + if (references.ContainsKey(referenceCanonicalName) && !force) + { + throw new NameConflictException("A reference with this name already exists."); + } + + references[referenceCanonicalName] = storage; } public override void Delete(string referenceCanonicalName) @@ -357,6 +665,120 @@ public override void Free() { references.Clear(); } + + public override RefdbIterator GenerateRefIterator(string glob) + { + return new MockRefDbIterator(References, glob); + } + + public override bool HasReflog(string refName) + { + return false; + } + + public override void EnsureReflog(string refName) + { + + } + + public override void ReadReflog() + { + } + + public override void WriteReflog() + { + throw new NotImplementedException(); + } + + public override void RenameReflog(string oldName, string newName) + { + throw new NotImplementedException(); + } + + public override void LockReference(string refName) + { + MockRefdbReference reference; + + if (!this.references.TryGetValue(refName, out reference)) + { + throw new LibGit2Sharp.NotFoundException( + string.Format("Reference {0} was not found.", refName)); + } + + if (reference.IsLocked) + { + throw new LibGit2Sharp.LibGit2SharpException( + string.Format("Reference {0} is already locked.", refName)); + } + + reference.IsLocked = true; + } + + public override void UnlockReference(string refName) + { + MockRefdbReference reference; + + if (!this.references.TryGetValue(refName, out reference)) + { + throw new LibGit2Sharp.NotFoundException( + string.Format("Reference {0} was not found.", refName)); + } + + reference.IsLocked = false; + } + } + + private class MockRefDbIterator : RefdbIterator + { + IDictionary references; + IEnumerator> nextIterator; + + public MockRefDbIterator(IDictionary allRefs, string glob) + { + if (!string.IsNullOrEmpty(glob)) + { + Regex globRegex = new Regex("^" + + Regex.Escape(glob).Replace(@"\*", ".*").Replace(@"\?", ".") + + "$"); + + references = allRefs.Where(kvp => globRegex.IsMatch(kvp.Key)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + } + else + { + references = new Dictionary(allRefs); + } + + nextIterator = references.GetEnumerator(); + } + + public override bool Next(out string referenceName, out bool isSymbolic, out ObjectId oid, out string symbolic) + { + if (!nextIterator.MoveNext()) + { + referenceName = null; + isSymbolic = false; + oid = null; + symbolic = null; + return false; + } + + KeyValuePair next = nextIterator.Current; + referenceName = next.Key; + isSymbolic = next.Value.Type == ReferenceType.Symbolic; + oid = next.Value.Oid; + symbolic = next.Value.Symbolic; + return true; + } + + public override string NextName() + { + if (nextIterator.MoveNext()) + { + return nextIterator.Current.Key; + } + + return null; + } } #endregion diff --git a/LibGit2Sharp/Core/GitRefdbBackend.cs b/LibGit2Sharp/Core/GitRefdbBackend.cs index 0c7b96ca1..0ceed71a1 100644 --- a/LibGit2Sharp/Core/GitRefdbBackend.cs +++ b/LibGit2Sharp/Core/GitRefdbBackend.cs @@ -5,130 +5,151 @@ namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] - internal struct GitRefdbBackend + internal struct GitRefDbBackend { - static GitRefdbBackend() + static GitRefDbBackend() { - GCHandleOffset = Marshal.OffsetOf(typeof(GitRefdbBackend), "GCHandle").ToInt32(); + GCHandleOffset = Marshal.OffsetOf(typeof(GitRefDbBackend), "GCHandle").ToInt32(); } public uint Version; public exists_callback Exists; public lookup_callback Lookup; - public foreach_callback Foreach; - public foreach_glob_callback ForeachGlob; + public iterator_callback Iter; public write_callback Write; + public rename_callback Rename; public delete_callback Delete; public compress_callback Compress; - public free_callback Free; + public has_log_callback HasLog; + public ensure_log_callback EnsureLog; + public free_callback FreeBackend; + public reflog_write_callback ReflogWrite; + public reflog_read_callback ReflogRead; + public reflog_rename_callback ReflogRename; + public reflog_delete_callback ReflogDelete; + public ref_lock_callback RefLock; + public ref_unlock_callback RefUnlock; /* 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; - /// - /// Queries the backend to determine if the given referenceName - /// exists. - /// - /// [out] If the call is successful, the backend will set this to 1 if the reference exists, 0 otherwise. - /// [in] A pointer to the backend which is being queried. - /// [in] The reference name to look up. - /// 0 if successful; an error code otherwise. - public delegate int exists_callback( - out IntPtr exists, - IntPtr backend, - IntPtr referenceName); - - /// - /// Queries the backend for the given reference. - /// - /// [out] If the call is successful, the backend will set this to the reference. - /// [in] A pointer to the backend which is being queried. - /// [in] The reference name to look up. - /// 0 if successful; GIT_EEXISTS or an error code otherwise. - public delegate int lookup_callback( - out IntPtr reference, + /// Queries the refdb backend to determine if the given ref_name + /// A refdb implementation must provide this function. + [return: MarshalAs(UnmanagedType.I4)] + public delegate GitErrorCode exists_callback( + [MarshalAs(UnmanagedType.Bool)] out bool exists, IntPtr backend, - IntPtr referenceName); + IntPtr refNamePtr); - /// - /// Iterates each reference that matches list_flags, calling back to the given callback. - /// - /// [in] A pointer to the backend to query. - /// [in] The references to list. - /// [in] The callback function to invoke. - /// [in] An arbitrary parameter to pass through to the callback - /// 0 if successful; GIT_EUSER or an error code otherwise. - public delegate int foreach_callback( + /// Queries the refdb backend for a given reference. A refdb + /// implementation must provide this function. + [return: MarshalAs(UnmanagedType.I4)] + public delegate GitErrorCode lookup_callback( + out IntPtr git_reference, IntPtr backend, - GitReferenceType list_flags, - foreach_callback_callback cb, - IntPtr data); + IntPtr refNamePtr); /// - /// Iterates each reference that matches the glob pattern and the list_flags, calling back to the given callback. + /// Allocate an iterator object for the backend. + /// A refdb implementation must provide this function. /// - /// [in] A pointer to the backend to query. - /// [in] A glob pattern. - /// [in] The references to list. - /// [in] The callback function to invoke. - /// [in] An arbitrary parameter to pass through to the callback - /// 0 if successful; GIT_EUSER or an error code otherwise. - public delegate int foreach_glob_callback( + [return: MarshalAs(UnmanagedType.I4)] + public delegate GitErrorCode iterator_callback( + out IntPtr iter, IntPtr backend, - IntPtr glob, - GitReferenceType list_flags, - foreach_callback_callback cb, - IntPtr data); + IntPtr globPtr); - /// - /// Writes the given reference. - /// - /// [in] A pointer to the backend to write to. - /// [in] The reference to write. - /// 0 if successful; an error code otherwise. - public delegate int write_callback( + /// Writes the given reference to the refdb. A refdb implementation + /// must provide this function. + [return: MarshalAs(UnmanagedType.I4)] + public delegate GitErrorCode write_callback( IntPtr backend, - IntPtr referencePtr); + IntPtr reference, // const git_reference * + [MarshalAs(UnmanagedType.Bool)] bool force, + IntPtr who, // const git_signature * + IntPtr messagePtr, // const char * + IntPtr oid, // const git_oid * + IntPtr old_target // const char * + ); + + [return: MarshalAs(UnmanagedType.I4)] + public delegate GitErrorCode rename_callback( + out IntPtr reference, // git_reference ** + IntPtr backend, // git_refdb_backend * + IntPtr old_name, // const char * + IntPtr new_name, // const char * + [MarshalAs(UnmanagedType.Bool)] bool force, + IntPtr who, // const git_signature * + IntPtr message // const char * + ); + + [return: MarshalAs(UnmanagedType.I4)] + public delegate GitErrorCode delete_callback( + IntPtr backend, // git_refdb_backend * + IntPtr ref_name, // const char * + IntPtr oldId, // const git_oid * + IntPtr old_target // const char * + ); + + [return: MarshalAs(UnmanagedType.I4)] + public delegate GitErrorCode compress_callback( + IntPtr backend // git_refdb_backend * + ); + + public delegate GitErrorCode has_log_callback( + IntPtr backend, // git_refdb_backend * + IntPtr refNamePtr // const char * + ); + + public delegate GitErrorCode ensure_log_callback( + IntPtr backend, // git_refdb_backend * + IntPtr refNamePtr // const char * + ); - /// - /// Deletes the given reference. - /// - /// [in] A pointer to the backend to delete. - /// [in] The reference to delete. - /// 0 if successful; an error code otherwise. - public delegate int delete_callback( - IntPtr backend, - IntPtr referencePtr); - - /// - /// Compresses the contained references, if possible. The backend is free to implement this in any implementation-defined way; or not at all. - /// - /// [in] A pointer to the backend to compress. - /// 0 if successful; an error code otherwise. - public delegate int compress_callback( - IntPtr backend); - - /// - /// The owner of this backend is finished with it. The backend is asked to clean up and shut down. - /// - /// [in] A pointer to the backend which is being freed. public delegate void free_callback( - IntPtr backend); - - /// - /// A callback for the backend's implementation of foreach. - /// - /// The reference name. - /// Pointer to payload data passed to the caller. - /// A zero result indicates the enumeration should continue. Otherwise, the enumeration should stop. - public delegate int foreach_callback_callback( - IntPtr referenceName, - IntPtr data); + IntPtr backend // git_refdb_backend * + ); + + public delegate GitErrorCode reflog_read_callback( + out IntPtr git_reflog, // git_reflog ** + IntPtr backend, // git_refdb_backend * + IntPtr refNamePtr // const char * + ); + + public delegate GitErrorCode reflog_write_callback( + IntPtr backend, // git_refdb_backend * + IntPtr git_reflog // git_reflog * + ); + + public delegate GitErrorCode reflog_rename_callback( + IntPtr backend, // git_refdb_backend + IntPtr oldNamePtr, // const char * + IntPtr newNamePtr // const char * + ); + + public delegate GitErrorCode reflog_delete_callback( + IntPtr backend, // git_refdb_backend + IntPtr namePtr // const char * + ); + + public delegate GitErrorCode ref_lock_callback( + IntPtr payload, // void ** + IntPtr backend, // git_refdb_backend + IntPtr namePtr // const char * + ); + + public delegate GitErrorCode ref_unlock_callback( + IntPtr backend, // git_refdb_backend + IntPtr payload, + [MarshalAs(UnmanagedType.Bool)] bool force, + [MarshalAs(UnmanagedType.Bool)] bool update_reflog, + IntPtr refNamePtr, // const char * + IntPtr who, // const git_signature * + IntPtr messagePtr // const char * + ); } } diff --git a/LibGit2Sharp/Core/GitRefdbIterator.cs b/LibGit2Sharp/Core/GitRefdbIterator.cs new file mode 100644 index 000000000..1c4671eb7 --- /dev/null +++ b/LibGit2Sharp/Core/GitRefdbIterator.cs @@ -0,0 +1,39 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + + [StructLayout(LayoutKind.Sequential)] + internal class GitRefdbIterator + { + static GitRefdbIterator() + { + GCHandleOffset = Marshal.OffsetOf(typeof(GitRefdbIterator), "GCHandle").ToInt32(); + } + + IntPtr refDb; + + public ref_db_next next; + public ref_db_next_name next_name; + public ref_db_free free; + + /* The libgit2 structure definition ends here. Subsequent fields are for libgit2sharp bookkeeping. */ + + public IntPtr GCHandle; + + public static int GCHandleOffset; + + public IntPtr RefNamePtr; + + internal delegate int ref_db_next( + out IntPtr reference, + IntPtr iter); + + internal delegate int ref_db_next_name( + out IntPtr refNamePtr, + IntPtr iter); + + internal delegate void ref_db_free(IntPtr iter); + } +} diff --git a/LibGit2Sharp/Core/Handles/Objects.cs b/LibGit2Sharp/Core/Handles/Objects.cs index b94a6ed20..71b22da51 100644 --- a/LibGit2Sharp/Core/Handles/Objects.cs +++ b/LibGit2Sharp/Core/Handles/Objects.cs @@ -559,7 +559,7 @@ public override void Free() internal unsafe class RefDatabaseHandle : Libgit2Object { - internal RefDatabaseHandle(git_refdb *ptr, bool owned) + internal RefDatabaseHandle(git_refdb *ptr, bool owned) : base((void *) ptr, owned) { } @@ -580,4 +580,27 @@ public override void Free() } } + internal unsafe class TransactionHandle : Libgit2Object + { + internal TransactionHandle(git_transaction *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal TransactionHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_transaction_free((git_transaction*) ptr); + } + + public static implicit operator git_transaction*(TransactionHandle handle) + { + return (git_transaction*) handle.Handle; + } + } + } diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index d0c1245f5..13d212b75 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -1044,6 +1044,9 @@ internal static extern unsafe int git_packbuilder_write( [DllImport(libgit2)] internal static extern unsafe void git_refdb_free(git_refdb* refdb); + [DllImport(libgit2)] + internal static extern unsafe void git_refdb_free(IntPtr refDb); + [DllImport(libgit2)] internal static extern unsafe IntPtr git_reference__alloc( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof (StrictUtf8Marshaler))] string name, @@ -1813,6 +1816,58 @@ internal static extern unsafe int git_tag_delete( internal unsafe delegate int git_transport_certificate_check_cb(git_certificate* cert, int valid, IntPtr hostname, IntPtr payload); + [DllImport(libgit2)] + internal static extern unsafe int git_transaction_new( + out git_transaction* transaction, + git_repository* repo); + + [DllImport(libgit2)] + internal static extern unsafe int git_transaction_lock_ref( + git_transaction* transaction, + string refName); + + [DllImport(libgit2)] + internal static extern unsafe int git_transaction_set_target( + git_transaction* tx, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName, + ref GitOid target, // const git_oid* + git_signature* sig, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string msg); // const char* msg + + [DllImport(libgit2)] + internal static extern unsafe int git_transaction_set_symbolic_target( + git_transaction* tx, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName, // const char* + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string target, // const char*, + git_signature* sig, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string msg // const char* msg); + ); + + [DllImport(libgit2)] + internal static extern unsafe int git_transaction_set_reflog( + git_transaction* tx, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName, // const char* refname, + IntPtr reflog // const git_reflog* reflog + ); + + [DllImport(libgit2)] + internal static extern unsafe int git_transaction_remove( + git_transaction* tx, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName // const char* refname, + ); + + [DllImport(libgit2)] + internal static extern unsafe int git_transaction_commit(git_transaction* tx); + + [DllImport(libgit2)] + internal static extern void git_transaction_free(IntPtr tx); + + [DllImport(libgit2)] + internal static extern unsafe void git_transaction_free(git_transaction* tx); + + [DllImport(libgit2)] + internal static extern int git_transaction_commit(IntPtr txn); + [DllImport(libgit2)] internal static extern int git_transport_register( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string prefix, @@ -1901,12 +1956,6 @@ internal static extern unsafe int git_cherrypick_commit(out git_index* index, git_object* our_commit, uint mainline, ref GitMergeOpts options); - - [DllImport(libgit2)] - internal static extern int git_transaction_commit(IntPtr txn); - - [DllImport(libgit2)] - internal static extern void git_transaction_free(IntPtr txn); } } // ReSharper restore InconsistentNaming diff --git a/LibGit2Sharp/Core/Opaques.cs b/LibGit2Sharp/Core/Opaques.cs index 722aacfba..bde9f9b53 100644 --- a/LibGit2Sharp/Core/Opaques.cs +++ b/LibGit2Sharp/Core/Opaques.cs @@ -29,5 +29,6 @@ internal struct git_rebase {} internal struct git_odb_stream {} internal struct git_refdb { } internal struct git_refdb_backend { } + internal struct git_transaction { } } diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 52b141a90..b7f32b841 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -2521,6 +2521,23 @@ public static bool git_repository_head_unborn(RepositoryHandle repo) return RepositoryStateChecker(repo, NativeMethods.git_repository_head_unborn); } + public static unsafe Identity git_repository_ident(RepositoryHandle repo) + { + string name; + string email; + + int res = NativeMethods.git_repository_ident(out name, out email, repo); + Ensure.ZeroResult(res); + + if (string.IsNullOrEmpty(name) || + string.IsNullOrEmpty(email)) + { + return null; + } + + return new Identity(name, email); + } + public static unsafe IndexHandle git_repository_index(RepositoryHandle repo) { git_index* handle; @@ -3265,6 +3282,75 @@ public static void git_trace_set(LogLevel level, NativeMethods.git_trace_cb call Ensure.ZeroResult(res); } + #endregion + + #region git_transaction_ + + public static unsafe TransactionHandle git_transaction_new(RepositoryHandle repo) + { + git_transaction* tx; + int res = NativeMethods.git_transaction_new(out tx, repo); + Ensure.ZeroResult(res); + return new TransactionHandle(tx, true); + } + + public static unsafe void git_transaction_lock_ref(TransactionHandle tx, string refName) + { + int res = NativeMethods.git_transaction_lock_ref(tx, refName); + Ensure.ZeroResult(res); + } + + public static unsafe void git_transaction_set_target(TransactionHandle tx, string refName, GitOid oid, Identity ident, string msg) + { + using (SignatureHandle sigHandle = ident.SafeBuildNowSignatureHandle()) + { + int res = NativeMethods.git_transaction_set_target(tx, refName, ref oid, sigHandle, msg); + Ensure.ZeroResult(res); + } + } + + public static unsafe void git_transaction_set_symbolic_target( + TransactionHandle tx, + string refName, + string target, + Identity ident, + string msg) + { + using (SignatureHandle sigHandle = ident.SafeBuildNowSignatureHandle()) + { + int res = NativeMethods.git_transaction_set_symbolic_target( + tx, + refName, + target, + sigHandle, + msg); + Ensure.ZeroResult(res); + } + } + + public static unsafe void git_transaction_set_reflog(TransactionHandle tx, string refName, IntPtr reflog) + { + int res = NativeMethods.git_transaction_set_reflog(tx, refName, reflog); + Ensure.ZeroResult(res); + } + + public static unsafe void git_transaction_remove(TransactionHandle tx, string refName) + { + int res = NativeMethods.git_transaction_remove(tx, refName); + Ensure.ZeroResult(res); + } + + public static unsafe void git_transaction_commit(TransactionHandle tx) + { + int res = NativeMethods.git_transaction_commit(tx); + Ensure.ZeroResult(res); + } + + public static unsafe void git_transaction_free(TransactionHandle tx) + { + NativeMethods.git_transaction_free(tx); + } + #endregion #region git_transport_ diff --git a/LibGit2Sharp/RefTransaction.cs b/LibGit2Sharp/RefTransaction.cs new file mode 100644 index 000000000..b4ac981de --- /dev/null +++ b/LibGit2Sharp/RefTransaction.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// + /// + public class RefTransaction : IDisposable + { + TransactionHandle transactionHandle; + RepositoryHandle repo; + + internal RefTransaction(Repository repository) + { + repo = repository.Handle; + transactionHandle = Proxy.git_transaction_new(repository.Handle); + } + + /// + /// + /// + /// + public void LockReference(Reference reference) + { + Proxy.git_transaction_lock_ref(this.transactionHandle, reference.CanonicalName); + } + + /// + /// + /// + /// + public void RemoveReference(Reference reference) + { + Proxy.git_transaction_remove(this.transactionHandle, reference.CanonicalName); + } + + /// + /// + /// + /// + /// + /// + /// + public virtual void UpdateTarget(Reference directRef, ObjectId targetId, string logMessage) + { + Ensure.ArgumentNotNull(directRef, "directRef"); + Ensure.ArgumentNotNull(targetId, "targetId"); + + Identity ident = Proxy.git_repository_ident(repo); + + Proxy.git_transaction_set_target(this.transactionHandle, directRef.CanonicalName, targetId.Oid, ident, logMessage); + } + + /// + /// + /// + /// + /// + /// + public void UpdateTarget(Reference symbolicRef, Reference targetRef, string logMessage) + { + Identity ident = Proxy.git_repository_ident(repo); + Proxy.git_transaction_set_symbolic_target(this.transactionHandle, symbolicRef.CanonicalName, targetRef.CanonicalName, ident, logMessage); + } + + /// + /// + /// + public void Commit() + { + Proxy.git_transaction_commit(this.transactionHandle); + } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + /// + /// + /// + /// + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + transactionHandle.SafeDispose(); + } + + disposedValue = true; + } + } + + // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. + // ~RefTransaction() { + // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + // Dispose(false); + // } + + // This code added to correctly implement the disposable pattern. + + /// + /// + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // TODO: uncomment the following line if the finalizer is overridden above. + // GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index fe961e4d2..9464def57 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -20,7 +20,7 @@ protected abstract Repository Repository } /// - /// Requests that the backend provide all optional operations that are supported. + /// The optional operations this backed supports /// protected abstract RefdbBackendOperations SupportedOperations { @@ -28,63 +28,57 @@ protected abstract RefdbBackendOperations SupportedOperations } /// - /// Queries the backend for whether a reference exists. + /// Queries the backend for whether a reference exists. /// /// Name of the reference to query /// True if the reference exists in the backend, false otherwise. public abstract bool Exists(string referenceName); /// - /// Queries the backend for the given reference + /// Queries the backend for the given reference /// /// Name of the reference to query - /// - /// True if the returned reference is symbolic. False if the returned reference is direct. - /// + /// True if the returned reference is a symbolic reference, + /// False if the returned reference is a direct reference. /// Object ID of the returned reference. Valued when is false. /// Target of the returned reference. Valued when is false /// True if the reference exists, false otherwise public abstract bool Lookup(string referenceName, out bool isSymbolic, out ObjectId oid, out string symbolic); /// - /// Iterates the references in this backend. + /// Generate the ref iterator. /// - /// The callback to execute for each reference - /// True is symbolic references should be enumerated. - /// True is symbolic references should be enumerated. - /// The return code from the callback - public abstract int Foreach(ForeachCallback callback, bool includeSymbolicRefs, bool includeDirectRefs); + /// + /// + public abstract RefdbIterator GenerateRefIterator(string glob); /// - /// Iterates the references in this backend. - /// - /// The glob pattern reference names must match - /// The callback to execute for each reference - /// True is symbolic references should be enumerated. - /// True is symbolic references should be enumerated. - /// The return code from the callback - public abstract int ForeachGlob(string glob, ForeachCallback callback, bool includeSymbolicRefs, bool includeDirectRefs); - - /// - /// The signature of the callback method provided to the reference iterators. - /// - /// The name of the reference in the backend - /// 0 if enumeration should continue, any other value on error - public delegate int ForeachCallback(string referenceCanonicalName); - - /// - /// Write the given direct reference to the backend. + /// Write the given direct reference to the backend. /// /// The reference to write /// The of the target . - public abstract void WriteDirectReference(string referenceCanonicalName, ObjectId target); + /// + public abstract void WriteDirectReference(string referenceCanonicalName, ObjectId target, bool force); /// /// Write the given symbolic reference to the backend. /// /// The reference to write /// The target of the symbolic reference - public abstract void WriteSymbolicReference(string referenceCanonicalName, string targetCanonicalName); + /// + public abstract void WriteSymbolicReference(string referenceCanonicalName, string targetCanonicalName, bool force); + + /// + /// + /// + /// + /// + /// + /// + /// + /// + public abstract void RenameReference(string referenceName, string newReferenceName, bool force, + out bool isSymbolic, out ObjectId oid, out string symbolic); /// /// Delete the given reference from the backend. @@ -102,6 +96,50 @@ protected abstract RefdbBackendOperations SupportedOperations /// public abstract void Free(); + /// + /// + /// + /// + /// + public abstract bool HasReflog(string refName); + + /// + /// + /// + /// + public abstract void EnsureReflog(string refName); + + /// + /// + /// + public abstract void ReadReflog(); + + /// + /// + /// + public abstract void WriteReflog(); + + /// + /// + /// + /// + /// + public abstract void RenameReflog(string oldName, string newName); + + /// + /// + /// + /// + /// + public abstract void LockReference(string refName); + + /// + /// + /// + /// + /// + public abstract void UnlockReference(string refname); + private IntPtr nativeBackendPointer; internal IntPtr GitRefdbBackendPointer @@ -110,29 +148,29 @@ internal IntPtr GitRefdbBackendPointer { if (IntPtr.Zero == nativeBackendPointer) { - var nativeBackend = new GitRefdbBackend(); + var nativeBackend = new GitRefDbBackend(); nativeBackend.Version = 1; // The "free" entry point is always provided. nativeBackend.Exists = BackendEntryPoints.ExistsCallback; nativeBackend.Lookup = BackendEntryPoints.LookupCallback; - nativeBackend.Foreach = BackendEntryPoints.ForeachCallback; + nativeBackend.Iter = BackendEntryPoints.IterCallback; nativeBackend.Write = BackendEntryPoints.WriteCallback; + nativeBackend.Rename = BackendEntryPoints.RenameCallback; nativeBackend.Delete = BackendEntryPoints.DeleteCallback; - nativeBackend.Free = BackendEntryPoints.FreeCallback; + nativeBackend.Compress = BackendEntryPoints.CompressCallback; + nativeBackend.HasLog = BackendEntryPoints.HasLogCallback; + nativeBackend.EnsureLog = BackendEntryPoints.EnsureLogCallback; + nativeBackend.FreeBackend = BackendEntryPoints.FreeCallback; + nativeBackend.ReflogWrite = BackendEntryPoints.ReflogWriteCallback; + nativeBackend.ReflogRead = BackendEntryPoints.ReflogReadCallback; + nativeBackend.ReflogRename = BackendEntryPoints.ReflogRenameCallback; + nativeBackend.ReflogDelete = BackendEntryPoints.ReflogDeleteCallback; + nativeBackend.RefLock = BackendEntryPoints.RefLockCallback; + nativeBackend.RefUnlock = BackendEntryPoints.RefUnlockCallback; var supportedOperations = this.SupportedOperations; - if ((supportedOperations & RefdbBackendOperations.ForeachGlob) != 0) - { - nativeBackend.ForeachGlob = BackendEntryPoints.ForeachGlobCallback; - } - - if ((supportedOperations & RefdbBackendOperations.Compress) != 0) - { - nativeBackend.Compress = BackendEntryPoints.CompressCallback; - } - nativeBackend.GCHandle = GCHandle.ToIntPtr(GCHandle.Alloc(this)); nativeBackendPointer = Marshal.AllocHGlobal(Marshal.SizeOf(nativeBackend)); Marshal.StructureToPtr(nativeBackend, nativeBackendPointer, false); @@ -148,20 +186,47 @@ private static class BackendEntryPoints // to native memory with StructureToPtr), we need to bind to static delegates. If at construction time // we were to bind to the methods directly, that's the same as newing up a fresh delegate every time. // Those delegates won't be rooted in the object graph and can be collected as soon as StructureToPtr finishes. - public static readonly GitRefdbBackend.exists_callback ExistsCallback = Exists; - public static readonly GitRefdbBackend.lookup_callback LookupCallback = Lookup; - public static readonly GitRefdbBackend.foreach_callback ForeachCallback = Foreach; - public static readonly GitRefdbBackend.foreach_glob_callback ForeachGlobCallback = ForeachGlob; - public static readonly GitRefdbBackend.write_callback WriteCallback = Write; - public static readonly GitRefdbBackend.delete_callback DeleteCallback = Delete; - public static readonly GitRefdbBackend.compress_callback CompressCallback = Compress; - public static readonly GitRefdbBackend.free_callback FreeCallback = Free; + public static readonly GitRefDbBackend.exists_callback ExistsCallback = Exists; + public static readonly GitRefDbBackend.lookup_callback LookupCallback = Lookup; + + public static readonly GitRefDbBackend.iterator_callback IterCallback = GetIterator; + + public static readonly GitRefDbBackend.write_callback WriteCallback = Write; + public static readonly GitRefDbBackend.rename_callback RenameCallback = Rename; + public static readonly GitRefDbBackend.delete_callback DeleteCallback = Delete; + + public static readonly GitRefDbBackend.compress_callback CompressCallback = Compress; + public static readonly GitRefDbBackend.free_callback FreeCallback = Free; + + public static readonly GitRefDbBackend.has_log_callback HasLogCallback = HasLog; + public static readonly GitRefDbBackend.ensure_log_callback EnsureLogCallback = EnsureLog; + + public static readonly GitRefDbBackend.reflog_write_callback ReflogWriteCallback = ReflogWrite; + public static readonly GitRefDbBackend.reflog_read_callback ReflogReadCallback = ReflogRead; + public static readonly GitRefDbBackend.reflog_rename_callback ReflogRenameCallback = ReflogRename; + public static readonly GitRefDbBackend.reflog_delete_callback ReflogDeleteCallback = ReflogDelete; + + public static readonly GitRefDbBackend.ref_lock_callback RefLockCallback = LockRef; + public static readonly GitRefDbBackend.ref_unlock_callback RefUnlockCallback = UnlockRef; + + private static RefdbBackend MarshalRefdbBackend(IntPtr backend) + { + var intPtr = Marshal.ReadIntPtr(backend, GitRefDbBackend.GCHandleOffset); + var handle = GCHandle.FromIntPtr(intPtr).Target as RefdbBackend; + + if (handle == null) + { + throw new Exception("Cannot retrieve the RefdbBackend handle."); + } + + return handle; + } private static bool TryMarshalRefdbBackend(out RefdbBackend refdbBackend, IntPtr backend) { refdbBackend = null; - var intPtr = Marshal.ReadIntPtr(backend, GitRefdbBackend.GCHandleOffset); + var intPtr = Marshal.ReadIntPtr(backend, GitRefDbBackend.GCHandleOffset); var handle = GCHandle.FromIntPtr(intPtr).Target as RefdbBackend; if (handle == null) @@ -174,163 +239,142 @@ private static bool TryMarshalRefdbBackend(out RefdbBackend refdbBackend, IntPtr return true; } - private static int Exists( - out IntPtr exists, + private static int ErrorMarshalingRefDbBacked() + { + Proxy.giterr_set_str(GitErrorCategory.Reference, "Cannot retrieve the RefdbBackend handle."); + return (int)GitErrorCode.Error; + } + + private static GitErrorCode Exists( + out bool exists, IntPtr backend, - IntPtr namePtr) + IntPtr refNamePtr) { - exists = IntPtr.Zero; + GitErrorCode res; - RefdbBackend refdbBackend; - if (!TryMarshalRefdbBackend(out refdbBackend, backend)) + try { - return (int)GitErrorCode.Error; - } + RefdbBackend refdbBackend = MarshalRefdbBackend(backend); + string refName = LaxUtf8Marshaler.FromNative(refNamePtr); - string referenceName = LaxUtf8Marshaler.FromNative(namePtr); + exists = refdbBackend.Exists(refName); - try - { - if (refdbBackend.Exists(referenceName)) - { - exists = (IntPtr)1; - } + res = GitErrorCode.Ok; } catch (Exception ex) { + exists = false; + Proxy.giterr_set_str(GitErrorCategory.Reference, ex); - return (int)GitErrorCode.Error; + res = GitErrorCode.Error; } - return (int)GitErrorCode.Ok; + return res; } - private static int Lookup( + private static GitErrorCode Lookup( out IntPtr referencePtr, IntPtr backend, - IntPtr namePtr) + IntPtr refNamePtr) { referencePtr = IntPtr.Zero; - - RefdbBackend refdbBackend; - if (!TryMarshalRefdbBackend(out refdbBackend, backend)) + GitErrorCode res; + try { - return (int)GitErrorCode.Error; - } + RefdbBackend refdbBackend = MarshalRefdbBackend(backend); - string referenceName = LaxUtf8Marshaler.FromNative(namePtr); + string refName = LaxUtf8Marshaler.FromNative(refNamePtr); - try - { bool isSymbolic; ObjectId oid; string symbolic; - if (!refdbBackend.Lookup(referenceName, out isSymbolic, out oid, out symbolic)) + // REVIEW: should .Lookup method throw or return false on not found... + if (refdbBackend.Lookup(refName, out isSymbolic, out oid, out symbolic)) { - return (int)GitErrorCode.NotFound; + referencePtr = AllocNativeRef(refName, isSymbolic, oid, symbolic); + res = GitErrorCode.Ok; + } + else + { + res = GitErrorCode.NotFound; } - - referencePtr = isSymbolic ? - Proxy.git_reference__alloc_symbolic(referenceName, symbolic) : - Proxy.git_reference__alloc(referenceName, oid); } catch (Exception ex) { Proxy.giterr_set_str(GitErrorCategory.Reference, ex); - return (int)GitErrorCode.Error; + res = GitErrorCode.Error; } - return referencePtr != IntPtr.Zero ? - (int) GitErrorCode.Ok : (int) GitErrorCode.Error; + return res; } - private static int Foreach( + private static GitErrorCode GetIterator( + out IntPtr iterPtr, IntPtr backend, - GitReferenceType list_flags, - GitRefdbBackend.foreach_callback_callback callback, - IntPtr data) + IntPtr globPtr) { - RefdbBackend refdbBackend; - if (!TryMarshalRefdbBackend(out refdbBackend, backend)) - { - return (int)GitErrorCode.Error; - } + iterPtr = IntPtr.Zero; + GitErrorCode res; try { - bool includeSymbolicRefs = list_flags.HasFlag(GitReferenceType.Symbolic); - bool includeDirectRefs = list_flags.HasFlag(GitReferenceType.Oid); + RefdbBackend refdbBackend = MarshalRefdbBackend(backend); + string glob = LaxUtf8Marshaler.FromNative(globPtr); - return refdbBackend.Foreach( - new ForeachState(callback, data).ManagedCallback, - includeSymbolicRefs, - includeDirectRefs); + RefdbIterator refIter = refdbBackend.GenerateRefIterator(glob); + iterPtr = refIter.GitRefdbIteratorPtr; + + res = GitErrorCode.Ok; } catch (Exception ex) { Proxy.giterr_set_str(GitErrorCategory.Reference, ex); - return (int)GitErrorCode.Error; + res = GitErrorCode.Error; } + + return res; } - private static int ForeachGlob( + private static GitErrorCode Write( IntPtr backend, - IntPtr globPtr, - GitReferenceType list_flags, - GitRefdbBackend.foreach_callback_callback callback, - IntPtr data) + IntPtr referencePtr, + bool force, + IntPtr who, + IntPtr messagePtr, + IntPtr oidPtr, + IntPtr oldTargetPtr) { - RefdbBackend refdbBackend; - if (!TryMarshalRefdbBackend(out refdbBackend, backend)) - { - return (int)GitErrorCode.Error; - } - - string glob = LaxUtf8Marshaler.FromNative(globPtr); + GitErrorCode res; try { - bool includeSymbolicRefs = list_flags.HasFlag(GitReferenceType.Symbolic); - bool includeDirectRefs = list_flags.HasFlag(GitReferenceType.Oid); - - return refdbBackend.ForeachGlob( - glob, - new ForeachState(callback, data).ManagedCallback, includeSymbolicRefs, includeDirectRefs); - } - catch (Exception ex) - { - Proxy.giterr_set_str(GitErrorCategory.Reference, ex); - return (int)GitErrorCode.Error; - } - } - - private static int Write( - IntPtr backend, - IntPtr referencePtr) - { - RefdbBackend refdbBackend; - if (!TryMarshalRefdbBackend(out refdbBackend, backend)) - { - return (int)GitErrorCode.Error; - } + RefdbBackend refdbBackend = MarshalRefdbBackend(backend); var referenceHandle = new ReferenceHandle(referencePtr, false); string name = Proxy.git_reference_name(referenceHandle); GitReferenceType type = Proxy.git_reference_type(referenceHandle); - try - { + // TODO: Marshal this correctly + if (oidPtr != IntPtr.Zero) + { + GitOid oid = new GitOid(); + Marshal.Copy(oidPtr, oid.Id, 0, 20); + } + + string message = LaxUtf8Marshaler.FromNative(messagePtr); + string oldTarget = LaxUtf8Marshaler.FromNative(oldTargetPtr); + switch (type) { case GitReferenceType.Oid: ObjectId targetOid = Proxy.git_reference_target(referenceHandle); - refdbBackend.WriteDirectReference(name, targetOid); + refdbBackend.WriteDirectReference(name, targetOid, force); break; case GitReferenceType.Symbolic: string targetIdentifier = Proxy.git_reference_symbolic_target(referenceHandle); - refdbBackend.WriteSymbolicReference(name, targetIdentifier); + refdbBackend.WriteSymbolicReference(name, targetIdentifier, force); break; default: @@ -338,61 +382,112 @@ private static int Write( String.Format(CultureInfo.InvariantCulture, "Unable to build a new reference from a type '{0}'.", type)); } + + res = GitErrorCode.Ok; + } + catch (NameConflictException ex) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, ex); + res = GitErrorCode.Exists; } catch (Exception ex) { Proxy.giterr_set_str(GitErrorCategory.Reference, ex); - return (int)GitErrorCode.Error; + res = GitErrorCode.Error; } - return (int)GitErrorCode.Ok; + return res; } - private static int Delete( + private static GitErrorCode Rename( + out IntPtr reference, IntPtr backend, - IntPtr referencePtr) + IntPtr oldNamePtr, + IntPtr newNamePtr, + bool force, + IntPtr who, + IntPtr messagePtr) { - RefdbBackend refdbBackend; - if (!TryMarshalRefdbBackend(out refdbBackend, backend)) + GitErrorCode res; + + try { - return (int)GitErrorCode.Error; - } + RefdbBackend refdbBackend = MarshalRefdbBackend(backend); - var referenceHandle = new ReferenceHandle(referencePtr, false); - string name = Proxy.git_reference_name(referenceHandle); + string oldName = LaxUtf8Marshaler.FromNative(oldNamePtr); + string newName = LaxUtf8Marshaler.FromNative(newNamePtr); - try + bool isSymbolic; + ObjectId oid; + string symbolic; + + // TODO: verify that old / new name is not null + refdbBackend.RenameReference(oldName, newName, force, + out isSymbolic, out oid, out symbolic); + + reference = AllocNativeRef(newName, isSymbolic, oid, symbolic); + res = GitErrorCode.Ok; + } + catch (NameConflictException ex) { - refdbBackend.Delete(name); + reference = IntPtr.Zero; + Proxy.giterr_set_str(GitErrorCategory.Reference, ex); + res = GitErrorCode.Exists; } catch (Exception ex) { + reference = IntPtr.Zero; Proxy.giterr_set_str(GitErrorCategory.Reference, ex); - return (int)GitErrorCode.Error; + res = GitErrorCode.Error; } - return (int)GitErrorCode.Ok; + return res; } - private static int Compress(IntPtr backend) + private static GitErrorCode Delete( + IntPtr backend, + IntPtr refNamePtr, + IntPtr oldId, + IntPtr oldTargetNamePtr) { - RefdbBackend refdbBackend; - if (!TryMarshalRefdbBackend(out refdbBackend, backend)) + GitErrorCode res; + + try { - return (int)GitErrorCode.Error; + RefdbBackend refdbBackend = MarshalRefdbBackend(backend); + string refName = LaxUtf8Marshaler.FromNative(refNamePtr); + + refdbBackend.Delete(refName); + + res = GitErrorCode.Ok; } + catch (Exception ex) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, ex); + res = GitErrorCode.Error; + } + + return res; + } + + private static GitErrorCode Compress(IntPtr backend) + { + GitErrorCode res; try { + RefdbBackend refdbBackend = MarshalRefdbBackend(backend); refdbBackend.Compress(); + + res = GitErrorCode.Ok; } catch (Exception ex) { Proxy.giterr_set_str(GitErrorCategory.Reference, ex); - return (int)GitErrorCode.Error; + res = GitErrorCode.Error; } - return (int)GitErrorCode.Ok; + return res; } private static void Free(IntPtr backend) @@ -400,33 +495,124 @@ private static void Free(IntPtr backend) RefdbBackend refdbBackend; if (!TryMarshalRefdbBackend(out refdbBackend, backend)) { - // Really? Looks weird. return; } refdbBackend.Free(); } - private class ForeachState + private static GitErrorCode ReflogRead(out IntPtr reflogPtr, IntPtr backendPtr, IntPtr refNamePtr) + { + reflogPtr = IntPtr.Zero; + Proxy.giterr_set_str(GitErrorCategory.Reference, "Not implemented"); + return GitErrorCode.Error; + } + + public static GitErrorCode ReflogWrite( + IntPtr backend, // git_refdb_backend * + IntPtr git_reflog // git_reflog * + ) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, "Not implemented"); + return GitErrorCode.Error; + } + + public static GitErrorCode ReflogRename( + IntPtr backend, // git_refdb_backend + IntPtr oldNamePtr, // const char * + IntPtr newNamePtr // const char * + ) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, "Not implemented"); + return GitErrorCode.Error; + } + + public static GitErrorCode ReflogDelete( + IntPtr backend, // git_refdb_backend + IntPtr namePtr // const char * + ) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, "Not implemented"); + return GitErrorCode.Error; + } + + public static GitErrorCode HasLog( + IntPtr backend, // git_refdb_backend * + IntPtr refNamePtr // const char * + ) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, "Not implemented"); + return GitErrorCode.Error; + } + + public static GitErrorCode EnsureLog( + IntPtr backend, // git_refdb_backend * + IntPtr refNamePtr // const char * + ) { - public ForeachState(GitRefdbBackend.foreach_callback_callback cb, IntPtr data) + Proxy.giterr_set_str(GitErrorCategory.Reference, "Not implemented"); + return GitErrorCode.Error; + } + + public static GitErrorCode LockRef( + IntPtr payload, // void ** + IntPtr backend, // git_refdb_backend + IntPtr namePtr // const char * + ) + { + GitErrorCode res; + + try { - this.cb = cb; - this.data = data; - this.ManagedCallback = CallbackMethod; + RefdbBackend refdbBackend = MarshalRefdbBackend(backend); + string refName = LaxUtf8Marshaler.FromNative(namePtr); + refdbBackend.LockReference(refName); + + res = GitErrorCode.Ok; + } + catch (Exception ex) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, ex); + res = GitErrorCode.Error; } - private int CallbackMethod(string name) + return res; + } + + public static GitErrorCode UnlockRef( + IntPtr backend, // git_refdb_backend + IntPtr payload, + [MarshalAs(UnmanagedType.Bool)] bool force, + [MarshalAs(UnmanagedType.Bool)] bool update_reflog, + IntPtr refNamePtr, // const char * + IntPtr who, // const git_signature * + IntPtr messagePtr // const char * + ) + { + GitErrorCode res; + + try { - IntPtr namePtr = StrictUtf8Marshaler.FromManaged(name); + RefdbBackend refdbBackend = MarshalRefdbBackend(backend); + string refName = LaxUtf8Marshaler.FromNative(refNamePtr); + refdbBackend.UnlockReference(refName); - return cb(namePtr, data); + res = GitErrorCode.Ok; + } + catch (Exception ex) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, ex); + res = GitErrorCode.Error; } - public readonly ForeachCallback ManagedCallback; + return res; + } - private readonly GitRefdbBackend.foreach_callback_callback cb; - private readonly IntPtr data; + private static IntPtr AllocNativeRef(string refName, bool isSymbolic, ObjectId oid, string symbolic) + { + return isSymbolic ? + Proxy.git_reference__alloc_symbolic(refName, symbolic) : + Proxy.git_reference__alloc(refName, oid); } } @@ -434,7 +620,7 @@ private int CallbackMethod(string name) /// Flags used by subclasses of RefdbBackend to indicate which operations they support. /// [Flags] - protected enum RefdbBackendOperations + public enum RefdbBackendOperations { /// /// This RefdbBackend declares that it supports the Compress method. @@ -442,9 +628,9 @@ protected enum RefdbBackendOperations Compress = 1, /// - /// This RefdbBackend declares that it supports the ForeachGlob method. + /// The RefdbBackend declares that it supports Reflog operations /// - ForeachGlob = 2, + Reflog = 2, } } } diff --git a/LibGit2Sharp/RefdbIterator.cs b/LibGit2Sharp/RefdbIterator.cs new file mode 100644 index 000000000..c8d00698b --- /dev/null +++ b/LibGit2Sharp/RefdbIterator.cs @@ -0,0 +1,150 @@ +using System; +using System.Runtime.InteropServices; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// + /// + public abstract class RefdbIterator + { + /// + /// + /// + public abstract bool Next(out string referenceName, out bool isSymbolic, out ObjectId oid, out string symbolic); + + /// + /// + /// + /// + public abstract string NextName(); + + private IntPtr nativeBackendPointer; + + internal IntPtr GitRefdbIteratorPtr + { + get + { + if (IntPtr.Zero == nativeBackendPointer) + { + var nativeBackend = new GitRefdbIterator(); + + // The "free" entry point is always provided. + nativeBackend.next = ReferenceIteratorEntryPoints.NextCallback; + nativeBackend.next_name = ReferenceIteratorEntryPoints.NextNameCallback; + nativeBackend.free = ReferenceIteratorEntryPoints.FreeCallback; + + nativeBackend.GCHandle = GCHandle.ToIntPtr(GCHandle.Alloc(this)); + nativeBackendPointer = Marshal.AllocHGlobal(Marshal.SizeOf(nativeBackend)); + Marshal.StructureToPtr(nativeBackend, nativeBackendPointer, false); + } + + return nativeBackendPointer; + } + } + + private static class ReferenceIteratorEntryPoints + { + public static readonly GitRefdbIterator.ref_db_next NextCallback = Next; + public static readonly GitRefdbIterator.ref_db_next_name NextNameCallback = NextName; + public static readonly GitRefdbIterator.ref_db_free FreeCallback = FreeIter; + + private static bool TryMarshalRefdbIterator(out RefdbIterator refdbiter, IntPtr refDbIterPtr) + { + refdbiter = null; + + var intPtr = Marshal.ReadIntPtr(refDbIterPtr, GitRefdbIterator.GCHandleOffset); + var handle = GCHandle.FromIntPtr(intPtr).Target as RefdbIterator; + + if (handle == null) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, "Cannot retrieve the RefDbIter handle."); + return false; + } + + refdbiter = handle; + return true; + } + + public static int Next(out IntPtr referencePtr, IntPtr refDbIterPtr) + { + referencePtr = IntPtr.Zero; + RefdbIterator refIter; + + if(!TryMarshalRefdbIterator(out refIter, refDbIterPtr)) + { + return (int)GitErrorCode.Error; + } + + string refName; + bool isSymbolic; + ObjectId oid; + string symbolic; + + if (!refIter.Next(out refName, out isSymbolic, out oid, out symbolic)) + { + return (int)GitErrorCode.IterOver; + } + + referencePtr = isSymbolic ? + Proxy.git_reference__alloc_symbolic(refName, symbolic) : + Proxy.git_reference__alloc(refName, oid); + + return (int)GitErrorCode.Ok; + } + + public static int NextName(out IntPtr refNamePtr, IntPtr refDbIterPtr) + { + refNamePtr = IntPtr.Zero; + RefdbIterator refIter; + + if (!TryMarshalRefdbIterator(out refIter, refDbIterPtr)) + { + return (int)GitErrorCode.Error; + } + + string refName; + + if ((refName = refIter.NextName()) == null) + { + return (int)GitErrorCode.IterOver; + } + + // Marshal the string to the global heap + refNamePtr = AllocRefNameOnHeap(refName, refIter); + + return (int)GitErrorCode.Ok; + } + + private static IntPtr AllocRefNameOnHeap(string refName, RefdbIterator iter) + { + IntPtr refNamePtr = StrictUtf8Marshaler.FromManaged(refName); + + IntPtr offset = Marshal.OffsetOf(typeof(GitRefdbIterator), "RefNamePtr"); + Marshal.WriteIntPtr(iter.GitRefdbIteratorPtr, (int) offset, refNamePtr); + return refNamePtr; + } + + public static void FreeIter(IntPtr refDbIterPtr) + { + RefdbIterator refIter; + if (!TryMarshalRefdbIterator(out refIter, refDbIterPtr)) + { + return; + } + + IntPtr offset = Marshal.OffsetOf(typeof(GitRefdbIterator), "RefNamePtr"); + + IntPtr refNamePtr = Marshal.ReadIntPtr(refIter.GitRefdbIteratorPtr, (int)offset); + + if (refNamePtr != IntPtr.Zero) + { + StrictUtf8Marshaler.Cleanup(refNamePtr); + Marshal.WriteIntPtr(refIter.GitRefdbIteratorPtr, (int)offset, IntPtr.Zero); + } + } + } + } +} diff --git a/LibGit2Sharp/ReferenceCollection.cs b/LibGit2Sharp/ReferenceCollection.cs index 5fa7f63ce..b992825ee 100644 --- a/LibGit2Sharp/ReferenceCollection.cs +++ b/LibGit2Sharp/ReferenceCollection.cs @@ -70,6 +70,15 @@ IEnumerator IEnumerable.GetEnumerator() #endregion + /// + /// + /// + /// + public virtual RefTransaction NewRefTransaction() + { + return new RefTransaction(this.repo); + } + /// /// Creates a direct or symbolic reference with the specified name and target /// From 10c10a7c70f3845475e301ba888955bf6da10609 Mon Sep 17 00:00:00 2001 From: Jameson Miller Date: Mon, 2 Nov 2015 15:07:36 -0800 Subject: [PATCH 03/12] (Partially) fix issue with unlock ref callback implementation --- LibGit2Sharp/RefdbBackend.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index 9464def57..f591e7765 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -584,7 +584,7 @@ public static GitErrorCode UnlockRef( IntPtr payload, [MarshalAs(UnmanagedType.Bool)] bool force, [MarshalAs(UnmanagedType.Bool)] bool update_reflog, - IntPtr refNamePtr, // const char * + IntPtr referencePtr, // const git_reference * IntPtr who, // const git_signature * IntPtr messagePtr // const char * ) @@ -594,7 +594,11 @@ IntPtr messagePtr // const char * try { RefdbBackend refdbBackend = MarshalRefdbBackend(backend); - string refName = LaxUtf8Marshaler.FromNative(refNamePtr); + + var referenceHandle = new NotOwnedReferenceSafeHandle(referencePtr); + string refName = Proxy.git_reference_name(referenceHandle); + GitReferenceType type = Proxy.git_reference_type(referenceHandle); + refdbBackend.UnlockReference(refName); res = GitErrorCode.Ok; From 06747c01127a217cc98f3541cd0da1a53109458b Mon Sep 17 00:00:00 2001 From: Kyle Wascher Date: Fri, 21 Apr 2017 17:32:29 -0400 Subject: [PATCH 04/12] Fix tests --- LibGit2Sharp.Tests/RefTransactionFixture.cs | 18 ++---- LibGit2Sharp.Tests/RefdbBackendFixture.cs | 25 +++----- LibGit2Sharp/Core/GitOid.cs | 28 ++++++++- LibGit2Sharp/Core/GitRefdbBackend.cs | 2 +- LibGit2Sharp/Core/Handles/Objects.cs | 2 - LibGit2Sharp/Core/Opaques.cs | 1 - LibGit2Sharp/RefTransaction.cs | 27 ++++---- LibGit2Sharp/RefdbBackend.cs | 68 +++++++++++++++++---- LibGit2Sharp/RefdbIterator.cs | 51 +++++++++++++--- 9 files changed, 158 insertions(+), 64 deletions(-) diff --git a/LibGit2Sharp.Tests/RefTransactionFixture.cs b/LibGit2Sharp.Tests/RefTransactionFixture.cs index 6dd201021..b166ef4b7 100644 --- a/LibGit2Sharp.Tests/RefTransactionFixture.cs +++ b/LibGit2Sharp.Tests/RefTransactionFixture.cs @@ -1,8 +1,4 @@ using LibGit2Sharp.Tests.TestHelpers; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using Xunit; namespace LibGit2Sharp.Tests @@ -24,8 +20,6 @@ public void CanCreateTransaction() } } - #region Shared transaction tests - [Fact] public void ReferenceIsNotRemovedWhenTransactionIsNotCommited() { @@ -44,13 +38,13 @@ public void ReferenceIsNotRemovedWhenTransactionIsNotCommited() } } - [Fact] + [SkippableFact(Skip = "Unsure of intended behavior")] public void ReferenceIsNotModifiedWhenTransactionIsNotCommitted() { } - [Fact] + [SkippableFact(Skip = "Unsure of intended behavior")] public void CanUpdateReferenceAfterTransactionIsAbandonded() { @@ -144,9 +138,7 @@ public void LockingNonExistingReferenceThrows() using (var tx = repo.Refs.NewRefTransaction()) { - // Should this throw (reference no longer exists...) - Assert.Throws( - () => tx.LockReference(myRef)); + Assert.Throws(() => tx.LockReference(myRef)); } } } @@ -165,11 +157,9 @@ public void LockingAlreadyLockedReferenceThrows() using (var tx2 = repo.Refs.NewRefTransaction()) { tx.LockReference(myRef); - Assert.Throws(() => tx2.LockReference(myRef)); + Assert.Throws(() => tx2.LockReference(myRef)); } } } - - #endregion } } diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs index 759213757..2264b25ed 100644 --- a/LibGit2Sharp.Tests/RefdbBackendFixture.cs +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -420,8 +420,11 @@ public void CanUpdateSymbolicReferenceInTransactionRefDb() { MockRefdbBackend backend = SetupBackend(repo); - var refTarget1 = repo.Refs.Add(refTargetName, oid1); - var refTarget2 = repo.Refs.Add(refTarget2Name, oid2); + backend.References[refTargetName] = new MockRefdbReference(oid1); + var refTarget1 = repo.Refs[refTargetName]; + backend.References[refTarget2Name] = new MockRefdbReference(oid2); + var refTarget2 = repo.Refs[refTarget2Name]; + var mySymRef = repo.Refs.Add(mySymRefName, refTargetName, null, true); using (var tx = repo.Refs.NewRefTransaction()) @@ -448,7 +451,9 @@ public void LockingAlreadyLockedReferenceThrowsRefDb() using (var repo = new Repository(path)) { MockRefdbBackend backend = SetupBackend(repo); - var myRef = repo.Refs.Add(myRefName, oid1); + + backend.References[myRefName] = new MockRefdbReference(oid1); + var myRef = repo.Refs[myRefName]; using (var tx = repo.Refs.NewRefTransaction()) using (var tx2 = repo.Refs.NewRefTransaction()) @@ -668,7 +673,7 @@ public override void Free() public override RefdbIterator GenerateRefIterator(string glob) { - return new MockRefDbIterator(References, glob); + return new MockRefDbIterator(this, References, glob); } public override bool HasReflog(string refName) @@ -733,7 +738,7 @@ private class MockRefDbIterator : RefdbIterator IDictionary references; IEnumerator> nextIterator; - public MockRefDbIterator(IDictionary allRefs, string glob) + public MockRefDbIterator(RefdbBackend refdb, IDictionary allRefs, string glob) : base(refdb) { if (!string.IsNullOrEmpty(glob)) { @@ -769,16 +774,6 @@ public override bool Next(out string referenceName, out bool isSymbolic, out Obj symbolic = next.Value.Symbolic; return true; } - - public override string NextName() - { - if (nextIterator.MoveNext()) - { - return nextIterator.Current.Key; - } - - return null; - } } #endregion diff --git a/LibGit2Sharp/Core/GitOid.cs b/LibGit2Sharp/Core/GitOid.cs index f466621b1..04d83d891 100644 --- a/LibGit2Sharp/Core/GitOid.cs +++ b/LibGit2Sharp/Core/GitOid.cs @@ -1,4 +1,5 @@ -using System.Runtime.InteropServices; +using System; +using System.Runtime.InteropServices; namespace LibGit2Sharp.Core { @@ -34,6 +35,31 @@ public static implicit operator ObjectId(GitOid? oid) return oid == null ? null : new ObjectId(oid.Value); } + internal static unsafe GitOid BuildFromPtr(IntPtr ptr) + { + return BuildFromPtr((git_oid*)ptr.ToPointer()); + } + + internal static unsafe GitOid BuildFromPtr(git_oid* id) + { + return id == null? Empty : new GitOid(id->Id); + } + + internal unsafe GitOid(byte* rawId) + { + var id = new byte[Size]; + + fixed(byte* p = id) + { + for (int i = 0; i < Size; i++) + { + p[i] = rawId[i]; + } + } + + Id = id; + } + /// /// Static convenience property to return an id (all zeros). /// diff --git a/LibGit2Sharp/Core/GitRefdbBackend.cs b/LibGit2Sharp/Core/GitRefdbBackend.cs index 0ceed71a1..29eb74d8a 100644 --- a/LibGit2Sharp/Core/GitRefdbBackend.cs +++ b/LibGit2Sharp/Core/GitRefdbBackend.cs @@ -145,7 +145,7 @@ IntPtr namePtr // const char * public delegate GitErrorCode ref_unlock_callback( IntPtr backend, // git_refdb_backend IntPtr payload, - [MarshalAs(UnmanagedType.Bool)] bool force, + IntPtr force, [MarshalAs(UnmanagedType.Bool)] bool update_reflog, IntPtr refNamePtr, // const char * IntPtr who, // const git_signature * diff --git a/LibGit2Sharp/Core/Handles/Objects.cs b/LibGit2Sharp/Core/Handles/Objects.cs index 71b22da51..f44886be8 100644 --- a/LibGit2Sharp/Core/Handles/Objects.cs +++ b/LibGit2Sharp/Core/Handles/Objects.cs @@ -557,7 +557,6 @@ public override void Free() } internal unsafe class RefDatabaseHandle : Libgit2Object - { internal RefDatabaseHandle(git_refdb *ptr, bool owned) : base((void *) ptr, owned) @@ -602,5 +601,4 @@ public override void Free() return (git_transaction*) handle.Handle; } } - } diff --git a/LibGit2Sharp/Core/Opaques.cs b/LibGit2Sharp/Core/Opaques.cs index bde9f9b53..45163ffe6 100644 --- a/LibGit2Sharp/Core/Opaques.cs +++ b/LibGit2Sharp/Core/Opaques.cs @@ -28,7 +28,6 @@ internal struct git_object {} internal struct git_rebase {} internal struct git_odb_stream {} internal struct git_refdb { } - internal struct git_refdb_backend { } internal struct git_transaction { } } diff --git a/LibGit2Sharp/RefTransaction.cs b/LibGit2Sharp/RefTransaction.cs index b4ac981de..3d3a26165 100644 --- a/LibGit2Sharp/RefTransaction.cs +++ b/LibGit2Sharp/RefTransaction.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; @@ -13,11 +10,14 @@ namespace LibGit2Sharp public class RefTransaction : IDisposable { TransactionHandle transactionHandle; - RepositoryHandle repo; + Repository repo; + + protected RefTransaction() + { } internal RefTransaction(Repository repository) { - repo = repository.Handle; + repo = repository; transactionHandle = Proxy.git_transaction_new(repository.Handle); } @@ -25,8 +25,13 @@ internal RefTransaction(Repository repository) /// /// /// - public void LockReference(Reference reference) + public virtual void LockReference(Reference reference) { + if (repo.Refs[reference.CanonicalName] == null) + { + throw new NotFoundException(string.Format("Reference {0} no longer exists.", reference.CanonicalName)); + } + Proxy.git_transaction_lock_ref(this.transactionHandle, reference.CanonicalName); } @@ -34,7 +39,7 @@ public void LockReference(Reference reference) /// /// /// - public void RemoveReference(Reference reference) + public virtual void RemoveReference(Reference reference) { Proxy.git_transaction_remove(this.transactionHandle, reference.CanonicalName); } @@ -51,7 +56,7 @@ public virtual void UpdateTarget(Reference directRef, ObjectId targetId, string Ensure.ArgumentNotNull(directRef, "directRef"); Ensure.ArgumentNotNull(targetId, "targetId"); - Identity ident = Proxy.git_repository_ident(repo); + Identity ident = Proxy.git_repository_ident(repo.Handle); Proxy.git_transaction_set_target(this.transactionHandle, directRef.CanonicalName, targetId.Oid, ident, logMessage); } @@ -62,16 +67,16 @@ public virtual void UpdateTarget(Reference directRef, ObjectId targetId, string /// /// /// - public void UpdateTarget(Reference symbolicRef, Reference targetRef, string logMessage) + public virtual void UpdateTarget(Reference symbolicRef, Reference targetRef, string logMessage) { - Identity ident = Proxy.git_repository_ident(repo); + Identity ident = Proxy.git_repository_ident(repo.Handle); Proxy.git_transaction_set_symbolic_target(this.transactionHandle, symbolicRef.CanonicalName, targetRef.CanonicalName, ident, logMessage); } /// /// /// - public void Commit() + public virtual void Commit() { Proxy.git_transaction_commit(this.transactionHandle); } diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index f591e7765..0f34ee45b 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -6,6 +6,27 @@ namespace LibGit2Sharp { + /// + /// Unlock type + /// + public enum RefdbBackendUnlockType + { + /// + /// Unforced + /// + Unforced = 0, + + /// + /// Forced + /// + Forced = 1, + + /// + /// Reference is to be deleted + /// + UnlockAndDelete = 2 + } + /// /// Base class for all custom managed backends for the libgit2 reference database. /// @@ -139,7 +160,6 @@ public abstract void RenameReference(string referenceName, string newReferenceNa /// /// public abstract void UnlockReference(string refname); - private IntPtr nativeBackendPointer; internal IntPtr GitRefdbBackendPointer @@ -289,7 +309,6 @@ private static GitErrorCode Lookup( ObjectId oid; string symbolic; - // REVIEW: should .Lookup method throw or return false on not found... if (refdbBackend.Lookup(refName, out isSymbolic, out oid, out symbolic)) { referencePtr = AllocNativeRef(refName, isSymbolic, oid, symbolic); @@ -351,15 +370,13 @@ private static GitErrorCode Write( { RefdbBackend refdbBackend = MarshalRefdbBackend(backend); - var referenceHandle = new ReferenceHandle(referencePtr, false); - string name = Proxy.git_reference_name(referenceHandle); - GitReferenceType type = Proxy.git_reference_type(referenceHandle); + var referenceHandle = new ReferenceHandle(referencePtr, false); + string name = Proxy.git_reference_name(referenceHandle); + GitReferenceType type = Proxy.git_reference_type(referenceHandle); - // TODO: Marshal this correctly if (oidPtr != IntPtr.Zero) { - GitOid oid = new GitOid(); - Marshal.Copy(oidPtr, oid.Id, 0, 20); + GitOid oid = GitOid.BuildFromPtr(oidPtr); } string message = LaxUtf8Marshaler.FromNative(messagePtr); @@ -582,7 +599,7 @@ IntPtr namePtr // const char * public static GitErrorCode UnlockRef( IntPtr backend, // git_refdb_backend IntPtr payload, - [MarshalAs(UnmanagedType.Bool)] bool force, + IntPtr force, [MarshalAs(UnmanagedType.Bool)] bool update_reflog, IntPtr referencePtr, // const git_reference * IntPtr who, // const git_signature * @@ -595,11 +612,40 @@ IntPtr messagePtr // const char * { RefdbBackend refdbBackend = MarshalRefdbBackend(backend); - var referenceHandle = new NotOwnedReferenceSafeHandle(referencePtr); + var referenceHandle = new ReferenceHandle(referencePtr, false); string refName = Proxy.git_reference_name(referenceHandle); GitReferenceType type = Proxy.git_reference_type(referenceHandle); + var unlockType = (RefdbBackendUnlockType)force.ToInt32(); - refdbBackend.UnlockReference(refName); + switch (unlockType) + { + case RefdbBackendUnlockType.Unforced: + refdbBackend.UnlockReference(refName); + break; + case RefdbBackendUnlockType.Forced: + + switch (type) + { + case GitReferenceType.Oid: + var target = Proxy.git_reference_target(referenceHandle); + refdbBackend.WriteDirectReference(refName, target, true); + break; + case GitReferenceType.Symbolic: + var targetId = Proxy.git_reference_symbolic_target(referenceHandle); + refdbBackend.WriteSymbolicReference(refName, targetId, true); + break; + default: + throw new LibGit2SharpException(string.Format("Unable to unlock reference from type '{0}'", type)); + } + + refdbBackend.UnlockReference(refName); + break; + case RefdbBackendUnlockType.UnlockAndDelete: + refdbBackend.Delete(refName); + break; + default: + throw new LibGit2SharpException(string.Format("Unknown unlock state '{0}'", unlockType)); + } res = GitErrorCode.Ok; } diff --git a/LibGit2Sharp/RefdbIterator.cs b/LibGit2Sharp/RefdbIterator.cs index c8d00698b..a08d78277 100644 --- a/LibGit2Sharp/RefdbIterator.cs +++ b/LibGit2Sharp/RefdbIterator.cs @@ -1,7 +1,6 @@ using System; using System.Runtime.InteropServices; using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { @@ -10,16 +9,52 @@ namespace LibGit2Sharp /// public abstract class RefdbIterator { + private readonly RefdbBackend refdbBackend; + + protected RefdbIterator(RefdbBackend refdbBackend) + { + this.refdbBackend = refdbBackend; + } + /// /// /// public abstract bool Next(out string referenceName, out bool isSymbolic, out ObjectId oid, out string symbolic); - /// - /// - /// - /// - public abstract string NextName(); + private bool FindNextUnbrokenName(out string referenceName) + { + bool isSymbolic; + ObjectId oid; + string symbolic; + if (Next(out referenceName, out isSymbolic, out oid, out symbolic)) + { + bool lookupIsSymbolic; + ObjectId lookupOid; + string lookupSymbolic; + if (isSymbolic && !refdbBackend.Lookup(symbolic, out lookupIsSymbolic, out lookupOid, out lookupSymbolic)) + { + return FindNextUnbrokenName(out referenceName); + } + return true; + } + return false; + } + + private bool FindNextUnbroken(out string referenceName, out bool isSymbolic, out ObjectId oid, out string symbolic) + { + if (Next(out referenceName, out isSymbolic, out oid, out symbolic)) + { + bool lookupIsSymbolic; + ObjectId lookupOid; + string lookupSymbolic; + if (isSymbolic && !refdbBackend.Lookup(symbolic, out lookupIsSymbolic, out lookupOid, out lookupSymbolic)) + { + return FindNextUnbroken(out referenceName, out isSymbolic, out oid, out symbolic); + } + return true; + } + return false; + } private IntPtr nativeBackendPointer; @@ -83,7 +118,7 @@ public static int Next(out IntPtr referencePtr, IntPtr refDbIterPtr) ObjectId oid; string symbolic; - if (!refIter.Next(out refName, out isSymbolic, out oid, out symbolic)) + if (!refIter.FindNextUnbroken(out refName, out isSymbolic, out oid, out symbolic)) { return (int)GitErrorCode.IterOver; } @@ -107,7 +142,7 @@ public static int NextName(out IntPtr refNamePtr, IntPtr refDbIterPtr) string refName; - if ((refName = refIter.NextName()) == null) + if (!refIter.FindNextUnbrokenName(out refName)) { return (int)GitErrorCode.IterOver; } From 8a7a29dcc4c048f40500720111322049fbbffe22 Mon Sep 17 00:00:00 2001 From: Kyle Wascher Date: Mon, 24 Apr 2017 15:25:50 -0400 Subject: [PATCH 05/12] Add compress method to refs collection --- LibGit2Sharp/Core/NativeMethods.cs | 3 +++ LibGit2Sharp/Core/Proxy.cs | 6 ++++++ LibGit2Sharp/ReferenceCollection.cs | 8 ++++++++ 3 files changed, 17 insertions(+) diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 13d212b75..1b0a03352 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -1038,6 +1038,9 @@ internal static extern unsafe int git_packbuilder_write( [DllImport(libgit2)] internal static extern unsafe int git_refdb_set_backend(git_refdb* refdb, IntPtr backend); + [DllImport(libgit2)] + internal static extern unsafe int git_refdb_compress(git_refdb* refdb); + [DllImport(libgit2)] internal static extern unsafe int git_refdb_open(out git_refdb* refdb, git_repository* repo); diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index b7f32b841..48537ebed 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -1869,6 +1869,12 @@ public static unsafe void git_refdb_set_backend(RefDatabaseHandle refdb, IntPtr Ensure.ZeroResult(NativeMethods.git_refdb_set_backend(refdb, backend)); } + public static unsafe void git_refdb_compress(RefDatabaseHandle refdb) + { + var result = NativeMethods.git_refdb_compress(refdb); + Ensure.ZeroResult(result); + } + #endregion #region git_reference_ diff --git a/LibGit2Sharp/ReferenceCollection.cs b/LibGit2Sharp/ReferenceCollection.cs index b992825ee..43ae38759 100644 --- a/LibGit2Sharp/ReferenceCollection.cs +++ b/LibGit2Sharp/ReferenceCollection.cs @@ -874,5 +874,13 @@ internal void EnsureHasLog(string canonicalName) { Proxy.git_reference_ensure_log(repo.Handle, canonicalName); } + + /// + /// Suggests that the given refdb compress or optimize its references. + /// + public virtual void Compress() + { + Proxy.git_refdb_compress(refDbHandle); + } } } From c1506846a9af1a7713e36977c323233a815edfd7 Mon Sep 17 00:00:00 2001 From: Kyle Wascher Date: Mon, 24 Apr 2017 18:03:46 -0400 Subject: [PATCH 06/12] Implement flags for RefdbBackendOperations --- .gitignore | 1 + LibGit2Sharp.Tests/RefdbBackendFixture.cs | 10 +- LibGit2Sharp/RefdbBackend.cs | 180 ++++++++++++++++------ 3 files changed, 141 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index 9c0b3cdb0..60e263a55 100644 --- a/.gitignore +++ b/.gitignore @@ -36,5 +36,6 @@ _ReSharper*/ *.userprefs *.swp *.DotSettings +packages/ _NCrunch_LibGit2Sharp/ diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs index 2264b25ed..af1745d5a 100644 --- a/LibGit2Sharp.Tests/RefdbBackendFixture.cs +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -582,7 +582,15 @@ protected override RefdbBackendOperations SupportedOperations { get { - return RefdbBackendOperations.Compress; + return RefdbBackendOperations.Exists | + RefdbBackendOperations.Lookup | + RefdbBackendOperations.Iterator | + RefdbBackendOperations.Rename | + RefdbBackendOperations.RefLock | + RefdbBackendOperations.RefUnlock | + RefdbBackendOperations.Write | + RefdbBackendOperations.Delete | + RefdbBackendOperations.Compress; } } diff --git a/LibGit2Sharp/RefdbBackend.cs b/LibGit2Sharp/RefdbBackend.cs index 0f34ee45b..e42e7e5b2 100644 --- a/LibGit2Sharp/RefdbBackend.cs +++ b/LibGit2Sharp/RefdbBackend.cs @@ -6,27 +6,6 @@ namespace LibGit2Sharp { - /// - /// Unlock type - /// - public enum RefdbBackendUnlockType - { - /// - /// Unforced - /// - Unforced = 0, - - /// - /// Forced - /// - Forced = 1, - - /// - /// Reference is to be deleted - /// - UnlockAndDelete = 2 - } - /// /// Base class for all custom managed backends for the libgit2 reference database. /// @@ -35,18 +14,12 @@ public abstract class RefdbBackend /// /// Requests the repository configured for this backend. /// - protected abstract Repository Repository - { - get; - } + protected abstract Repository Repository { get; } /// /// The optional operations this backed supports /// - protected abstract RefdbBackendOperations SupportedOperations - { - get; - } + protected abstract RefdbBackendOperations SupportedOperations { get; } /// /// Queries the backend for whether a reference exists. @@ -172,24 +145,62 @@ internal IntPtr GitRefdbBackendPointer nativeBackend.Version = 1; // The "free" entry point is always provided. - nativeBackend.Exists = BackendEntryPoints.ExistsCallback; - nativeBackend.Lookup = BackendEntryPoints.LookupCallback; - nativeBackend.Iter = BackendEntryPoints.IterCallback; - nativeBackend.Write = BackendEntryPoints.WriteCallback; - nativeBackend.Rename = BackendEntryPoints.RenameCallback; - nativeBackend.Delete = BackendEntryPoints.DeleteCallback; - nativeBackend.Compress = BackendEntryPoints.CompressCallback; - nativeBackend.HasLog = BackendEntryPoints.HasLogCallback; - nativeBackend.EnsureLog = BackendEntryPoints.EnsureLogCallback; nativeBackend.FreeBackend = BackendEntryPoints.FreeCallback; - nativeBackend.ReflogWrite = BackendEntryPoints.ReflogWriteCallback; - nativeBackend.ReflogRead = BackendEntryPoints.ReflogReadCallback; - nativeBackend.ReflogRename = BackendEntryPoints.ReflogRenameCallback; - nativeBackend.ReflogDelete = BackendEntryPoints.ReflogDeleteCallback; - nativeBackend.RefLock = BackendEntryPoints.RefLockCallback; - nativeBackend.RefUnlock = BackendEntryPoints.RefUnlockCallback; - var supportedOperations = this.SupportedOperations; + var supportedOperations = SupportedOperations; + + if (supportedOperations.HasFlag(RefdbBackendOperations.Exists)) + { + nativeBackend.Exists = BackendEntryPoints.ExistsCallback; + } + if (supportedOperations.HasFlag(RefdbBackendOperations.Lookup)) + { + nativeBackend.Lookup = BackendEntryPoints.LookupCallback; + } + if (supportedOperations.HasFlag(RefdbBackendOperations.Iterator)) + { + nativeBackend.Iter = BackendEntryPoints.IterCallback; + } + if (supportedOperations.HasFlag(RefdbBackendOperations.Write)) + { + nativeBackend.Write = BackendEntryPoints.WriteCallback; + } + if (supportedOperations.HasFlag(RefdbBackendOperations.Rename)) + { + nativeBackend.Rename = BackendEntryPoints.RenameCallback; + } + if (supportedOperations.HasFlag(RefdbBackendOperations.Delete)) + { + nativeBackend.Delete = BackendEntryPoints.DeleteCallback; + } + if (supportedOperations.HasFlag(RefdbBackendOperations.Compress)) + { + nativeBackend.Compress = BackendEntryPoints.CompressCallback; + } + if (supportedOperations.HasFlag(RefdbBackendOperations.HasLog)) + { + nativeBackend.HasLog = BackendEntryPoints.HasLogCallback; + } + if (supportedOperations.HasFlag(RefdbBackendOperations.EnsureLog)) + { + nativeBackend.EnsureLog = BackendEntryPoints.EnsureLogCallback; + } + if (supportedOperations.HasFlag(RefdbBackendOperations.RefLock)) + { + nativeBackend.RefLock = BackendEntryPoints.RefLockCallback; + } + if (supportedOperations.HasFlag(RefdbBackendOperations.RefUnlock)) + { + nativeBackend.RefUnlock = BackendEntryPoints.RefUnlockCallback; + } + + if (supportedOperations.HasFlag(RefdbBackendOperations.Reflog)) + { + nativeBackend.ReflogWrite = BackendEntryPoints.ReflogWriteCallback; + nativeBackend.ReflogRead = BackendEntryPoints.ReflogReadCallback; + nativeBackend.ReflogRename = BackendEntryPoints.ReflogRenameCallback; + nativeBackend.ReflogDelete = BackendEntryPoints.ReflogDeleteCallback; + } nativeBackend.GCHandle = GCHandle.ToIntPtr(GCHandle.Alloc(this)); nativeBackendPointer = Marshal.AllocHGlobal(Marshal.SizeOf(nativeBackend)); @@ -666,6 +677,27 @@ private static IntPtr AllocNativeRef(string refName, bool isSymbolic, ObjectId o } } + /// + /// Unlock type + /// + public enum RefdbBackendUnlockType + { + /// + /// Unforced + /// + Unforced = 0, + + /// + /// Forced + /// + Forced = 1, + + /// + /// Reference is to be deleted + /// + UnlockAndDelete = 2 + } + /// /// Flags used by subclasses of RefdbBackend to indicate which operations they support. /// @@ -673,14 +705,64 @@ private static IntPtr AllocNativeRef(string refName, bool isSymbolic, ObjectId o public enum RefdbBackendOperations { /// - /// This RefdbBackend declares that it supports the Compress method. + /// This RefdbBackend declares that it supports the Exists method. + /// + Exists = 1 << 0, + + /// + /// This RefdbBackend declares that it supports the Lookup method. + /// + Lookup = 1 << 1, + + /// + /// This RefdbBackend declares that it supports the Iterator method. + /// + Iterator = 1 << 2, + + /// + /// This RefdbBackend declares that it supports the Write method. + /// + Write = 1 << 3, + + /// + /// This RefdbBackend declares that it supports the Rename method. + /// + Rename = 1 << 4, + + /// + /// This RefdbBackend declares that it supports the Delete method. + /// + Delete = 1 << 5, + + /// + /// This RefdbBackend declares that it supports the Compress method. + /// + Compress = 1 << 6, + + /// + /// This RefdbBackend declares that it supports the HasLog method. + /// + HasLog = 1 << 7, + + /// + /// This RefdbBackend declares that it supports the EnsureLog method. + /// + EnsureLog = 1 << 8, + + /// + /// This RefdbBackend declares that it supports the RefLock method. + /// + RefLock = 1 << 9, + + /// + /// This RefdbBackend declares that it supports the RefUnlock method. /// - Compress = 1, + RefUnlock = 1 << 10, /// - /// The RefdbBackend declares that it supports Reflog operations + /// This RefdbBackend declares that it supports Reflog operations. /// - Reflog = 2, + Reflog = 1 << 11 } } } From bba208b6a91a08a28189a52e582b5c4bbfb79ffe Mon Sep 17 00:00:00 2001 From: Kyle Wascher Date: Thu, 27 Jul 2017 16:38:41 -0400 Subject: [PATCH 07/12] flip all native methods that use custom CustomMarshaler to private --- LibGit2Sharp/Core/NativeMethods.cs | 38 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 1b0a03352..037bfd7c8 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -1051,18 +1051,18 @@ internal static extern unsafe int git_packbuilder_write( internal static extern unsafe void git_refdb_free(IntPtr refDb); [DllImport(libgit2)] - internal static extern unsafe IntPtr git_reference__alloc( - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof (StrictUtf8Marshaler))] string name, + private static extern unsafe IntPtr git_reference__alloc( + [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* name, IntPtr oid, IntPtr peel); [DllImport(libgit2)] - internal static extern unsafe IntPtr git_reference__alloc_symbolic( - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string target); + private static extern unsafe IntPtr git_reference__alloc_symbolic( + [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* name, + [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* target); [DllImport(libgit2)] - internal static extern unsafe int git_reference_create( + private static extern unsafe int git_reference_create( out git_reference* reference, git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, @@ -1830,33 +1830,33 @@ internal static extern unsafe int git_transaction_lock_ref( string refName); [DllImport(libgit2)] - internal static extern unsafe int git_transaction_set_target( + private static extern unsafe int git_transaction_set_target( git_transaction* tx, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName, + [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* refName, ref GitOid target, // const git_oid* git_signature* sig, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string msg); // const char* msg + [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* msg); [DllImport(libgit2)] - internal static extern unsafe int git_transaction_set_symbolic_target( + private static extern unsafe int git_transaction_set_symbolic_target( git_transaction* tx, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName, // const char* - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string target, // const char*, + [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* refName, + [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* target, git_signature* sig, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string msg // const char* msg); + [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* msg ); [DllImport(libgit2)] - internal static extern unsafe int git_transaction_set_reflog( + private static extern unsafe int git_transaction_set_reflog( git_transaction* tx, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName, // const char* refname, + [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* refName, IntPtr reflog // const git_reflog* reflog ); [DllImport(libgit2)] - internal static extern unsafe int git_transaction_remove( + private static extern unsafe int git_transaction_remove( git_transaction* tx, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName // const char* refname, + [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* refName ); [DllImport(libgit2)] @@ -1872,8 +1872,8 @@ internal static extern unsafe int git_transaction_remove( internal static extern int git_transaction_commit(IntPtr txn); [DllImport(libgit2)] - internal static extern int git_transport_register( - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string prefix, + private static extern unsafe int git_transport_register( + [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* prefix, IntPtr transport_cb, IntPtr payload); From e1fb411c5334fe9b2eee4402fc019248c4cafe1f Mon Sep 17 00:00:00 2001 From: Kyle Wascher Date: Fri, 4 Aug 2017 09:16:42 -0400 Subject: [PATCH 08/12] Fix inmemory odb backends. ObjectDatabase now has to create a new odb and set it on the repository when it is InMemory. --- LibGit2Sharp.Tests/OdbBackendFixture.cs | 15 +++++++++++++++ LibGit2Sharp.Tests/RepositoryFixture.cs | 3 ++- LibGit2Sharp/Core/NativeMethods.cs | 6 ++++++ LibGit2Sharp/Core/Proxy.cs | 14 ++++++++++++++ LibGit2Sharp/ObjectDatabase.cs | 14 ++++++++++++-- LibGit2Sharp/Repository.cs | 6 ++++-- 6 files changed, 53 insertions(+), 5 deletions(-) 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/RepositoryFixture.cs b/LibGit2Sharp.Tests/RepositoryFixture.cs index 5c551fabd..b60e403dc 100644 --- a/LibGit2Sharp.Tests/RepositoryFixture.cs +++ b/LibGit2Sharp.Tests/RepositoryFixture.cs @@ -4,7 +4,6 @@ using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; -using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -683,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/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 037bfd7c8..bf5c5dabe 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -1447,6 +1447,12 @@ internal static extern unsafe int git_repository_message( internal static extern unsafe int git_repository_new( out git_repository* repo); + [DllImport(libgit2)] + internal static extern unsafe void git_repository_set_odb(git_repository* repo, IntPtr odb); + + [DllImport(libgit2)] + internal static extern unsafe int git_odb_new(out git_odb* odb); + [DllImport(libgit2)] internal static extern unsafe int git_repository_odb(out git_odb* odb, git_repository* repo); diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 48537ebed..a2b26e709 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -2609,6 +2609,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/ObjectDatabase.cs b/LibGit2Sharp/ObjectDatabase.cs index 3a4ebcdb6..3748ce9b1 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/Repository.cs b/LibGit2Sharp/Repository.cs index 84110f409..bb4b10c4f 100644 --- a/LibGit2Sharp/Repository.cs +++ b/LibGit2Sharp/Repository.cs @@ -114,7 +114,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; } @@ -172,7 +173,8 @@ 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); From b3d131e1b87f8cf9fdaeb455614e0ec55e4d8649 Mon Sep 17 00:00:00 2001 From: Kyle Wascher Date: Fri, 18 May 2018 16:51:00 -0400 Subject: [PATCH 09/12] fixup nativemethods --- LibGit2Sharp/Core/NativeMethods.cs | 39 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index bf5c5dabe..e926e0aea 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -1051,18 +1051,18 @@ internal static extern unsafe int git_packbuilder_write( internal static extern unsafe void git_refdb_free(IntPtr refDb); [DllImport(libgit2)] - private static extern unsafe IntPtr git_reference__alloc( - [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* name, + internal static extern unsafe IntPtr git_reference__alloc( + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, IntPtr oid, IntPtr peel); [DllImport(libgit2)] - private static extern unsafe IntPtr git_reference__alloc_symbolic( - [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* name, - [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* target); + internal static extern unsafe IntPtr 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); [DllImport(libgit2)] - private static extern unsafe int git_reference_create( + internal static extern unsafe int git_reference_create( out git_reference* reference, git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, @@ -1836,33 +1836,32 @@ internal static extern unsafe int git_transaction_lock_ref( string refName); [DllImport(libgit2)] - private static extern unsafe int git_transaction_set_target( + internal static extern unsafe int git_transaction_set_target( git_transaction* tx, - [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* refName, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName, ref GitOid target, // const git_oid* git_signature* sig, - [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* msg); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string msg); [DllImport(libgit2)] - private static extern unsafe int git_transaction_set_symbolic_target( + internal static extern unsafe int git_transaction_set_symbolic_target( git_transaction* tx, - [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* refName, - [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* target, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string target, git_signature* sig, - [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* msg - ); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string msg); [DllImport(libgit2)] - private static extern unsafe int git_transaction_set_reflog( + internal static extern unsafe int git_transaction_set_reflog( git_transaction* tx, - [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* refName, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName, IntPtr reflog // const git_reflog* reflog ); [DllImport(libgit2)] - private static extern unsafe int git_transaction_remove( + internal static extern unsafe int git_transaction_remove( git_transaction* tx, - [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* refName + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refName ); [DllImport(libgit2)] @@ -1878,8 +1877,8 @@ private static extern unsafe int git_transaction_remove( internal static extern int git_transaction_commit(IntPtr txn); [DllImport(libgit2)] - private static extern unsafe int git_transport_register( - [CustomMarshaler(typeof(StrictUtf8Marshaler), typeof(string))] byte* prefix, + internal static extern unsafe int git_transport_register( + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string prefix, IntPtr transport_cb, IntPtr payload); From e223bfe7ad69daf657d6f05174c12d22f53ac090 Mon Sep 17 00:00:00 2001 From: Kyle Wascher Date: Tue, 22 May 2018 08:28:46 -0400 Subject: [PATCH 10/12] update to internal nuget package for native binaries --- LibGit2Sharp/LibGit2Sharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 9b910685e..5c5615e4d 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -32,7 +32,7 @@ - + From d9b3351d88f2ac7c912dbffe45028e60d92127cb Mon Sep 17 00:00:00 2001 From: Kyle Wascher Date: Tue, 22 May 2018 08:34:10 -0400 Subject: [PATCH 11/12] add internal packagesource --- nuget.config | 1 + 1 file changed, 1 insertion(+) diff --git a/nuget.config b/nuget.config index 19d85b78f..b14c41e48 100644 --- a/nuget.config +++ b/nuget.config @@ -2,5 +2,6 @@ + From 23b500d85709fd2714a4d267ba0222075e46f570 Mon Sep 17 00:00:00 2001 From: Kyle Wascher Date: Tue, 22 May 2018 10:47:43 -0400 Subject: [PATCH 12/12] update nativebinaries --- LibGit2Sharp/LibGit2Sharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 5c5615e4d..a7f282ce6 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -32,7 +32,7 @@ - +