diff --git a/bots/tester/src/test/java/org/openjdk/skara/bots/tester/InMemoryHostedRepository.java b/bots/tester/src/test/java/org/openjdk/skara/bots/tester/InMemoryHostedRepository.java index 883d3f511..bff21edd3 100644 --- a/bots/tester/src/test/java/org/openjdk/skara/bots/tester/InMemoryHostedRepository.java +++ b/bots/tester/src/test/java/org/openjdk/skara/bots/tester/InMemoryHostedRepository.java @@ -169,4 +169,9 @@ public Optional commitMetadata(Hash commit) { public List allChecks(Hash hash) { return List.of(); } + + @Override + public List recentCommitComments() { + return List.of(); + } } diff --git a/forge/src/main/java/org/openjdk/skara/forge/CommitComment.java b/forge/src/main/java/org/openjdk/skara/forge/CommitComment.java index 5d9139c91..16eda5479 100644 --- a/forge/src/main/java/org/openjdk/skara/forge/CommitComment.java +++ b/forge/src/main/java/org/openjdk/skara/forge/CommitComment.java @@ -29,9 +29,11 @@ import java.nio.file.Path; import java.time.ZonedDateTime; import java.util.*; +import java.util.function.Supplier; public class CommitComment extends Comment { - private final Hash commit; + private Hash commit; + private final Supplier commitSupplier; private final Path path; private final int line; @@ -39,6 +41,16 @@ public CommitComment(Hash commit, Path path, int line, String id, String body, H super(id, body, author, createdAt, updatedAt); this.commit = commit; + this.commitSupplier = null; + this.path = path; + this.line = line; + } + + public CommitComment(Supplier commitSupplier, Path path, int line, String id, String body, HostUser author, ZonedDateTime createdAt, ZonedDateTime updatedAt) { + super(id, body, author, createdAt, updatedAt); + + this.commit = null; + this.commitSupplier = commitSupplier; this.path = path; this.line = line; } @@ -47,6 +59,9 @@ public CommitComment(Hash commit, Path path, int line, String id, String body, H * Returns the hash of the commit. */ public Hash commit() { + if (commit == null) { + commit = commitSupplier.get(); + } return commit; } diff --git a/forge/src/main/java/org/openjdk/skara/forge/HostedRepository.java b/forge/src/main/java/org/openjdk/skara/forge/HostedRepository.java index e9f711a18..8a6125230 100644 --- a/forge/src/main/java/org/openjdk/skara/forge/HostedRepository.java +++ b/forge/src/main/java/org/openjdk/skara/forge/HostedRepository.java @@ -68,6 +68,7 @@ PullRequest createPullRequest(HostedRepository target, Hash branchHash(String ref); List branches(); List commitComments(Hash hash); + List recentCommitComments(); void addCommitComment(Hash hash, String body); Optional commitMetadata(Hash hash); List allChecks(Hash hash); diff --git a/forge/src/main/java/org/openjdk/skara/forge/github/GitHubHost.java b/forge/src/main/java/org/openjdk/skara/forge/github/GitHubHost.java index bc11a4b37..2c29a5e96 100644 --- a/forge/src/main/java/org/openjdk/skara/forge/github/GitHubHost.java +++ b/forge/src/main/java/org/openjdk/skara/forge/github/GitHubHost.java @@ -199,10 +199,21 @@ HostUser parseUserField(JSONValue json) { } HostUser parseUserObject(JSONValue json) { + return hostUser(json.get("id").asInt(), json.get("login").asString()); + } + + HostUser hostUser(int id, String username) { + return HostUser.builder() + .id(id) + .username(username) + .supplier(() -> user(username).orElseThrow()) + .build(); + } + + HostUser hostUser(String username) { return HostUser.builder() - .id(json.get("id").asInt()) - .username(json.get("login").asString()) - .supplier(() -> user(json.get("login").asString()).orElseThrow()) + .username(username) + .supplier(() -> user(username).orElseThrow()) .build(); } @@ -269,10 +280,10 @@ public Optional user(String username) { return Optional.empty(); } - return Optional.of(asHostUser(details.asObject())); + return Optional.of(toHostUser(details.asObject())); } - private static HostUser asHostUser(JSONObject details) { + private HostUser toHostUser(JSONObject details) { // Always present var login = details.get("login").asString(); var id = details.get("id").asInt(); @@ -302,7 +313,7 @@ public HostUser currentUser() { // on Windows always return "PersonalAccessToken" as username. // Query GitHub for the username instead. var details = request.get("user").execute().asObject(); - currentUser = asHostUser(details); + currentUser = toHostUser(details); } else { throw new IllegalStateException("No credentials present"); } diff --git a/forge/src/main/java/org/openjdk/skara/forge/github/GitHubRepository.java b/forge/src/main/java/org/openjdk/skara/forge/github/GitHubRepository.java index 7198f13d1..08769f62a 100644 --- a/forge/src/main/java/org/openjdk/skara/forge/github/GitHubRepository.java +++ b/forge/src/main/java/org/openjdk/skara/forge/github/GitHubRepository.java @@ -268,29 +268,80 @@ public List branches() { .collect(Collectors.toList()); } + private CommitComment toCommitComment(JSONValue o) { + var hash = new Hash(o.get("commit_id").asString()); + var line = o.get("line").isNull()? -1 : o.get("line").asInt(); + var path = o.get("path").isNull()? null : Path.of(o.get("path").asString()); + return new CommitComment(hash, + path, + line, + o.get("id").toString(), + o.get("body").asString(), + gitHubHost.parseUserField(o), + ZonedDateTime.parse(o.get("created_at").asString()), + ZonedDateTime.parse(o.get("updated_at").asString())); + } + @Override public List commitComments(Hash hash) { return request.get("commits/" + hash.hex() + "/comments") .execute() .stream() - .map(JSONValue::asObject) - .map(o -> { - var line = o.get("line").isNull()? -1 : o.get("line").asInt(); - var path = o.get("path").isNull()? null : Path.of(o.get("path").asString()); - return new CommitComment(hash, - path, - line, - o.get("id").toString(), - o.get("body").asString(), - gitHubHost.parseUserField(o), - ZonedDateTime.parse(o.get("created_at").asString()), - ZonedDateTime.parse(o.get("updated_at").asString())); - - - }) + .map(this::toCommitComment) .collect(Collectors.toList()); } + @Override + public List recentCommitComments() { + var parts = name().split("/"); + var owner = parts[0]; + var name = parts[1]; + + var query = String.join("\n", List.of( + "query {", + " repository(owner: \"" + owner + "\", name: \"" + name + "\") {", + " commitComments(last: 200) {", + " nodes {", + " createdAt", + " updatedAt", + " author { login }", + " databaseId", + " commit { oid }", + " body", + " }", + " }", + " }", + "}" + )); + + var data = gitHubHost.graphQL() + .post() + .body(JSON.object().put("query", query)) + .execute() + .get("data"); + return data.get("repository") + .get("commitComments") + .get("nodes") + .stream() + .map(o -> { + var hash = new Hash(o.get("commit").get("oid").asString()); + var createdAt = ZonedDateTime.parse(o.get("createdAt").asString()); + var updatedAt = ZonedDateTime.parse(o.get("updatedAt").asString()); + var id = o.get("databaseId").asString(); + var body = o.get("body").asString(); + var user = gitHubHost.hostUser(o.get("login").asString()); + return new CommitComment(hash, + null, + -1, + id, + body, + user, + createdAt, + updatedAt); + }) + .collect(Collectors.toList()); + } + @Override public void addCommitComment(Hash hash, String body) { var query = JSON.object().put("body", body); diff --git a/forge/src/main/java/org/openjdk/skara/forge/gitlab/GitLabRepository.java b/forge/src/main/java/org/openjdk/skara/forge/gitlab/GitLabRepository.java index 80c78f6cd..7bfb45bfe 100644 --- a/forge/src/main/java/org/openjdk/skara/forge/gitlab/GitLabRepository.java +++ b/forge/src/main/java/org/openjdk/skara/forge/gitlab/GitLabRepository.java @@ -22,6 +22,7 @@ */ package org.openjdk.skara.forge.gitlab; +import org.openjdk.skara.host.HostUser; import org.openjdk.skara.forge.*; import org.openjdk.skara.json.*; import org.openjdk.skara.network.*; @@ -33,6 +34,7 @@ import java.time.*; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -290,27 +292,90 @@ public List branches() { .collect(Collectors.toList()); } + private CommitComment toCommitComment(Hash hash, JSONValue o) { + var line = o.get("line").isNull()? -1 : o.get("line").asInt(); + var path = o.get("path").isNull()? null : Path.of(o.get("path").asString()); + // GitLab does not offer updated_at for commit comments + var createdAt = ZonedDateTime.parse(o.get("created_at").asString()); + // GitLab does not offer an id for commit comments + var id = ""; + return new CommitComment(hash, + path, + line, + id, + o.get("note").asString(), + gitLabHost.parseAuthorField(o), + createdAt, + createdAt); + } + @Override public List commitComments(Hash hash) { return request.get("repository/commits/" + hash.hex() + "/comments") .execute() .stream() - .map(JSONValue::asObject) + .map(o -> toCommitComment(hash, o)) + .collect(Collectors.toList()); + } + + private Hash commitWithComment(String commitTitle, + String commentBody, + ZonedDateTime commentCreatedAt, + HostUser author) { + var result = request.get("search") + .param("scope", "commits") + .param("search", commitTitle) + .execute() + .stream() + .filter(o -> o.get("title").asString().equals(commitTitle)) + .map(o -> new Hash(o.get("id").asString())) + .collect(Collectors.toList()); + if (result.isEmpty()) { + throw new IllegalArgumentException("No commit with title: " + commitTitle); + } + if (result.size() > 1) { + var filtered = result.stream() + .flatMap(hash -> commitComments(hash).stream() + .filter(c -> c.body().equals(commentBody)) + .filter(c -> c.createdAt().equals(commentCreatedAt)) + .filter(c -> c.author().equals(author))) + .map(c -> c.commit()) + .collect(Collectors.toList()); + if (filtered.isEmpty()) { + throw new IllegalStateException("No commit with title '" + commitTitle + + "' and comment '" + commentBody + "'"); + } + if (filtered.size() > 1) { + var hashes = filtered.stream().map(Hash::hex).collect(Collectors.toList()); + throw new IllegalStateException("Multiple commits with identical comment '" + commentBody + "': " + + String.join(",", hashes)); + } + return filtered.get(0); + } + return result.get(0); + } + + @Override + public List recentCommitComments() { + var twoDaysAgo = ZonedDateTime.now().minusDays(2); + var formatter = DateTimeFormatter.ofPattern("yyyy-MM-DD"); + return request.get("events") + .param("after", twoDaysAgo.format(formatter)) + .execute() + .stream() + .filter(o -> o.contains("note") && + o.get("note").contains("noteable_type") && + o.get("note").get("noteable_type").asString().equals("Commit")) .map(o -> { - var line = o.get("line").isNull()? -1 : o.get("line").asInt(); - var path = o.get("path").isNull()? null : Path.of(o.get("path").asString()); - // GitLab does not offer updated_at for commit comments - var createdAt = ZonedDateTime.parse(o.get("created_at").asString()); - // GitLab does not offer an id for commit comments - var id = ""; - return new CommitComment(hash, - path, - line, - id, - o.get("note").asString(), - gitLabHost.parseAuthorField(o), - createdAt, - createdAt); + var createdAt = ZonedDateTime.parse(o.get("note").get("created_at").asString()); + var body = o.get("note").get("body").asString(); + var user = gitLabHost.parseAuthorField(o); + var id = o.get("note").get("id").asString(); + Supplier hash = () -> commitWithComment(o.get("target_title").asString(), + body, + createdAt, + user); + return new CommitComment(hash, null, -1, id, body, user, createdAt, createdAt); }) .collect(Collectors.toList()); } diff --git a/test/src/main/java/org/openjdk/skara/test/TestHostedRepository.java b/test/src/main/java/org/openjdk/skara/test/TestHostedRepository.java index 489f49ef0..e777f0f89 100644 --- a/test/src/main/java/org/openjdk/skara/test/TestHostedRepository.java +++ b/test/src/main/java/org/openjdk/skara/test/TestHostedRepository.java @@ -211,6 +211,14 @@ public List commitComments(Hash hash) { return commitComments.get(hash); } + @Override + public List recentCommitComments() { + return commitComments.values() + .stream() + .flatMap(e -> e.stream()) + .collect(Collectors.toList()); + } + @Override public void addCommitComment(Hash hash, String body) { var id = nextCommitCommentId;