diff --git a/LibGit2Sharp.Tests/BranchFixture.cs b/LibGit2Sharp.Tests/BranchFixture.cs index 3f5321efe..7478c9577 100644 --- a/LibGit2Sharp.Tests/BranchFixture.cs +++ b/LibGit2Sharp.Tests/BranchFixture.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -10,6 +12,63 @@ public class BranchFixture : BaseFixture { private readonly string[] expectedBranches = new[] { "br2", "master", "packed", "packed-test", "test", }; + [Fact] + public void Parents() + { + using (var repo = new Repository(@"D:\temp\linux\linux")) + { + int max = 42000; + var commits = new List(); + int iter = 0; + var s = new Stack(); + + var id = repo.Head.Tip.Id; + + s.Push(id); + + while (s.Count > 0 && iter < max) + { + var c = new NewCommit(repo, s.Pop()); + + var p = c.Parents.FirstOrDefault(); + s.Push(p.Id); + + commits.Add(new NewCommit(repo, c.Id)); + iter++; + } + + s.Clear(); + s = null; + + GC.Collect(); + + MaxNumberOfParents(commits, x => x.ParentsCount); + GC.Collect(); + + MaxNumberOfParents(commits, x => x.Parents.Count()); + } + } + + private void MaxNumberOfParents(List commits, Func parentCountEvaluator) + { + int maxP = -1; + + var sw = Stopwatch.StartNew(); + foreach (var newCommit in commits) + { + int count = parentCountEvaluator(newCommit); + if (maxP > count) + continue; + + maxP = count; + } + sw.Stop(); + + Console.WriteLine("Commits: {0}", commits.Count); + Console.WriteLine("Time: {0}", sw.ElapsedMilliseconds); + Console.WriteLine("MaxParents: {0}", maxP); + } + [Theory] [InlineData("unit_test")] [InlineData("Ångström")] diff --git a/LibGit2Sharp/Core/LazyProperty.cs b/LibGit2Sharp/Core/LazyProperty.cs new file mode 100644 index 000000000..834a72d2e --- /dev/null +++ b/LibGit2Sharp/Core/LazyProperty.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp.Core +{ + internal class LazyProperty : IEvaluator + { + private readonly Func evaluator; + private readonly GitObjectLazyGroup lazyGroup; + + private TType value; + private bool hasBeenEvaluated; + + public LazyProperty(Func evaluator, GitObjectLazyGroup lazyGroup) + { + this.evaluator = evaluator; + this.lazyGroup = lazyGroup; + } + + public TType Value + { + get { return Evaluate(); } + } + + private TType Evaluate() + { + if (!hasBeenEvaluated) + { + lock (lazyGroup) + { + if (!hasBeenEvaluated) + { + lazyGroup.TriggerEvaluation(); + } + } + } + + return value; + } + + void IEvaluator.Evaluate(GitObjectSafeHandle objectPtr) + { + hasBeenEvaluated = true; + value = evaluator(objectPtr); + } + } + + internal interface IEvaluator + { + void Evaluate(T input); + } + + internal class GitObjectLazyGroup + { + private readonly Repository repo; + private readonly ObjectId id; + + private readonly IList> lazies = new List>(); + + public GitObjectLazyGroup(Repository repo, ObjectId id) + { + this.repo = repo; + this.id = id; + } + + public LazyProperty AddLazy(Func evaluator) + { + var lazy = new LazyProperty(evaluator, this); + lazies.Add(lazy); + return lazy; + } + + public void TriggerEvaluation() + { + using (var osw = new ObjectSafeWrapper(id, repo.Handle)) + { + foreach (var lazy in lazies) + { + lazy.Evaluate(osw.ObjectPtr); + } + } + } + } +} \ No newline at end of file diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 823fca66d..988cbe645 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -257,7 +257,12 @@ public static int git_commit_parentcount(RepositorySafeHandle repo, ObjectId id) public static int git_commit_parentcount(ObjectSafeWrapper obj) { - return (int)NativeMethods.git_commit_parentcount(obj.ObjectPtr); + return git_commit_parentcount(obj.ObjectPtr); + } + + public static int git_commit_parentcount(GitObjectSafeHandle obj) + { + return (int)NativeMethods.git_commit_parentcount(obj); } public static ObjectId git_commit_tree_oid(GitObjectSafeHandle obj) diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 8de2023b4..7703a6551 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -68,6 +68,8 @@ + + diff --git a/LibGit2Sharp/NewCommit.cs b/LibGit2Sharp/NewCommit.cs new file mode 100644 index 000000000..39f36ed5e --- /dev/null +++ b/LibGit2Sharp/NewCommit.cs @@ -0,0 +1,113 @@ +using System.Collections; +using System.Collections.Generic; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Compat; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + internal class ParentsList : IEnumerable + { + private readonly Lazy> _parents; + private readonly Lazy _count; + + public ParentsList(Repository repo, ObjectId id) + { + _count = new Lazy(() => Proxy.git_commit_parentcount(repo.Handle, id)); + _parents = new Lazy>(() => RetrieveParentsOfCommit(repo, id)); + } + + private IList RetrieveParentsOfCommit(Repository repo, ObjectId oid) + { + var parents = new List(); + + using (var obj = new ObjectSafeWrapper(oid, repo.Handle)) + { + int parentsCount = _count.Value; + + for (uint i = 0; i < parentsCount; i++) + { + ObjectId parentCommitId = Proxy.git_commit_parent_oid(obj.ObjectPtr, i); + parents.Add(new NewCommit(repo, parentCommitId)); + } + } + + return parents; + } + + public IEnumerator GetEnumerator() + { + return _parents.Value.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public int Count + { + get { return _parents.Value.Count; } + } + } + + public class NewCommit : GitObject + { + private readonly Repository repo; + + private readonly GitObjectLazyGroup group1; + private readonly GitObjectLazyGroup group2; + + private readonly ParentsList parents; + private readonly LazyProperty _lazyMessage; + private readonly LazyProperty _lazyEncoding; + private readonly LazyProperty _lazyAuthor; + private readonly LazyProperty _lazyCommitter; + private readonly LazyProperty _lazyTreeId; + + protected NewCommit() + {} + + public NewCommit(Repository repo, ObjectId id) + : base(id) + { + this.repo = repo; + group1 = new GitObjectLazyGroup(repo, id); + group2 = new GitObjectLazyGroup(repo, id); + + _lazyTreeId = group1.AddLazy(Proxy.git_commit_tree_oid); + _lazyAuthor = group1.AddLazy(Proxy.git_commit_author); + _lazyMessage = group1.AddLazy(Proxy.git_commit_message); + + _lazyEncoding = group2.AddLazy(RetrieveEncodingOf); + _lazyCommitter = group2.AddLazy(Proxy.git_commit_committer); + + parents = new ParentsList(repo, id); + } + + // Lazy batch loaded properies + private ObjectId TreeId { get { return _lazyTreeId.Value; } } + public Signature Author { get { return _lazyAuthor.Value; } } + public string Message { get { return _lazyMessage.Value; } } + + public string Encoding { get { return _lazyEncoding.Value; } } + public Signature Committer { get { return _lazyCommitter.Value; } } + + // On demand lazy loaded properties + public IEnumerable Parents { get { return parents; } } + + // Other properties + public int ParentsCount { get { return parents.Count; } } + + public Tree Tree { get { return repo.Lookup(TreeId); } } + + private static string RetrieveEncodingOf(GitObjectSafeHandle obj) + { + string encoding = Proxy.git_commit_message_encoding(obj); + + return encoding ?? "UTF-8"; + } + + + } +}