Class: Utils::Analytics::Releases Private
Overview
This class is part of a private API. This class may only be used in the Homebrew/brew repository. Third parties should avoid using this class if possible, as it may be removed or changed without warning.
Release analytics derived from stable Git tags and their commit ranges.
Instance Method Summary collapse
- #git(*args) ⇒ String private
- #initialize(path) ⇒ void constructor private
- #report ⇒ Hash{String => T.anything} private
- #statistics(*revisions) ⇒ Hash{String => T.anything} private
Constructor Details
#initialize(path) ⇒ void
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
11 12 13 |
# File 'utils/analytics/releases.rb', line 11 def initialize(path) @path = path end |
Instance Method Details
#git(*args) ⇒ String
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
140 141 142 |
# File 'utils/analytics/releases.rb', line 140 def git(*args) Utils.safe_popen_read("git", "-C", @path.to_s, *args).chomp end |
#report ⇒ Hash{String => T.anything}
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'utils/analytics/releases.rb', line 16 def report if git("rev-parse", "--is-shallow-repository") == "true" raise "Release analytics require complete brew Git history" end = git("for-each-ref", "--merged=HEAD", "--format=%(refname:strip=2)%09%(objectname)%09%(*objectname)%09" \ "%(committerdate:unix)%09%(*committerdate:unix)", "refs/tags").lines.filter_map do |line| name, revision, annotated_revision, , = line.chomp.split("\t", -1) next if name.nil? || revision.nil? || .nil? next unless name.match?(/\Av?\d+(?:\.\d+){1,2}\z/) [name, annotated_revision.presence || revision, .presence || ] end signature = Digest::SHA256.hexdigest([ Digest::SHA256.file(__FILE__).hexdigest, git("ls-tree", "HEAD", "--", ".mailmap") ].join("\n")) cache = HOMEBREW_CACHE/"analytics/git/releases.json" data = cache.exist? ? JSON.parse(cache.read) : {} data = {} if data["signature"] != signature if data["tags"] != first_parents = git("rev-list", "--parents", "HEAD").lines.to_h do |line| revision, parent = line.split [revision, parent || ""] end versions = .to_h { |name, revision, _| [revision, name] } saved = data.fetch("releases", []).to_h { |row| [row.fetch("version"), row] } releases = .map do |name, revision, | previous = first_parents.fetch(revision, "") previous = first_parents.fetch(previous, "") until previous.empty? || versions.key?(previous) row = saved[name] next row if row && row["revision"] == revision && row["previous_revision"] == previous.presence { "version" => name, "revision" => revision, "previous_version" => versions[previous], "previous_revision" => previous.presence, "date" => Time.at(.to_i).utc.strftime("%F"), }.merge(statistics(previous.empty? ? revision : "#{previous}..#{revision}")) end data["releases"] = releases.sort_by do |row| [row.fetch("date"), Version.new(row.fetch("version"))] end.reverse seen = Set.new data.fetch("releases").reverse_each do |row| identities = row.fetch("authors") row["new_contributors"] = (identities - seen.to_a).size seen.merge(identities) end boundaries = data.fetch("releases").select { |row| row.fetch("version").end_with?(".0") } boundaries.sort_by! { |row| Version.new(row.fetch("version")) } boundaries.reverse! data["aggregates"] = boundaries.filter_map do |row| version = Version.new(row.fetch("version")) kind = version.minor.to_i.zero? ? "major" : "minor" previous = boundaries.find do |candidate| value = Version.new(candidate.fetch("version")) value < version && (kind == "minor" || value.minor.to_i.zero?) end next unless previous saved = data.fetch("aggregates", []).find do |candidate| candidate["revision"] == row["revision"] && candidate["previous_revision"] == previous["revision"] end next saved if saved aggregate = row.slice("version", "revision", "date").merge( "kind" => kind, "previous_version" => previous.fetch("version"), "previous_revision" => previous.fetch("revision") ).merge(statistics("#{previous.fetch("revision")}..#{row.fetch("revision")}")) earlier = git("log", "--no-merges", "--format=%aE", previous.fetch("revision")) .b.lines(chomp: true).map { |email| email.unpack1("H*") } aggregate["new_contributors"] = (aggregate.fetch("authors") - earlier).size aggregate end data["totals"] = statistics(*.map { |_, revision, _| revision }).except("authors") data["totals"]["releases"] = .size data["totals"]["new_contributors"] = data.fetch("totals").fetch("contributors") data["tags"] = data["signature"] = signature cache.dirname.mkpath cache.atomic_write(JSON.generate(data)) end releases = data.fetch("releases").map { |row| row.except("authors") } years = releases.map { |row| row.fetch("date")[0, 4] }.tally { "generated_at" => Time.now.utc.iso8601, "source" => "https://github.com/Homebrew/brew", "revision" => git("rev-parse", "HEAD"), "years" => [{ "period" => "current", "releases" => releases.size }, *(2009..Time.now.utc.year).to_a.reverse.map do |year| { "period" => year.to_s, "releases" => years.fetch(year.to_s, 0) } end], "releases" => releases, "aggregates" => data.fetch("aggregates").map { |row| row.except("authors") }, "totals" => data.fetch("totals"), } end |
#statistics(*revisions) ⇒ Hash{String => T.anything}
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'utils/analytics/releases.rb', line 118 def statistics(*revisions) = Set.new pull_requests = Set.new commits = 0 unless revisions.empty? git("log", "--format=%P%x09%aE%x09%s", *revisions).b.each_line do |line| parents, email, subject = line.chomp.split("\t", 3) next if parents.nil? || email.nil? || subject.nil? if parents.split.size < 2 commits += 1 << email end number = subject[/\AMerge pull request #(\d+)\b/, 1] || subject[/\(#(\d+)\)\z/, 1] pull_requests << number if number end end { "commits" => commits, "pull_requests" => pull_requests.size, "contributors" => .size, "authors" => .map { |email| email.unpack1("H*") } } end |