1 /* 2 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package org.openjdk.skara.bots.pr; 24 25 import org.openjdk.skara.host.*; 26 import org.openjdk.skara.jcheck.*; 27 import org.openjdk.skara.jcheck.Check; 28 import org.openjdk.skara.vcs.Hash; 29 30 import java.util.*; 31 import java.util.logging.Logger; 32 import java.util.stream.Collectors; 33 34 class PullRequestCheckIssueVisitor implements IssueVisitor { 35 private final Set<String> messages = new HashSet<>(); 36 private final List<CheckAnnotation> annotations = new LinkedList<>(); 37 private final Set<Check> enabledChecks; 38 private final Set<Class<? extends Check>> failedChecks = new HashSet<>(); 39 40 private boolean readyForReview; 41 42 private final Logger log = Logger.getLogger("org.openjdk.skara.bots.pr"); 43 44 private final Set<Class<? extends Check>> displayedChecks = Set.of( 45 DuplicateIssuesCheck.class, 46 ReviewersCheck.class, 47 WhitespaceCheck.class, 48 IssuesCheck.class 49 ); 50 51 PullRequestCheckIssueVisitor(Set<Check> enabledChecks) { 52 this.enabledChecks = enabledChecks; 53 readyForReview = true; 54 } 55 56 List<String> getMessages() { 57 return new ArrayList<>(messages); 58 } 59 60 Map<String, Boolean> getChecks() { 61 return enabledChecks.stream() 62 .filter(check -> displayedChecks.contains(check.getClass())) 63 .collect(Collectors.toMap(Check::description, 64 check -> !failedChecks.contains(check.getClass()))); 65 } 66 67 List<CheckAnnotation> getAnnotations() { return annotations; } 68 69 boolean isReadyForReview() { 70 return readyForReview; 71 } 72 73 public void visit(DuplicateIssuesIssue e) { 74 var id = e.issue().id(); 75 var other = e.hashes() 76 .stream() 77 .map(Hash::abbreviate) 78 .map(s -> " - " + s) 79 .collect(Collectors.toList()); 80 81 var output = new StringBuilder(); 82 output.append("Issue id ").append(id).append(" is already used in these commits:\n"); 83 other.forEach(h -> output.append(" * ").append(h).append("\n")); 84 messages.add(output.toString()); 85 failedChecks.add(e.check().getClass()); 86 readyForReview = false; 87 } 88 89 @Override 90 public void visit(TagIssue e) { 91 log.fine("ignored: illegal tag name: " + e.tag().name()); 92 } 93 94 @Override 95 public void visit(BranchIssue e) { 96 log.fine("ignored: illegal branch name: " + e.branch().name()); 97 } 98 99 @Override 100 public void visit(SelfReviewIssue e) 101 { 102 messages.add("Self-reviews are not allowed"); 103 failedChecks.add(e.check().getClass()); 104 readyForReview = false; 105 } 106 107 @Override 108 public void visit(TooFewReviewersIssue e) { 109 messages.add(String.format("Too few reviewers found (have %d, need at least %d)", e.numActual(), e.numRequired())); 110 failedChecks.add(e.check().getClass()); 111 } 112 113 @Override 114 public void visit(InvalidReviewersIssue e) { 115 log.fine("ignored: invalid reviewers: " + e.invalid()); 116 } 117 118 @Override 119 public void visit(MergeMessageIssue e) { 120 var hex = e.commit().hash().abbreviate(); 121 log.fine("ignored: " + hex + ": merge commits should only have commit message 'Merge'"); 122 } 123 124 @Override 125 public void visit(HgTagCommitIssue e) { 126 log.fine("ignored: invalid tag commit"); 127 } 128 129 @Override 130 public void visit(CommitterIssue e) { 131 log.fine("ignored: invalid author: " + e.commit().author().name()); 132 } 133 134 @Override 135 public void visit(CommitterNameIssue issue) { 136 log.fine("ignored: invalid committer name"); 137 } 138 139 @Override 140 public void visit(CommitterEmailIssue issue) { 141 log.fine("ignored: invalid committer email"); 142 } 143 144 @Override 145 public void visit(AuthorNameIssue issue) { 146 log.fine("ignored: invalid author name"); 147 } 148 149 @Override 150 public void visit(AuthorEmailIssue issue) { 151 log.fine("ignored: invalid author email"); 152 } 153 154 @Override 155 public void visit(WhitespaceIssue e) { 156 var startColumn = Integer.MAX_VALUE; 157 var endColumn = Integer.MIN_VALUE; 158 var details = new LinkedList<String>(); 159 for (var error : e.errors()) { 160 startColumn = Math.min(error.index(), startColumn); 161 endColumn = Math.max(error.index(), endColumn); 162 details.add("Column " + error.index() + ": " + error.kind().toString()); 163 } 164 165 var annotationBuilder = CheckAnnotationBuilder.create( 166 e.path().toString(), 167 e.row(), 168 e.row(), 169 CheckAnnotationLevel.FAILURE, 170 String.join(" \n", details)); 171 172 if (startColumn < Integer.MAX_VALUE) { 173 annotationBuilder.startColumn(startColumn); 174 } 175 if (endColumn > Integer.MIN_VALUE) { 176 annotationBuilder.endColumn(endColumn); 177 } 178 179 var annotation = annotationBuilder.title("Whitespace error").build(); 180 annotations.add(annotation); 181 182 messages.add("Whitespace errors"); 183 failedChecks.add(e.check().getClass()); 184 readyForReview = false; 185 } 186 187 @Override 188 public void visit(MessageIssue issue) { 189 log.fine("ignored: incorrectly formatted commit message"); 190 } 191 192 @Override 193 public void visit(IssuesIssue issue) { 194 messages.add("The commit message does not reference any issue. To add an issue reference to this PR, " + 195 "edit the title to be of the format <issue number>: <message>."); 196 failedChecks.add(issue.check().getClass()); 197 readyForReview = false; 198 } 199 200 @Override 201 public void visit(ExecutableIssue issue) { 202 messages.add(String.format("Executable files are not allowed (file: %s)", issue.path())); 203 failedChecks.add(issue.check().getClass()); 204 readyForReview = false; 205 } 206 207 @Override 208 public void visit(BlacklistIssue issue) { 209 log.fine("ignored: blacklisted commit"); 210 } 211 }