Skip to content
Permalink
Browse files
222: Implement backport issue creation
Reviewed-by: ehelin
  • Loading branch information
rwestberg committed Jan 13, 2020
1 parent af856f5 commit 7d878e7da133a03ce2efd971bed20f4006bf41c6
Showing 21 changed files with 712 additions and 98 deletions.
@@ -34,6 +34,8 @@
import java.nio.file.Path;
import java.util.*;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.stream.*;

public class IssueUpdater implements RepositoryUpdateConsumer, PullRequestUpdateConsumer {
private final IssueProject issueProject;
@@ -55,6 +57,148 @@ public class IssueUpdater implements RepositoryUpdateConsumer, PullRequestUpdate
this.fixVersion = fixVersion;
}

private final static Set<String> primaryTypes = Set.of("Bug", "New Feature", "Enhancement", "Task", "Sub-task");

private boolean isPrimaryIssue(Issue issue) {
var properties = issue.properties();
if (!properties.containsKey("type")) {
throw new RuntimeException("Unknown type for issue " + issue);
}
var type = properties.get("type");
return primaryTypes.contains(type);
}

private final static Pattern majorVersionPattern = Pattern.compile("([0-9]+)(u[0-9]+)?");

/**
* Extracts the major version part of the string, if possible.
*/
private Optional<String> majorVersion(String version) {
var matcher = majorVersionPattern.matcher(version);
if (matcher.matches()) {
return Optional.of(matcher.group(1));
} else {
return Optional.empty();
}
}

private List<Issue> findBackports(Issue primary) {
var links = primary.links();
return links.stream()
.filter(l -> l.issue().isPresent())
.map(l -> l.issue().get())
.filter(i -> i.properties().containsKey("type"))
.filter(i -> i.properties().get("type").equals("Backport"))
.collect(Collectors.toList());
}

private boolean isNonScratchVersion(String version) {
return !version.startsWith("tbd") && !version.toLowerCase().equals("unknown");
}

/**
* Return true if the issue's fixVersionList matches fixVersion.
*
* fixVersionsList must contain one entry that is an exact match for fixVersions; any
* other entries must be scratch values.
*/
private boolean matchVersion(Issue issue, String fixVersion) {
var nonScratch = issue.fixVersions().stream()
.filter(this::isNonScratchVersion)
.collect(Collectors.toList());
return nonScratch.size() == 1 && nonScratch.get(0).equals(fixVersion);
}

/**
* Return true if the issue's fixVersionList is a match for fixVersion, using "-pool" or "-open".
*
* If fixVersion has a major release of <N>, it matches the fixVersionList has an
* <N>-pool or <N>-open entry and all other entries are scratch values.
*/
private boolean matchPoolVersion(Issue issue, String fixVersion) {
var majorVersion = majorVersion(fixVersion);
if (majorVersion.isEmpty()) {
return false;
}
var poolVersion = majorVersion.get() + "-pool";
var openVersion = majorVersion.get() + "-open";

var nonScratch = issue.fixVersions().stream()
.filter(this::isNonScratchVersion)
.collect(Collectors.toList());
return nonScratch.size() == 1 && (nonScratch.get(0).equals(poolVersion) || nonScratch.get(0).equals(openVersion));
}

/**
* Return true if fixVersionList is empty or contains only scratch values.
*/
private boolean matchScratchVersion(Issue issue) {
var nonScratch = issue.fixVersions().stream()
.filter(this::isNonScratchVersion)
.collect(Collectors.toList());
return nonScratch.size() == 0;
}

/**
* Create a backport of issue.
*/
private Issue createBackportIssue(Issue primary) {
var properties = primary.properties();
properties.put("type", "Backport");

var backport = primary.project().createIssue(primary.title(), primary.body().lines().collect(Collectors.toList()), properties);

var backportLink = Link.create(backport, "backported by").build();
primary.addLink(backportLink);;
return backport;
}

/**
* Return issue or one of its backports that applies to fixVersion.
*
* If the main issue has the correct fixVersion, use it.
* If an existing Backport has the correct fixVersion, use it.
* If the main issue has a matching <N>-pool/open fixVersion, use it.
* If an existing Backport has a matching <N>-pool/open fixVersion, use it.
* If the main issue has a "scratch" fixVersion, use it.
* If an existing Backport has a "scratch" fixVersion, use it.
*
* Otherwise, create a new Backport.
*
* A "scratch" fixVersion is empty, "tbd.*", or "unknown".
*/
private Issue findIssue(Issue primary, String fixVersion) {
log.info("Searching for properly versioned issue for primary issue " + primary.id());
var candidates = Stream.concat(Stream.of(primary), findBackports(primary).stream()).collect(Collectors.toList());
candidates.forEach(c -> log.fine("Candidate: " + c.id() + " with versions: " + String.join(",", c.fixVersions())));
var matchingVersionIssue = candidates.stream()
.filter(i -> matchVersion(i, fixVersion))
.findFirst();
if (matchingVersionIssue.isPresent()) {
log.info("Issue " + matchingVersionIssue.get().id() + " has a correct fixVersion");
return matchingVersionIssue.get();
}

var matchingPoolVersionIssue = candidates.stream()
.filter(i -> matchPoolVersion(i, fixVersion))
.findFirst();
if (matchingPoolVersionIssue.isPresent()) {
log.info("Issue " + matchingPoolVersionIssue.get().id() + " has a matching pool version");
return matchingPoolVersionIssue.get();
}

var matchingScratchVersionIssue = candidates.stream()
.filter(this::matchScratchVersion)
.findFirst();
if (matchingScratchVersionIssue.isPresent()) {
log.info("Issue " + matchingScratchVersionIssue.get().id() + " has a scratch fixVersion");
return matchingScratchVersionIssue.get();
}

log.info("Creating new backport for " + primary.id());
return createBackportIssue(primary);
}

@Override
public void handleCommits(HostedRepository repository, Repository localRepository, List<Commit> commits, Branch branch) {
for (var commit : commits) {
@@ -68,6 +212,36 @@ public void handleCommits(HostedRepository repository, Repository localRepositor
continue;
}
var issue = optionalIssue.get();

// We only update primary type issues
if (!isPrimaryIssue(issue)) {
log.severe("Issue " + issue.id() + " isn't of a primary type - ignoring");
// TODO: search for the primary issue
continue;
}

// The actual issue to be updated can change depending on the fix version
String requestedVersion = null;
if (setFixVersion) {
requestedVersion = fixVersion;
if (requestedVersion == null) {
try {
var conf = localRepository.lines(Path.of(".jcheck/conf"), commit.hash());
if (conf.isPresent()) {
var parsed = JCheckConfiguration.parse(conf.get());
var version = parsed.general().version();
requestedVersion = version.orElse(null);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}

if (requestedVersion != null) {
issue = findIssue(issue, requestedVersion);
}
}

var existingComments = issue.comments();
var hashUrl = repository.webUrl(commit.hash()).toString();
var alreadyPostedComment = existingComments.stream()
@@ -89,21 +263,11 @@ public void handleCommits(HostedRepository repository, Repository localRepositor
}

if (setFixVersion) {
var requestedVersion = fixVersion;
if (requestedVersion == null) {
try {
var conf = localRepository.lines(Path.of(".jcheck/conf"), commit.hash());
if (conf.isPresent()) {
var parsed = JCheckConfiguration.parse(conf.get());
var version = parsed.general().version();
requestedVersion = version.orElse(null);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}

if (requestedVersion != null) {
// Remove any previously set versions (can only be scratch versions)
for (var oldVersion : issue.fixVersions()) {
issue.removeFixVersion(oldVersion);
}
issue.addFixVersion(requestedVersion);
}
}
@@ -159,6 +323,7 @@ public void handleRemovedIssue(PullRequest pr, org.openjdk.skara.vcs.openjdk.Iss
return;
}

realIssue.get().removeLink(pr.webUrl());
var link = Link.create(pr.webUrl(), "").build();
realIssue.get().removeLink(link);
}
}

0 comments on commit 7d878e7

Please sign in to comment.