Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Steinhardt <psteinhardt@gitlab.com>2021-12-17 09:56:50 +0300
committerPatrick Steinhardt <psteinhardt@gitlab.com>2021-12-17 11:01:09 +0300
commit0fa3cb8bdb7b17a6986494ad633c1b5ba2e5c6a7 (patch)
tree5baf37054ef693a10cd546b2cb796ce43261def3 /danger/rules
parent61471fff6a262bc2c9c5270016f8a677e48638bb (diff)
danger: Move rules into subdirectory
It's best practice for Danger rules to live in a separate "rules/" directory. Let's move them there, which also allows us to use globs to import all rules.
Diffstat (limited to 'danger/rules')
-rw-r--r--danger/rules/changelog/Dangerfile91
-rw-r--r--danger/rules/labels/Dangerfile48
-rw-r--r--danger/rules/merge_request/Dangerfile13
-rw-r--r--danger/rules/milestones/Dangerfile23
-rw-r--r--danger/rules/roulette/Dangerfile74
5 files changed, 249 insertions, 0 deletions
diff --git a/danger/rules/changelog/Dangerfile b/danger/rules/changelog/Dangerfile
new file mode 100644
index 000000000..e1079b78a
--- /dev/null
+++ b/danger/rules/changelog/Dangerfile
@@ -0,0 +1,91 @@
+require 'yaml'
+
+def lint_commit(commit)
+ trailer = commit.message.match(/^(?<name>Changelog):\s*(?<category>.+)$/i)
+
+ return :missing if trailer.nil? || trailer[:category].nil?
+
+ name = trailer[:name]
+
+ unless name == 'Changelog'
+ self.fail(
+ "The changelog trailer for commit #{commit.sha} must be `Changelog` (starting with a capital C), not `#{name}`"
+ )
+
+ return :invalid
+ end
+
+ category = trailer[:category]
+
+ return :valid if CATEGORIES.include?(category)
+
+ self.fail(
+ "Commit #{commit.sha} uses an invalid changelog category: #{category}"
+ )
+
+ :invalid
+end
+
+def presented_no_changelog_labels
+ NO_CHANGELOG_LABELS.map { |label| %(~"#{label}") }.join(', ')
+end
+
+NO_CHANGELOG_LABELS = [
+ 'documentation',
+ 'type::tooling',
+ 'tooling::pipelines',
+ 'tooling::workflow',
+ 'ci-build',
+ 'meta'
+].freeze
+
+CATEGORIES = YAML
+ .load_file(File.expand_path('../../../.gitlab/changelog_config.yml', __dir__))
+ .fetch('categories')
+ .keys
+ .freeze
+
+SEE_DOC = "See [the documentation](https://docs.gitlab.com/ee/development/changelog.html).".freeze
+
+CHANGELOG_MISSING = <<~MSG.freeze
+**[CHANGELOG missing](https://docs.gitlab.com/ee/development/changelog.html).**
+
+To ceate a changelog, annotate one or more commits with the `Changelog` Git
+trailer. If you want to annotate the latest commit, you can do so using `git
+commit --amend`. If you want to annotate older or multiple commits, you need to
+do so using `git rebase -i`.
+
+When adding the trailer, you can use the following values:
+
+- #{CATEGORIES.join("\n- ")}
+
+For example:
+
+```
+This is the subject of your commit.
+
+This would be the body of your commit containing some extra details.
+
+Changelog: added
+```
+
+If your merge request doesn't warrant a CHANGELOG entry, consider adding any of
+the #{presented_no_changelog_labels} labels.
+
+#{SEE_DOC}
+MSG
+
+changelog_needed = (gitlab.mr_labels & NO_CHANGELOG_LABELS).empty?
+
+if changelog_needed
+ checked = 0
+
+ git.commits.each do |commit|
+ case lint_commit(commit)
+ when :valid, :invalid
+ checked += 1
+ end
+ end
+
+ warn(CHANGELOG_MISSING) if checked.zero?
+end
diff --git a/danger/rules/labels/Dangerfile b/danger/rules/labels/Dangerfile
new file mode 100644
index 000000000..6bfee7178
--- /dev/null
+++ b/danger/rules/labels/Dangerfile
@@ -0,0 +1,48 @@
+def changelog_entry
+ @changelog_entry ||=
+ begin
+ file = git.added_files.find { |path| path =~ %r{\Achangelogs/unreleased/} }
+
+ YAML.safe_load(File.read(file))
+ rescue
+ # The change log danger file will handle this
+ {}
+ end
+end
+
+required_labels = %w[devops::create]
+required_labels << "group::gitaly" if GITALY_TEAM.include?(gitlab.mr_author)
+
+TYPE_TO_LABEL = {
+ 'added' => %w[type::feature feature::addition],
+ 'fixed' => ['type::bug'],
+ 'changed' => ['type::maintenance'],
+ 'deprecated' => ['type::maintenance'],
+ 'security' => ['security'],
+ 'removed' => ['type::maintenance'],
+ 'performance' => %w[type::maintenance performance],
+ 'other' => ['type::tooling'],
+ nil => []
+}
+
+INHERITABLE_LABELS = TYPE_TO_LABEL.values.flatten + %w[Deliverable]
+
+def inherited_labels
+ gitlab.api
+ .merge_request_closes_issues( gitlab.mr_json['project_id'], gitlab.mr_json['iid'])
+ .flat_map { |i| i.labels }
+ .compact
+ .uniq
+ .select { |label| INHERITABLE_LABELS.include?(label) }
+end
+
+required_labels.concat(TYPE_TO_LABEL[changelog_entry["type"]])
+
+mr_labels = gitlab.mr_labels | required_labels | inherited_labels
+gitlab.api.update_merge_request(
+ gitlab.mr_json['project_id'],
+ gitlab.mr_json['iid'],
+ labels: mr_labels.join(",")
+)
+
+# vim: ft=ruby
diff --git a/danger/rules/merge_request/Dangerfile b/danger/rules/merge_request/Dangerfile
new file mode 100644
index 000000000..b9c81d2af
--- /dev/null
+++ b/danger/rules/merge_request/Dangerfile
@@ -0,0 +1,13 @@
+unless /^([[:alnum:]][[:lower:]]+: )?[[:upper:]]/ =~ gitlab.mr_title
+ warn("Please capitalize the merge request title")
+end
+
+if gitlab.mr_body.empty?
+ fail("Please provide a merge request description")
+end
+
+if gitlab.mr_title.length > 72
+ warn "The title of this merge requests it too long"
+end
+
+# vim: ft=ruby
diff --git a/danger/rules/milestones/Dangerfile b/danger/rules/milestones/Dangerfile
new file mode 100644
index 000000000..ce74bbb87
--- /dev/null
+++ b/danger/rules/milestones/Dangerfile
@@ -0,0 +1,23 @@
+unless gitlab.mr_json["milestone"]
+ issues = gitlab.api.merge_request_closes_issues(gitlab.mr_json['project_id'], gitlab.mr_json['iid'])
+
+ milestone_id = issues
+ .map { |i| i.milestone }
+ .compact
+ .reject { |m| m.state == "closed" || m.due_date.nil? }
+ .sort_by { |m| m.due_date }
+ .first
+ &.id
+
+ if milestone_id
+ gitlab.api.update_merge_request(
+ gitlab.mr_json['project_id'],
+ gitlab.mr_json['iid'],
+ milestone_id: milestone_id
+ )
+ else
+ warn "No milestone was set, nor could it be detected from the issues this merge request closes."
+ end
+end
+
+# vim: ft=ruby
diff --git a/danger/rules/roulette/Dangerfile b/danger/rules/roulette/Dangerfile
new file mode 100644
index 000000000..80ee666df
--- /dev/null
+++ b/danger/rules/roulette/Dangerfile
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+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 and by a
+maintainer in all other categories.
+MARKDOWN
+
+CATEGORY_TABLE_HEADER = <<MARKDOWN
+
+To spread load more evenly across eligible reviewers, Danger has picked a candidate for each
+review slot. Feel free to
+[override these selections](https://about.gitlab.com/handbook/engineering/projects/#gitaly)
+if you think someone else would be better-suited
+or use the [GitLab Review Workload Dashboard](https://gitlab-org.gitlab.io/gitlab-roulette/) to find other available reviewers.
+
+To read more on how to use the reviewer roulette, please take a look at the
+[Engineering workflow](https://about.gitlab.com/handbook/engineering/workflow/#basics)
+and [code review guidelines](https://docs.gitlab.com/ee/development/code_review.html).
+Please consider assigning a reviewer or maintainer who is a
+[domain expert](https://about.gitlab.com/handbook/engineering/projects/#gitaly) in the area of the merge request.
+
+Once you've decided who will review this merge request, mention them as you
+normally would! Danger does not automatically notify them for you.
+
+| Category | Reviewer | Maintainer |
+| -------- | -------- | ---------- |
+MARKDOWN
+
+OPTIONAL_REVIEW_TEMPLATE = '%{role} review is optional for %{category}'
+NOT_AVAILABLE_TEMPLATE = 'No %{role} available'
+
+def note_for_spins_role(spins, role)
+ spins.each do |spin|
+ note = note_for_spin_role(spin, role)
+
+ return note if note
+ end
+
+ NOT_AVAILABLE_TEMPLATE % { role: role }
+end
+
+def note_for_spin_role(spin, role)
+ if spin.optional_role == role
+ return OPTIONAL_REVIEW_TEMPLATE % { role: role.capitalize, category: helper.label_for_category(spin.category) }
+ end
+
+ spin.public_send(role)&.markdown_name(author: roulette.team_mr_author) # rubocop:disable GitlabSecurity/PublicSend
+end
+
+def markdown_row_for_spins(category, spins_array)
+ reviewer_note = note_for_spins_role(spins_array, :reviewer)
+ maintainer_note = note_for_spins_role(spins_array, :maintainer)
+
+ "| #{helper.label_for_category(category)} | #{reviewer_note} | #{maintainer_note} |"
+end
+
+changes = project_helper.changes_by_category
+
+if changes.any?
+ categories = changes.keys
+ project = project_helper.project_name
+
+ random_roulette_spins = roulette.spin(project, [nil], timezone_experiment: false)
+
+ rows = random_roulette_spins.map do |spin|
+ markdown_row_for_spins(spin.category, [spin])
+ end
+
+ markdown(MESSAGE)
+ markdown(CATEGORY_TABLE_HEADER + rows.join("\n")) unless rows.empty?
+end