diff --git a/.gitignore b/.gitignore index 164c6cf73..ab4b90674 100644 --- a/.gitignore +++ b/.gitignore @@ -26,9 +26,10 @@ Thumbs.db obj/ [Rr]elease*/ _ReSharper*/ +*.ReSharper +*.ReSharper.user [Tt]est[Rr]esult* build/ *.pidb *.userprefs -*.swp -%temp% \ No newline at end of file +*.swp \ No newline at end of file diff --git a/LibGit2Sharp.Tests/BranchFixture.cs b/LibGit2Sharp.Tests/BranchFixture.cs index 9f35369b2..a65f6f5df 100644 --- a/LibGit2Sharp.Tests/BranchFixture.cs +++ b/LibGit2Sharp.Tests/BranchFixture.cs @@ -41,6 +41,7 @@ public void CanCreateBranchFromAnotherBranch() newBranch.ShouldNotBeNull(); newBranch.Name.ShouldEqual(name); newBranch.CanonicalName.ShouldEqual("refs/heads/" + name); + newBranch.IsCurrentRepositoryHead.ShouldBeFalse(); newBranch.Tip.ShouldNotBeNull(); newBranch.Tip.Sha.ShouldEqual("4c062a6361ae6959e06292c1fa5e2822d9c96345"); repo.Branches.SingleOrDefault(p => p.Name == name).ShouldNotBeNull(); @@ -49,6 +50,48 @@ public void CanCreateBranchFromAnotherBranch() } } + [Test] + public void TwoBranchesPointingAtTheSameCommitAreNotBothCurrent() + { + using (var path = new TemporaryCloneOfTestRepo()) + using (var repo = new Repository(path.RepositoryPath)) + { + var master = repo.Branches["refs/heads/master"]; + + var newBranch = repo.Branches.Create("clone-of-master", master.Tip.Sha); + newBranch.IsCurrentRepositoryHead.ShouldBeFalse(); + } + } + + [Test] + public void OnlyOneBranchIsTheHead() + { + using (var repo = new Repository(Constants.TestRepoPath)) + { + Branch head = null; + + foreach (var branch in repo.Branches) + { + bool isHead = branch.IsCurrentRepositoryHead; + + if (!isHead) + { + continue; + } + + if (head == null) + { + head = branch; + continue; + } + + Assert.Fail("Both '{0}' and '{1}' appear to be Head.", head.CanonicalName, branch.CanonicalName); + } + + head.ShouldNotBeNull(); + } + } + [Test] public void CanListAllBranches() { @@ -73,6 +116,7 @@ public void CanLookupLocalBranch() master.IsRemote.ShouldBeFalse(); master.Name.ShouldEqual("master"); master.CanonicalName.ShouldEqual("refs/heads/master"); + master.IsCurrentRepositoryHead.ShouldBeTrue(); master.Tip.Sha.ShouldEqual("4c062a6361ae6959e06292c1fa5e2822d9c96345"); } } diff --git a/LibGit2Sharp.Tests/ReferenceFixture.cs b/LibGit2Sharp.Tests/ReferenceFixture.cs index 2486df74a..82889f484 100644 --- a/LibGit2Sharp.Tests/ReferenceFixture.cs +++ b/LibGit2Sharp.Tests/ReferenceFixture.cs @@ -9,7 +9,7 @@ namespace LibGit2Sharp.Tests [TestFixture] public class ReferenceFixture { - private readonly List expectedRefs = new List { "refs/heads/packed-test", "refs/heads/packed", "refs/heads/br2", "refs/heads/master", "refs/heads/test", "refs/tags/test", "refs/tags/e90810b", "refs/tags/lw" }; + private readonly List expectedRefs = new List {"refs/heads/packed-test", "refs/heads/packed", "refs/heads/br2", "refs/heads/master", "refs/heads/test", "refs/tags/test", "refs/tags/e90810b", "refs/tags/lw"}; [Test] public void CanCreateReferenceFromSha() @@ -40,32 +40,13 @@ public void CanCreateReferenceFromSymbol() newRef.ShouldNotBeNull(); newRef.CanonicalName.ShouldEqual(name); newRef.Target.ShouldNotBeNull(); - ((DirectReference)newRef.Target).Target.Sha.ShouldEqual("4c062a6361ae6959e06292c1fa5e2822d9c96345"); + ((DirectReference) newRef.Target).Target.Sha.ShouldEqual("4c062a6361ae6959e06292c1fa5e2822d9c96345"); repo.Refs.SingleOrDefault(p => p.CanonicalName == name).ShouldNotBeNull(); repo.Refs.Delete(newRef.CanonicalName); } } - [Test] - public void DeleteWithNullNameThrows() - { - using (var repo = new Repository(Constants.TestRepoPath)) - { - Assert.Throws(() => repo.Refs.Delete(null)); - } - } - - [Test] - public void DeleteWithEmptyNameThrows() - { - using (var repo = new Repository(Constants.TestRepoPath)) - { - Assert.Throws(() => repo.Refs.Delete(string.Empty)); - } - } - - [Test] public void CanListAllReferences() { @@ -90,7 +71,7 @@ public void CanResolveHeadByName() head.CanonicalName.ShouldEqual("HEAD"); head.Target.ShouldNotBeNull(); head.Target.CanonicalName.ShouldEqual("refs/heads/master"); - ((DirectReference)head.Target).Target.Sha.ShouldEqual("4c062a6361ae6959e06292c1fa5e2822d9c96345"); + ((DirectReference) head.Target).Target.Sha.ShouldEqual("4c062a6361ae6959e06292c1fa5e2822d9c96345"); Assert.IsInstanceOf(((DirectReference) head.Target).Target); var head2 = (SymbolicReference) repo.Refs.Head; @@ -103,30 +84,30 @@ public void CanResolveHeadByName() } [Test] - public void CanResolveReferenceToAnAnnotatedTag() + public void CanResolveReferenceToALightweightTag() { using (var repo = new Repository(Constants.TestRepoPath)) { - var annTag = (DirectReference)repo.Refs["refs/tags/test"]; - annTag.ShouldNotBeNull(); - annTag.CanonicalName.ShouldEqual("refs/tags/test"); - annTag.Target.ShouldNotBeNull(); - annTag.Target.Sha.ShouldEqual("b25fa35b38051e4ae45d4222e795f9df2e43f1d1"); - Assert.IsInstanceOf(annTag.Target); + var lwTag = (DirectReference) repo.Refs["refs/tags/lw"]; + lwTag.ShouldNotBeNull(); + lwTag.CanonicalName.ShouldEqual("refs/tags/lw"); + lwTag.Target.ShouldNotBeNull(); + lwTag.Target.Sha.ShouldEqual("e90810b8df3e80c413d903f631643c716887138d"); + Assert.IsInstanceOf(lwTag.Target); } } [Test] - public void CanResolveReferenceToALightweightTag() + public void CanResolveReferenceToAnAnnotatedTag() { using (var repo = new Repository(Constants.TestRepoPath)) { - var lwTag = (DirectReference)repo.Refs["refs/tags/lw"]; - lwTag.ShouldNotBeNull(); - lwTag.CanonicalName.ShouldEqual("refs/tags/lw"); - lwTag.Target.ShouldNotBeNull(); - lwTag.Target.Sha.ShouldEqual("e90810b8df3e80c413d903f631643c716887138d"); - Assert.IsInstanceOf(lwTag.Target); + var annTag = (DirectReference) repo.Refs["refs/tags/test"]; + annTag.ShouldNotBeNull(); + annTag.CanonicalName.ShouldEqual("refs/tags/test"); + annTag.Target.ShouldNotBeNull(); + annTag.Target.Sha.ShouldEqual("b25fa35b38051e4ae45d4222e795f9df2e43f1d1"); + Assert.IsInstanceOf(annTag.Target); } } @@ -144,6 +125,43 @@ public void CanResolveRefsByName() } } + [Test] + public void CanUpdateTargetOnReference() + { + const string masterRef = "refs/heads/master"; + using (var path = new TemporaryCloneOfTestRepo()) + using (var repo = new Repository(path.RepositoryPath)) + { + var sha = repo.Refs["refs/heads/test"].ResolveToDirectReference().Target.Sha; + var master = repo.Refs[masterRef]; + master.ResolveToDirectReference().Target.Sha.ShouldNotEqual(sha); + + repo.Refs.UpdateTarget(masterRef, sha); + + master = repo.Refs[masterRef]; + master.ResolveToDirectReference().Target.Sha.ShouldEqual(sha); + } + } + + [Test] + public void CanUpdateTargetOnSymolicReference() + { + const string name = "refs/heads/unit_test"; + using (var path = new TemporaryCloneOfTestRepo()) + using (var repo = new Repository(path.RepositoryPath)) + { + var newRef = (SymbolicReference) repo.Refs.Create(name, "refs/heads/master"); + newRef.ShouldNotBeNull(); + + repo.Refs.UpdateTarget(newRef.CanonicalName, "refs/heads/test"); + + newRef = (SymbolicReference) repo.Refs[newRef.CanonicalName]; + newRef.ResolveToDirectReference().Target.ShouldEqual(repo.Refs["refs/heads/test"].ResolveToDirectReference().Target); + + repo.Refs.Delete(newRef.CanonicalName); + } + } + [Test] public void CreateWithEmptyStringForTargetThrows() { @@ -170,7 +188,7 @@ public void CreateWithNullForTargetThrows() using (var path = new TemporaryCloneOfTestRepo()) using (var repo = new Repository(path.RepositoryPath)) { - Assert.Throws(() => repo.Refs.Create("refs/heads/newref", (string)null)); + Assert.Throws(() => repo.Refs.Create("refs/heads/newref", (string) null)); } } @@ -184,6 +202,24 @@ public void CreateWithNullStringThrows() } } + [Test] + public void DeleteWithEmptyNameThrows() + { + using (var repo = new Repository(Constants.TestRepoPath)) + { + Assert.Throws(() => repo.Refs.Delete(string.Empty)); + } + } + + [Test] + public void DeleteWithNullNameThrows() + { + using (var repo = new Repository(Constants.TestRepoPath)) + { + Assert.Throws(() => repo.Refs.Delete(null)); + } + } + [Test] public void ResolvingWithEmptyStringThrows() { @@ -201,5 +237,46 @@ public void ResolvingWithNullThrows() Assert.Throws(() => { var head = repo.Refs[null]; }); } } + + [Test] + public void TryingToUpdateADirectRefWithSymbolFails() + { + const string name = "refs/heads/unit_test"; + using (var path = new TemporaryCloneOfTestRepo()) + using (var repo = new Repository(path.RepositoryPath)) + { + var newRef = (SymbolicReference) repo.Refs.Create(name, "refs/heads/master"); + newRef.ShouldNotBeNull(); + + Assert.Throws( + () => repo.Refs.UpdateTarget(newRef.CanonicalName, repo.Refs["refs/heads/test"].ResolveToDirectReference().Target.Sha)); + + repo.Refs.Delete(newRef.CanonicalName); + } + } + + [Test] + public void TryingToUpdateASymbolicRefWithOidFails() + { + const string masterRef = "refs/heads/master"; + using (var path = new TemporaryCloneOfTestRepo()) + using (var repo = new Repository(path.RepositoryPath)) + { + Assert.Throws(() => repo.Refs.UpdateTarget(masterRef, "refs/heads/test")); + } + } + + [Test] + public void UpdateReferenceTargetWithBadParametersFails() + { + using (var path = new TemporaryCloneOfTestRepo()) + using (var repo = new Repository(path.RepositoryPath)) + { + Assert.Throws(() => repo.Refs.UpdateTarget(string.Empty, "refs/heads/packed")); + Assert.Throws(() => repo.Refs.UpdateTarget("master", string.Empty)); + Assert.Throws(() => repo.Refs.UpdateTarget(null, "refs/heads/packed")); + Assert.Throws(() => repo.Refs.UpdateTarget("master", null)); + } + } } } \ No newline at end of file diff --git a/LibGit2Sharp/Branch.cs b/LibGit2Sharp/Branch.cs index d1c477652..1621ef224 100644 --- a/LibGit2Sharp/Branch.cs +++ b/LibGit2Sharp/Branch.cs @@ -50,9 +50,9 @@ internal Branch(string canonicalName, Commit tip, Repository repo) /// /// true if this instance is current branch; otherwise, false. /// - public bool IsCurrentBranch + public bool IsCurrentRepositoryHead { - get { return CanonicalName == repo.Refs.Head.ResolveToDirectReference().CanonicalName; } + get { return repo.Refs[CanonicalName] == repo.Refs.Head.ResolveToDirectReference(); } } /// diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index e7756819b..3b0c60682 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -7,6 +7,12 @@ internal class NativeMethods { private const string libgit2 = "git2.dll"; + [DllImport(libgit2, SetLastError = true)] + public static extern IntPtr git_blob_rawcontent(IntPtr blob); + + [DllImport(libgit2, SetLastError = true)] + public static extern int git_blob_rawsize(IntPtr blob); + [DllImport(libgit2, SetLastError = true)] public static extern IntPtr git_commit_author(IntPtr commit); @@ -85,6 +91,12 @@ internal class NativeMethods [DllImport(libgit2)] public static extern int git_reference_resolve(out IntPtr resolvedReference, IntPtr reference); + [DllImport(libgit2, SetLastError = true)] + public static extern int git_reference_set_oid(IntPtr reference, ref GitOid id); + + [DllImport(libgit2, SetLastError = true)] + public static extern int git_reference_set_target(IntPtr reference, string target); + [DllImport(libgit2)] [return: MarshalAs(UnmanagedType.AnsiBStr)] public static extern string git_reference_target(IntPtr reference); @@ -107,7 +119,7 @@ internal class NativeMethods [DllImport(libgit2)] [return: MarshalAs(UnmanagedType.AnsiBStr)] public static extern string git_repository_path(RepositorySafeHandle repository); - + [DllImport(libgit2)] [return: MarshalAs(UnmanagedType.AnsiBStr)] public static extern string git_repository_workdir(RepositorySafeHandle repository); @@ -152,36 +164,27 @@ internal class NativeMethods [DllImport(libgit2, SetLastError = true)] public static extern IntPtr git_tag_target_oid(IntPtr tag); - - /* Blob */ - [DllImport(libgit2, SetLastError = true)] - public static extern int git_blob_rawsize(IntPtr blob); [DllImport(libgit2, SetLastError = true)] - public static extern IntPtr git_blob_rawcontent(IntPtr blob); - - /* Tree */ + public static extern int git_tree_entry_2object(out IntPtr obj, RepositorySafeHandle repo, IntPtr entry); [DllImport(libgit2, SetLastError = true)] - [return: MarshalAs(UnmanagedType.AnsiBStr)] - public static extern string git_tree_entry_name(IntPtr entry); - + public static extern int git_tree_entry_attributes(IntPtr entry); + [DllImport(libgit2, SetLastError = true)] public static extern IntPtr git_tree_entry_byindex(IntPtr tree, int idx); [DllImport(libgit2, SetLastError = true)] public static extern IntPtr git_tree_entry_byname(IntPtr tree, string filename); - - [DllImport(libgit2, SetLastError = true)] - public static extern int git_tree_entrycount(IntPtr tree); [DllImport(libgit2, SetLastError = true)] public static extern IntPtr git_tree_entry_id(IntPtr tree); [DllImport(libgit2, SetLastError = true)] - public static extern int git_tree_entry_2object(out IntPtr obj, RepositorySafeHandle repo, IntPtr entry); + [return: MarshalAs(UnmanagedType.AnsiBStr)] + public static extern string git_tree_entry_name(IntPtr entry); [DllImport(libgit2, SetLastError = true)] - public static extern int git_tree_entry_attributes(IntPtr entry); + public static extern int git_tree_entrycount(IntPtr tree); } } \ No newline at end of file diff --git a/LibGit2Sharp/ReferenceCollection.cs b/LibGit2Sharp/ReferenceCollection.cs index 6dc789c22..05b50d891 100644 --- a/LibGit2Sharp/ReferenceCollection.cs +++ b/LibGit2Sharp/ReferenceCollection.cs @@ -35,6 +35,15 @@ public Reference this[string name] get { return Resolve(name); } } + /// + /// Shortcut to return the reference to HEAD + /// + /// + public Reference Head + { + get { return this[headReferenceName]; } + } + #region IEnumerable Members public IEnumerator GetEnumerator() @@ -94,7 +103,7 @@ public Reference Create(string name, ObjectId target) } /// - /// Delete a reference with the specified name + /// Delete a reference with the specified name /// public void Delete(string name) { @@ -108,28 +117,51 @@ public void Delete(string name) } /// - /// Shortcut to return the reference to HEAD + /// Gets the with the specified name. /// + /// The name. /// - public Reference Head + internal T Resolve(string name) where T : class { - get { return this[headReferenceName]; } + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + IntPtr reference; + var res = NativeMethods.git_reference_lookup(out reference, repo.Handle, name); + Ensure.Success(res); + + return Reference.BuildFromPtr(reference, repo); } /// - /// Gets the with the specified name. + /// Updates the target on a reference. /// - /// The name. - /// - internal T Resolve(string name) where T : class + /// The name of the reference. + /// The target which can be either a sha or the name of another reference. + public void UpdateTarget(string name, string target) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(target, "target"); IntPtr reference; var res = NativeMethods.git_reference_lookup(out reference, repo.Handle, name); Ensure.Success(res); - return Reference.BuildFromPtr(reference, repo); + var id = ObjectId.CreateFromMaybeSha(target); + var type = NativeMethods.git_reference_type(reference); + switch (type) + { + case GitReferenceType.Oid: + if (id == null) throw new ArgumentException(String.Format("The reference specified by {0} is an Oid reference, you must provide a sha as the target.", name), "target"); + var oid = id.Oid; + res = NativeMethods.git_reference_set_oid(reference, ref oid); + break; + case GitReferenceType.Symbolic: + if (id != null) throw new ArgumentException(String.Format("The reference specified by {0} is an Symbolic reference, you must provide a symbol as the target.", name), "target"); + res = NativeMethods.git_reference_set_target(reference, target); + break; + } + + Ensure.Success(res); } } } \ No newline at end of file