Class: Homebrew::DevCmd::Contributions Private

Inherits:
AbstractCommand show all
Defined in:
dev-cmd/contributions.rb,
sorbet/rbi/dsl/homebrew/dev_cmd/contributions.rbi

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.

Defined Under Namespace

Classes: Args

Constant Summary collapse

PRIMARY_REPOS =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

T.let(%w[
  Homebrew/brew
  Homebrew/homebrew-core
  Homebrew/homebrew-cask
].freeze, T::Array[String])
ALL_REPOS =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

T.let([
  *PRIMARY_REPOS,
  *OFFICIAL_CMD_TAPS.keys,
].freeze, T::Array[String])
CONTRIBUTION_TYPES =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

T.let({
  merged_pr_author:   "merged PR author",
  approved_pr_review: "approved PR reviewer",
  committer:          "commit author or committer",
  coauthor:           "commit coauthor",
}.freeze, T::Hash[Symbol, String])
MAX_COMMITS =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

T.let(1000, Integer)
MAX_PR_SEARCH =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

T.let(100, Integer)

Instance Method Summary collapse

Methods inherited from AbstractCommand

command, command_name, dev_cmd?, #initialize, parser, ruby_cmd?

Methods included from Utils::Output::Mixin

#odebug, #odeprecated, #odie, #odisabled, #ofail, #oh1, #oh1_title, #ohai, #ohai_title, #onoe, #opoo, #opoo_outside_github_actions, #pretty_duration, #pretty_installed, #pretty_outdated, #pretty_uninstalled

Constructor Details

This class inherits a constructor from Homebrew::AbstractCommand

Instance Method Details

#argsHomebrew::DevCmd::Contributions::Args

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.



10
# File 'sorbet/rbi/dsl/homebrew/dev_cmd/contributions.rbi', line 10

def args; end

#runvoid

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.

This method returns an undefined value.



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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'dev-cmd/contributions.rb', line 61

def run
  odie "Cannot get contributions as `$HOMEBREW_NO_GITHUB_API` is set!" if Homebrew::EnvConfig.no_github_api?

  Homebrew.install_bundler_gems!(groups: ["contributions"]) if args.csv?

  require "utils/github"

  results = {}
  grand_totals = {}
  from = args.from.presence || Date.today.prev_year.iso8601
  to = args.to.presence || (Date.today + 1).iso8601
  organisation = nil

  users = if (team = args.team.presence)
    team_sections = team.split("/")
    organisation = team_sections.first.presence
    team_name = team_sections.last.presence
    if team_sections.length != 2 || organisation.nil? || team_name.nil?
      odie "Team must be in the format `organisation/team`!"
    end

    puts "Getting members for #{organisation}/#{team_name}..." if args.verbose?
    GitHub.members_by_team(organisation, team_name).keys
  elsif (users = args.user.presence)
    users
  else
    puts "Getting members for Homebrew/maintainers..." if args.verbose?
    GitHub.members_by_team("Homebrew", "maintainers").keys
  end

  repositories = if (org = organisation.presence) || (org = args.organisation.presence)
    organisation = org
    puts "Getting repositories for #{organisation}..." if args.verbose?
    GitHub.organisation_repositories(organisation, from, to, args.verbose?)
  elsif (repos = args.repositories.presence) && repos.length == 1 && (first_repository = repos.first)
    case first_repository
    when "primary"
      PRIMARY_REPOS
    when "all"
      ALL_REPOS
    else
      Array(first_repository)
    end
  elsif (repos = args.repositories.presence)
    organisations = repos.map { |repository| repository.split("/").first }.uniq
    odie "All repositories must be under the same user or organisation!" if organisations.length > 1

    repos
  else
    PRIMARY_REPOS
  end
  organisation ||= T.must(repositories.fetch(0).split("/").first)

  users.each do |username|
    # TODO: Using the GitHub username to scan the `git log` undercounts some
    #       contributions as people might not always have configured their Git
    #       committer details to match the ones on GitHub.
    # TODO: Switch to using the GitHub APIs instead of `git log` if
    #       they ever support trailers.
    results[username] = scan_repositories(organisation, repositories, username, from:, to:)
    grand_totals[username] = total(results[username])

    search_types = [:merged_pr_author, :approved_pr_review].freeze
    greater_than_total = T.let(false, T::Boolean)
    contributions = CONTRIBUTION_TYPES.keys.filter_map do |type|
      type_count = grand_totals[username][type]
      next if type_count.nil? || type_count.zero?

      count_prefix = ""
      if (search_types.include?(type) && type_count == MAX_PR_SEARCH) ||
         (type == :committer && type_count == MAX_COMMITS)
        greater_than_total ||= true
        count_prefix = ">="
      end

      pretty_type = CONTRIBUTION_TYPES.fetch(type)
      "#{count_prefix}#{Utils.pluralize("time", type_count, include_count: true)} (#{pretty_type})"
    end
    total = Utils.pluralize("time", grand_totals[username].values.sum, include_count: true)
    total_prefix = ">=" if greater_than_total
    contributions << "#{total_prefix}#{total} (total)"

    contributions_string = [
      "#{username} contributed",
      *contributions.to_sentence,
      "#{time_period(from:, to: args.to)}.",
    ].join(" ")
    if args.csv?
      $stderr.puts contributions_string
    else
      puts contributions_string
    end
  end

  return unless args.csv?

  $stderr.puts
  puts generate_csv(grand_totals)
end