diff --git a/LibGit2Sharp.Tests/BranchFixture.cs b/LibGit2Sharp.Tests/BranchFixture.cs index d954e6a57..0aef1a27f 100644 --- a/LibGit2Sharp.Tests/BranchFixture.cs +++ b/LibGit2Sharp.Tests/BranchFixture.cs @@ -75,6 +75,7 @@ public void CanCreateAnUnbornBranch() // The branch now exists... Branch orphan = repo.Branches["orphan"]; Assert.NotNull(orphan); + AssertBelongsToARepository(repo, orphan); // ...and points to that newly created commit Assert.Equal(c, orphan.Tip); @@ -489,6 +490,7 @@ public void CanGetTrackingInformationFromBranchSharingNoHistoryWithItsTrackedBra Assert.True(master.IsTracking); Assert.NotNull(master.TrackedBranch); + AssertBelongsToARepository(repo, master.TrackedBranch); Assert.NotNull(master.TrackingDetails); Assert.Equal(9, master.TrackingDetails.AheadBy); diff --git a/LibGit2Sharp.Tests/CheckoutFixture.cs b/LibGit2Sharp.Tests/CheckoutFixture.cs index abe4ad9aa..b3a095644 100644 --- a/LibGit2Sharp.Tests/CheckoutFixture.cs +++ b/LibGit2Sharp.Tests/CheckoutFixture.cs @@ -32,9 +32,11 @@ public void CanCheckoutAnExistingBranch(string branchName) Branch branch = repo.Branches[branchName]; Assert.NotNull(branch); + AssertBelongsToARepository(repo, branch); Branch test = repo.Checkout(branch); Assert.False(repo.Info.IsHeadDetached); + AssertBelongsToARepository(repo, test); Assert.False(test.IsRemote); Assert.True(test.IsCurrentRepositoryHead); @@ -114,6 +116,7 @@ public void CanCheckoutAnArbitraryCommit(string commitPointer, bool checkoutByCo Assert.False(repo.Index.RetrieveStatus().IsDirty); var commit = repo.Lookup(commitPointer); + AssertBelongsToARepository(repo, commit); Branch detachedHead = checkoutByCommitOrBranchSpec ? repo.Checkout(commitPointer) : repo.Checkout(commit); diff --git a/LibGit2Sharp.Tests/MetaFixture.cs b/LibGit2Sharp.Tests/MetaFixture.cs index 3cff83cc1..fb1b567cb 100644 --- a/LibGit2Sharp.Tests/MetaFixture.cs +++ b/LibGit2Sharp.Tests/MetaFixture.cs @@ -12,6 +12,11 @@ namespace LibGit2Sharp.Tests { public class MetaFixture { + private static readonly HashSet explicitOnlyInterfaces = new HashSet + { + typeof(IBelongToARepository), + }; + [Fact] public void PublicTestMethodsAreFactsOrTheories() { @@ -114,7 +119,7 @@ public void LibGit2SharpPublicInterfacesCoverAllPublicMembers() var methodsMissingFromInterfaces = from t in Assembly.GetAssembly(typeof(IRepository)).GetExportedTypes() where !t.IsInterface - where t.GetInterfaces().Any(i => i.IsPublic && i.Namespace == typeof(IRepository).Namespace) + where t.GetInterfaces().Any(i => i.IsPublic && i.Namespace == typeof(IRepository).Namespace && !explicitOnlyInterfaces.Contains(i)) let interfaceTargetMethods = from i in t.GetInterfaces() from im in t.GetInterfaceMap(i).TargetMethods select im @@ -126,6 +131,24 @@ from tm in t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | Bindin methodsMissingFromInterfaces.ToArray())); } + [Fact] + public void LibGit2SharpExplicitOnlyInterfacesAreIndeedExplicitOnly() + { + var methodsMissingFromInterfaces = + from t in Assembly.GetAssembly(typeof(IRepository)).GetExportedTypes() + where t.GetInterfaces().Any(explicitOnlyInterfaces.Contains) + let interfaceTargetMethods = from i in t.GetInterfaces() + where explicitOnlyInterfaces.Contains(i) + from im in t.GetInterfaceMap(i).TargetMethods + select im + from tm in t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance) + where interfaceTargetMethods.Contains(tm) + select t.Name + " has public method " + tm.Name + " which should be explicitly implemented."; + + Assert.Equal("", string.Join(Environment.NewLine, + methodsMissingFromInterfaces.ToArray())); + } + [Fact] public void EnumsWithFlagsHaveMutuallyExclusiveValues() { diff --git a/LibGit2Sharp.Tests/RemoteFixture.cs b/LibGit2Sharp.Tests/RemoteFixture.cs index 72a2fa674..0299860a3 100644 --- a/LibGit2Sharp.Tests/RemoteFixture.cs +++ b/LibGit2Sharp.Tests/RemoteFixture.cs @@ -14,6 +14,7 @@ public void CanGetRemoteOrigin() { Remote origin = repo.Network.Remotes["origin"]; Assert.NotNull(origin); + AssertBelongsToARepository(repo, origin); Assert.Equal("origin", origin.Name); Assert.Equal("c:/GitHub/libgit2sharp/Resources/testrepo.git", origin.Url); } @@ -38,6 +39,7 @@ public void CanEnumerateTheRemotes() foreach (Remote remote in repo.Network.Remotes) { Assert.NotNull(remote); + AssertBelongsToARepository(repo, remote); count++; } diff --git a/LibGit2Sharp.Tests/SubmoduleFixture.cs b/LibGit2Sharp.Tests/SubmoduleFixture.cs index e1b378b71..2463912fd 100644 --- a/LibGit2Sharp.Tests/SubmoduleFixture.cs +++ b/LibGit2Sharp.Tests/SubmoduleFixture.cs @@ -60,6 +60,7 @@ public void CanRetrieveTheCommitIdsOfASubmodule(string name, string headId, stri { var submodule = repo.Submodules[name]; Assert.NotNull(submodule); + AssertBelongsToARepository(repo, submodule); Assert.Equal(name, submodule.Name); Assert.Equal((ObjectId)headId, submodule.HeadCommitId); diff --git a/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs b/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs index b01d04c40..0200a256c 100644 --- a/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs +++ b/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs @@ -412,5 +412,11 @@ public static bool StreamEquals(Stream one, Stream two) return true; } + + public void AssertBelongsToARepository(IRepository repo, T instance) + where T : IBelongToARepository + { + Assert.Same(repo, ((IBelongToARepository)instance).Repository); + } } } diff --git a/LibGit2Sharp/Branch.cs b/LibGit2Sharp/Branch.cs index 6b5cb25ce..2e0266ec1 100644 --- a/LibGit2Sharp/Branch.cs +++ b/LibGit2Sharp/Branch.cs @@ -260,7 +260,7 @@ private Branch ResolveTrackedBranch() return branch; } - return new Branch(repo, new VoidReference(trackedReferenceName), trackedReferenceName); + return new Branch(repo, new VoidReference(repo, trackedReferenceName), trackedReferenceName); } private static bool IsRemoteBranch(string canonicalName) diff --git a/LibGit2Sharp/DirectReference.cs b/LibGit2Sharp/DirectReference.cs index d5de46621..00de258a6 100644 --- a/LibGit2Sharp/DirectReference.cs +++ b/LibGit2Sharp/DirectReference.cs @@ -16,7 +16,7 @@ protected DirectReference() { } internal DirectReference(string canonicalName, IRepository repo, ObjectId targetId) - : base(canonicalName, targetId.Sha) + : base(repo, canonicalName, targetId.Sha) { targetBuilder = new Lazy(() => repo.Lookup(targetId)); } diff --git a/LibGit2Sharp/GitObject.cs b/LibGit2Sharp/GitObject.cs index ad4614dc4..5e11489a2 100644 --- a/LibGit2Sharp/GitObject.cs +++ b/LibGit2Sharp/GitObject.cs @@ -11,7 +11,7 @@ namespace LibGit2Sharp /// A GitObject /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public abstract class GitObject : IEquatable + public abstract class GitObject : IEquatable, IBelongToARepository { internal static IDictionary TypeToKindMap = new Dictionary @@ -154,5 +154,7 @@ private string DebuggerDisplay { get { return Id.ToString(7); } } + + IRepository IBelongToARepository.Repository { get { return repo; } } } } diff --git a/LibGit2Sharp/IBelongToARepository.cs b/LibGit2Sharp/IBelongToARepository.cs new file mode 100644 index 000000000..f0297c6cc --- /dev/null +++ b/LibGit2Sharp/IBelongToARepository.cs @@ -0,0 +1,26 @@ +namespace LibGit2Sharp +{ + /// + /// Can be used to reference the from which + /// an instance was created. + /// + /// While convenient in some situations (e.g. Checkout branch bound to UI element), + /// it is important to ensure instances created from an + /// are not used after it is disposed. + /// + /// + /// It's generally better to create and dependant instances + /// on demand, with a short lifespan. + /// + /// + public interface IBelongToARepository + { + /// + /// The from which this instance was created. + /// + /// The returned value should not be disposed. + /// + /// + IRepository Repository { get; } + } +} diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 67929273a..ad733e41c 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -84,6 +84,7 @@ + diff --git a/LibGit2Sharp/Reference.cs b/LibGit2Sharp/Reference.cs index c338bf050..8ab7d1b78 100644 --- a/LibGit2Sharp/Reference.cs +++ b/LibGit2Sharp/Reference.cs @@ -10,11 +10,12 @@ namespace LibGit2Sharp /// A Reference to another git object /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public abstract class Reference : IEquatable + public abstract class Reference : IEquatable, IBelongToARepository { private static readonly LambdaEqualityHelper equalityHelper = new LambdaEqualityHelper(x => x.CanonicalName, x => x.TargetIdentifier); + private readonly IRepository repo; private readonly string canonicalName; private readonly string targetIdentifier; @@ -29,8 +30,19 @@ protected Reference() /// /// The canonical name. /// The target identifier. + [Obsolete("This ctor will be removed in a future release.")] protected Reference(string canonicalName, string targetIdentifier) + : this(null, canonicalName, targetIdentifier) { + } + + /// + /// This would be protected+internal, were that supported by C#. + /// Do not use except in subclasses. + /// + internal Reference(IRepository repo, string canonicalName, string targetIdentifier) + { + this.repo = repo; this.canonicalName = canonicalName; this.targetIdentifier = targetIdentifier; } @@ -48,7 +60,7 @@ internal static T BuildFromPtr(ReferenceSafeHandle handle, Repository repo) w string targetIdentifier = Proxy.git_reference_symbolic_target(handle); var targetRef = repo.Refs[targetIdentifier]; - reference = new SymbolicReference(name, targetIdentifier, targetRef); + reference = new SymbolicReference(repo, name, targetIdentifier, targetRef); break; case GitReferenceType.Oid: @@ -197,5 +209,7 @@ private string DebuggerDisplay "{0} => \"{1}\"", CanonicalName, TargetIdentifier); } } + + IRepository IBelongToARepository.Repository { get { return repo; } } } } diff --git a/LibGit2Sharp/ReferenceWrapper.cs b/LibGit2Sharp/ReferenceWrapper.cs index aaf560709..583c68d4f 100644 --- a/LibGit2Sharp/ReferenceWrapper.cs +++ b/LibGit2Sharp/ReferenceWrapper.cs @@ -10,7 +10,7 @@ namespace LibGit2Sharp /// /// The type of the referenced Git object. [DebuggerDisplay("{DebuggerDisplay,nq}")] - public abstract class ReferenceWrapper : IEquatable> where TObject : GitObject + public abstract class ReferenceWrapper : IEquatable>, IBelongToARepository where TObject : GitObject { /// /// The repository. @@ -160,5 +160,7 @@ private string DebuggerDisplay (TargetObject != null) ? TargetObject.Id.ToString(7) : "?"); } } + + IRepository IBelongToARepository.Repository { get { return repo; } } } } diff --git a/LibGit2Sharp/Remote.cs b/LibGit2Sharp/Remote.cs index e999d08d4..dfff15fed 100644 --- a/LibGit2Sharp/Remote.cs +++ b/LibGit2Sharp/Remote.cs @@ -12,7 +12,7 @@ namespace LibGit2Sharp /// A remote repository whose branches are tracked. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public class Remote : IEquatable + public class Remote : IEquatable, IBelongToARepository { private static readonly LambdaEqualityHelper equalityHelper = new LambdaEqualityHelper(x => x.Name, x => x.Url); @@ -174,5 +174,7 @@ private string DebuggerDisplay "{0} => {1}", Name, Url); } } + + IRepository IBelongToARepository.Repository { get { return repository; } } } } diff --git a/LibGit2Sharp/Submodule.cs b/LibGit2Sharp/Submodule.cs index 3d0b35629..c15e224d0 100644 --- a/LibGit2Sharp/Submodule.cs +++ b/LibGit2Sharp/Submodule.cs @@ -9,7 +9,7 @@ namespace LibGit2Sharp /// A Submodule. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public class Submodule : IEquatable + public class Submodule : IEquatable, IBelongToARepository { private static readonly LambdaEqualityHelper equalityHelper = new LambdaEqualityHelper(x => x.Name, x => x.HeadCommitId); @@ -155,5 +155,7 @@ private string DebuggerDisplay "{0} => {1}", Name, Url); } } + + IRepository IBelongToARepository.Repository { get { return repo; } } } } diff --git a/LibGit2Sharp/SymbolicReference.cs b/LibGit2Sharp/SymbolicReference.cs index 2b2ae05fa..5bb475312 100644 --- a/LibGit2Sharp/SymbolicReference.cs +++ b/LibGit2Sharp/SymbolicReference.cs @@ -17,8 +17,8 @@ public class SymbolicReference : Reference protected SymbolicReference() { } - internal SymbolicReference(string canonicalName, string targetIdentifier, Reference target) - : base(canonicalName, targetIdentifier) + internal SymbolicReference(IRepository repo, string canonicalName, string targetIdentifier, Reference target) + : base(repo, canonicalName, targetIdentifier) { this.target = target; } diff --git a/LibGit2Sharp/VoidReference.cs b/LibGit2Sharp/VoidReference.cs index b8defc8c0..cc20d1e39 100644 --- a/LibGit2Sharp/VoidReference.cs +++ b/LibGit2Sharp/VoidReference.cs @@ -2,8 +2,8 @@ { internal class VoidReference : Reference { - internal VoidReference(string canonicalName) - : base(canonicalName, null) + internal VoidReference(IRepository repo, string canonicalName) + : base(repo, canonicalName, null) { } public override DirectReference ResolveToDirectReference()