gitlab-org--gitlab-foss/danger/roulette/Dangerfile
Sean McGivern 28531ab436 Pick reviewers based on branch name
Change reviewer roulette to always pick the same reviewers for the same
branch name. We do this by:

1. Making the branch name 'canonical' across CE and EE by stripping a
   leading 'ce-' or 'ee-' and a trailing '-ce' or '-ee'. If people are
   following our branch naming guidelines, this should give the same
   branch name in both repos.
2. Converting the branch name to a stable integer by taking the integer
   form of its MD5.
3. Passing that integer as a seed to Ruby's `Random` class, which 'may
   be used to ensure repeatable sequences of pseudo-random numbers
   between different runs of the program' (from the Ruby documentation).

The upshot is that the same branch name (in CE and EE) should always
pick the same reviewers, and those should be evenly distributed across
the set of possible reviewers due to the use of MD5.
2019-04-08 11:56:17 +01:00

100 lines
3.5 KiB
Ruby

# frozen_string_literal: true
require 'digest/md5'
MESSAGE = <<MARKDOWN
## Reviewer roulette
Changes that require review have been detected! A merge request is normally
reviewed by both a reviewer and a maintainer in its primary category (e.g.
~frontend or ~backend), and by a maintainer in all other categories.
MARKDOWN
CATEGORY_TABLE_HEADER = <<MARKDOWN
To spread load more evenly across eligible reviewers, Danger has randomly picked
a candidate for each review slot. Feel free to override this selection if you
think someone else would be better-suited, or the chosen person is unavailable.
Once you've decided who will review this merge request, mention them as you
normally would! Danger does not (yet?) automatically notify them for you.
| Category | Reviewer | Maintainer |
| -------- | -------- | ---------- |
MARKDOWN
UNKNOWN_FILES_MESSAGE = <<MARKDOWN
These files couldn't be categorised, so Danger was unable to suggest a reviewer.
Please consider creating a merge request to
[add support](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/danger/helper.rb)
for them.
MARKDOWN
def spin(team, project, category, branch_name)
rng = Random.new(Digest::MD5.hexdigest(branch_name).to_i(16))
reviewers = team.select { |member| member.reviewer?(project, category) }
traintainers = team.select { |member| member.traintainer?(project, category) }
maintainers = team.select { |member| member.maintainer?(project, category) }
# TODO: filter out people who are currently not in the office
# https://gitlab.com/gitlab-org/gitlab-ce/issues/57652
#
# TODO: take CODEOWNERS into account?
# https://gitlab.com/gitlab-org/gitlab-ce/issues/57653
# Make traintainers have triple the chance to be picked as a reviewer
reviewer = (reviewers + traintainers + traintainers).sample(random: rng)
maintainer = maintainers.sample(random: rng)
"| #{helper.label_for_category(category)} | #{reviewer&.markdown_name} | #{maintainer&.markdown_name} |"
end
def build_list(items)
list = items.map { |filename| "* `#{filename}`" }.join("\n")
if items.size > 10
"\n<details>\n\n#{list}\n\n</details>"
else
list
end
end
changes = helper.changes_by_category
# Ignore any files that are known but uncategoried. Prompt for any unknown files
changes.delete(:none)
categories = changes.keys - [:unknown]
# Single codebase MRs are reviewed using a slightly different process, so we
# disable the review roulette for such MRs.
# CSS Clean up MRs are reviewed using a slightly different process, so we
# disable the review roulette for such MRs.
if changes.any? && !gitlab.mr_labels.include?('single codebase') && !gitlab.mr_labels.include?('CSS cleanup')
# Strip leading and trailing CE/EE markers
canonical_branch_name = gitlab
.mr_json['source_branch']
.gsub(/^[ce]e-/, '')
.gsub(/-[ce]e$/, '')
team =
begin
helper.project_team
rescue => err
warn("Reviewer roulette failed to load team data: #{err.message}")
[]
end
# Exclude the MR author from the team for selection purposes
team.delete_if { |teammate| teammate.username == gitlab.mr_author }
project = helper.project_name
unknown = changes.fetch(:unknown, [])
rows = categories.map { |category| spin(team, project, category, canonical_branch_name) }
markdown(MESSAGE)
markdown(CATEGORY_TABLE_HEADER + rows.join("\n")) unless rows.empty?
markdown(UNKNOWN_FILES_MESSAGE + build_list(unknown)) unless unknown.empty?
end