1 package org.openjdk.skara.bots.mlbridge; 2 3 import org.openjdk.skara.email.Email; 4 import org.openjdk.skara.host.*; 5 import org.openjdk.skara.vcs.*; 6 7 import java.net.URI; 8 import java.time.format.DateTimeFormatter; 9 import java.util.Arrays; 10 import java.util.regex.Pattern; 11 import java.util.stream.Collectors; 12 13 class ArchiveMessages { 14 private static String formatCommit(Commit commit) { 15 var ret = new StringBuilder(); 16 var message = commit.message(); 17 if (message.size() == 0) { 18 ret.append("<no commit message found>"); 19 } else { 20 var abbrev = commit.hash().abbreviate(); 21 ret.append(" - ").append(abbrev).append(": ").append(message.get(0).strip()); 22 } 23 return ret.toString(); 24 } 25 26 private static final String infoSeparator = "----------------"; 27 28 private static final Pattern commentPattern = Pattern.compile("<!--.*?-->", 29 Pattern.DOTALL | Pattern.MULTILINE); 30 private static final Pattern cutoffPattern = Pattern.compile("(.*?)<!-- Anything below this marker will be .*? -->", 31 Pattern.DOTALL | Pattern.MULTILINE); 32 private static String filterComments(String body) { 33 var cutoffMatcher = cutoffPattern.matcher(body); 34 if (cutoffMatcher.find()) { 35 body = cutoffMatcher.group(1); 36 } 37 38 var commentMatcher = commentPattern.matcher(body); 39 body = commentMatcher.replaceAll(""); 40 41 body = MarkdownToText.removeFormatting(body); 42 return body.strip(); 43 } 44 45 private static String quoteBody(String body) { 46 return Arrays.stream(body.strip().split("\\R")) 47 .map(line -> line.length() > 0 ? line.charAt(0) == '>' ? ">" + line : "> " + line : "> ") 48 .collect(Collectors.joining("\n")); 49 } 50 51 private static String replyFooter(PullRequestInstance prInstance) { 52 return "PR: " + prInstance.pr().getWebUrl(); 53 } 54 55 // When changing this, ensure that the PR pattern in the notifier still matches 56 static String composeConversation(PullRequestInstance prInstance, URI webrev) { 57 var commitMessages = prInstance.formatCommitMessages(prInstance.baseHash(), prInstance.headHash(), ArchiveMessages::formatCommit); 58 var filteredBody = filterComments(prInstance.pr().getBody()); 59 if (filteredBody.isEmpty()) { 60 filteredBody = prInstance.pr().getTitle().strip(); 61 } 62 var issueString = prInstance.issueUrl().map(url -> " Issue: " + url + "\n").orElse(""); 63 return filteredBody + "\n\n" + 64 infoSeparator + "\n\n" + 65 "Commits:\n" + 66 commitMessages + "\n\n" + 67 "Changes: " + prInstance.changeUrl() + "\n" + 68 " Webrev: " + webrev.toString() + "\n" + 69 issueString + 70 " Stats: " + prInstance.stats(prInstance.baseHash(), prInstance.headHash()) + "\n" + 71 " Patch: " + prInstance.diffUrl() + "\n" + 72 " Fetch: " + prInstance.fetchCommand() + "\n\n" + 73 replyFooter(prInstance); 74 } 75 76 static String composeRebaseComment(PullRequestInstance prInstance, URI fullWebrev) { 77 var commitMessages = prInstance.formatCommitMessages(prInstance.baseHash(), prInstance.headHash(), ArchiveMessages::formatCommit); 78 var issueString = prInstance.issueUrl().map(url -> " Issue: " + url + "\n").orElse(""); 79 return "The pull request has been updated with a complete new set of changes (possibly due to a rebase).\n\n" + 80 infoSeparator + "\n\n" + 81 "Commits:\n" + 82 commitMessages + "\n\n" + 83 "Changes: " + prInstance.changeUrl() + "\n" + 84 " Webrev: " + fullWebrev.toString() + "\n" + 85 issueString + 86 " Stats: " + prInstance.stats(prInstance.baseHash(), prInstance.headHash()) + "\n" + 87 " Patch: " + prInstance.diffUrl() + "\n" + 88 " Fetch: " + prInstance.fetchCommand() + "\n\n" + 89 replyFooter(prInstance); } 90 91 static String composeIncrementalComment(Hash lastHead, PullRequestInstance prInstance, URI fullWebrev, URI incrementalWebrev) { 92 var newCommitMessages = prInstance.formatCommitMessages(lastHead, prInstance.headHash(), ArchiveMessages::formatCommit); 93 var issueString = prInstance.issueUrl().map(url -> " Issue: " + url + "\n").orElse(""); 94 return "The pull request has been updated with additional changes.\n\n" + 95 infoSeparator + "\n\n" + 96 "Added commits:\n" + 97 newCommitMessages + "\n\n" + 98 "Changes:\n" + 99 " - all: " + prInstance.pr().getWebUrl() + "/files\n" + 100 " - new: " + prInstance.changeUrl(lastHead, prInstance.headHash()) + "\n\n" + 101 "Webrevs:\n" + 102 " - full: " + fullWebrev.toString() + "\n" + 103 " - incr: " + incrementalWebrev.toString() + "\n\n" + 104 issueString + 105 " Stats: " + prInstance.stats(lastHead, prInstance.headHash()) + "\n" + 106 " Patch: " + prInstance.diffUrl() + "\n" + 107 " Fetch: " + prInstance.fetchCommand() + "\n\n" + 108 replyFooter(prInstance); 109 } 110 111 private static String filterParentBody(Email parent, PullRequestInstance prInstance) { 112 var parentFooter = ArchiveMessages.replyFooter(prInstance); 113 var filteredParentBody = parent.body().strip(); 114 if (filteredParentBody.endsWith(parentFooter)) { 115 return filteredParentBody.substring(0, filteredParentBody.length() - parentFooter.length()).strip(); 116 } else { 117 return filteredParentBody; 118 } 119 } 120 121 static String composeReply(Email parent, String body, PullRequestInstance prInstance) { 122 return "On " + parent.date().format(DateTimeFormatter.RFC_1123_DATE_TIME) + ", " + parent.author().toString() + " wrote:\n" + 123 "\n" + 124 quoteBody(filterParentBody(parent, prInstance)) + 125 "\n\n" + 126 filterComments(body) + 127 "\n\n" + 128 replyFooter(prInstance); 129 } 130 131 static String composeCombinedReply(Email parent, String body, PullRequestInstance prInstance) { 132 return filterParentBody(parent, prInstance) + 133 "\n\n" + 134 filterComments(body) + 135 "\n\n" + 136 replyFooter(prInstance); 137 } 138 139 static String reviewCommentBody(String body, Review.Verdict verdict, String user, String role) { 140 var result = new StringBuilder(filterComments(body)); 141 if (verdict != Review.Verdict.NONE) { 142 if (result.length() > 0) { 143 result.append("\n\n"); 144 result.append(infoSeparator); 145 result.append("\n\n"); 146 } 147 if (verdict == Review.Verdict.APPROVED) { 148 result.append("Approved"); 149 } else { 150 result.append("Disapproved"); 151 } 152 result.append(" by "); 153 result.append(user); 154 result.append(" ("); 155 result.append(role); 156 result.append(")."); 157 } 158 return result.toString(); 159 } 160 }