From 3f07b241f9457e6270b1672dc206e31d348cb7be Mon Sep 17 00:00:00 2001 From: Michael Wurster Date: Thu, 28 Dec 2023 15:15:05 +0100 Subject: [PATCH] feat: add ability to download repository content as zip/tar (#152) * separate methods to download the tar or zip archive * methods return an Optional InputStream that can be consumed --- .../github/v3/clients/RepositoryClient.java | 53 ++++++++++++++++ .../v3/clients/RepositoryClientTest.java | 61 +++++++++++++++++++ 2 files changed, 114 insertions(+) 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..d3f247d1 100644 --- a/src/main/java/com/spotify/github/v3/clients/RepositoryClient.java +++ b/src/main/java/com/spotify/github/v3/clients/RepositoryClient.java @@ -50,6 +50,7 @@ import com.spotify.github.v3.repos.Status; import com.spotify.github.v3.repos.requests.AuthenticatedUserRepositoriesFilter; import com.spotify.github.v3.repos.requests.RepositoryCreateStatus; +import java.io.InputStream; import java.lang.invoke.MethodHandles; import java.util.Iterator; import java.util.List; @@ -91,6 +92,8 @@ public class RepositoryClient { private static final String REPOSITORY_COLLABORATOR = "/repos/%s/%s/collaborators/%s"; private static final String REPOSITORY_INVITATION = "/repos/%s/%s/invitations/%s"; private static final String REPOSITORY_INVITATIONS = "/repos/%s/%s/invitations"; + private static final String REPOSITORY_DOWNLOAD_TARBALL = "/repos/%s/%s/tarball/%s"; + private static final String REPOSITORY_DOWNLOAD_ZIPBALL = "/repos/%s/%s/zipball/%s"; private final String owner; private final String repo; private final GitHubClient github; @@ -242,6 +245,56 @@ public CompletableFuture> listInvitations() { return github.request(path, LIST_REPOSITORY_INVITATION); } + /** + * Downloads a tar archive of the repository’s default branch (usually main). + * + * @return a CompletableFuture that resolves to an Optional InputStream + */ + public CompletableFuture> downloadTarball() { + return downloadRepository(REPOSITORY_DOWNLOAD_TARBALL, Optional.empty()); + } + + /** + * Downloads a tar archive of the repository. Use :ref to specify a branch or tag to download. + * + * @return a CompletableFuture that resolves to an Optional InputStream + */ + public CompletableFuture> downloadTarball(final String ref) { + return downloadRepository(REPOSITORY_DOWNLOAD_TARBALL, Optional.of(ref)); + } + + /** + * Downloads a zip archive of the repository’s default branch (usually main). + * + * @return a CompletableFuture that resolves to an Optional InputStream + */ + public CompletableFuture> downloadZipball() { + return downloadRepository(REPOSITORY_DOWNLOAD_ZIPBALL, Optional.empty()); + } + + /** + * Downloads a zip archive of the repository. Use :ref to specify a branch or tag to download. + * + * @return a CompletableFuture that resolves to an Optional InputStream + */ + public CompletableFuture> downloadZipball(final String ref) { + return downloadRepository(REPOSITORY_DOWNLOAD_ZIPBALL, Optional.of(ref)); + } + + private CompletableFuture> downloadRepository(final String path, final Optional maybeRef) { + final var repoRef = maybeRef.orElse(""); + final var repoPath = String.format(path, owner, repo, repoRef); + return github.request(repoPath).thenApply(response -> { + var body = response.body(); + + if (body == null) { + return Optional.empty(); + } + + return Optional.of(body.byteStream()); + }); + } + /** * Create a webhook. * 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 bee911d9..28a5242c 100644 --- a/src/test/java/com/spotify/github/v3/clients/RepositoryClientTest.java +++ b/src/test/java/com/spotify/github/v3/clients/RepositoryClientTest.java @@ -65,6 +65,8 @@ import com.spotify.github.v3.repos.Status; import com.spotify.github.v3.repos.requests.ImmutableAuthenticatedUserRepositoriesFilter; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -537,4 +539,63 @@ public void mergeNoop() { final Optional maybeCommit = repoClient.merge("basebranch", "headbranch").join(); assertThat(maybeCommit, is(Optional.empty())); } + + @Test + public void shouldDownloadTarball() throws Exception { + CompletableFuture fixture = completedFuture( + new Response.Builder() + .request(new Request.Builder().url("https://example.com/whatever").build()) + .protocol(Protocol.HTTP_1_1) + .message("") + .code(200) + .body( + ResponseBody.create( + "some bytes".getBytes(StandardCharsets.UTF_8), + MediaType.get("application/gzip") + )) + .build()); + when(github.request("/repos/someowner/somerepo/tarball/")).thenReturn(fixture); + + try(InputStream response = repoClient.downloadTarball().get().orElseThrow()) { + String result = new String(response.readAllBytes(), StandardCharsets.UTF_8); + assertThat(result, is("some bytes")); + } + } + + @Test + public void shouldDownloadZipball() throws Exception { + CompletableFuture fixture = completedFuture( + new Response.Builder() + .request(new Request.Builder().url("https://example.com/whatever").build()) + .protocol(Protocol.HTTP_1_1) + .message("") + .code(200) + .body( + ResponseBody.create( + "some bytes".getBytes(StandardCharsets.UTF_8), + MediaType.get("application/gzip") + )) + .build()); + when(github.request("/repos/someowner/somerepo/zipball/")).thenReturn(fixture); + + try (InputStream response = repoClient.downloadZipball().get().orElseThrow()) { + String result = new String(response.readAllBytes(), StandardCharsets.UTF_8); + assertThat(result, is("some bytes")); + } + } + + @Test + public void shouldReturnEmptyOptionalWhenResponseBodyNotPresent() throws Exception { + CompletableFuture fixture = completedFuture( + new Response.Builder() + .request(new Request.Builder().url("https://example.com/whatever").build()) + .protocol(Protocol.HTTP_1_1) + .message("") + .code(204) // No Content + .build()); + when(github.request("/repos/someowner/somerepo/zipball/master")).thenReturn(fixture); + + Optional response = repoClient.downloadZipball("master").get(); + assertThat(response, is(Optional.empty())); + } }