utils/github: Fix double counting of author/committer numbers

- The usage of this in `brew contributions` wasn't correct for a user
  with 5 authored commits to homebrew/cask that had been committed by
  other people, the numbers would turn out as 5 authored, 5 committed.
- I decided to do this properly by getting the SHAs for author and
  committer and determine the differences between the two arrays.
  This also accounts for when authored commits are 0, or committed
  commits, or both.
- Add tests, because I don't want to fix this a third time!
This commit is contained in:
Issy Long 2023-03-05 14:13:48 +00:00
parent 180c89e174
commit edeefebf61
No known key found for this signature in database
GPG Key ID: 8247C390DADC67D4
3 changed files with 70 additions and 10 deletions

View File

@ -171,14 +171,9 @@ module Homebrew
puts "Determining contributions for #{person} on #{repo_full_name}..." if args.verbose? puts "Determining contributions for #{person} on #{repo_full_name}..." if args.verbose?
commits_authored = GitHub.repo_commit_count_for_user(repo_full_name, person, "author", args)
commits_committed = GitHub.repo_commit_count_for_user(repo_full_name, person, "committer", args)
# Only count committers where the author is not the same person. Avoid negative numbers or subtracting zero.
commits_committed = commits_authored - commits_committed if commits_authored > commits_committed
data[repo] = { data[repo] = {
author: commits_authored, author: GitHub.count_repo_commits(repo_full_name, person, "author", args),
committer: commits_committed, committer: GitHub.count_repo_commits(repo_full_name, person, "committer", args),
coauthorships: git_log_trailers_cmd(T.must(repo_path), person, "Co-authored-by", args), coauthorships: git_log_trailers_cmd(T.must(repo_path), person, "Co-authored-by", args),
reviews: GitHub.count_issues( reviews: GitHub.count_issues(
"", "",

View File

@ -93,4 +93,56 @@ describe GitHub do
expect(described_class.pull_request_commits("Homebrew", "legacy-homebrew", 50678, per_page: 1)).to eq(hashes) expect(described_class.pull_request_commits("Homebrew", "legacy-homebrew", 50678, per_page: 1)).to eq(hashes)
end end
end end
describe "::count_repo_commits" do
let(:five_shas) { %w[abcdef ghjkl mnop qrst uvwxyz] }
let(:ten_shas) { %w[abcdef ghjkl mnop qrst uvwxyz fedcba lkjhg ponm tsrq zyxwvu] }
it "counts commits authored by a user" do
allow(described_class).to receive(:repo_commits_for_user)
.with("homebrew/cask", "user1", "author", {}).and_return(five_shas)
expect(described_class.count_repo_commits("homebrew/cask", "user1", "author", {})).to eq(5)
end
it "counts commits committed by a user" do
allow(described_class).to receive(:repo_commits_for_user)
.with("homebrew/core", "user1", "author", {}).and_return([])
allow(described_class).to receive(:repo_commits_for_user)
.with("homebrew/core", "user1", "committer", {}).and_return(five_shas)
expect(described_class.count_repo_commits("homebrew/core", "user1", "committer", {})).to eq(5)
end
it "calculates correctly when authored > committed with different shas" do
allow(described_class).to receive(:repo_commits_for_user)
.with("homebrew/cask", "user1", "author", {}).and_return(ten_shas)
allow(described_class).to receive(:repo_commits_for_user)
.with("homebrew/cask", "user1", "committer", {}).and_return(%w[1 2 3 4 5])
expect(described_class.count_repo_commits("homebrew/cask", "user1", "author", {})).to eq(10)
expect(described_class.count_repo_commits("homebrew/cask", "user1", "committer", {})).to eq(5)
end
it "calculates correctly when committed > authored" do
allow(described_class).to receive(:repo_commits_for_user)
.with("homebrew/cask", "user1", "author", {}).and_return(five_shas)
allow(described_class).to receive(:repo_commits_for_user)
.with("homebrew/cask", "user1", "committer", {}).and_return(ten_shas)
expect(described_class.count_repo_commits("homebrew/cask", "user1", "author", {})).to eq(5)
expect(described_class.count_repo_commits("homebrew/cask", "user1", "committer", {})).to eq(5)
end
it "deduplicates commits authored and committed by the same user" do
allow(described_class).to receive(:repo_commits_for_user)
.with("homebrew/core", "user1", "author", {}).and_return(five_shas)
allow(described_class).to receive(:repo_commits_for_user)
.with("homebrew/core", "user1", "committer", {}).and_return(five_shas)
# Because user1 authored and committed the same 5 commits.
expect(described_class.count_repo_commits("homebrew/core", "user1", "author", {})).to eq(5)
expect(described_class.count_repo_commits("homebrew/core", "user1", "committer", {})).to eq(0)
end
end
end end

View File

@ -717,17 +717,30 @@ module GitHub
output[/^Status: (200)/, 1] != "200" output[/^Status: (200)/, 1] != "200"
end end
def repo_commit_count_for_user(nwo, user, filter, args) def repo_commits_for_user(nwo, user, filter, args)
return if Homebrew::EnvConfig.no_github_api? return if Homebrew::EnvConfig.no_github_api?
params = ["#{filter}=#{user}"] params = ["#{filter}=#{user}"]
params << "since=#{DateTime.parse(args.from).iso8601}" if args.from params << "since=#{DateTime.parse(args.from).iso8601}" if args.from
params << "until=#{DateTime.parse(args.to).iso8601}" if args.to params << "until=#{DateTime.parse(args.to).iso8601}" if args.to
commits = 0 commits = []
API.paginate_rest("#{API_URL}/repos/#{nwo}/commits", additional_query_params: params.join("&")) do |result| API.paginate_rest("#{API_URL}/repos/#{nwo}/commits", additional_query_params: params.join("&")) do |result|
commits += result.length commits.concat(result.map { |c| c["sha"] })
end end
commits commits
end end
def count_repo_commits(nwo, user, filter, args)
return if Homebrew::EnvConfig.no_github_api?
author_shas = repo_commits_for_user(nwo, user, "author", args)
return author_shas.count if filter == "author"
committer_shas = repo_commits_for_user(nwo, user, "committer", args)
return 0 if committer_shas.empty?
# Only count commits where the author and committer are different.
committer_shas.difference(author_shas).count
end
end end