From 0a6c5bf5c5b107654a1f59eb68831a6b76b60f9b Mon Sep 17 00:00:00 2001 From: Julien Richard Date: Sat, 20 May 2023 20:41:41 +0200 Subject: [PATCH] Add RevWalker --- LibGit2Sharp.Tests/RepositoryFixture.cs | 41 ++++++ LibGit2Sharp/CommitLog.cs | 77 ++++------ LibGit2Sharp/Core/NativeMethods.cs | 16 +++ LibGit2Sharp/Core/Proxy.cs | 37 ++++- LibGit2Sharp/RevWalker.cs | 179 ++++++++++++++++++++++++ 5 files changed, 293 insertions(+), 57 deletions(-) create mode 100644 LibGit2Sharp/RevWalker.cs diff --git a/LibGit2Sharp.Tests/RepositoryFixture.cs b/LibGit2Sharp.Tests/RepositoryFixture.cs index bf27b6091..35b8ed41c 100644 --- a/LibGit2Sharp.Tests/RepositoryFixture.cs +++ b/LibGit2Sharp.Tests/RepositoryFixture.cs @@ -775,5 +775,46 @@ public void ReadingReferenceTargetFromListRemoteReferencesThrows(string url) }); } } + + [Fact] + public void RevWalkRepository() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + using (var walker = new RevWalker(repo)) + { + walker.Sorting(CommitSortStrategies.Topological); + + Assert.Empty(GetCommits().ToList()); + + walker.PushRef("HEAD"); + Assert.Equal(7, GetCommits().Count()); + + walker.HideRef("HEAD"); + Assert.Empty(GetCommits()); + + walker.Reset(); + + walker.PushGlob("refs/heads/*"); + Assert.Equal(12, GetCommits().Count()); + + walker.HideGlob("refs/*"); + Assert.Empty(GetCommits()); + + IEnumerable GetCommits() + { + while (true) + { + var objectId = walker.Next(); + if (objectId == null) + break; + + var commit = repo.Lookup(objectId); + yield return commit; + } + } + } + + } } } diff --git a/LibGit2Sharp/CommitLog.cs b/LibGit2Sharp/CommitLog.cs index 4a6ab1de3..9b1e858a7 100644 --- a/LibGit2Sharp/CommitLog.cs +++ b/LibGit2Sharp/CommitLog.cs @@ -108,19 +108,34 @@ public IEnumerable QueryBy(string path, CommitFilter filter) private class CommitEnumerator : IEnumerator { private readonly Repository repo; - private readonly RevWalkerHandle handle; + private readonly RevWalker walker; private ObjectId currentOid; public CommitEnumerator(Repository repo, CommitFilter filter) { this.repo = repo; - handle = Proxy.git_revwalk_new(repo.Handle); - repo.RegisterForCleanup(handle); - Sort(filter.SortBy); - Push(filter.SinceList); - Hide(filter.UntilList); - FirstParentOnly(filter.FirstParentOnly); + walker = new RevWalker(repo); + + walker.Sorting(filter.SortBy); + + foreach (ObjectId actedOn in repo.Committishes(filter.SinceList).TakeWhile(o => o != null)) + { + walker.Push(actedOn); + } + + if(filter.UntilList != null) + { + foreach (ObjectId actedOn in repo.Committishes(filter.UntilList).TakeWhile(o => o != null)) + { + walker.Hide(actedOn); + } + } + + if (filter.FirstParentOnly) + { + walker.SimplifyFirstParent(); + } } #region IEnumerator Members @@ -137,21 +152,19 @@ object IEnumerator.Current public bool MoveNext() { - ObjectId id = Proxy.git_revwalk_next(handle); - + ObjectId id = walker.Next(); if (id == null) { return false; } currentOid = id; - return true; } public void Reset() { - Proxy.git_revwalk_reset(handle); + walker.Reset(); } #endregion @@ -164,47 +177,7 @@ public void Dispose() private void Dispose(bool disposing) { - handle.SafeDispose(); - } - - private delegate void HidePushSignature(RevWalkerHandle handle, ObjectId id); - - private void InternalHidePush(IList identifier, HidePushSignature hidePush) - { - IEnumerable oids = repo.Committishes(identifier).TakeWhile(o => o != null); - - foreach (ObjectId actedOn in oids) - { - hidePush(handle, actedOn); - } - } - - private void Push(IList identifier) - { - InternalHidePush(identifier, Proxy.git_revwalk_push); - } - - private void Hide(IList identifier) - { - if (identifier == null) - { - return; - } - - InternalHidePush(identifier, Proxy.git_revwalk_hide); - } - - private void Sort(CommitSortStrategies options) - { - Proxy.git_revwalk_sorting(handle, options); - } - - private void FirstParentOnly(bool firstParent) - { - if (firstParent) - { - Proxy.git_revwalk_simplify_first_parent(handle); - } + walker.SafeDispose(); } } } diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index e20d755ba..c4959bd42 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -1686,6 +1686,14 @@ internal static extern unsafe int git_revparse_ext( [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern unsafe int git_revwalk_hide(git_revwalk* walker, ref GitOid commit_id); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_hide_glob(git_revwalk* walker, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string glob); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_hide_ref(git_revwalk* walker, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refname); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern unsafe int git_revwalk_new(out git_revwalk* walker, git_repository* repo); @@ -1695,6 +1703,14 @@ internal static extern unsafe int git_revparse_ext( [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern unsafe int git_revwalk_push(git_revwalk* walker, ref GitOid id); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_push_glob(git_revwalk* walker, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string glob); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_push_ref(git_revwalk* walker, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refname); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern unsafe int git_revwalk_reset(git_revwalk* walker); diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 50cefc0df..36ffa573c 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -2752,6 +2752,18 @@ public static unsafe void git_revwalk_hide(RevWalkerHandle walker, ObjectId comm Ensure.ZeroResult(res); } + public static unsafe void git_revwalk_hide_glob(RevWalkerHandle walker, string glob) + { + int res = NativeMethods.git_revwalk_hide_glob(walker, glob); + Ensure.ZeroResult(res); + } + + public static unsafe void git_revwalk_hide_ref(RevWalkerHandle walker, string refName) + { + int res = NativeMethods.git_revwalk_hide_ref(walker, refName); + Ensure.ZeroResult(res); + } + public static unsafe RevWalkerHandle git_revwalk_new(RepositoryHandle repo) { git_revwalk* handle; @@ -2783,19 +2795,34 @@ public static unsafe void git_revwalk_push(RevWalkerHandle walker, ObjectId id) Ensure.ZeroResult(res); } + public static unsafe void git_revwalk_push_glob(RevWalkerHandle walker, string glob) + { + int res = NativeMethods.git_revwalk_push_glob(walker, glob); + Ensure.ZeroResult(res); + } + + public static unsafe void git_revwalk_push_ref(RevWalkerHandle walker, string refName) + { + int res = NativeMethods.git_revwalk_push_ref(walker, refName); + Ensure.ZeroResult(res); + } + public static unsafe void git_revwalk_reset(RevWalkerHandle walker) { - NativeMethods.git_revwalk_reset(walker); + int res = NativeMethods.git_revwalk_reset(walker); + Ensure.ZeroResult(res); } - public static unsafe int git_revwalk_sorting(RevWalkerHandle walker, CommitSortStrategies options) + public static unsafe void git_revwalk_sorting(RevWalkerHandle walker, CommitSortStrategies options) { - return NativeMethods.git_revwalk_sorting(walker, options); + int res = NativeMethods.git_revwalk_sorting(walker, options); + Ensure.ZeroResult(res); } - public static unsafe int git_revwalk_simplify_first_parent(RevWalkerHandle walker) + public static unsafe void git_revwalk_simplify_first_parent(RevWalkerHandle walker) { - return NativeMethods.git_revwalk_simplify_first_parent(walker); + int res = NativeMethods.git_revwalk_simplify_first_parent(walker); + Ensure.ZeroResult(res); } #endregion diff --git a/LibGit2Sharp/RevWalker.cs b/LibGit2Sharp/RevWalker.cs new file mode 100644 index 000000000..cca213de6 --- /dev/null +++ b/LibGit2Sharp/RevWalker.cs @@ -0,0 +1,179 @@ +using System; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// Creates a new revision walker to iterate through repository. + /// + /// + /// This revision walker uses a custom memory pool and an internal commit cache, so it is relatively expensive to allocate. + /// + /// For maximum performance, this revision walker should be reused for different walks. + /// + /// This revision walker is *not* thread safe: it may only be used to walk a repository on a single thread; + /// however, it is possible to have several revision walkers in several different threads walking the same repository. + /// + public sealed class RevWalker : IDisposable + { + private readonly RevWalkerHandle handle; + + /// + /// Creates a new revision walker to iterate through a repo. + /// + public RevWalker(Repository repo) + { + handle = Proxy.git_revwalk_new(repo.Handle); + repo.RegisterForCleanup(handle); + } + + /// + /// Resets the revision walker for reuse. + /// + /// + /// This will clear all the pushed and hidden commits, and leave the walker in a blank state (just like at creation) + /// ready to receive new commit pushes and start a new walk. + /// + /// The revision walk is automatically reset when a walk is over. + /// + public void Reset() + { + Proxy.git_revwalk_reset(handle); + } + + /// + /// Change the sorting mode when iterating through the repository's contents. + /// + /// + /// Changing the sorting mode resets the walker. + /// + public void Sorting(CommitSortStrategies sortMode) + { + Proxy.git_revwalk_sorting(handle, sortMode); + } + + /// + /// Gets the next commit from the revision walk. + /// + /// + /// The initial call to this method is *not* blocking when iterating through a repo with a time-sorting mode. + /// + /// Iterating with Topological or inverted modes makes the initial call blocking to preprocess the commit list, + /// but this block should be mostly unnoticeable on most repositories (topological preprocessing times at 0.3s + /// on the git.git repo). + /// + /// The revision walker is reset when the walk is over. + /// + /// New commit ID or null on error. + public ObjectId Next() + { + return Proxy.git_revwalk_next(handle); + } + + /// + /// Marks a commit (and its ancestors) uninteresting for the output. + /// + /// + /// The given ID must belong to a committish on the walked repository. + /// + /// The resolved commit and all its parents will be hidden from the output on the revision walk. + /// + public void Hide(ObjectId commitId) + { + Proxy.git_revwalk_hide(handle, commitId); + } + + /// + /// Hide matching references. + /// + /// + /// The OIDs pointed to by the references that match the given glob pattern and their ancestors will be hidden + /// from the output on the// revision walk. + /// + /// A leading 'refs/' is implied if not present as well as a trailing '/\*' if the glob lacks '?', '\*' or '['. + /// + /// Any references matching this glob which do not point to a committish will be ignored. + /// + /// the glob pattern references should match + public void HideGlob(string glob) + { + Proxy.git_revwalk_hide_glob(handle, glob); + } + + /// + /// Hide the OID pointed to by a reference + /// + /// + /// The reference must point to a committish. + /// + /// the reference to hide + public void HideRef(string refName) + { + Proxy.git_revwalk_hide_ref(handle, refName); + } + + /// + /// Marks a commit to start traversal from. + /// + /// + /// The given OID must belong to a commit on the walked repository. + /// + /// The given commit will be used as one of the roots when starting the revision walk. At least one commit must + /// be pushed the repository before a walk can be started. + public void Push(ObjectId commitId) + { + Proxy.git_revwalk_push(handle, commitId); + } + + /// + /// Push matching references + /// + /// + /// The OIDs pointed to by the references that match the given glob pattern will be pushed to the revision walker. + /// + /// A leading 'refs/' is implied if not present as well as a trailing '/\*' if the glob lacks '?', '\*' or '['. + /// + /// Any references matching this glob which do not point to a committish will be ignored. + /// + /// The glob pattern references should match + public void PushGlob(string glob) + { + Proxy.git_revwalk_push_glob(handle, glob); + } + + /// + /// Push the OID pointed to by a reference + /// + /// + /// The reference must point to a committish. + /// + public void PushRef(string refName) + { + Proxy.git_revwalk_push_ref(handle, refName); + } + + /// + /// Simplify the history by first-parent. + /// + /// + /// No parents other than the first for each commit will be enqueued. + /// + public void SimplifyFirstParent() + { + Proxy.git_revwalk_simplify_first_parent(handle); + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + handle.SafeDispose(); + } + } +}