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/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/RefTransactionFixture.cs b/LibGit2Sharp.Tests/RefTransactionFixture.cs new file mode 100644 index 000000000..b166ef4b7 --- /dev/null +++ b/LibGit2Sharp.Tests/RefTransactionFixture.cs @@ -0,0 +1,165 @@ +using LibGit2Sharp.Tests.TestHelpers; +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()) + { + } + } + } + + [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]); + } + } + + [SkippableFact(Skip = "Unsure of intended behavior")] + public void ReferenceIsNotModifiedWhenTransactionIsNotCommitted() + { + + } + + [SkippableFact(Skip = "Unsure of intended behavior")] + 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()) + { + 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)); + } + } + } + } +} diff --git a/LibGit2Sharp.Tests/RefdbBackendFixture.cs b/LibGit2Sharp.Tests/RefdbBackendFixture.cs new file mode 100644 index 000000000..af1745d5a --- /dev/null +++ b/LibGit2Sharp.Tests/RefdbBackendFixture.cs @@ -0,0 +1,797 @@ +using System; +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 CannotOverwriteExistingDirectReferenceInRefdbBackend() + { + 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 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() + { + 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 CanIterateBrokenTypesInRefdbBackend() + { + 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")); + + 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" })); + } + } + + [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.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" }); + } + } + + [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); + + 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()) + { + 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); + + backend.References[myRefName] = new MockRefdbReference(oid1); + var myRef = repo.Refs[myRefName]; + + 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 + /// + 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 bool IsLocked { get; 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.Exists | + RefdbBackendOperations.Lookup | + RefdbBackendOperations.Iterator | + RefdbBackendOperations.Rename | + RefdbBackendOperations.RefLock | + RefdbBackendOperations.RefUnlock | + RefdbBackendOperations.Write | + RefdbBackendOperations.Delete | + RefdbBackendOperations.Compress; + } + } + + 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; + + if (!references.TryGetValue(referenceName, out reference)) + { + isSymbolic = false; + oid = null; + symbolic = null; + return false; + } + + isSymbolic = reference.Type == ReferenceType.Symbolic; + oid = reference.Oid; + symbolic = reference.Symbolic; + return true; + } + + public override void WriteDirectReference(string referenceCanonicalName, ObjectId target, bool force) + { + var storage = new MockRefdbReference(target); + if (references.ContainsKey(referenceCanonicalName) && !force) + { + throw new NameConflictException("A reference with this name already exists."); + } + + references[referenceCanonicalName] = storage; + } + + public override void RenameReference(string referenceName, string newReferenceName, bool force, + out bool isSymbolic, out ObjectId oid, out string symbolic) + { + if (references.ContainsKey(referenceName)) + { + + if (references.ContainsKey(newReferenceName) && !force) + { + throw new NameConflictException("error"); + } + + var refToRename = references[referenceName]; + references[newReferenceName] = refToRename; + references.Remove(referenceName); + + isSymbolic = refToRename.Type == ReferenceType.Symbolic; + oid = refToRename.Oid; + symbolic = refToRename.Symbolic; + } + else + { + throw new Exception("error"); + } + } + + public override void WriteSymbolicReference(string referenceCanonicalName, string targetCanonicalName, bool force) + { + var storage = new MockRefdbReference(targetCanonicalName); + if (references.ContainsKey(referenceCanonicalName) && !force) + { + throw new NameConflictException("A reference with this name already exists."); + } + + references[referenceCanonicalName] = storage; + } + + public override void Delete(string referenceCanonicalName) + { + references.Remove(referenceCanonicalName); + } + + public override void Compress() + { + Compressed = true; + } + + public override void Free() + { + references.Clear(); + } + + public override RefdbIterator GenerateRefIterator(string glob) + { + return new MockRefDbIterator(this, 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(RefdbBackend refdb, IDictionary allRefs, string glob) : base(refdb) + { + 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; + } + } + + #endregion + + private static MockRefdbBackend SetupBackend(Repository repository) + { + var backend = new MockRefdbBackend(repository); + repository.Refs.SetBackend(backend); + + return backend; + } + } +} 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/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 new file mode 100644 index 000000000..29eb74d8a --- /dev/null +++ b/LibGit2Sharp/Core/GitRefdbBackend.cs @@ -0,0 +1,155 @@ +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 iterator_callback Iter; + public write_callback Write; + public rename_callback Rename; + public delete_callback Delete; + public compress_callback Compress; + 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; + + public static int GCHandleOffset; + + /// 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 refNamePtr); + + /// 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, + IntPtr refNamePtr); + + /// + /// Allocate an iterator object for the backend. + /// A refdb implementation must provide this function. + /// + [return: MarshalAs(UnmanagedType.I4)] + public delegate GitErrorCode iterator_callback( + out IntPtr iter, + IntPtr backend, + IntPtr globPtr); + + /// 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 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 * + ); + + public delegate void free_callback( + 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, + IntPtr 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/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..f44886be8 100644 --- a/LibGit2Sharp/Core/Handles/Objects.cs +++ b/LibGit2Sharp/Core/Handles/Objects.cs @@ -556,4 +556,49 @@ 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; + } + } + + 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 b5aa8097e..e926e0aea 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -1035,6 +1035,32 @@ 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_compress(git_refdb* refdb); + + [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 void git_refdb_free(IntPtr refDb); + + [DllImport(libgit2)] + 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)] + 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)] internal static extern unsafe int git_reference_create( out git_reference* reference, @@ -1421,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); @@ -1440,6 +1472,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, @@ -1791,7 +1826,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 int git_transport_register( + 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); + + [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, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string target, + git_signature* sig, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string 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, + 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 + ); + + [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 unsafe int git_transport_register( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string prefix, IntPtr transport_cb, IntPtr payload); @@ -1878,12 +1964,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 0d0bb55f0..45163ffe6 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_transaction { } } diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 9c1d1218f..a2b26e709 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -1862,6 +1862,21 @@ 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)); + } + + public static unsafe void git_refdb_compress(RefDatabaseHandle refdb) + { + var result = NativeMethods.git_refdb_compress(refdb); + Ensure.ZeroResult(result); + } + + #endregion + #region git_reference_ public static unsafe ReferenceHandle git_reference_create( @@ -1879,6 +1894,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 +1996,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 +2012,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 +2057,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); @@ -2459,6 +2527,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; @@ -2524,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; @@ -2581,6 +2680,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); @@ -3194,6 +3302,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/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 9b910685e..a7f282ce6 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -32,7 +32,7 @@ - + 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/RefTransaction.cs b/LibGit2Sharp/RefTransaction.cs new file mode 100644 index 000000000..3d3a26165 --- /dev/null +++ b/LibGit2Sharp/RefTransaction.cs @@ -0,0 +1,124 @@ +using System; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// + /// + public class RefTransaction : IDisposable + { + TransactionHandle transactionHandle; + Repository repo; + + protected RefTransaction() + { } + + internal RefTransaction(Repository repository) + { + repo = repository; + transactionHandle = Proxy.git_transaction_new(repository.Handle); + } + + /// + /// + /// + /// + 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); + } + + /// + /// + /// + /// + public virtual 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.Handle); + + Proxy.git_transaction_set_target(this.transactionHandle, directRef.CanonicalName, targetId.Oid, ident, logMessage); + } + + /// + /// + /// + /// + /// + /// + public virtual void UpdateTarget(Reference symbolicRef, Reference targetRef, string logMessage) + { + Identity ident = Proxy.git_repository_ident(repo.Handle); + Proxy.git_transaction_set_symbolic_target(this.transactionHandle, symbolicRef.CanonicalName, targetRef.CanonicalName, ident, logMessage); + } + + /// + /// + /// + public virtual 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 new file mode 100644 index 000000000..e42e7e5b2 --- /dev/null +++ b/LibGit2Sharp/RefdbBackend.cs @@ -0,0 +1,768 @@ +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; } + + /// + /// The optional operations this backed supports + /// + 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 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); + + /// + /// Generate the ref iterator. + /// + /// + /// + public abstract RefdbIterator GenerateRefIterator(string glob); + + /// + /// Write the given direct reference to the backend. + /// + /// The reference to write + /// The of the 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, 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. + /// + /// 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(); + + /// + /// + /// + /// + /// + 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 + { + get + { + if (IntPtr.Zero == nativeBackendPointer) + { + var nativeBackend = new GitRefDbBackend(); + nativeBackend.Version = 1; + + // The "free" entry point is always provided. + nativeBackend.FreeBackend = BackendEntryPoints.FreeCallback; + + 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)); + 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.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 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 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 refNamePtr) + { + GitErrorCode res; + + try + { + RefdbBackend refdbBackend = MarshalRefdbBackend(backend); + string refName = LaxUtf8Marshaler.FromNative(refNamePtr); + + exists = refdbBackend.Exists(refName); + + res = GitErrorCode.Ok; + } + catch (Exception ex) + { + exists = false; + + Proxy.giterr_set_str(GitErrorCategory.Reference, ex); + res = GitErrorCode.Error; + } + + return res; + } + + private static GitErrorCode Lookup( + out IntPtr referencePtr, + IntPtr backend, + IntPtr refNamePtr) + { + referencePtr = IntPtr.Zero; + GitErrorCode res; + try + { + RefdbBackend refdbBackend = MarshalRefdbBackend(backend); + + string refName = LaxUtf8Marshaler.FromNative(refNamePtr); + + bool isSymbolic; + ObjectId oid; + string symbolic; + + if (refdbBackend.Lookup(refName, out isSymbolic, out oid, out symbolic)) + { + referencePtr = AllocNativeRef(refName, isSymbolic, oid, symbolic); + res = GitErrorCode.Ok; + } + else + { + res = GitErrorCode.NotFound; + } + } + catch (Exception ex) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, ex); + res = GitErrorCode.Error; + } + + return res; + } + + private static GitErrorCode GetIterator( + out IntPtr iterPtr, + IntPtr backend, + IntPtr globPtr) + { + iterPtr = IntPtr.Zero; + GitErrorCode res; + + try + { + RefdbBackend refdbBackend = MarshalRefdbBackend(backend); + string glob = LaxUtf8Marshaler.FromNative(globPtr); + + RefdbIterator refIter = refdbBackend.GenerateRefIterator(glob); + iterPtr = refIter.GitRefdbIteratorPtr; + + res = GitErrorCode.Ok; + } + catch (Exception ex) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, ex); + res = GitErrorCode.Error; + } + + return res; + } + + private static GitErrorCode Write( + IntPtr backend, + IntPtr referencePtr, + bool force, + IntPtr who, + IntPtr messagePtr, + IntPtr oidPtr, + IntPtr oldTargetPtr) + { + GitErrorCode res; + + try + { + RefdbBackend refdbBackend = MarshalRefdbBackend(backend); + + var referenceHandle = new ReferenceHandle(referencePtr, false); + string name = Proxy.git_reference_name(referenceHandle); + GitReferenceType type = Proxy.git_reference_type(referenceHandle); + + if (oidPtr != IntPtr.Zero) + { + GitOid oid = GitOid.BuildFromPtr(oidPtr); + } + + 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, force); + break; + + case GitReferenceType.Symbolic: + string targetIdentifier = Proxy.git_reference_symbolic_target(referenceHandle); + refdbBackend.WriteSymbolicReference(name, targetIdentifier, force); + break; + + default: + throw new LibGit2SharpException( + 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); + res = GitErrorCode.Error; + } + + return res; + } + + private static GitErrorCode Rename( + out IntPtr reference, + IntPtr backend, + IntPtr oldNamePtr, + IntPtr newNamePtr, + bool force, + IntPtr who, + IntPtr messagePtr) + { + GitErrorCode res; + + try + { + RefdbBackend refdbBackend = MarshalRefdbBackend(backend); + + string oldName = LaxUtf8Marshaler.FromNative(oldNamePtr); + string newName = LaxUtf8Marshaler.FromNative(newNamePtr); + + 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) + { + 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); + res = GitErrorCode.Error; + } + + return res; + } + + private static GitErrorCode Delete( + IntPtr backend, + IntPtr refNamePtr, + IntPtr oldId, + IntPtr oldTargetNamePtr) + { + GitErrorCode res; + + try + { + 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); + res = GitErrorCode.Error; + } + + return res; + } + + private static void Free(IntPtr backend) + { + RefdbBackend refdbBackend; + if (!TryMarshalRefdbBackend(out refdbBackend, backend)) + { + return; + } + + refdbBackend.Free(); + } + + 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 * + ) + { + 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 + { + 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; + } + + return res; + } + + public static GitErrorCode UnlockRef( + IntPtr backend, // git_refdb_backend + IntPtr payload, + IntPtr force, + [MarshalAs(UnmanagedType.Bool)] bool update_reflog, + IntPtr referencePtr, // const git_reference * + IntPtr who, // const git_signature * + IntPtr messagePtr // const char * + ) + { + GitErrorCode res; + + try + { + RefdbBackend refdbBackend = MarshalRefdbBackend(backend); + + 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(); + + 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; + } + catch (Exception ex) + { + Proxy.giterr_set_str(GitErrorCategory.Reference, ex); + res = GitErrorCode.Error; + } + + return res; + } + + 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); + } + } + + /// + /// 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. + /// + [Flags] + public enum RefdbBackendOperations + { + /// + /// 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. + /// + RefUnlock = 1 << 10, + + /// + /// This RefdbBackend declares that it supports Reflog operations. + /// + Reflog = 1 << 11 + } + } +} diff --git a/LibGit2Sharp/RefdbIterator.cs b/LibGit2Sharp/RefdbIterator.cs new file mode 100644 index 000000000..a08d78277 --- /dev/null +++ b/LibGit2Sharp/RefdbIterator.cs @@ -0,0 +1,185 @@ +using System; +using System.Runtime.InteropServices; +using LibGit2Sharp.Core; + +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); + + 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; + + 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.FindNextUnbroken(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 (!refIter.FindNextUnbrokenName(out refName)) + { + 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 602a20f17..43ae38759 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); } /// @@ -66,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 /// @@ -807,6 +820,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. /// @@ -850,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); + } } } 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); diff --git a/nuget.config b/nuget.config index 19d85b78f..b14c41e48 100644 --- a/nuget.config +++ b/nuget.config @@ -2,5 +2,6 @@ +