Skip to content

Commit 213eff4

Browse files
authored
Merge pull request #1214 from ravingbool/1209_ghcompare_supports_per_page_and_page+params
1209 GHCompare supports per_page and page params
2 parents 4b37a82 + a60a93f commit 213eff4

46 files changed

Lines changed: 59946 additions & 14 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

pom.xml

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@
166166
<exclude>org.kohsuke.github.extras.OkHttp3Connector</exclude>
167167
<exclude>org.kohsuke.github.EnforcementLevel</exclude>
168168
<exclude>org.kohsuke.github.GHPerson.1</exclude>
169-
169+
<exclude>org.kohsuke.github.GHCompare.User</exclude>
170+
170171
<!-- TODO: Some coverage, but more needed -->
171172
<exclude>org.kohsuke.github.GHPullRequestReviewBuilder.DraftReviewComment</exclude>
172173
<exclude>org.kohsuke.github.GHIssue.PullRequest</exclude>
@@ -179,11 +180,6 @@
179180
<exclude>org.kohsuke.github.GHBranchProtectionBuilder.Restrictions</exclude>
180181
<exclude>org.kohsuke.github.GHBranchProtection.Restrictions</exclude>
181182
<exclude>org.kohsuke.github.GHCommentAuthorAssociation</exclude>
182-
<exclude>org.kohsuke.github.GHCompare.Commit</exclude>
183-
<exclude>org.kohsuke.github.GHCompare.InnerCommit</exclude>
184-
<exclude>org.kohsuke.github.GHCompare.Tree</exclude>
185-
<exclude>org.kohsuke.github.GHCompare.User</exclude>
186-
<exclude>org.kohsuke.github.GHCompare</exclude>
187183
<exclude>org.kohsuke.github.GHDeployKey</exclude>
188184
<exclude>org.kohsuke.github.GHEmail</exclude>
189185
<exclude>org.kohsuke.github.GHInvitation</exclude>

src/main/java/org/kohsuke/github/GHCompare.java

Lines changed: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
package org.kohsuke.github;
22

3+
import com.fasterxml.jackson.annotation.JacksonInject;
34
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
45
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
6+
import org.jetbrains.annotations.NotNull;
57

8+
import java.io.IOException;
9+
import java.net.MalformedURLException;
610
import java.net.URL;
11+
import java.util.Collections;
12+
import java.util.Iterator;
13+
14+
import javax.annotation.Nonnull;
715

816
/**
917
* The model user for comparing 2 commits in the GitHub API.
@@ -21,6 +29,9 @@ public class GHCompare {
2129

2230
private GHRepository owner;
2331

32+
@JacksonInject("GHCompare_usePaginatedCommits")
33+
private boolean usePaginatedCommits;
34+
2435
/**
2536
* Gets url.
2637
*
@@ -123,16 +134,56 @@ public Commit getMergeBaseCommit() {
123134
/**
124135
* Gets an array of commits.
125136
*
137+
* By default, the commit list is limited to 250 results.
138+
*
139+
* Since
140+
* <a href="https://github.blog/changelog/2021-03-22-compare-rest-api-now-supports-pagination/">2021-03-22</a>,
141+
* compare supports pagination of commits. This makes the initial {@link GHCompare} response return faster and
142+
* supports comparisons with more than 250 commits. To read commits progressively using pagination, set
143+
* {@link GHRepository#setCompareUsePaginatedCommits(boolean)} to true before calling
144+
* {@link GHRepository#getCompare(String, String)}.
145+
*
126146
* @return A copy of the array being stored in the class.
127147
*/
128148
public Commit[] getCommits() {
129-
Commit[] newValue = new Commit[commits.length];
130-
System.arraycopy(commits, 0, newValue, 0, commits.length);
131-
return newValue;
149+
try {
150+
return listCommits().withPageSize(100).toArray();
151+
} catch (IOException e) {
152+
throw new GHException(e.getMessage(), e);
153+
}
132154
}
133155

134156
/**
135-
* Gets an array of commits.
157+
* Iterable of commits for this comparison.
158+
*
159+
* By default, the commit list is limited to 250 results.
160+
*
161+
* Since
162+
* <a href="https://github.blog/changelog/2021-03-22-compare-rest-api-now-supports-pagination/">2021-03-22</a>,
163+
* compare supports pagination of commits. This makes the initial {@link GHCompare} response return faster and
164+
* supports comparisons with more than 250 commits. To read commits progressively using pagination, set
165+
* {@link GHRepository#setCompareUsePaginatedCommits(boolean)} to true before calling
166+
* {@link GHRepository#getCompare(String, String)}.
167+
*
168+
* @return iterable of commits
169+
*/
170+
public PagedIterable<Commit> listCommits() {
171+
if (usePaginatedCommits) {
172+
return new GHCompareCommitsIterable();
173+
} else {
174+
// if not using paginated commits, adapt the returned commits array
175+
return new PagedIterable<Commit>() {
176+
@NotNull
177+
@Override
178+
public PagedIterator<Commit> _iterator(int pageSize) {
179+
return new PagedIterator<>(Collections.singleton(commits).iterator(), null);
180+
}
181+
};
182+
}
183+
}
184+
185+
/**
186+
* Gets an array of files.
136187
*
137188
* @return A copy of the array being stored in the class.
138189
*/
@@ -283,4 +334,54 @@ public static class User extends GitUser {
283334
public static enum Status {
284335
behind, ahead, identical, diverged
285336
}
337+
338+
/**
339+
* Iterable for commit listing.
340+
*/
341+
class GHCompareCommitsIterable extends PagedIterable<Commit> {
342+
343+
private GHCompare result;
344+
345+
public GHCompareCommitsIterable() {
346+
}
347+
348+
@Nonnull
349+
@Override
350+
public PagedIterator<Commit> _iterator(int pageSize) {
351+
try {
352+
GitHubRequest request = owner.getRoot()
353+
.createRequest()
354+
.injectMappingValue("GHCompare_usePaginatedCommits", usePaginatedCommits)
355+
.withUrlPath(owner.getApiTailUrl(url.substring(url.lastIndexOf("/compare/"))))
356+
.build();
357+
358+
// page_size must be set for GHCompare commit pagination
359+
if (pageSize == 0) {
360+
pageSize = 10;
361+
}
362+
return new PagedIterator<>(
363+
adapt(GitHubPageIterator
364+
.create(owner.getRoot().getClient(), GHCompare.class, request, pageSize)),
365+
item -> item.wrapUp(owner));
366+
} catch (MalformedURLException e) {
367+
throw new GHException("Malformed URL", e);
368+
}
369+
}
370+
371+
protected Iterator<Commit[]> adapt(final Iterator<GHCompare> base) {
372+
return new Iterator<Commit[]>() {
373+
public boolean hasNext() {
374+
return base.hasNext();
375+
}
376+
377+
public Commit[] next() {
378+
GHCompare v = base.next();
379+
if (result == null) {
380+
result = v;
381+
}
382+
return v.commits;
383+
}
384+
};
385+
}
386+
}
286387
}

src/main/java/org/kohsuke/github/GHRepository.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ public class GHRepository extends GHObject {
125125
private GHRepository source, parent;
126126

127127
private Boolean isTemplate;
128+
private boolean compareUsePaginatedCommits;
128129

129130
static GHRepository read(GitHub root, String owner, String name) throws IOException {
130131
return root.createRequest().withUrlPath("/repos/" + owner + '/' + name).fetch(GHRepository.class).wrap(root);
@@ -1642,6 +1643,21 @@ public GHHook getHook(int id) throws IOException {
16421643
return GHHooks.repoContext(this, owner).getHook(id);
16431644
}
16441645

1646+
/**
1647+
* Sets {@link #getCompare(String, String)} to return a {@link GHCompare} that uses a paginated commit list instead
1648+
* of limiting to 250 results.
1649+
*
1650+
* By default, {@link GHCompare} returns all commits in the comparison as part of the request, limited to 250
1651+
* results. More recently GitHub added the ability to return the commits as a paginated query allowing for more than
1652+
* 250 results.
1653+
*
1654+
* @param value
1655+
* true if you want commits returned in paginated form.
1656+
*/
1657+
public void setCompareUsePaginatedCommits(boolean value) {
1658+
compareUsePaginatedCommits = value;
1659+
}
1660+
16451661
/**
16461662
* Gets a comparison between 2 points in the repository. This would be similar to calling
16471663
* <code>git log id1...id2</code> against a local repository.
@@ -1656,9 +1672,14 @@ public GHHook getHook(int id) throws IOException {
16561672
* on failure communicating with GitHub
16571673
*/
16581674
public GHCompare getCompare(String id1, String id2) throws IOException {
1659-
GHCompare compare = root.createRequest()
1660-
.withUrlPath(getApiTailUrl(String.format("compare/%s...%s", id1, id2)))
1661-
.fetch(GHCompare.class);
1675+
final Requester requester = root.createRequest()
1676+
.withUrlPath(getApiTailUrl(String.format("compare/%s...%s", id1, id2)));
1677+
1678+
if (compareUsePaginatedCommits) {
1679+
requester.with("per_page", 1).with("page", 1);
1680+
}
1681+
requester.injectMappingValue("GHCompare_usePaginatedCommits", compareUsePaginatedCommits);
1682+
GHCompare compare = requester.fetch(GHCompare.class);
16621683
return compare.wrap(this);
16631684
}
16641685

@@ -1705,7 +1726,6 @@ public GHCompare getCompare(GHBranch id1, GHBranch id2) throws IOException {
17051726
}
17061727

17071728
return getCompare(id1.getName(), id2.getName());
1708-
17091729
}
17101730

17111731
/**

src/test/java/org/kohsuke/github/GHRepositoryTest.java

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,4 +866,123 @@ public void getLastCommitStatus() throws Exception {
866866
assertThat(status.getState(), equalTo(GHCommitState.SUCCESS));
867867
assertThat(status.getContext(), equalTo("ci/circleci: build"));
868868
}
869+
870+
@Test
871+
public void listCommitsBetween() throws Exception {
872+
GHRepository repository = getRepository();
873+
int startingCount = mockGitHub.getRequestCount();
874+
GHCompare compare = repository.getCompare("e46a9f3f2ac55db96de3c5c4706f2813b3a96465",
875+
"8051615eff597f4e49f4f47625e6fc2b49f26bfc");
876+
int actualCount = 0;
877+
for (GHCompare.Commit item : compare.listCommits().withPageSize(5)) {
878+
assertThat(item, notNullValue());
879+
actualCount++;
880+
}
881+
assertThat(compare.getTotalCommits(), is(9));
882+
assertThat(actualCount, is(9));
883+
assertThat(mockGitHub.getRequestCount(), equalTo(startingCount + 1));
884+
}
885+
886+
@Test
887+
public void listCommitsBetweenPaginated() throws Exception {
888+
GHRepository repository = getRepository();
889+
int startingCount = mockGitHub.getRequestCount();
890+
repository.setCompareUsePaginatedCommits(true);
891+
GHCompare compare = repository.getCompare("e46a9f3f2ac55db96de3c5c4706f2813b3a96465",
892+
"8051615eff597f4e49f4f47625e6fc2b49f26bfc");
893+
int actualCount = 0;
894+
for (GHCompare.Commit item : compare.listCommits().withPageSize(5)) {
895+
assertThat(item, notNullValue());
896+
actualCount++;
897+
}
898+
assertThat(compare.getTotalCommits(), is(9));
899+
assertThat(actualCount, is(9));
900+
assertThat(mockGitHub.getRequestCount(), equalTo(startingCount + 3));
901+
}
902+
903+
@Test
904+
public void getCommitsBetweenOver250() throws Exception {
905+
GHRepository repository = getRepository();
906+
int startingCount = mockGitHub.getRequestCount();
907+
GHCompare compare = repository.getCompare("4261c42949915816a9f246eb14c3dfd21a637bc2",
908+
"94ff089e60064bfa43e374baeb10846f7ce82f40");
909+
int actualCount = 0;
910+
for (GHCompare.Commit item : compare.getCommits()) {
911+
assertThat(item, notNullValue());
912+
actualCount++;
913+
}
914+
assertThat(compare.getTotalCommits(), is(283));
915+
assertThat(actualCount, is(250));
916+
assertThat(mockGitHub.getRequestCount(), equalTo(startingCount + 1));
917+
918+
// Additional GHCompare checks
919+
assertThat(compare.getAheadBy(), equalTo(283));
920+
assertThat(compare.getBehindBy(), equalTo(0));
921+
assertThat(compare.getStatus(), equalTo(GHCompare.Status.ahead));
922+
assertThat(compare.getDiffUrl().toString(),
923+
endsWith(
924+
"compare/4261c42949915816a9f246eb14c3dfd21a637bc2...94ff089e60064bfa43e374baeb10846f7ce82f40.diff"));
925+
assertThat(compare.getHtmlUrl().toString(),
926+
endsWith(
927+
"compare/4261c42949915816a9f246eb14c3dfd21a637bc2...94ff089e60064bfa43e374baeb10846f7ce82f40"));
928+
assertThat(compare.getPatchUrl().toString(),
929+
endsWith(
930+
"compare/4261c42949915816a9f246eb14c3dfd21a637bc2...94ff089e60064bfa43e374baeb10846f7ce82f40.patch"));
931+
assertThat(compare.getPermalinkUrl().toString(),
932+
endsWith("compare/hub4j-test-org:4261c42...hub4j-test-org:94ff089"));
933+
assertThat(compare.getUrl().toString(),
934+
endsWith(
935+
"compare/4261c42949915816a9f246eb14c3dfd21a637bc2...94ff089e60064bfa43e374baeb10846f7ce82f40"));
936+
937+
assertThat(compare.getBaseCommit().getSHA1(), equalTo("4261c42949915816a9f246eb14c3dfd21a637bc2"));
938+
939+
assertThat(compare.getMergeBaseCommit().getSHA1(), equalTo("4261c42949915816a9f246eb14c3dfd21a637bc2"));
940+
// it appears this field is not present in the returned JSON. Strange.
941+
assertThat(compare.getMergeBaseCommit().getCommit().getSha(), nullValue());
942+
assertThat(compare.getMergeBaseCommit().getCommit().getUrl(),
943+
endsWith("/commits/4261c42949915816a9f246eb14c3dfd21a637bc2"));
944+
assertThat(compare.getMergeBaseCommit().getCommit().getMessage(),
945+
endsWith("[maven-release-plugin] prepare release github-api-1.123"));
946+
assertThat(compare.getMergeBaseCommit().getCommit().getAuthor().getName(), equalTo("Liam Newman"));
947+
assertThat(compare.getMergeBaseCommit().getCommit().getCommitter().getName(), equalTo("Liam Newman"));
948+
949+
assertThat(compare.getMergeBaseCommit().getCommit().getTree().getSha(),
950+
equalTo("5da98090976978c93aba0bdfa550e05675543f99"));
951+
assertThat(compare.getMergeBaseCommit().getCommit().getTree().getUrl(),
952+
endsWith("/git/trees/5da98090976978c93aba0bdfa550e05675543f99"));
953+
954+
assertThat(compare.getFiles().length, equalTo(300));
955+
assertThat(compare.getFiles()[0].getFileName(), equalTo(".github/PULL_REQUEST_TEMPLATE.md"));
956+
assertThat(compare.getFiles()[0].getLinesAdded(), equalTo(8));
957+
assertThat(compare.getFiles()[0].getLinesChanged(), equalTo(15));
958+
assertThat(compare.getFiles()[0].getLinesDeleted(), equalTo(7));
959+
assertThat(compare.getFiles()[0].getFileName(), equalTo(".github/PULL_REQUEST_TEMPLATE.md"));
960+
assertThat(compare.getFiles()[0].getPatch(), startsWith("@@ -1,15 +1,16 @@"));
961+
assertThat(compare.getFiles()[0].getPreviousFilename(), nullValue());
962+
assertThat(compare.getFiles()[0].getStatus(), equalTo("modified"));
963+
assertThat(compare.getFiles()[0].getSha(), equalTo("e4234f5f6f39899282a6ef1edff343ae1269222e"));
964+
965+
assertThat(compare.getFiles()[0].getBlobUrl().toString(),
966+
endsWith("/blob/94ff089e60064bfa43e374baeb10846f7ce82f40/.github/PULL_REQUEST_TEMPLATE.md"));
967+
assertThat(compare.getFiles()[0].getRawUrl().toString(),
968+
endsWith("/raw/94ff089e60064bfa43e374baeb10846f7ce82f40/.github/PULL_REQUEST_TEMPLATE.md"));
969+
}
970+
971+
@Test
972+
public void getCommitsBetweenPaged() throws Exception {
973+
GHRepository repository = getRepository();
974+
int startingCount = mockGitHub.getRequestCount();
975+
repository.setCompareUsePaginatedCommits(true);
976+
GHCompare compare = repository.getCompare("4261c42949915816a9f246eb14c3dfd21a637bc2",
977+
"94ff089e60064bfa43e374baeb10846f7ce82f40");
978+
int actualCount = 0;
979+
for (GHCompare.Commit item : compare.getCommits()) {
980+
assertThat(item, notNullValue());
981+
actualCount++;
982+
}
983+
assertThat(compare.getTotalCommits(), is(283));
984+
assertThat(actualCount, is(283));
985+
assertThat(mockGitHub.getRequestCount(), equalTo(startingCount + 4));
986+
}
987+
869988
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"login": "hub4j-test-org",
3+
"id": 7544739,
4+
"node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
5+
"url": "https://api.github.com/orgs/hub4j-test-org",
6+
"repos_url": "https://api.github.com/orgs/hub4j-test-org/repos",
7+
"events_url": "https://api.github.com/orgs/hub4j-test-org/events",
8+
"hooks_url": "https://api.github.com/orgs/hub4j-test-org/hooks",
9+
"issues_url": "https://api.github.com/orgs/hub4j-test-org/issues",
10+
"members_url": "https://api.github.com/orgs/hub4j-test-org/members{/member}",
11+
"public_members_url": "https://api.github.com/orgs/hub4j-test-org/public_members{/member}",
12+
"avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4",
13+
"description": "Hub4j Test Org Description (this could be null or blank too)",
14+
"name": "Hub4j Test Org Name (this could be null or blank too)",
15+
"company": null,
16+
"blog": "https://hub4j.url.io/could/be/null",
17+
"location": "Hub4j Test Org Location (this could be null or blank too)",
18+
"email": "hub4jtestorgemail@could.be.null.com",
19+
"twitter_username": null,
20+
"is_verified": false,
21+
"has_organization_projects": true,
22+
"has_repository_projects": true,
23+
"public_repos": 19,
24+
"public_gists": 0,
25+
"followers": 0,
26+
"following": 0,
27+
"html_url": "https://github.com/hub4j-test-org",
28+
"created_at": "2014-05-10T19:39:11Z",
29+
"updated_at": "2020-06-04T05:56:10Z",
30+
"type": "Organization",
31+
"total_private_repos": 2,
32+
"owned_private_repos": 2,
33+
"private_gists": 0,
34+
"disk_usage": 11979,
35+
"collaborators": 0,
36+
"billing_email": "kk@kohsuke.org",
37+
"default_repository_permission": "none",
38+
"members_can_create_repositories": false,
39+
"two_factor_requirement_enabled": false,
40+
"members_can_create_pages": true,
41+
"members_can_create_public_pages": true,
42+
"members_can_create_private_pages": true,
43+
"plan": {
44+
"name": "free",
45+
"space": 976562499,
46+
"private_repos": 10000,
47+
"filled_seats": 26,
48+
"seats": 3
49+
}
50+
}

0 commit comments

Comments
 (0)