Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@
<exclude>org.kohsuke.github.extras.OkHttp3Connector</exclude>
<exclude>org.kohsuke.github.EnforcementLevel</exclude>
<exclude>org.kohsuke.github.GHPerson.1</exclude>

<exclude>org.kohsuke.github.GHCompare.User</exclude>

<!-- TODO: Some coverage, but more needed -->
<exclude>org.kohsuke.github.GHPullRequestReviewBuilder.DraftReviewComment</exclude>
<exclude>org.kohsuke.github.GHIssue.PullRequest</exclude>
Expand All @@ -179,11 +180,6 @@
<exclude>org.kohsuke.github.GHBranchProtectionBuilder.Restrictions</exclude>
<exclude>org.kohsuke.github.GHBranchProtection.Restrictions</exclude>
<exclude>org.kohsuke.github.GHCommentAuthorAssociation</exclude>
<exclude>org.kohsuke.github.GHCompare.Commit</exclude>
<exclude>org.kohsuke.github.GHCompare.InnerCommit</exclude>
<exclude>org.kohsuke.github.GHCompare.Tree</exclude>
<exclude>org.kohsuke.github.GHCompare.User</exclude>
<exclude>org.kohsuke.github.GHCompare</exclude>
<exclude>org.kohsuke.github.GHDeployKey</exclude>
<exclude>org.kohsuke.github.GHEmail</exclude>
<exclude>org.kohsuke.github.GHInvitation</exclude>
Expand Down
109 changes: 105 additions & 4 deletions src/main/java/org/kohsuke/github/GHCompare.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
package org.kohsuke.github;

import com.fasterxml.jackson.annotation.JacksonInject;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Iterator;

import javax.annotation.Nonnull;

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

private GHRepository owner;

@JacksonInject("GHCompare_usePaginatedCommits")
private boolean usePaginatedCommits;

/**
* Gets url.
*
Expand Down Expand Up @@ -123,16 +134,56 @@ public Commit getMergeBaseCommit() {
/**
* Gets an array of commits.
*
* By default, the commit list is limited to 250 results.
*
* Since
* <a href="https://github.blog/changelog/2021-03-22-compare-rest-api-now-supports-pagination/">2021-03-22</a>,
* compare supports pagination of commits. This makes the initial {@link GHCompare} response return faster and
* supports comparisons with more than 250 commits. To read commits progressively using pagination, set
* {@link GHRepository#setCompareUsePaginatedCommits(boolean)} to true before calling
* {@link GHRepository#getCompare(String, String)}.
*
* @return A copy of the array being stored in the class.
*/
public Commit[] getCommits() {
Commit[] newValue = new Commit[commits.length];
System.arraycopy(commits, 0, newValue, 0, commits.length);
return newValue;
try {
return listCommits().withPageSize(100).toArray();
} catch (IOException e) {
throw new GHException(e.getMessage(), e);
}
}

/**
* Gets an array of commits.
* Iterable of commits for this comparison.
*
* By default, the commit list is limited to 250 results.
*
* Since
* <a href="https://github.blog/changelog/2021-03-22-compare-rest-api-now-supports-pagination/">2021-03-22</a>,
* compare supports pagination of commits. This makes the initial {@link GHCompare} response return faster and
* supports comparisons with more than 250 commits. To read commits progressively using pagination, set
* {@link GHRepository#setCompareUsePaginatedCommits(boolean)} to true before calling
* {@link GHRepository#getCompare(String, String)}.
*
* @return iterable of commits
*/
public PagedIterable<Commit> listCommits() {
if (usePaginatedCommits) {
return new GHCompareCommitsIterable();
} else {
// if not using paginated commits, adapt the returned commits array
return new PagedIterable<Commit>() {
@NotNull
@Override
public PagedIterator<Commit> _iterator(int pageSize) {
return new PagedIterator<>(Collections.singleton(commits).iterator(), null);
}
};
}
}

/**
* Gets an array of files.
*
* @return A copy of the array being stored in the class.
*/
Expand Down Expand Up @@ -283,4 +334,54 @@ public static class User extends GitUser {
public static enum Status {
behind, ahead, identical, diverged
}

/**
* Iterable for commit listing.
*/
class GHCompareCommitsIterable extends PagedIterable<Commit> {

private GHCompare result;

public GHCompareCommitsIterable() {
}

@Nonnull
@Override
public PagedIterator<Commit> _iterator(int pageSize) {
try {
GitHubRequest request = owner.getRoot()
.createRequest()
.injectMappingValue("GHCompare_usePaginatedCommits", usePaginatedCommits)
.withUrlPath(owner.getApiTailUrl(url.substring(url.lastIndexOf("/compare/"))))
.build();

// page_size must be set for GHCompare commit pagination
if (pageSize == 0) {
pageSize = 10;
}
return new PagedIterator<>(
adapt(GitHubPageIterator
.create(owner.getRoot().getClient(), GHCompare.class, request, pageSize)),
item -> item.wrapUp(owner));
} catch (MalformedURLException e) {
throw new GHException("Malformed URL", e);
}
}

protected Iterator<Commit[]> adapt(final Iterator<GHCompare> base) {
return new Iterator<Commit[]>() {
public boolean hasNext() {
return base.hasNext();
}

public Commit[] next() {
GHCompare v = base.next();
if (result == null) {
result = v;
}
return v.commits;
}
};
}
}
}
28 changes: 24 additions & 4 deletions src/main/java/org/kohsuke/github/GHRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ public class GHRepository extends GHObject {
private GHRepository source, parent;

private Boolean isTemplate;
private boolean compareUsePaginatedCommits;

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

/**
* Sets {@link #getCompare(String, String)} to return a {@link GHCompare} that uses a paginated commit list instead
* of limiting to 250 results.
*
* By default, {@link GHCompare} returns all commits in the comparison as part of the request, limited to 250
* results. More recently GitHub added the ability to return the commits as a paginated query allowing for more than
* 250 results.
*
* @param value
* true if you want commits returned in paginated form.
*/
public void setCompareUsePaginatedCommits(boolean value) {
compareUsePaginatedCommits = value;
}

/**
* Gets a comparison between 2 points in the repository. This would be similar to calling
* <code>git log id1...id2</code> against a local repository.
Expand All @@ -1656,9 +1672,14 @@ public GHHook getHook(int id) throws IOException {
* on failure communicating with GitHub
*/
public GHCompare getCompare(String id1, String id2) throws IOException {
GHCompare compare = root.createRequest()
.withUrlPath(getApiTailUrl(String.format("compare/%s...%s", id1, id2)))
.fetch(GHCompare.class);
final Requester requester = root.createRequest()
.withUrlPath(getApiTailUrl(String.format("compare/%s...%s", id1, id2)));

if (compareUsePaginatedCommits) {
requester.with("per_page", 1).with("page", 1);
}
requester.injectMappingValue("GHCompare_usePaginatedCommits", compareUsePaginatedCommits);
GHCompare compare = requester.fetch(GHCompare.class);
return compare.wrap(this);
}

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

return getCompare(id1.getName(), id2.getName());

}

/**
Expand Down
119 changes: 119 additions & 0 deletions src/test/java/org/kohsuke/github/GHRepositoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -866,4 +866,123 @@ public void getLastCommitStatus() throws Exception {
assertThat(status.getState(), equalTo(GHCommitState.SUCCESS));
assertThat(status.getContext(), equalTo("ci/circleci: build"));
}

@Test
public void listCommitsBetween() throws Exception {
GHRepository repository = getRepository();
int startingCount = mockGitHub.getRequestCount();
GHCompare compare = repository.getCompare("e46a9f3f2ac55db96de3c5c4706f2813b3a96465",
"8051615eff597f4e49f4f47625e6fc2b49f26bfc");
int actualCount = 0;
for (GHCompare.Commit item : compare.listCommits().withPageSize(5)) {
assertThat(item, notNullValue());
actualCount++;
}
assertThat(compare.getTotalCommits(), is(9));
assertThat(actualCount, is(9));
assertThat(mockGitHub.getRequestCount(), equalTo(startingCount + 1));
}

@Test
public void listCommitsBetweenPaginated() throws Exception {
GHRepository repository = getRepository();
int startingCount = mockGitHub.getRequestCount();
repository.setCompareUsePaginatedCommits(true);
GHCompare compare = repository.getCompare("e46a9f3f2ac55db96de3c5c4706f2813b3a96465",
"8051615eff597f4e49f4f47625e6fc2b49f26bfc");
int actualCount = 0;
for (GHCompare.Commit item : compare.listCommits().withPageSize(5)) {
assertThat(item, notNullValue());
actualCount++;
}
assertThat(compare.getTotalCommits(), is(9));
assertThat(actualCount, is(9));
assertThat(mockGitHub.getRequestCount(), equalTo(startingCount + 3));
}

@Test
public void getCommitsBetweenOver250() throws Exception {
GHRepository repository = getRepository();
int startingCount = mockGitHub.getRequestCount();
GHCompare compare = repository.getCompare("4261c42949915816a9f246eb14c3dfd21a637bc2",
"94ff089e60064bfa43e374baeb10846f7ce82f40");
int actualCount = 0;
for (GHCompare.Commit item : compare.getCommits()) {
assertThat(item, notNullValue());
actualCount++;
}
assertThat(compare.getTotalCommits(), is(283));
assertThat(actualCount, is(250));
assertThat(mockGitHub.getRequestCount(), equalTo(startingCount + 1));

// Additional GHCompare checks
assertThat(compare.getAheadBy(), equalTo(283));
assertThat(compare.getBehindBy(), equalTo(0));
assertThat(compare.getStatus(), equalTo(GHCompare.Status.ahead));
assertThat(compare.getDiffUrl().toString(),
endsWith(
"compare/4261c42949915816a9f246eb14c3dfd21a637bc2...94ff089e60064bfa43e374baeb10846f7ce82f40.diff"));
assertThat(compare.getHtmlUrl().toString(),
endsWith(
"compare/4261c42949915816a9f246eb14c3dfd21a637bc2...94ff089e60064bfa43e374baeb10846f7ce82f40"));
assertThat(compare.getPatchUrl().toString(),
endsWith(
"compare/4261c42949915816a9f246eb14c3dfd21a637bc2...94ff089e60064bfa43e374baeb10846f7ce82f40.patch"));
assertThat(compare.getPermalinkUrl().toString(),
endsWith("compare/hub4j-test-org:4261c42...hub4j-test-org:94ff089"));
assertThat(compare.getUrl().toString(),
endsWith(
"compare/4261c42949915816a9f246eb14c3dfd21a637bc2...94ff089e60064bfa43e374baeb10846f7ce82f40"));

assertThat(compare.getBaseCommit().getSHA1(), equalTo("4261c42949915816a9f246eb14c3dfd21a637bc2"));

assertThat(compare.getMergeBaseCommit().getSHA1(), equalTo("4261c42949915816a9f246eb14c3dfd21a637bc2"));
// it appears this field is not present in the returned JSON. Strange.
assertThat(compare.getMergeBaseCommit().getCommit().getSha(), nullValue());
assertThat(compare.getMergeBaseCommit().getCommit().getUrl(),
endsWith("/commits/4261c42949915816a9f246eb14c3dfd21a637bc2"));
assertThat(compare.getMergeBaseCommit().getCommit().getMessage(),
endsWith("[maven-release-plugin] prepare release github-api-1.123"));
assertThat(compare.getMergeBaseCommit().getCommit().getAuthor().getName(), equalTo("Liam Newman"));
assertThat(compare.getMergeBaseCommit().getCommit().getCommitter().getName(), equalTo("Liam Newman"));

assertThat(compare.getMergeBaseCommit().getCommit().getTree().getSha(),
equalTo("5da98090976978c93aba0bdfa550e05675543f99"));
assertThat(compare.getMergeBaseCommit().getCommit().getTree().getUrl(),
endsWith("/git/trees/5da98090976978c93aba0bdfa550e05675543f99"));

assertThat(compare.getFiles().length, equalTo(300));
assertThat(compare.getFiles()[0].getFileName(), equalTo(".github/PULL_REQUEST_TEMPLATE.md"));
assertThat(compare.getFiles()[0].getLinesAdded(), equalTo(8));
assertThat(compare.getFiles()[0].getLinesChanged(), equalTo(15));
assertThat(compare.getFiles()[0].getLinesDeleted(), equalTo(7));
assertThat(compare.getFiles()[0].getFileName(), equalTo(".github/PULL_REQUEST_TEMPLATE.md"));
assertThat(compare.getFiles()[0].getPatch(), startsWith("@@ -1,15 +1,16 @@"));
assertThat(compare.getFiles()[0].getPreviousFilename(), nullValue());
assertThat(compare.getFiles()[0].getStatus(), equalTo("modified"));
assertThat(compare.getFiles()[0].getSha(), equalTo("e4234f5f6f39899282a6ef1edff343ae1269222e"));

assertThat(compare.getFiles()[0].getBlobUrl().toString(),
endsWith("/blob/94ff089e60064bfa43e374baeb10846f7ce82f40/.github/PULL_REQUEST_TEMPLATE.md"));
assertThat(compare.getFiles()[0].getRawUrl().toString(),
endsWith("/raw/94ff089e60064bfa43e374baeb10846f7ce82f40/.github/PULL_REQUEST_TEMPLATE.md"));
}

@Test
public void getCommitsBetweenPaged() throws Exception {
GHRepository repository = getRepository();
int startingCount = mockGitHub.getRequestCount();
repository.setCompareUsePaginatedCommits(true);
GHCompare compare = repository.getCompare("4261c42949915816a9f246eb14c3dfd21a637bc2",
"94ff089e60064bfa43e374baeb10846f7ce82f40");
int actualCount = 0;
for (GHCompare.Commit item : compare.getCommits()) {
assertThat(item, notNullValue());
actualCount++;
}
assertThat(compare.getTotalCommits(), is(283));
assertThat(actualCount, is(283));
assertThat(mockGitHub.getRequestCount(), equalTo(startingCount + 4));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"login": "hub4j-test-org",
"id": 7544739,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjc1NDQ3Mzk=",
"url": "https://api.github.com/orgs/hub4j-test-org",
"repos_url": "https://api.github.com/orgs/hub4j-test-org/repos",
"events_url": "https://api.github.com/orgs/hub4j-test-org/events",
"hooks_url": "https://api.github.com/orgs/hub4j-test-org/hooks",
"issues_url": "https://api.github.com/orgs/hub4j-test-org/issues",
"members_url": "https://api.github.com/orgs/hub4j-test-org/members{/member}",
"public_members_url": "https://api.github.com/orgs/hub4j-test-org/public_members{/member}",
"avatar_url": "https://avatars.githubusercontent.com/u/7544739?v=4",
"description": "Hub4j Test Org Description (this could be null or blank too)",
"name": "Hub4j Test Org Name (this could be null or blank too)",
"company": null,
"blog": "https://hub4j.url.io/could/be/null",
"location": "Hub4j Test Org Location (this could be null or blank too)",
"email": "hub4jtestorgemail@could.be.null.com",
"twitter_username": null,
"is_verified": false,
"has_organization_projects": true,
"has_repository_projects": true,
"public_repos": 19,
"public_gists": 0,
"followers": 0,
"following": 0,
"html_url": "https://github.com/hub4j-test-org",
"created_at": "2014-05-10T19:39:11Z",
"updated_at": "2020-06-04T05:56:10Z",
"type": "Organization",
"total_private_repos": 2,
"owned_private_repos": 2,
"private_gists": 0,
"disk_usage": 11979,
"collaborators": 0,
"billing_email": "kk@kohsuke.org",
"default_repository_permission": "none",
"members_can_create_repositories": false,
"two_factor_requirement_enabled": false,
"members_can_create_pages": true,
"members_can_create_public_pages": true,
"members_can_create_private_pages": true,
"plan": {
"name": "free",
"space": 976562499,
"private_repos": 10000,
"filled_seats": 26,
"seats": 3
}
}
Loading