From 0355c60787c01b86f49c0028d470115e17bb294f Mon Sep 17 00:00:00 2001 From: Issy Long Date: Sun, 24 Jul 2022 22:06:00 +0100 Subject: [PATCH] dev-cmd/contributions: Retrieve a user's repo contributions over time - Before each AGM it's currently a manual process for a PLC member to search commit logs and GitHub to figure out who contributed to Homebrew, so who should remain a member. - I noticed that [looking at commits for a user](https://github.com/Homebrew/homebrew-core/commits?author=issyl0&since=2022-01-01&until=2023-01-01) would not count `Co-Authored-By`, which happens a lot now there's an autosquash action on PRs in `Homebrew/homebrew-core`, say if someone fixed a formula's build or tests or whatever and then the PR got auto-merged. - Here's `brew contributions` that uses `git log` to be able to go back through all time or a specific time period (`--from`, `--to`). It's up to individual PLC discretion for "activity", but it does at least go some way to automating the data retrieval. - Example (I can use my username as `--email` because my username is in all of the email addresses that I use for committing to Homebrew): ``` $ brew contributions --email=issyl0 --repos=brew,core Person issyl0 directly authored 732 commits and co-authored 31 commits to brew, core in all time. ``` --- Library/Homebrew/dev-cmd/contributions.rb | 101 ++++++++++++++++++ .../test/dev-cmd/contributions_spec.rb | 8 ++ 2 files changed, 109 insertions(+) create mode 100755 Library/Homebrew/dev-cmd/contributions.rb create mode 100644 Library/Homebrew/test/dev-cmd/contributions_spec.rb diff --git a/Library/Homebrew/dev-cmd/contributions.rb b/Library/Homebrew/dev-cmd/contributions.rb new file mode 100755 index 0000000000..2a528cf86b --- /dev/null +++ b/Library/Homebrew/dev-cmd/contributions.rb @@ -0,0 +1,101 @@ +# typed: true +# frozen_string_literal: true + +require "cli/parser" + +module Homebrew + extend T::Sig + + module_function + + SUPPORTED_REPOS = %w[brew core cask bundle].freeze + + sig { returns(CLI::Parser) } + def contributions_args + Homebrew::CLI::Parser.new do + usage_banner <<~EOS + `contributions` + + Contributions to Homebrew repos for a user. + EOS + + flag "--email=", + description: "A user's email address that they commit with." + + flag "--from=", + description: "Date (ISO-8601 format) to start searching contributions." + + flag "--to=", + description: "Date (ISO-8601 format) to stop searching contributions." + + comma_array "--repos=", + description: "The Homebrew repositories to search for contributions in. " \ + "Comma separated. Supported repos: #{SUPPORTED_REPOS.join(", ")}." + + named_args :none + end + end + + sig { returns(NilClass) } + def contributions + args = contributions_args.parse + + return ofail "`--repos` and `--email` are required." if !args[:repos] || !args[:email] + + commits = 0 + coauthorships = 0 + + args[:repos].each do |repo| + repo_location = find_repo_path_for_repo(repo) + unless repo_location + return ofail "Couldn't find location for #{repo}. Do you have it tapped, or is there a typo? " \ + "We only support #{SUPPORTED_REPOS.join(", ")} repos so far." + end + + commits += git_log_cmd("author", repo_location, args) + coauthorships += git_log_cmd("coauthorships", repo_location, args) + end + + sentence = "Person #{args[:email]} directly authored #{commits} commits" + sentence += " and co-authored #{coauthorships} commits" + sentence += " to #{args[:repos].join(", ")}" + sentence += if args[:from] && args[:to] + " between #{args[:from]} and #{args[:to]}" + elsif args[:from] + " after #{args[:from]}" + elsif args[:to] + " before #{args[:to]}" + else + " in all time" + end + sentence += "." + + puts sentence + end + + sig { params(repo: String).returns(T.nilable(String)) } + def find_repo_path_for_repo(repo) + case repo + when "brew" + HOMEBREW_REPOSITORY + when "core" + "#{HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-core" + when "cask" + "#{HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-cask" + when "bundle" + "#{HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-bundle" + end + end + + sig { params(kind: String, repo_location: String, args: Homebrew::CLI::Args).returns(Integer) } + def git_log_cmd(kind, repo_location, args) + cmd = "git -C #{repo_location} log --oneline" + cmd += " --author=#{args[:email]}" if kind == "author" + cmd += " --format='%(trailers:key=Co-authored-by:)'" if kind == "coauthorships" + cmd += " --before=#{args[:to]}" if args[:to] + cmd += " --after=#{args[:from]}" if args[:from] + cmd += " | grep #{args[:email]}" if kind == "coauthorships" + + `#{cmd} | wc -l`.strip.to_i + end +end diff --git a/Library/Homebrew/test/dev-cmd/contributions_spec.rb b/Library/Homebrew/test/dev-cmd/contributions_spec.rb new file mode 100644 index 0000000000..bd60333dc0 --- /dev/null +++ b/Library/Homebrew/test/dev-cmd/contributions_spec.rb @@ -0,0 +1,8 @@ +# typed: false +# frozen_string_literal: true + +require "cmd/shared_examples/args_parse" + +describe "brew contributions" do + it_behaves_like "parseable arguments" +end