From 17cc5218be05a4f1159b26d79b8b7d2b8393b882 Mon Sep 17 00:00:00 2001 From: Vootele Rotov Date: Thu, 7 Sep 2023 09:59:39 +0300 Subject: [PATCH] feat: add support for creating and updating file contents See https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28#create-or-update-file-contents. --- .../github/v3/clients/RepositoryClient.java | 29 ++++++++ .../v3/repos/CommitWithFolderContent.java | 41 ++++++++++++ .../github/v3/repos/requests/FileCreate.java | 49 ++++++++++++++ .../github/v3/repos/requests/FileUpdate.java | 52 +++++++++++++++ .../v3/clients/RepositoryClientTest.java | 66 +++++++++++++++++++ .../v3/repos/create-content-repsonse.json | 52 +++++++++++++++ .../v3/repos/create-content-request.json | 4 ++ .../v3/repos/update-content-repsonse.json | 52 +++++++++++++++ .../v3/repos/update-content-request.json | 6 ++ 9 files changed, 351 insertions(+) create mode 100644 src/main/java/com/spotify/github/v3/repos/CommitWithFolderContent.java create mode 100644 src/main/java/com/spotify/github/v3/repos/requests/FileCreate.java create mode 100644 src/main/java/com/spotify/github/v3/repos/requests/FileUpdate.java create mode 100644 src/test/resources/com/spotify/github/v3/repos/create-content-repsonse.json create mode 100644 src/test/resources/com/spotify/github/v3/repos/create-content-request.json create mode 100644 src/test/resources/com/spotify/github/v3/repos/update-content-repsonse.json create mode 100644 src/test/resources/com/spotify/github/v3/repos/update-content-request.json diff --git a/src/main/java/com/spotify/github/v3/clients/RepositoryClient.java b/src/main/java/com/spotify/github/v3/clients/RepositoryClient.java index f9ee2a60..4f8881e7 100644 --- a/src/main/java/com/spotify/github/v3/clients/RepositoryClient.java +++ b/src/main/java/com/spotify/github/v3/clients/RepositoryClient.java @@ -42,7 +42,10 @@ import com.spotify.github.v3.repos.CommitComparison; import com.spotify.github.v3.repos.CommitItem; import com.spotify.github.v3.repos.CommitStatus; +import com.spotify.github.v3.repos.CommitWithFolderContent; import com.spotify.github.v3.repos.Content; +import com.spotify.github.v3.repos.requests.FileCreate; +import com.spotify.github.v3.repos.requests.FileUpdate; import com.spotify.github.v3.repos.FolderContent; import com.spotify.github.v3.repos.Languages; import com.spotify.github.v3.repos.Repository; @@ -394,6 +397,32 @@ public CompletableFuture getFileContent(final String path, final String return github.request(getContentPath(path, "?ref=" + ref), Content.class); } + /** + * Create a file + * + * @param path path to a file + * @param request file creation request + * @return commit with content + */ + public CompletableFuture createFileContent(final String path, final FileCreate request) { + final String contentPath = getContentPath(path, ""); + final String requestBody = github.json().toJsonUnchecked(request); + return github.put(contentPath, requestBody, CommitWithFolderContent.class); + } + + /** + * Update file contents + * + * @param path path to a file + * @param request file update request + * @return commit with content + */ + public CompletableFuture updateFileContent(final String path, final FileUpdate request) { + final String contentPath = getContentPath(path, ""); + final String requestBody = github.json().toJsonUnchecked(request); + return github.put(contentPath, requestBody, CommitWithFolderContent.class); + } + /** * Get repository contents of a folder. * diff --git a/src/main/java/com/spotify/github/v3/repos/CommitWithFolderContent.java b/src/main/java/com/spotify/github/v3/repos/CommitWithFolderContent.java new file mode 100644 index 00000000..6effc972 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/repos/CommitWithFolderContent.java @@ -0,0 +1,41 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2023 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.repos; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import com.spotify.github.v3.git.Commit; +import org.immutables.value.Value; + +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableCommitWithFolderContent.class) +@JsonDeserialize(as = ImmutableCommitWithFolderContent.class) +public interface CommitWithFolderContent { + + /** Repository content resource */ + FolderContent content(); + + /** Commit resource */ + Commit commit(); + +} diff --git a/src/main/java/com/spotify/github/v3/repos/requests/FileCreate.java b/src/main/java/com/spotify/github/v3/repos/requests/FileCreate.java new file mode 100644 index 00000000..33ccd2c6 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/repos/requests/FileCreate.java @@ -0,0 +1,49 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2023 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.repos.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; + +import javax.annotation.Nullable; + +/** + * Request to create a file. + */ +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableFileCreate.class) +@JsonDeserialize(as = ImmutableFileCreate.class) +public interface FileCreate { + + /** The commit message */ + String message(); + + /** The new file content, using Base64 encoding */ + String content(); + + /** The branch name. Default: the repository’s default branch */ + @Nullable + String branch(); + +} diff --git a/src/main/java/com/spotify/github/v3/repos/requests/FileUpdate.java b/src/main/java/com/spotify/github/v3/repos/requests/FileUpdate.java new file mode 100644 index 00000000..1a100884 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/repos/requests/FileUpdate.java @@ -0,0 +1,52 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2023 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.repos.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; + +import javax.annotation.Nullable; + +/** + * Request to update file content. + */ +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableFileUpdate.class) +@JsonDeserialize(as = ImmutableFileUpdate.class) +public interface FileUpdate { + + /** The commit message */ + String message(); + + /** The new file content, using Base64 encoding */ + String content(); + + /** The SHA of the file being replaced. */ + String sha(); + + /** The branch name. Default: the repository’s default branch */ + @Nullable + String branch(); + +} diff --git a/src/test/java/com/spotify/github/v3/clients/RepositoryClientTest.java b/src/test/java/com/spotify/github/v3/clients/RepositoryClientTest.java index f2eea5ef..28af422e 100644 --- a/src/test/java/com/spotify/github/v3/clients/RepositoryClientTest.java +++ b/src/test/java/com/spotify/github/v3/clients/RepositoryClientTest.java @@ -40,7 +40,10 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.matches; +import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -56,6 +59,7 @@ import com.spotify.github.v3.repos.CommitComparison; import com.spotify.github.v3.repos.CommitItem; import com.spotify.github.v3.repos.CommitStatus; +import com.spotify.github.v3.repos.CommitWithFolderContent; import com.spotify.github.v3.repos.Content; import com.spotify.github.v3.repos.FolderContent; import com.spotify.github.v3.repos.Repository; @@ -63,12 +67,18 @@ import com.spotify.github.v3.repos.RepositoryPermission; import com.spotify.github.v3.repos.RepositoryTest; import com.spotify.github.v3.repos.Status; +import com.spotify.github.v3.repos.requests.FileCreate; +import com.spotify.github.v3.repos.requests.FileUpdate; import com.spotify.github.v3.repos.requests.ImmutableAuthenticatedUserRepositoriesFilter; +import com.spotify.github.v3.repos.requests.ImmutableFileCreate; import java.io.IOException; +import java.util.HashMap; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; + +import com.spotify.github.v3.repos.requests.ImmutableFileUpdate; import okhttp3.Headers; import okhttp3.MediaType; import okhttp3.Protocol; @@ -79,8 +89,10 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import uk.co.datumedge.hamcrest.json.SameJSONAs; @RunWith(PowerMockRunner.class) @PrepareForTest({ Headers.class, ResponseBody.class, Response.class}) @@ -298,6 +310,60 @@ public void getFileContent() throws Exception { assertThat(fileContent.content(), is("encoded content ...")); } + @Test + public void createFileContent() throws Exception { + String rawFileCreateRequest = getFixture("create-content-request.json"); + final CompletableFuture fixture = completedFuture( + json.fromJson(getFixture("create-content-repsonse.json"), CommitWithFolderContent.class) + ); + when(github.put( + eq("/repos/someowner/somerepo/contents/test/README.md"), + argThat(body -> SameJSONAs.sameJSONAs(rawFileCreateRequest).matches(body)), + eq(CommitWithFolderContent.class) + )).thenReturn(fixture); + + FileCreate fileCreateRequest = ImmutableFileCreate.builder() + .message("my commit message") + .content("encoded content ...") + .build(); + + final CommitWithFolderContent commitWithFolderContent = + repoClient.createFileContent("test/README.md", fileCreateRequest).get(); + assertThat(commitWithFolderContent.commit().message(), is("my commit message")); + assertThat(commitWithFolderContent.content().type(), is("file")); + assertThat(commitWithFolderContent.content().name(), is("README.md")); + assertThat(commitWithFolderContent.content().path(), is("test/README.md")); + } + + @Test + public void updateFileContent() throws Exception { + String rawFileUpdateRequest = getFixture("update-content-request.json"); + final CompletableFuture fixture = completedFuture( + json.fromJson(getFixture("create-content-repsonse.json"), CommitWithFolderContent.class) + ); + when(github.put( + eq("/repos/someowner/somerepo/contents/test/README.md"), + argThat(body -> SameJSONAs.sameJSONAs(rawFileUpdateRequest).matches(body)), + eq(CommitWithFolderContent.class) + )).thenReturn(fixture); + + FileUpdate fileUpdateRequest = ImmutableFileUpdate.builder() + .message("my commit message") + .content("encoded content ...") + .branch("test-branch") + .sha("12345") + .build(); + + final CommitWithFolderContent commitWithFolderContent = + repoClient.updateFileContent("test/README.md", fileUpdateRequest).get(); + assertThat(commitWithFolderContent.commit().message(), is("my commit message")); + assertThat(commitWithFolderContent.content().type(), is("file")); + assertThat(commitWithFolderContent.content().name(), is("README.md")); + assertThat(commitWithFolderContent.content().path(), is("test/README.md")); + } + + + @Test public void getFolderContent() throws Exception { final CompletableFuture> fixture = diff --git a/src/test/resources/com/spotify/github/v3/repos/create-content-repsonse.json b/src/test/resources/com/spotify/github/v3/repos/create-content-repsonse.json new file mode 100644 index 00000000..9ddec3dc --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/repos/create-content-repsonse.json @@ -0,0 +1,52 @@ +{ + "content": { + "name": "README.md", + "path": "test/README.md", + "sha": "95b966ae1c166bd92f8ae7d1c313e738c731dfc3", + "size": 9, + "url": "https://api.github.com/repos/someowner/somerepo/contents/test/README.md", + "html_url": "https://github.com/someowner/somerepo/blob/master/test/README.md", + "git_url": "https://api.github.com/repos/someowner/somerepo/git/blobs/95b966ae1c166bd92f8ae7d1c313e738c731dfc3", + "download_url": "https://raw.githubusercontent.com/someowner/HelloWorld/master/test/README.md", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/someowner/somerepo/contents/test/README.md", + "git": "https://api.github.com/repos/someowner/somerepo/git/blobs/95b966ae1c166bd92f8ae7d1c313e738c731dfc3", + "html": "https://github.com/someowner/somerepo/blob/master/test/README.md" + } + }, + "commit": { + "sha": "7638417db6d59f3c431d3e1f261cc637155684cd", + "node_id": "MDY6Q29tbWl0NzYzODQxN2RiNmQ1OWYzYzQzMWQzZTFmMjYxY2M2MzcxNTU2ODRjZA==", + "url": "https://api.github.com/repos/someowner/somerepo/git/commits/7638417db6d59f3c431d3e1f261cc637155684cd", + "html_url": "https://github.com/someowner/somerepo/git/commit/7638417db6d59f3c431d3e1f261cc637155684cd", + "author": { + "date": "2014-11-07T22:01:45Z", + "name": "Monalisa Octocat", + "email": "octocat@github.com" + }, + "committer": { + "date": "2014-11-07T22:01:45Z", + "name": "Monalisa Octocat", + "email": "octocat@github.com" + }, + "message": "my commit message", + "tree": { + "url": "https://api.github.com/repos/someowner/somerepo/git/trees/691272480426f78a0138979dd3ce63b77f706feb", + "sha": "691272480426f78a0138979dd3ce63b77f706feb" + }, + "parents": [ + { + "url": "https://api.github.com/repos/someowner/somerepo/git/commits/1acc419d4d6a9ce985db7be48c6349a0475975b5", + "html_url": "https://github.com/someowner/somerepo/git/commit/1acc419d4d6a9ce985db7be48c6349a0475975b5", + "sha": "1acc419d4d6a9ce985db7be48c6349a0475975b5" + } + ], + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null + } + } +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/repos/create-content-request.json b/src/test/resources/com/spotify/github/v3/repos/create-content-request.json new file mode 100644 index 00000000..360f517c --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/repos/create-content-request.json @@ -0,0 +1,4 @@ +{ + "message": "my commit message", + "content": "encoded content ..." +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/repos/update-content-repsonse.json b/src/test/resources/com/spotify/github/v3/repos/update-content-repsonse.json new file mode 100644 index 00000000..9ddec3dc --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/repos/update-content-repsonse.json @@ -0,0 +1,52 @@ +{ + "content": { + "name": "README.md", + "path": "test/README.md", + "sha": "95b966ae1c166bd92f8ae7d1c313e738c731dfc3", + "size": 9, + "url": "https://api.github.com/repos/someowner/somerepo/contents/test/README.md", + "html_url": "https://github.com/someowner/somerepo/blob/master/test/README.md", + "git_url": "https://api.github.com/repos/someowner/somerepo/git/blobs/95b966ae1c166bd92f8ae7d1c313e738c731dfc3", + "download_url": "https://raw.githubusercontent.com/someowner/HelloWorld/master/test/README.md", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/someowner/somerepo/contents/test/README.md", + "git": "https://api.github.com/repos/someowner/somerepo/git/blobs/95b966ae1c166bd92f8ae7d1c313e738c731dfc3", + "html": "https://github.com/someowner/somerepo/blob/master/test/README.md" + } + }, + "commit": { + "sha": "7638417db6d59f3c431d3e1f261cc637155684cd", + "node_id": "MDY6Q29tbWl0NzYzODQxN2RiNmQ1OWYzYzQzMWQzZTFmMjYxY2M2MzcxNTU2ODRjZA==", + "url": "https://api.github.com/repos/someowner/somerepo/git/commits/7638417db6d59f3c431d3e1f261cc637155684cd", + "html_url": "https://github.com/someowner/somerepo/git/commit/7638417db6d59f3c431d3e1f261cc637155684cd", + "author": { + "date": "2014-11-07T22:01:45Z", + "name": "Monalisa Octocat", + "email": "octocat@github.com" + }, + "committer": { + "date": "2014-11-07T22:01:45Z", + "name": "Monalisa Octocat", + "email": "octocat@github.com" + }, + "message": "my commit message", + "tree": { + "url": "https://api.github.com/repos/someowner/somerepo/git/trees/691272480426f78a0138979dd3ce63b77f706feb", + "sha": "691272480426f78a0138979dd3ce63b77f706feb" + }, + "parents": [ + { + "url": "https://api.github.com/repos/someowner/somerepo/git/commits/1acc419d4d6a9ce985db7be48c6349a0475975b5", + "html_url": "https://github.com/someowner/somerepo/git/commit/1acc419d4d6a9ce985db7be48c6349a0475975b5", + "sha": "1acc419d4d6a9ce985db7be48c6349a0475975b5" + } + ], + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null + } + } +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/repos/update-content-request.json b/src/test/resources/com/spotify/github/v3/repos/update-content-request.json new file mode 100644 index 00000000..16bf148c --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/repos/update-content-request.json @@ -0,0 +1,6 @@ +{ + "message": "my commit message", + "content": "encoded content ...", + "sha": "12345", + "branch": "test-branch" +} \ No newline at end of file