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 }