1#! /usr/bin/python 2# 3# CDDL HEADER START 4# 5# The contents of this file are subject to the terms of the 6# Common Development and Distribution License (the "License"). 7# You may not use this file except in compliance with the License. 8# 9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10# or http://www.opensolaris.org/os/licensing. 11# See the License for the specific language governing permissions 12# and limitations under the License. 13# 14# When distributing Covered Code, include this CDDL HEADER in each 15# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16# If applicable, add the following below this CDDL HEADER, with the 17# fields enclosed by brackets "[]" replaced with your own identifying 18# information: Portions Copyright [yyyy] [name of copyright owner] 19# 20# CDDL HEADER END 21# 22 23# 24# Copyright 2009 Sun Microsystems, Inc. All rights reserved. 25# Use is subject to license terms. 26# 27 28# Copyright 2007, 2010 Richard Lowe 29# Copyright 2019 OmniOS Community Edition (OmniOSce) Association. 30# Copyright 2024 Bill Sommerfeld 31 32# 33# Check delta comments: 34# - Have the correct form. 35# - Have a synopsis matching that of the bug 36# - Appear only once. 37# - Do not contain common spelling errors. 38# 39 40import re, sys 41from onbld.Checks.DbLookups import BugDB 42from onbld.Checks.SpellCheck import spellcheck_line 43 44 45bugre = re.compile(r'^(\d{2,7}) (.*)$') 46 47 48def isBug(comment): 49 return bugre.match(comment) 50 51def changeid_present(comments): 52 if len(comments) < 3: 53 return False 54 if comments[-2] != '': 55 return False 56 if re.match('^Change-Id: I[0-9a-f]+', comments[-1]): 57 return True 58 return False 59 60def comchk(comments, check_db=True, output=sys.stderr, bugs=None): 61 '''Validate checkin comments against ON standards. 62 63 Comments must be a list of one-line comments, with no trailing 64 newline. 65 66 If check_db is True (the default), validate bug synopses against the 67 databases. 68 69 Error messages intended for the user are written to output, 70 which defaults to stderr 71 ''' 72 bugnospcre = re.compile(r'^(\d{2,7})([^ ].*)') 73 ignorere = re.compile(r'^(' + 74 r'Portions contributed by|' + 75 r'Imported[ -]from|' + 76 r'Contributed by|' + 77 r'Reviewed[ -]by|' + 78 r'Approved[ -]by|' + 79 r'back[ -]?out)' + 80 r'[: ]') 81 82 errors = { 'bugnospc': [], 83 'changeid': [], 84 'mutant': [], 85 'dup': [], 86 'nomatch': [], 87 'nonexistent': [], 88 'spelling': [] } 89 if bugs is None: 90 bugs = {} 91 newbugs = set() 92 ret = 0 93 blanks = False 94 95 if changeid_present(comments): 96 comments = comments[:-2] 97 errors['changeid'].append('Change Id present') 98 99 lineno = 0 100 for com in comments: 101 lineno += 1 102 103 # Our input must be newline-free, comments are line-wise. 104 if com.find('\n') != -1: 105 raise ValueError("newline in comment '%s'" % com) 106 107 # Ignore valid comments we can't check 108 if ignorere.search(com): 109 continue 110 111 if not com or com.isspace(): 112 blanks = True 113 continue 114 115 for err in spellcheck_line(com): 116 errors['spelling'].append( 117 'comment line {} - {}'.format(lineno, err)) 118 119 match = bugre.search(com) 120 if match: 121 (bugid, synopsis) = match.groups() 122 bugs.setdefault(bugid, []).append(synopsis) 123 newbugs.add(bugid) 124 continue 125 126 # 127 # Bugs missing a space after the ID are still bugs 128 # for the purposes of the duplicate ID and synopsis 129 # checks. 130 # 131 match = bugnospcre.search(com) 132 if match: 133 (bugid, synopsis) = match.groups() 134 bugs.setdefault(bugid, []).append(synopsis) 135 newbugs.add(bugid) 136 errors['bugnospc'].append(com) 137 continue 138 139 # Anything else is bogus 140 errors['mutant'].append(com) 141 142 if len(bugs) > 0 and check_db: 143 bugdb = BugDB() 144 results = bugdb.lookup(list(bugs.keys())) 145 146 for crid in sorted(newbugs): 147 insts = bugs[crid] 148 if len(insts) > 1: 149 errors['dup'].append(crid) 150 151 if not check_db: 152 continue 153 154 if crid not in results: 155 errors['nonexistent'].append(crid) 156 continue 157 158 # 159 # For each synopsis, compare the real synopsis with 160 # that in the comments, allowing for possible '(fix 161 # stuff)'-like trailing text 162 # 163 for entered in insts: 164 synopsis = results[crid]["synopsis"] 165 if not re.search(r'^' + re.escape(synopsis) + 166 r'( \([^)]+\))?$', entered): 167 errors['nomatch'].append([crid, synopsis, 168 entered]) 169 170 171 if blanks: 172 output.write("WARNING: Blank line(s) in comments\n") 173 ret = 1 174 175 if errors['dup']: 176 ret = 1 177 output.write("These IDs appear more than once in your " 178 "comments:\n") 179 for err in errors['dup']: 180 output.write(" %s\n" % err) 181 182 if errors['bugnospc']: 183 ret = 1 184 output.write("These bugs are missing a single space following " 185 "the ID:\n") 186 for com in errors['bugnospc']: 187 output.write(" %s\n" % com) 188 189 if errors['changeid']: 190 ret = 1 191 output.write("NOTE: Change-Id present in comment\n") 192 193 if errors['mutant']: 194 ret = 1 195 output.write("These comments are not valid bugs:\n") 196 for com in errors['mutant']: 197 output.write(" %s\n" % com) 198 199 if errors['nonexistent']: 200 ret = 1 201 output.write("These bugs were not found in the databases:\n") 202 for id in errors['nonexistent']: 203 output.write(" %s\n" % id) 204 205 if errors['nomatch']: 206 ret = 1 207 output.write("These bug synopses don't match " 208 "the database entries:\n") 209 for err in errors['nomatch']: 210 output.write("Synopsis of %s is wrong:\n" % err[0]) 211 output.write(" should be: '%s'\n" % err[1]) 212 output.write(" is: '%s'\n" % err[2]) 213 214 if errors['spelling']: 215 ret = 1 216 output.write("Spellcheck:\n") 217 for err in errors['spelling']: 218 output.write('{}\n'.format(err)) 219 220 return ret 221