From f7a622232ccd300996079144065ee622fc29a00b Mon Sep 17 00:00:00 2001 From: Adam Ralph Date: Sun, 21 Oct 2018 22:36:24 +0200 Subject: [PATCH] remove recursion to avoid stack overflow on large histories - also cached the tags, which improved the performance dramatically - also added stderr logging --- MinVer/Versioner.cs | 84 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 14 deletions(-) diff --git a/MinVer/Versioner.cs b/MinVer/Versioner.cs index 8f8c9ac1..883c0847 100644 --- a/MinVer/Versioner.cs +++ b/MinVer/Versioner.cs @@ -1,5 +1,6 @@ namespace MinVer { + using System; using System.Collections.Generic; using System.Linq; using LibGit2Sharp; @@ -10,38 +11,93 @@ public static Version GetVersion(string path) { using (var repo = new Repository(path)) { - return GetVersion(repo.Commits.FirstOrDefault(), 0, repo.Tags, new HashSet()); + return GetVersion(repo.Commits.FirstOrDefault(), repo.Tags.ToList()); } } - private static Version GetVersion(Commit commit, int height, TagCollection tags, HashSet commitsChecked) + private static Version GetVersion(Commit commit, List tags) { if (commit == default) { return new Version(); } - if (!commitsChecked.Add(commit.Sha)) + var commitsChecked = new HashSet(); + var count = 0; + var height = 0; + var candidates = new List(); + var commitsToCheck = new Stack>(); + + while (true) { - return null; + if (commitsChecked.Add(commit.Sha)) + { + ++count; + + var commitVersion = GetVersionOrDefault(tags, commit); + + if (commitVersion != default) + { + var candidate = new Candidate { Version = commitVersion, Commit = commit, Height = height }; + Log($"Detected {candidate}."); + candidates.Add(candidate); + } + else + { + foreach (var parent in commit.Parents.Reverse()) + { + commitsToCheck.Push(Tuple.Create(parent, height + 1)); + } + + if (commitsToCheck.Count == 0 || commitsToCheck.Peek().Item2 <= height) + { + var candidate = new Candidate { Version = new Version(), Commit = commit, Height = height }; + Log($"Inferred {candidate}."); + candidates.Add(candidate); + } + } + } + + if (commitsToCheck.Count == 0) + { + break; + } + + (commit, height) = commitsToCheck.Pop(); } - var version = GetVersionOrDefault(tags, commit); + Log($"{count:N0} commits checked."); - if (version != default) + var orderedCandidates = candidates.OrderBy(candidate => candidate.Version).ToList(); + + foreach (var candidate in orderedCandidates.Take(orderedCandidates.Count - 1)) { - return version.AddHeight(height); + Log($"Ignoring {candidate}..."); } - return commit.Parents - .Select(parent => GetVersion(parent, height + 1, tags, commitsChecked)) - .Where(_ => _ != default) - .OrderByDescending(_ => _) - .FirstOrDefault() ?? - new Version().AddHeight(height); + var selectedCandidate = orderedCandidates.Last(); + Log($"Using {selectedCandidate}."); + + var calculatedVersion = selectedCandidate.Version.AddHeight(selectedCandidate.Height); + Log($"Calculated {calculatedVersion}."); + + return calculatedVersion; + } + + private class Candidate + { + public Version Version { get; set; } + + public Commit Commit { get; set; } + + public int Height { get; set; } + + public override string ToString() => $"{{ {nameof(this.Version)}: {this.Version}, {nameof(this.Commit)}: {this.Commit}, {nameof(this.Height)}: {this.Height} }}"; } - private static Version GetVersionOrDefault(TagCollection tags, Commit commit) => tags + private static void Log(string message) => Console.Error.WriteLine($"MinVer: {message}"); + + private static Version GetVersionOrDefault(List tags, Commit commit) => tags .Where(tag => tag.Target.Sha == commit.Sha) .Select(tag => Version.ParseOrDefault(tag.FriendlyName)) .Where(_ => _ != default)