1############################################################################ 2# Copyright (C) Internet Systems Consortium, Inc. ("ISC") 3# 4# This Source Code Form is subject to the terms of the Mozilla Public 5# License, v. 2.0. If a copy of the MPL was not distributed with this 6# file, You can obtain one at http://mozilla.org/MPL/2.0/. 7# 8# See the COPYRIGHT file distributed with this work for additional 9# information regarding copyright ownership. 10############################################################################ 11 12import re 13 14# Helper functions and variables 15 16def added_lines(target_branch, paths): 17 import subprocess 18 subprocess.check_output(['/usr/bin/git', 'fetch', '--depth', '1', 'origin', 19 target_branch]) 20 diff = subprocess.check_output(['/usr/bin/git', 'diff', 'FETCH_HEAD..', 21 '--'] + paths) 22 added_lines = [] 23 for line in diff.splitlines(): 24 if line.startswith(b'+') and not line.startswith(b'+++'): 25 added_lines.append(line) 26 return added_lines 27 28issue_or_mr_id_regex = re.compile(br'\[(GL [#!]|RT #)[0-9]+\]') 29release_notes_regex = re.compile(r'doc/(arm|notes)/notes-.*\.(rst|xml)') 30 31modified_files = danger.git.modified_files 32mr_labels = danger.gitlab.mr.labels 33target_branch = danger.gitlab.mr.target_branch 34 35############################################################################### 36# COMMIT MESSAGES 37############################################################################### 38# 39# - FAIL if any of the following is true for any commit on the MR branch: 40# 41# * The subject line starts with "fixup!" or "Apply suggestion". 42# 43# * There is no empty line between the subject line and the log message. 44# 45# - WARN if any of the following is true for any commit on the MR branch: 46# 47# * The length of the subject line exceeds 72 characters. 48# 49# * There is no log message present (i.e. commit only has a subject) and the 50# subject line does not contain any of the following strings: "fixup! ", 51# " CHANGES ", " release note". 52# 53# * Any line of the log message is longer than 72 characters. This rule is 54# not evaluated for lines starting with four spaces, which allows long 55# lines to be included in the commit log message by prefixing them with 56# four spaces (useful for pasting compiler warnings, static analyzer 57# messages, log lines, etc.) 58 59for commit in danger.git.commits: 60 message_lines = commit.message.splitlines() 61 subject = message_lines[0] 62 if subject.startswith('fixup!') or subject.startswith('Apply suggestion'): 63 fail('Fixup commits are still present in this merge request. ' 64 'Please squash them before merging.') 65 if len(subject) > 72: 66 warn( 67 f'Subject line for commit {commit.sha} is too long: ' 68 f'```{subject}``` ({len(subject)} > 72 characters).' 69 ) 70 if len(message_lines) > 1 and message_lines[1]: 71 fail(f'No empty line after subject for commit {commit.sha}.') 72 if (len(message_lines) < 3 and 73 'fixup! ' not in subject and 74 ' CHANGES ' not in subject and 75 ' release note' not in subject): 76 warn(f'Please write a log message for commit {commit.sha}.') 77 for line in message_lines[2:]: 78 if len(line) > 72 and not line.startswith(' '): 79 warn( 80 f'Line too long in log message for commit {commit.sha}: ' 81 f'```{line}``` ({len(line)} > 72 characters).' 82 ) 83 84############################################################################### 85# MILESTONE 86############################################################################### 87# 88# FAIL if the merge request is not assigned to any milestone. 89 90if not danger.gitlab.mr.milestone: 91 fail('Please assign this merge request to a milestone.') 92 93############################################################################### 94# VERSION LABELS 95############################################################################### 96# 97# FAIL if any of the following is true for the merge request: 98# 99# * The "Backport" label is set and the number of version labels set is 100# different than 1. (For backports, the version label is used for indicating 101# its target branch. This is a rather ugly attempt to address a UI 102# deficiency - the target branch for each MR is not visible on milestone 103# dashboards.) 104# 105# * Neither the "Backport" label nor any version label is set. (If the merge 106# request is not a backport, version labels are used for indicating 107# backporting preferences.) 108 109backport_label_set = 'Backport' in mr_labels 110version_labels = [l for l in mr_labels if l.startswith('v9.')] 111if backport_label_set and len(version_labels) != 1: 112 fail('The *Backport* label is set for this merge request. ' 113 'Please also set exactly one version label (*v9.x*).') 114if not backport_label_set and not version_labels: 115 fail('If this merge request is a backport, set the *Backport* label and ' 116 'a single version label (*v9.x*) indicating the target branch. ' 117 'If not, set version labels for all targeted backport branches.') 118 119############################################################################### 120# OTHER LABELS 121############################################################################### 122# 123# WARN if any of the following is true for the merge request: 124# 125# * The "Review" label is not set. (It may be intentional, but rarely is.) 126# 127# * The "Review" label is set, but the "LGTM" label is not set. (This aims to 128# remind developers about the need to set the latter on merge requests which 129# passed review.) 130 131if 'Review' not in mr_labels: 132 warn('This merge request does not have the *Review* label set. ' 133 'Please set it if you would like the merge request to be reviewed.') 134elif 'LGTM (Merge OK)' not in mr_labels: 135 warn('This merge request is currently in review. ' 136 'It should not be merged until it is marked with the *LGTM* label.') 137 138############################################################################### 139# 'CHANGES' FILE 140############################################################################### 141# 142# FAIL if any of the following is true: 143# 144# * The merge request does not update the CHANGES file, but it does not have 145# the "No CHANGES" label set. (This attempts to ensure that the author of 146# the MR did not forget about adding a CHANGES entry.) 147# 148# * The merge request updates the CHANGES file, but it has the "No CHANGES" 149# label set. (This attempts to ensure the the "No CHANGES" label is used in 150# a sane way.) 151# 152# * The merge request adds a new CHANGES entry that does not contain any 153# GitLab/RT issue/MR identifiers. 154 155changes_modified = 'CHANGES' in modified_files 156no_changes_label_set = 'No CHANGES' in mr_labels 157if not changes_modified and not no_changes_label_set: 158 fail('This merge request does not modify `CHANGES`. ' 159 'Add a `CHANGES` entry or set the *No CHANGES* label.') 160if changes_modified and no_changes_label_set: 161 fail('This merge request modifies `CHANGES`. ' 162 'Revert `CHANGES` modifications or unset the *No Changes* label.') 163 164changes_added_lines = added_lines(target_branch, ['CHANGES']) 165identifiers_found = filter(issue_or_mr_id_regex.search, changes_added_lines) 166if changes_added_lines and not any(identifiers_found): 167 fail('No valid issue/MR identifiers found in added `CHANGES` entries.') 168 169############################################################################### 170# RELEASE NOTES 171############################################################################### 172# 173# - FAIL if any of the following is true: 174# 175# * The merge request does not update release notes and has the "Release 176# Notes" label set. (This attempts to point out missing release notes.) 177# 178# * The merge request updates release notes but does not have the "Release 179# Notes" label set. (This ensures that merge requests updating release 180# notes can be easily found using the "Release Notes" label.) 181# 182# - WARN if this merge request updates release notes, but no GitLab/RT issue/MR 183# identifiers are found in the lines added to the release notes by this MR. 184 185release_notes_regex = re.compile(r'doc/(arm|notes)/notes-.*\.(rst|xml)') 186release_notes_changed = list(filter(release_notes_regex.match, modified_files)) 187release_notes_label_set = 'Release Notes' in mr_labels 188if not release_notes_changed and release_notes_label_set: 189 fail('This merge request has the *Release Notes* label set. ' 190 'Add a release note or unset the *Release Notes* label.') 191if release_notes_changed and not release_notes_label_set: 192 fail('This merge request modifies release notes. ' 193 'Revert release note modifications or set the *Release Notes* label.') 194 195if release_notes_changed: 196 notes_added_lines = added_lines(target_branch, release_notes_changed) 197 identifiers_found = filter(issue_or_mr_id_regex.search, notes_added_lines) 198 if notes_added_lines and not any(identifiers_found): 199 warn('No valid issue/MR identifiers found in added release notes.') 200