| package com.googlesource.gerrit.plugins.hooks.util; |
| |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Sets; |
| import com.google.gerrit.server.config.GerritServerConfig; |
| import com.google.inject.Inject; |
| |
| import com.googlesource.gerrit.plugins.hooks.its.ItsName; |
| |
| import org.apache.commons.lang.StringUtils; |
| import org.eclipse.jgit.lib.Config; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| public class IssueExtractor { |
| private static final Logger log = LoggerFactory.getLogger( |
| IssueExtractor.class); |
| |
| private final Config gerritConfig; |
| private final String itsName; |
| private final CommitMessageFetcher commitMessageFetcher; |
| |
| @Inject |
| IssueExtractor(@GerritServerConfig Config gerritConfig, |
| @ItsName String itsName, CommitMessageFetcher commitMessageFetcher) { |
| this.gerritConfig = gerritConfig; |
| this.itsName = itsName; |
| this.commitMessageFetcher = commitMessageFetcher; |
| } |
| |
| /** |
| * Gets issue ids from a string. |
| * |
| * @param haystack String to extract issue ids from |
| * @return array of {@link Strings}. Each String being a found issue id. |
| */ |
| public String[] getIssueIds(String haystack) { |
| Pattern pattern = getPattern(); |
| if (pattern == null) return new String[] {}; |
| |
| log.debug("Matching '" + haystack + "' against " + pattern.pattern()); |
| |
| Set<String> issues = Sets.newHashSet(); |
| Matcher matcher = pattern.matcher(haystack); |
| |
| while (matcher.find()) { |
| int groupIdx = Math.min(matcher.groupCount(), 1); |
| issues.add(matcher.group(groupIdx)); |
| } |
| |
| return issues.toArray(new String[issues.size()]); |
| } |
| |
| /** |
| * Gets the regular expression used to identify issue ids. |
| * @return the regular expression, or {@code null}, if there is no pattern |
| * to match issue ids. |
| */ |
| public Pattern getPattern() { |
| Pattern ret = null; |
| String match = gerritConfig.getString("commentLink", itsName, "match"); |
| if (match != null) { |
| ret = Pattern.compile(match); |
| } |
| return ret; |
| } |
| |
| /** |
| * Helper funcion for {@link #getIssueIds(String, String)}. |
| * <p> |
| * Adds a text's issues for a given occurrence to the map returned by |
| * {@link #getIssueIds(String, String)}. |
| * |
| * @param text The text to extract issues from. |
| * @param occurrence The occurrence the issues get added at in {@code map}. |
| * @param map The map that the issues should get added to. |
| */ |
| private void addIssuesOccurrence(String text, String occurrence, Map<String,Set<String>> map) { |
| for (String issue : getIssueIds(text)) { |
| Set<String> occurrences = map.get(issue); |
| if (occurrences == null) { |
| occurrences = Sets.newLinkedHashSet(); |
| map.put(issue, occurrences); |
| } |
| occurrences.add(occurrence); |
| } |
| } |
| |
| /** |
| * Gets issues for a commit. |
| * |
| * @param projectName The project to fetch {@code commitId} from. |
| * @param commitId The commit id to fetch issues for. |
| * @return A mapping, whose keys are issue ids and whose values is a set of |
| * places where the issue occurs. Each issue occurs at least in |
| * "somewhere". Issues from the first line get tagged with an occurrence |
| * "subject". Issues in the last block get tagged with "footer". Issues |
| * occurring between "subject" and "footer" get tagged with "body". |
| */ |
| public Map<String,Set<String>> getIssueIds(String projectName, |
| String commitId) { |
| Map<String,Set<String>> ret = Maps.newHashMap(); |
| String commitMessage = commitMessageFetcher.fetchGuarded(projectName, |
| commitId); |
| |
| addIssuesOccurrence(commitMessage, "somewhere", ret); |
| |
| String[] lines = commitMessage.split("\n"); |
| if (lines.length > 0) { |
| // Parsing for "subject" |
| addIssuesOccurrence(lines[0], "subject", ret); |
| |
| // Determining footer line numbers |
| int currentLine = lines.length-1; |
| while (currentLine >=0 && lines[currentLine].isEmpty()) { |
| currentLine--; |
| } |
| int footerEnd = currentLine + 1; |
| while (currentLine >=0 && !lines[currentLine].isEmpty()) { |
| currentLine--; |
| } |
| int footerStart = currentLine + 1; |
| |
| if (footerStart == 0) { |
| // The first block of non-blank lines is not considered a footer, so |
| // we adjust that. |
| footerStart = -1; |
| } |
| |
| // Parsing for "body", and "footer" |
| String body = null; |
| String footer = null; |
| if (footerStart == -1) { |
| // No footer could be found. So all lines after the first one (that's |
| // the subject) is the body. |
| //body = String[] templateParameters = |
| // Arrays.copyOfRange(allParameters, 1, allParameters.length); |
| if (lines.length > 0) { |
| body = StringUtils.join(lines, "\n", 1, lines.length); |
| } |
| } else { |
| body = StringUtils.join(lines, "\n", 1, footerStart - 1); |
| footer = StringUtils.join(lines, "\n", footerStart, footerEnd); |
| } |
| if (body != null) { |
| addIssuesOccurrence(body, "body", ret); |
| } |
| if (footer != null) { |
| addIssuesOccurrence(footer, "footer", ret); |
| } |
| } |
| return ret; |
| } |
| } |