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 return filteredBody + "\n\n" + 63 infoSeparator + "\n\n" + 64 "Commits:\n" + 65 commitMessages + "\n\n" + 66 "Changes: " + prInstance.changeUrl() + "\n" + 67 " Webrev: " + webrev.toString() + "\n" + 68 " Stats: " + prInstance.stats(prInstance.baseHash(), prInstance.headHash()) + "\n" + 69 " Patch: " + prInstance.diffUrl() + "\n" + 70 " Fetch: " + prInstance.fetchCommand() + "\n\n" + 71 replyFooter(prInstance); 72 } 73 74 static String composeRebaseComment(PullRequestInstance prInstance, URI fullWebrev) { 75 var commitMessages = prInstance.formatCommitMessages(prInstance.baseHash(), prInstance.headHash(), ArchiveMessages::formatCommit); 76 return "The pull request has been updated with a complete new set of changes (possibly due to a rebase).\n\n" + 77 infoSeparator + "\n\n" + 78 "Commits:\n" + 79 commitMessages + "\n\n" + 80 "Changes: " + prInstance.changeUrl() + "\n" + 81 " Webrev: " + fullWebrev.toString() + "\n" + 82 " Stats: " + prInstance.stats(prInstance.baseHash(), prInstance.headHash()) + "\n" + 83 " Patch: " + prInstance.diffUrl() + "\n" + 84 " Fetch: " + prInstance.fetchCommand() + "\n\n" + 85 replyFooter(prInstance); } 86 87 static String composeIncrementalComment(Hash lastHead, PullRequestInstance prInstance, URI fullWebrev, URI incrementalWebrev) { 88 var newCommitMessages = prInstance.formatCommitMessages(lastHead, prInstance.headHash(), ArchiveMessages::formatCommit); 89 return "The pull request has been updated with additional changes.\n\n" + 90 infoSeparator + "\n\n" + 91 "Added commits:\n" + 92 newCommitMessages + "\n\n" + 93 "Changes:\n" + 94 " - all: " + prInstance.pr().getWebUrl() + "/files\n" + 95 " - new: " + prInstance.changeUrl(lastHead, prInstance.headHash()) + "\n\n" + 96 "Webrevs:\n" + 97 " - full: " + fullWebrev.toString() + "\n" + 98 " - incr: " + incrementalWebrev.toString() + "\n\n" + 99 " Stats: " + prInstance.stats(lastHead, prInstance.headHash()) + "\n" + 100 " Patch: " + prInstance.diffUrl() + "\n" + 101 " Fetch: " + prInstance.fetchCommand() + "\n\n" + 102 replyFooter(prInstance); 103 } 104 105 private static String filterParentBody(Email parent, PullRequestInstance prInstance) { 106 var parentFooter = ArchiveMessages.replyFooter(prInstance); 107 var filteredParentBody = parent.body().strip(); 108 if (filteredParentBody.endsWith(parentFooter)) { 109 return filteredParentBody.substring(0, filteredParentBody.length() - parentFooter.length()).strip(); 110 } else { 111 return filteredParentBody; 112 } 113 } 114 115 static String composeReply(Email parent, String body, PullRequestInstance prInstance) { 116 return "On " + parent.date().format(DateTimeFormatter.RFC_1123_DATE_TIME) + ", " + parent.author().toString() + " wrote:\n" + 117 "\n" + 118 quoteBody(filterParentBody(parent, prInstance)) + 119 "\n\n" + 120 filterComments(body) + 121 "\n\n" + 122 replyFooter(prInstance); 123 } 124 125 static String composeCombinedReply(Email parent, String body, PullRequestInstance prInstance) { 126 return filterParentBody(parent, prInstance) + 127 "\n\n" + 128 filterComments(body) + 129 "\n\n" + 130 replyFooter(prInstance); 131 } 132 133 static String reviewCommentBody(String body, Review.Verdict verdict, String user, String role) { 134 var result = new StringBuilder(filterComments(body)); 135 if (verdict != Review.Verdict.NONE) { 136 if (result.length() > 0) { 137 result.append("\n\n"); 138 result.append(infoSeparator); 139 result.append("\n\n"); 140 } 141 if (verdict == Review.Verdict.APPROVED) { 142 result.append("Approved"); 143 } else { 144 result.append("Disapproved"); 145 } 146 result.append(" by "); 147 result.append(user); 148 result.append(" ("); 149 result.append(role); 150 result.append(")."); 151 } 152 return result.toString(); 153 } 154 }