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'Contributed by|' + 76 r'Reviewed[ -]by|' + 77 r'Approved[ -]by|' + 78 r'back[ -]?out)' + 79 r'[: ]') 80 81 errors = { 'bugnospc': [], 82 'changeid': [], 83 'mutant': [], 84 'dup': [], 85 'nomatch': [], 86 'nonexistent': [], 87 'spelling': [] } 88 if bugs is None: 89 bugs = {} 90 newbugs = set() 91 ret = 0 92 blanks = False 93 94 if changeid_present(comments): 95 comments = comments[:-2] 96 errors['changeid'].append('Change Id present') 97 98 lineno = 0 99 for com in comments: 100 lineno += 1 101 102 # Our input must be newline-free, comments are line-wise. 103 if com.find('\n') != -1: 104 raise ValueError("newline in comment '%s'" % com) 105 106 # Ignore valid comments we can't check 107 if ignorere.search(com): 108 continue 109 110 if not com or com.isspace(): 111 blanks = True 112 continue 113 114 for err in spellcheck_line(com): 115 errors['spelling'].append( 116 'comment line {} - {}'.format(lineno, err)) 117 118 match = bugre.search(com) 119 if match: 120 (bugid, synopsis) = match.groups() 121 bugs.setdefault(bugid, []).append(synopsis) 122 newbugs.add(bugid) 123 continue 124 125 # 126 # Bugs missing a space after the ID are still bugs 127 # for the purposes of the duplicate ID and synopsis 128 # checks. 129 # 130 match = bugnospcre.search(com) 131 if match: 132 (bugid, synopsis) = match.groups() 133 bugs.setdefault(bugid, []).append(synopsis) 134 newbugs.add(bugid) 135 errors['bugnospc'].append(com) 136 continue 137 138 # Anything else is bogus 139 errors['mutant'].append(com) 140 141 if len(bugs) > 0 and check_db: 142 bugdb = BugDB() 143 results = bugdb.lookup(list(bugs.keys())) 144 145 for crid in sorted(newbugs): 146 insts = bugs[crid] 147 if len(insts) > 1: 148 errors['dup'].append(crid) 149 150 if not check_db: 151 continue 152 153 if crid not in results: 154 errors['nonexistent'].append(crid) 155 continue 156 157 # 158 # For each synopsis, compare the real synopsis with 159 # that in the comments, allowing for possible '(fix 160 # stuff)'-like trailing text 161 # 162 for entered in insts: 163 synopsis = results[crid]["synopsis"] 164 if not re.search(r'^' + re.escape(synopsis) + 165 r'( \([^)]+\))?$', entered): 166 errors['nomatch'].append([crid, synopsis, 167 entered]) 168 169 170 if blanks: 171 output.write("WARNING: Blank line(s) in comments\n") 172 ret = 1 173 174 if errors['dup']: 175 ret = 1 176 output.write("These IDs appear more than once in your " 177 "comments:\n") 178 for err in errors['dup']: 179 output.write(" %s\n" % err) 180 181 if errors['bugnospc']: 182 ret = 1 183 output.write("These bugs are missing a single space following " 184 "the ID:\n") 185 for com in errors['bugnospc']: 186 output.write(" %s\n" % com) 187 188 if errors['changeid']: 189 ret = 1 190 output.write("NOTE: Change-Id present in comment\n") 191 192 if errors['mutant']: 193 ret = 1 194 output.write("These comments are not valid bugs:\n") 195 for com in errors['mutant']: 196 output.write(" %s\n" % com) 197 198 if errors['nonexistent']: 199 ret = 1 200 output.write("These bugs were not found in the databases:\n") 201 for id in errors['nonexistent']: 202 output.write(" %s\n" % id) 203 204 if errors['nomatch']: 205 ret = 1 206 output.write("These bug synopses don't match " 207 "the database entries:\n") 208 for err in errors['nomatch']: 209 output.write("Synopsis of %s is wrong:\n" % err[0]) 210 output.write(" should be: '%s'\n" % err[1]) 211 output.write(" is: '%s'\n" % err[2]) 212 213 if errors['spelling']: 214 ret = 1 215 output.write("Spellcheck:\n") 216 for err in errors['spelling']: 217 output.write('{}\n'.format(err)) 218 219 return ret 220