From ae3cb1927f582bf3c72e001daa4b69109d4509e3 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 4 Jun 2025 05:51:59 +0000 Subject: [PATCH 1/5] [maven-release-plugin] prepare for next development iteration --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 910fa511..9431046b 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 github-client - 0.4.7 + 0.4.8-SNAPSHOT com.spotify @@ -23,7 +23,7 @@ scm:git:https://github.com/spotify/github-java-client.git scm:git:git@github.com:spotify/github-java-client.git scm:https://github.com/spotify/github-java-client/ - v0.4.7 + v0.3.7 @@ -67,7 +67,7 @@ UTF-8 UTF-8 - 1749016228 + 1749016319 spotbugsexclude.xml error checkstyle.xml From b5cf400551ac08b7a6578217371b34961cef99c6 Mon Sep 17 00:00:00 2001 From: Abhishek Jain Date: Thu, 12 Jun 2025 16:36:20 +0200 Subject: [PATCH 2/5] feat: Add changedFiles for PR Client (#238) --- .../github/v3/clients/GitHubClient.java | 4 + .../github/v3/clients/PullRequestClient.java | 40 ++-- .../v3/clients/PullRequestClientTest.java | 177 ++++++++++++------ 3 files changed, 148 insertions(+), 73 deletions(-) diff --git a/src/main/java/com/spotify/github/v3/clients/GitHubClient.java b/src/main/java/com/spotify/github/v3/clients/GitHubClient.java index 9bfae602..563dbc17 100644 --- a/src/main/java/com/spotify/github/v3/clients/GitHubClient.java +++ b/src/main/java/com/spotify/github/v3/clients/GitHubClient.java @@ -40,6 +40,7 @@ import com.spotify.github.v3.comment.CommentReaction; import com.spotify.github.v3.exceptions.ReadOnlyRepositoryException; import com.spotify.github.v3.exceptions.RequestNotOkException; +import com.spotify.github.v3.git.FileItem; import com.spotify.github.v3.git.Reference; import com.spotify.github.v3.orgs.TeamInvitation; import com.spotify.github.v3.prs.PullRequestItem; @@ -108,6 +109,9 @@ public class GitHubClient { static final TypeReference> LIST_PENDING_TEAM_INVITATIONS = new TypeReference<>() {}; + static final TypeReference> LIST_FILE_ITEMS = + new TypeReference<>() {}; + private static final String GET_ACCESS_TOKEN_URL = "app/installations/%s/access_tokens"; private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); diff --git a/src/main/java/com/spotify/github/v3/clients/PullRequestClient.java b/src/main/java/com/spotify/github/v3/clients/PullRequestClient.java index feefc801..6a8ef8e3 100644 --- a/src/main/java/com/spotify/github/v3/clients/PullRequestClient.java +++ b/src/main/java/com/spotify/github/v3/clients/PullRequestClient.java @@ -20,28 +20,18 @@ package com.spotify.github.v3.clients; -import java.io.InputStreamReader; -import java.io.Reader; -import java.lang.invoke.MethodHandles; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import static java.util.Objects.isNull; -import java.util.concurrent.CompletableFuture; - -import javax.ws.rs.core.HttpHeaders; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableMap; -import com.spotify.github.async.AsyncPage; import static com.spotify.github.v3.clients.GitHubClient.IGNORE_RESPONSE_CONSUMER; import static com.spotify.github.v3.clients.GitHubClient.LIST_COMMIT_TYPE_REFERENCE; +import static com.spotify.github.v3.clients.GitHubClient.LIST_FILE_ITEMS; import static com.spotify.github.v3.clients.GitHubClient.LIST_PR_TYPE_REFERENCE; import static com.spotify.github.v3.clients.GitHubClient.LIST_REVIEW_REQUEST_TYPE_REFERENCE; import static com.spotify.github.v3.clients.GitHubClient.LIST_REVIEW_TYPE_REFERENCE; +import static java.util.Objects.isNull; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; +import com.spotify.github.async.AsyncPage; +import com.spotify.github.v3.git.FileItem; import com.spotify.github.v3.prs.Comment; import com.spotify.github.v3.prs.MergeParameters; import com.spotify.github.v3.prs.PullRequest; @@ -54,6 +44,16 @@ import com.spotify.github.v3.prs.requests.PullRequestParameters; import com.spotify.github.v3.prs.requests.PullRequestUpdate; import com.spotify.github.v3.repos.CommitItem; +import java.io.InputStreamReader; +import java.io.Reader; +import java.lang.invoke.MethodHandles; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import javax.ws.rs.core.HttpHeaders; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** Pull call API client */ public class PullRequestClient { @@ -63,6 +63,7 @@ public class PullRequestClient { private static final String PR_NUMBER_TEMPLATE = "/repos/%s/%s/pulls/%s"; private static final String PR_COMMITS_TEMPLATE = "/repos/%s/%s/pulls/%s/commits"; private static final String PR_REVIEWS_TEMPLATE = "/repos/%s/%s/pulls/%s/reviews"; + private static final String PR_CHANGED_FILES_TEMPLATE = "/repos/%s/%s/pulls/%s/files"; private static final String PR_REVIEW_REQUESTS_TEMPLATE = "/repos/%s/%s/pulls/%s/requested_reviewers"; private static final String PR_COMMENT_REPLIES_TEMPLATE = @@ -449,6 +450,11 @@ public CompletableFuture diff(final long prNumber) { }); } + public Iterator> changedFiles(final long prNumber) { + final String path = String.format(PR_CHANGED_FILES_TEMPLATE, owner, repo, prNumber); + return new GithubPageIterator<>(new GithubPage<>(github, path, LIST_FILE_ITEMS)); + } + /** * List pull requests using given parameters. * diff --git a/src/test/java/com/spotify/github/v3/clients/PullRequestClientTest.java b/src/test/java/com/spotify/github/v3/clients/PullRequestClientTest.java index 2d590036..10a36dc0 100644 --- a/src/test/java/com/spotify/github/v3/clients/PullRequestClientTest.java +++ b/src/test/java/com/spotify/github/v3/clients/PullRequestClientTest.java @@ -20,21 +20,16 @@ package com.spotify.github.v3.clients; -import java.io.IOException; -import java.io.Reader; -import java.net.URI; +import static com.google.common.io.Resources.getResource; +import static com.spotify.github.MockHelper.createMockResponse; +import static java.lang.String.format; import static java.nio.charset.Charset.defaultCharset; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -import org.apache.commons.io.IOUtils; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.stream.Collectors.toList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; @@ -42,8 +37,12 @@ import com.google.common.collect.ImmutableList; import com.google.common.io.Resources; -import static com.google.common.io.Resources.getResource; +import com.spotify.github.async.Async; +import com.spotify.github.async.AsyncPage; +import com.spotify.github.http.HttpResponse; import com.spotify.github.v3.exceptions.RequestNotOkException; +import com.spotify.github.v3.git.FileItem; +import com.spotify.github.v3.git.ImmutableFileItem; import com.spotify.github.v3.prs.Comment; import com.spotify.github.v3.prs.ImmutableRequestReviewParameters; import com.spotify.github.v3.prs.PullRequest; @@ -52,7 +51,12 @@ import com.spotify.github.v3.prs.requests.ImmutablePullRequestUpdate; import com.spotify.github.v3.prs.requests.PullRequestCreate; import com.spotify.github.v3.prs.requests.PullRequestUpdate; - +import java.io.IOException; +import java.io.Reader; +import java.net.URI; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import okhttp3.Call; import okhttp3.Callback; import okhttp3.MediaType; @@ -61,10 +65,16 @@ import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; public class PullRequestClientTest { + private static final String PR_CHANGED_FILES_TEMPLATE = "/repos/%s/%s/pulls/%s/files"; private GitHubClient github; + private GitHubClient mockGithub; private OkHttpClient client; private static String getFixture(String resource) throws IOException { @@ -74,7 +84,10 @@ private static String getFixture(String resource) throws IOException { @BeforeEach public void setUp() { client = mock(OkHttpClient.class); - github = GitHubClient.create(client, URI.create("http://bogus"), URI.create("https://bogus/graphql"), "token"); + github = + GitHubClient.create( + client, URI.create("http://bogus"), URI.create("https://bogus/graphql"), "token"); + mockGithub = mock(GitHubClient.class); } @Test @@ -95,18 +108,16 @@ public void createPullRequest() throws Exception { .message("Created") .body( ResponseBody.create( - MediaType.get("application/json"), - getFixture("pull_request.json"))) + MediaType.get("application/json"), getFixture("pull_request.json"))) .request(new Request.Builder().url("http://localhost/").build()) .build(); when(client.newCall(any())).thenReturn(call); - final PullRequestClient pullRequestClient = - PullRequestClient.create(github, "owner", "repo"); + final PullRequestClient pullRequestClient = PullRequestClient.create(github, "owner", "repo"); - final PullRequestCreate request = ImmutablePullRequestCreate.builder().title(title).body(body) - .head(head).base(base).build(); + final PullRequestCreate request = + ImmutablePullRequestCreate.builder().title(title).body(body).head(head).base(base).build(); final CompletableFuture result = pullRequestClient.create(request); @@ -136,18 +147,16 @@ public void updatePullRequest() throws Exception { .message("OK") .body( ResponseBody.create( - MediaType.get("application/json"), - getFixture("pull_request.json"))) + MediaType.get("application/json"), getFixture("pull_request.json"))) .request(new Request.Builder().url("http://localhost/").build()) .build(); when(client.newCall(any())).thenReturn(call); - final PullRequestClient pullRequestClient = - PullRequestClient.create(github, "owner", "repo"); + final PullRequestClient pullRequestClient = PullRequestClient.create(github, "owner", "repo"); - final PullRequestUpdate request = ImmutablePullRequestUpdate.builder().title(title).body(body) - .build(); + final PullRequestUpdate request = + ImmutablePullRequestUpdate.builder().title(title).body(body).build(); final CompletableFuture result = pullRequestClient.update(1L, request); @@ -172,18 +181,15 @@ public void testListReviewRequests() throws Throwable { .message("OK") .body( ResponseBody.create( - MediaType.get("application/json"), - getFixture("requestedReviews.json"))) + MediaType.get("application/json"), getFixture("requestedReviews.json"))) .request(new Request.Builder().url("http://localhost/").build()) .build(); when(client.newCall(any())).thenReturn(call); - final PullRequestClient pullRequestClient = - PullRequestClient.create(github, "owner", "repo"); + final PullRequestClient pullRequestClient = PullRequestClient.create(github, "owner", "repo"); - final CompletableFuture result = - pullRequestClient.listReviewRequests(1L); + final CompletableFuture result = pullRequestClient.listReviewRequests(1L); capture.getValue().onResponse(call, response); @@ -212,13 +218,14 @@ public void testRemoveRequestedReview() throws Throwable { when(client.newCall(any())).thenReturn(call); - PullRequestClient pullRequestClient = - PullRequestClient.create(github, "owner", "repo"); + PullRequestClient pullRequestClient = PullRequestClient.create(github, "owner", "repo"); CompletableFuture result = - pullRequestClient.removeRequestedReview(1L, ImmutableRequestReviewParameters.builder() - .reviewers(ImmutableList.of("user1", "user2")) - .build()); + pullRequestClient.removeRequestedReview( + 1L, + ImmutableRequestReviewParameters.builder() + .reviewers(ImmutableList.of("user1", "user2")) + .build()); capture.getValue().onResponse(call, response); @@ -243,13 +250,14 @@ public void testRemoveRequestedReview_failure() throws Throwable { when(client.newCall(any())).thenReturn(call); - PullRequestClient pullRequestClient = - PullRequestClient.create(github, "owner", "repo"); + PullRequestClient pullRequestClient = PullRequestClient.create(github, "owner", "repo"); CompletableFuture result = - pullRequestClient.removeRequestedReview(1L, ImmutableRequestReviewParameters.builder() - .reviewers(ImmutableList.of("user1", "user2")) - .build()); + pullRequestClient.removeRequestedReview( + 1L, + ImmutableRequestReviewParameters.builder() + .reviewers(ImmutableList.of("user1", "user2")) + .build()); capture.getValue().onResponse(call, response); @@ -270,18 +278,15 @@ public void testGetPatch() throws Throwable { .message("OK") .body( ResponseBody.create( - MediaType.get("application/vnd.github.patch"), - getFixture("patch.txt"))) + MediaType.get("application/vnd.github.patch"), getFixture("patch.txt"))) .request(new Request.Builder().url("http://localhost/").build()) .build(); when(client.newCall(any())).thenReturn(call); - final PullRequestClient pullRequestClient = - PullRequestClient.create(github, "owner", "repo"); + final PullRequestClient pullRequestClient = PullRequestClient.create(github, "owner", "repo"); - final CompletableFuture result = - pullRequestClient.patch(1L); + final CompletableFuture result = pullRequestClient.patch(1L); capture.getValue().onResponse(call, response); @@ -303,18 +308,15 @@ public void testGetDiff() throws Throwable { .message("OK") .body( ResponseBody.create( - MediaType.get("application/vnd.github.diff"), - getFixture("diff.txt"))) + MediaType.get("application/vnd.github.diff"), getFixture("diff.txt"))) .request(new Request.Builder().url("http://localhost/").build()) .build(); when(client.newCall(any())).thenReturn(call); - final PullRequestClient pullRequestClient = - PullRequestClient.create(github, "owner", "repo"); + final PullRequestClient pullRequestClient = PullRequestClient.create(github, "owner", "repo"); - final CompletableFuture result = - pullRequestClient.diff(1L); + final CompletableFuture result = pullRequestClient.diff(1L); capture.getValue().onResponse(call, response); @@ -343,8 +345,7 @@ public void testCreateCommentReply() throws Throwable { when(client.newCall(any())).thenReturn(call); - final PullRequestClient pullRequestClient = - PullRequestClient.create(github, "owner", "repo"); + final PullRequestClient pullRequestClient = PullRequestClient.create(github, "owner", "repo"); final String replyBody = "Thanks for the feedback!"; final CompletableFuture result = @@ -356,7 +357,8 @@ public void testCreateCommentReply() throws Throwable { assertThat(comment.body(), is("Great stuff!")); assertThat(comment.id(), is(10L)); - assertThat(comment.diffHunk(), is("@@ -16,33 +16,40 @@ public class Connection : IConnection...")); + assertThat( + comment.diffHunk(), is("@@ -16,33 +16,40 @@ public class Connection : IConnection...")); assertThat(comment.path(), is("file1.txt")); assertThat(comment.position(), is(1)); assertThat(comment.originalPosition(), is(4)); @@ -373,4 +375,67 @@ public void testCreateCommentReply() throws Throwable { assertThat(comment.side(), is("RIGHT")); assertThat(comment.pullRequestReviewId(), is(42L)); } + + @Test + void testChangedFiles() throws IOException { + FileItem file1 = + ImmutableFileItem.builder() + .filename("file1.txt") + .status("added") + .additions(103) + .deletions(21) + .changes(124) + .rawUrl( + URI.create( + "https://github.com/octocat/Hello-World/raw/6dcb09b5b57875f334f61aebed695e2e4193db5e/file1.txt")) + .blobUrl( + URI.create( + "https://github.com/octocat/Hello-World/blob/6dcb09b5b57875f334f61aebed695e2e4193db5e/file1.txt")) + .patch("@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test") + .contentsUrl( + URI.create( + "https://api.github.com/repos/octocat/Hello-World/contents/file1.txt?ref=6dcb09b5b57875f334f61aebed695e2e4193db5e")) + .sha("bbcd538c8e72b8c175046e27cc8f907076331401") + .build(); + FileItem file2 = + ImmutableFileItem.builder() + .filename("file2.txt") + .status("modified") + .additions(103) + .deletions(21) + .changes(124) + .rawUrl( + URI.create( + "https://github.com/octocat/Hello-World/raw/6dcb09b5b57875f334f61aebed695e2e4193db5e/file2.txt")) + .blobUrl( + URI.create( + "https://github.com/octocat/Hello-World/blob/6dcb09b5b57875f334f61aebed695e2e4193db5e/file2.txt")) + .patch("@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test") + .contentsUrl( + URI.create( + "https://api.github.com/repos/octocat/Hello-World/contents/file2.txt?ref=6dcb09b5b57875f334f61aebed695e2e4193db5e")) + .sha("bbcd538c8e72b8c175046e27cc8f907076331401") + .build(); + List expectedFiles = List.of(file1, file2); + final String expectedBody = github.json().toJsonUnchecked(expectedFiles); + + final String pageLink = + "; rel=\"first\""; + + final HttpResponse firstPageResponse = createMockResponse(pageLink, expectedBody); + + when(mockGithub.request(format(PR_CHANGED_FILES_TEMPLATE, "owner", "repo", "1"))) + .thenReturn(completedFuture(firstPageResponse)); + + when(mockGithub.json()).thenReturn(github.json()); + + final PullRequestClient pullRequestClient = + PullRequestClient.create(mockGithub, "owner", "repo"); + + final Iterable> pageIterator = () -> pullRequestClient.changedFiles(1L); + List actualFiles = Async.streamFromPaginatingIterable(pageIterator).collect(toList()); + assertEquals(actualFiles.size(), expectedFiles.size()); + assertEquals(actualFiles.get(0).filename(), expectedFiles.get(0).filename()); + assertEquals(actualFiles.get(1).filename(), expectedFiles.get(1).filename()); + } } From 13ba657081275d98de76cbeffd7f973a5e343945 Mon Sep 17 00:00:00 2001 From: Abhishek Jain Date: Fri, 13 Jun 2025 13:32:03 +0200 Subject: [PATCH 3/5] fix: Fix breaking change by making the previous change Nullable/Optional (#231) --- .../java/com/spotify/github/v3/checks/CheckRunResponse.java | 4 ++-- .../com/spotify/github/v3/prs/PartialPullRequestItem.java | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/spotify/github/v3/checks/CheckRunResponse.java b/src/main/java/com/spotify/github/v3/checks/CheckRunResponse.java index 9c50f3e0..3acd1379 100644 --- a/src/main/java/com/spotify/github/v3/checks/CheckRunResponse.java +++ b/src/main/java/com/spotify/github/v3/checks/CheckRunResponse.java @@ -80,7 +80,7 @@ public interface CheckRunResponse extends CheckRunBase { /** * Pull Requests where this check is applied. - * @return the list of pull requests + * @return the optional of list of pull requests */ - List pullRequests(); + Optional> pullRequests(); } diff --git a/src/main/java/com/spotify/github/v3/prs/PartialPullRequestItem.java b/src/main/java/com/spotify/github/v3/prs/PartialPullRequestItem.java index 35dd2018..9e8a1177 100644 --- a/src/main/java/com/spotify/github/v3/prs/PartialPullRequestItem.java +++ b/src/main/java/com/spotify/github/v3/prs/PartialPullRequestItem.java @@ -34,12 +34,15 @@ @JsonDeserialize(as = ImmutablePartialPullRequestItem.class) public interface PartialPullRequestItem extends CloseTracking { /** ID. */ + @Nullable Long id(); /** URL. */ + @Nullable URI url(); /** Number. */ + @Nullable Long number(); /** Head reference. */ From 843c5a53addcae0a4aa3e3bcbf6530c9a1bd6a58 Mon Sep 17 00:00:00 2001 From: Abhishek Jain Date: Fri, 13 Jun 2025 14:45:12 +0200 Subject: [PATCH 4/5] feat: Add updateTeamPermissions for TeamClient (#233) --- .../spotify/github/v3/clients/TeamClient.java | 54 ++++++++++++++----- .../requests/TeamRepoPermissionUpdate.java | 38 +++++++++++++ .../github/v3/clients/TeamClientTest.java | 36 ++++++++++--- 3 files changed, 108 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/spotify/github/v3/orgs/requests/TeamRepoPermissionUpdate.java diff --git a/src/main/java/com/spotify/github/v3/clients/TeamClient.java b/src/main/java/com/spotify/github/v3/clients/TeamClient.java index 630c86de..1dfdff67 100644 --- a/src/main/java/com/spotify/github/v3/clients/TeamClient.java +++ b/src/main/java/com/spotify/github/v3/clients/TeamClient.java @@ -18,7 +18,6 @@ * -/-/- */ - package com.spotify.github.v3.clients; import static com.spotify.github.v3.clients.GitHubClient.*; @@ -28,8 +27,10 @@ import com.spotify.github.v3.User; import com.spotify.github.v3.orgs.Membership; import com.spotify.github.v3.orgs.TeamInvitation; +import com.spotify.github.v3.orgs.requests.ImmutableTeamRepoPermissionUpdate; import com.spotify.github.v3.orgs.requests.MembershipCreate; import com.spotify.github.v3.orgs.requests.TeamCreate; +import com.spotify.github.v3.orgs.requests.TeamRepoPermissionUpdate; import com.spotify.github.v3.orgs.requests.TeamUpdate; import java.lang.invoke.MethodHandles; import java.util.Iterator; @@ -54,6 +55,8 @@ public class TeamClient { private static final String INVITATIONS_TEMPLATE = "/orgs/%s/teams/%s/invitations"; + private static final String REPO_TEMPLATE = "/orgs/%s/teams/%s/repos/%s/%s"; + private final GitHubClient github; private final String org; @@ -75,7 +78,7 @@ static TeamClient create(final GitHubClient github, final String org) { */ public CompletableFuture createTeam(final TeamCreate request) { final String path = String.format(TEAM_TEMPLATE, org); - log.debug("Creating team in: " + path); + log.debug("Creating team in: {}", path); return github.post(path, github.json().toJsonUnchecked(request), Team.class); } @@ -87,7 +90,7 @@ public CompletableFuture createTeam(final TeamCreate request) { */ public CompletableFuture getTeam(final String slug) { final String path = String.format(TEAM_SLUG_TEMPLATE, org, slug); - log.debug("Fetching team from " + path); + log.debug("Fetching team from {}", path); return github.request(path, Team.class); } @@ -98,7 +101,7 @@ public CompletableFuture getTeam(final String slug) { */ public CompletableFuture> listTeams() { final String path = String.format(TEAM_TEMPLATE, org); - log.debug("Fetching teams from " + path); + log.debug("Fetching teams from {}", path); return github.request(path, LIST_TEAMS); } @@ -111,7 +114,7 @@ public CompletableFuture> listTeams() { */ public CompletableFuture updateTeam(final TeamUpdate request, final String slug) { final String path = String.format(TEAM_SLUG_TEMPLATE, org, slug); - log.debug("Updating team in: " + path); + log.debug("Updating team in: {}", path); return github.patch(path, github.json().toJsonUnchecked(request), Team.class); } @@ -123,7 +126,7 @@ public CompletableFuture updateTeam(final TeamUpdate request, final String */ public CompletableFuture deleteTeam(final String slug) { final String path = String.format(TEAM_SLUG_TEMPLATE, org, slug); - log.debug("Deleting team from: " + path); + log.debug("Deleting team from: {}", path); return github.delete(path).thenAccept(IGNORE_RESPONSE_CONSUMER); } @@ -133,9 +136,10 @@ public CompletableFuture deleteTeam(final String slug) { * @param request update membership request * @return membership */ - public CompletableFuture updateMembership(final MembershipCreate request, final String slug, final String username) { + public CompletableFuture updateMembership( + final MembershipCreate request, final String slug, final String username) { final String path = String.format(MEMBERSHIP_TEMPLATE, org, slug, username); - log.debug("Updating membership in: " + path); + log.debug("Updating membership in: {}", path); return github.put(path, github.json().toJsonUnchecked(request), Membership.class); } @@ -148,7 +152,7 @@ public CompletableFuture updateMembership(final MembershipCreate req */ public CompletableFuture getMembership(final String slug, final String username) { final String path = String.format(MEMBERSHIP_TEMPLATE, org, slug, username); - log.debug("Fetching membership for: " + path); + log.debug("Fetching membership for: {}", path); return github.request(path, Membership.class); } @@ -160,7 +164,7 @@ public CompletableFuture getMembership(final String slug, final Stri */ public CompletableFuture> listTeamMembers(final String slug) { final String path = String.format(MEMBERS_TEMPLATE, org, slug); - log.debug("Fetching members for: " + path); + log.debug("Fetching members for: {}", path); return github.request(path, LIST_TEAM_MEMBERS); } @@ -173,7 +177,7 @@ public CompletableFuture> listTeamMembers(final String slug) { */ public Iterator> listTeamMembers(final String slug, final int pageSize) { final String path = String.format(PAGED_MEMBERS_TEMPLATE, org, slug, pageSize); - log.debug("Fetching members for: " + path); + log.debug("Fetching members for: {}", path); return new GithubPageIterator<>(new GithubPage<>(github, path, LIST_TEAM_MEMBERS)); } @@ -185,7 +189,7 @@ public Iterator> listTeamMembers(final String slug, final int pa */ public CompletableFuture deleteMembership(final String slug, final String username) { final String path = String.format(MEMBERSHIP_TEMPLATE, org, slug, username); - log.debug("Deleting membership from: " + path); + log.debug("Deleting membership from: {}", path); return github.delete(path).thenAccept(IGNORE_RESPONSE_CONSUMER); } @@ -197,7 +201,31 @@ public CompletableFuture deleteMembership(final String slug, final String */ public CompletableFuture> listPendingTeamInvitations(final String slug) { final String path = String.format(INVITATIONS_TEMPLATE, org, slug); - log.debug("Fetching pending invitations for: " + path); + log.debug("Fetching pending invitations for: {}", path); return github.request(path, LIST_PENDING_TEAM_INVITATIONS); } + + /** + * Update permissions for a team on a specific repository. + * + * @param slug the team slug + * @param repo the repository name + * @param permission the permission level (pull, push, maintain, triage, admin, or a custom repo defined role name) + * @return void status code 204 if successful + */ + public CompletableFuture updateTeamPermissions( + final String slug, final String repo, final String permission) { + final String path = String.format(REPO_TEMPLATE, org, slug, org, repo); + final TeamRepoPermissionUpdate request = + ImmutableTeamRepoPermissionUpdate.builder() + .org(org) + .repo(repo) + .teamSlug(slug) + .permission(permission) + .build(); + log.debug("Updating team permissions for: {}", path); + return github + .put(path, github.json().toJsonUnchecked(request)) + .thenAccept(IGNORE_RESPONSE_CONSUMER); + } } diff --git a/src/main/java/com/spotify/github/v3/orgs/requests/TeamRepoPermissionUpdate.java b/src/main/java/com/spotify/github/v3/orgs/requests/TeamRepoPermissionUpdate.java new file mode 100644 index 00000000..5263ff5f --- /dev/null +++ b/src/main/java/com/spotify/github/v3/orgs/requests/TeamRepoPermissionUpdate.java @@ -0,0 +1,38 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.orgs.requests; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import org.immutables.value.Value; + +/** Request to update permissions of a team for a specific repo */ +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableTeamRepoPermissionUpdate.class) +@JsonDeserialize(as = ImmutableTeamRepoPermissionUpdate.class) +public interface TeamRepoPermissionUpdate { + String org(); + String repo(); + String teamSlug(); + String permission(); +} diff --git a/src/test/java/com/spotify/github/v3/clients/TeamClientTest.java b/src/test/java/com/spotify/github/v3/clients/TeamClientTest.java index a7195fbc..82ba2897 100644 --- a/src/test/java/com/spotify/github/v3/clients/TeamClientTest.java +++ b/src/test/java/com/spotify/github/v3/clients/TeamClientTest.java @@ -21,6 +21,7 @@ package com.spotify.github.v3.clients; import static com.google.common.io.Resources.getResource; +import static com.spotify.github.MockHelper.createMockHttpResponse; import static com.spotify.github.v3.clients.GitHubClient.LIST_PENDING_TEAM_INVITATIONS; import static com.spotify.github.v3.clients.GitHubClient.LIST_TEAMS; import static com.spotify.github.v3.clients.GitHubClient.LIST_TEAM_MEMBERS; @@ -48,6 +49,7 @@ import com.spotify.github.v3.orgs.requests.TeamUpdate; import java.io.IOException; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import org.junit.jupiter.api.BeforeEach; @@ -135,7 +137,10 @@ public void updateTeam() throws Exception { assertThat(actualResponse.get().name(), is("Justice League2")); verify(github, times(1)) - .patch(eq("/orgs/github/teams/justice-league"), eq("{\"name\":\"Justice League2\"}"), eq(Team.class)); + .patch( + eq("/orgs/github/teams/justice-league"), + eq("{\"name\":\"Justice League2\"}"), + eq(Team.class)); } @Test @@ -165,22 +170,24 @@ public void listTeamMembers() throws Exception { @Test public void listTeamMembersPaged() throws Exception { final String firstPageLink = - "; rel=\"next\", ; rel=\"last\""; + "; rel=\"next\", ; rel=\"last\""; final String firstPageBody = - Resources.toString(getResource(this.getClass(), "list_members_page1.json"), defaultCharset()); + Resources.toString( + getResource(this.getClass(), "list_members_page1.json"), defaultCharset()); final HttpResponse firstPageResponse = createMockResponse(firstPageLink, firstPageBody); final String lastPageLink = - "; rel=\"first\", ; rel=\"prev\""; + "; rel=\"first\", ; rel=\"prev\""; final String lastPageBody = - Resources.toString(getResource(this.getClass(), "list_members_page2.json"), defaultCharset()); + Resources.toString( + getResource(this.getClass(), "list_members_page2.json"), defaultCharset()); final HttpResponse lastPageResponse = createMockResponse(lastPageLink, lastPageBody); when(github.request(endsWith("/orgs/github/teams/1/members?per_page=1"))) - .thenReturn(completedFuture(firstPageResponse)); + .thenReturn(completedFuture(firstPageResponse)); when(github.request(endsWith("/orgs/github/teams/1/members?page=2"))) - .thenReturn(completedFuture(lastPageResponse)); + .thenReturn(completedFuture(lastPageResponse)); final Iterable> pageIterator = () -> teamClient.listTeamMembers("1", 1); final List users = Async.streamFromPaginatingIterable(pageIterator).collect(toList()); @@ -229,4 +236,19 @@ public void listPendingTeamInvitations() throws Exception { assertThat(pendingInvitations.get(1).id(), is(2)); assertThat(pendingInvitations.size(), is(2)); } + + @Test + void updateTeamPermissions() { + String apiUrl = "/orgs/github/teams/cat-squad/repos/github/octocat"; + HttpResponse response = createMockHttpResponse(apiUrl, 204, "", Map.of()); + when(github.put(eq(apiUrl), any())).thenReturn(completedFuture(response)); + + teamClient.updateTeamPermissions("cat-squad", "octocat", "pull").join(); + + verify(github, times(1)) + .put( + eq(apiUrl), + eq( + "{\"org\":\"github\",\"repo\":\"octocat\",\"team_slug\":\"cat-squad\",\"permission\":\"pull\"}")); + } } From 3d416786810935e7dee1dc1d400b372096f2ac32 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Fri, 13 Jun 2025 12:52:39 +0000 Subject: [PATCH 5/5] [maven-release-plugin] prepare release v0.4.8 --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 9431046b..bf8ec0d4 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 github-client - 0.4.8-SNAPSHOT + 0.4.8 com.spotify @@ -23,7 +23,7 @@ scm:git:https://github.com/spotify/github-java-client.git scm:git:git@github.com:spotify/github-java-client.git scm:https://github.com/spotify/github-java-client/ - v0.3.7 + v0.4.8 @@ -67,7 +67,7 @@ UTF-8 UTF-8 - 1749016319 + 1749819070 spotbugsexclude.xml error checkstyle.xml