From 26d08ebfead5532ada0afbb8c044a498cb9a5a8d Mon Sep 17 00:00:00 2001 From: Doug Miller Date: Fri, 19 Feb 2021 00:06:56 -0600 Subject: [PATCH] Fixing 'stack level too deep error' in commits_in_tag --- .../octo_fetcher.rb | 47 ++++++++++++------- spec/unit/octo_fetcher_spec.rb | 43 +++++++++++++++++ 2 files changed, 72 insertions(+), 18 deletions(-) diff --git a/lib/github_changelog_generator/octo_fetcher.rb b/lib/github_changelog_generator/octo_fetcher.rb index fb36b6aa4..028aa9ffe 100644 --- a/lib/github_changelog_generator/octo_fetcher.rb +++ b/lib/github_changelog_generator/octo_fetcher.rb @@ -41,6 +41,7 @@ def initialize(options = {}) @branches = nil @graph = nil @client = nil + @commits_in_tag_cache = {} end def middleware @@ -342,35 +343,45 @@ def commits_in_branch(name) end end - def commits_in_tag(sha, shas = Set.new) - @graph ||= commits.map { |commit| [commit["sha"], commit] }.to_h - - return if shas.include?(sha) - - shas << sha - - if (top = @graph[sha]) - top[:parents].each do |parent| - commits_in_tag(parent[:sha], shas) - end - end - - shas - end - # Fetch all SHAs occurring in or before a given tag and add them to # "shas_in_tag" # # @param [Array] tags The array of tags. # @return [Nil] No return; tags are updated in-place. def fetch_tag_shas(tags) - tags.each do |tag| - tag["shas_in_tag"] = commits_in_tag(tag["commit"]["sha"]) + # Reverse the tags array to gain max benefit from the @commits_in_tag_cache + tags.reverse_each do |tag| + tag["shas_in_tag"] = commits_in_tag(tag[:commit][:sha]) end end private + def commits_in_tag(sha, shas = Set.new) + # Reduce multiple runs for the same tag + return @commits_in_tag_cache[sha] if @commits_in_tag_cache.key?(sha) + + @graph ||= commits.map { |commit| [commit[:sha], commit] }.to_h + return shas unless (current = @graph[sha]) + + queue = [current] + while queue.any? + commit = queue.shift + # If we've already processed this sha, just grab it's parents from the cache + if @commits_in_tag_cache.key?(commit[:sha]) + shas.merge(@commits_in_tag_cache[commit[:sha]]) + else + shas.add(commit[:sha]) + commit[:parents].each do |p| + queue.push(@graph[p[:sha]]) unless shas.include?(p[:sha]) + end + end + end + + @commits_in_tag_cache[sha] = shas + shas + end + def stringify_keys_deep(indata) case indata when Array diff --git a/spec/unit/octo_fetcher_spec.rb b/spec/unit/octo_fetcher_spec.rb index f128872fc..e29f4e7bb 100644 --- a/spec/unit/octo_fetcher_spec.rb +++ b/spec/unit/octo_fetcher_spec.rb @@ -85,6 +85,49 @@ end end + describe "#fetch_tag_shas" do + let(:commits) do + [ + { sha: "0", parents: [{ sha: "1" }, { sha: "6" }] }, + { sha: "1", parents: [{ sha: "2" }] }, + { sha: "2", parents: [{ sha: "3" }] }, + { sha: "3", parents: [{ sha: "4" }] }, + { sha: "4", parents: [{ sha: "5" }] }, + { sha: "5", parents: [] }, + { sha: "6", parents: [{ sha: "7" }, { sha: "8" }] }, + { sha: "7", parents: [{ sha: "9" }, { sha: "10" }] }, + { sha: "8", parents: [{ sha: "11" }, { sha: "12" }] }, + { sha: "9", parents: [] }, + { sha: "10", parents: [] }, + { sha: "11", parents: [] }, + { sha: "12", parents: [] } + ] + end + + let(:tag0) { { name: "tag-0", commit: { sha: "0" } } } + let(:tag1) { { name: "tag-1", commit: { sha: "1" } } } + let(:tag6) { { name: "tag-6", commit: { sha: "6" } } } + + before do + allow(fetcher).to receive(:commits).and_return(commits) + end + + it "should find all shas with single parents" do + fetcher.fetch_tag_shas([tag1]) + expect(tag1["shas_in_tag"]).to eq(Set.new(%w[1 2 3 4 5])) + end + + it "should find all shas with multiple parents" do + fetcher.fetch_tag_shas([tag6]) + expect(tag6["shas_in_tag"]).to eq(Set.new(%w[6 7 8 9 10 11 12])) + end + + it "should find all shas with mixed parents" do + fetcher.fetch_tag_shas([tag0]) + expect(tag0["shas_in_tag"]).to eq(Set.new(%w[0 1 2 3 4 5 6 7 8 9 10 11 12])) + end + end + describe "#github_fetch_tags" do context "when wrong token provided", :vcr do let(:options) do