From 90d8e65a3b5f5e09645dd75b2a9408a6f31e5727 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Tue, 1 Dec 2015 16:27:29 +0100 Subject: [PATCH 01/13] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 4847f4885c..0157b91a59 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ github-api - 1.71 + 1.72-SNAPSHOT GitHub API for Java http://github-api.kohsuke.org/ GitHub API for Java @@ -16,7 +16,7 @@ scm:git:git@github.com/kohsuke/${project.artifactId}.git scm:git:ssh://git@github.com/kohsuke/${project.artifactId}.git http://${project.artifactId}.kohsuke.org/ - github-api-1.71 + HEAD From ed8cd0ad19189314d8d43add511ed251dc68e032 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Tue, 1 Dec 2015 17:02:53 +0100 Subject: [PATCH 02/13] Added getter for ID And the actual value has already gone past 'int' This fixes issue #199 --- src/main/java/org/kohsuke/github/GHEventInfo.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/kohsuke/github/GHEventInfo.java b/src/main/java/org/kohsuke/github/GHEventInfo.java index 9772cba16e..0268924ea2 100644 --- a/src/main/java/org/kohsuke/github/GHEventInfo.java +++ b/src/main/java/org/kohsuke/github/GHEventInfo.java @@ -18,6 +18,7 @@ public class GHEventInfo { // we don't want to expose Jackson dependency to the user. This needs databinding private ObjectNode payload; + private long id; private String created_at; private String type; @@ -54,6 +55,10 @@ public GHEvent getType() { return this; } + public long getId() { + return id; + } + public Date getCreatedAt() { return GitHub.parseDate(created_at); } From 8cb70948034db5efb068b19f72199571afa34716 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 2 Dec 2015 08:35:56 +0100 Subject: [PATCH 03/13] Added pagination for following & follower --- src/main/java/org/kohsuke/github/GHUser.java | 32 ++++++++++++++++--- .../java/org/kohsuke/github/UserTest.java | 30 +++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 src/test/java/org/kohsuke/github/UserTest.java diff --git a/src/main/java/org/kohsuke/github/GHUser.java b/src/main/java/org/kohsuke/github/GHUser.java index 2242f4a6e4..a3202c95cc 100644 --- a/src/main/java/org/kohsuke/github/GHUser.java +++ b/src/main/java/org/kohsuke/github/GHUser.java @@ -56,8 +56,14 @@ public void unfollow() throws IOException { */ @WithBridgeMethods(Set.class) public GHPersonSet getFollows() throws IOException { - GHUser[] followers = root.retrieve().to("/users/" + login + "/following", GHUser[].class); - return new GHPersonSet(Arrays.asList(wrap(followers,root))); + return new GHPersonSet(listFollows().asList()); + } + + /** + * Lists the users that this user is following + */ + public PagedIterable listFollows() { + return listUser("following"); } /** @@ -65,8 +71,26 @@ public GHPersonSet getFollows() throws IOException { */ @WithBridgeMethods(Set.class) public GHPersonSet getFollowers() throws IOException { - GHUser[] followers = root.retrieve().to("/users/" + login + "/followers", GHUser[].class); - return new GHPersonSet(Arrays.asList(wrap(followers,root))); + return new GHPersonSet(listFollowers().asList()); + } + + /** + * Lists the users who are following this user. + */ + public PagedIterable listFollowers() { + return listUser("followers"); + } + + private PagedIterable listUser(final String suffix) { + return new PagedIterable() { + public PagedIterator iterator() { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl(suffix), GHUser[].class)) { + protected void wrapUp(GHUser[] page) { + GHUser.wrap(page,root); + } + }; + } + }; } /** diff --git a/src/test/java/org/kohsuke/github/UserTest.java b/src/test/java/org/kohsuke/github/UserTest.java new file mode 100644 index 0000000000..d35632fda2 --- /dev/null +++ b/src/test/java/org/kohsuke/github/UserTest.java @@ -0,0 +1,30 @@ +package org.kohsuke.github; + +import org.junit.Test; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +/** + * @author Kohsuke Kawaguchi + */ +public class UserTest extends AbstractGitHubApiTestBase { + @Test + public void listFollowsAndFollowers() throws IOException { + GHUser u = gitHub.getUser("rtyler"); + assertNotEquals( + count50(u.listFollowers()), + count50(u.listFollows())); + } + + private Set count50(PagedIterable l) { + Set users = new HashSet(); + PagedIterator itr = l.iterator(); + for (int i=0; i<50 && itr.hasNext(); i++) { + users.add(itr.next()); + } + assertEquals(50, users.size()); + return users; + } +} From 83ffe75baaf2b61ba41ee8bacf93264690c07cab Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 2 Dec 2015 12:03:39 +0100 Subject: [PATCH 04/13] Fixed rate handling limit handling Issue #220. If RateLimitHandler returns normally, it should retry. --- src/main/java/org/kohsuke/github/Requester.java | 1 + src/test/java/Foo.java | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/org/kohsuke/github/Requester.java b/src/main/java/org/kohsuke/github/Requester.java index 81fe507d8b..b1e750ff74 100644 --- a/src/main/java/org/kohsuke/github/Requester.java +++ b/src/main/java/org/kohsuke/github/Requester.java @@ -501,6 +501,7 @@ private InputStream wrapStream(InputStream in) throws IOException { if ("0".equals(uc.getHeaderField("X-RateLimit-Remaining"))) { root.rateLimitHandler.onError(e,uc); + return; } InputStream es = wrapStream(uc.getErrorStream()); diff --git a/src/test/java/Foo.java b/src/test/java/Foo.java index 5df13995c5..a29baac4b4 100644 --- a/src/test/java/Foo.java +++ b/src/test/java/Foo.java @@ -1,4 +1,5 @@ import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHUser; import org.kohsuke.github.GitHub; import java.util.Collection; @@ -14,4 +15,11 @@ public static void main(String[] args) throws Exception { } System.out.println(lst.size()); } + + private static void testRateLimit() throws Exception { + GitHub g = GitHub.connectAnonymously(); + for (GHUser u : g.getOrganization("jenkinsci").listMembers()) { + u.getFollowersCount(); + } + } } From 841f77bac28d28aa73ceee22f45df8ac7fa88f92 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Thu, 3 Dec 2015 16:33:39 +0100 Subject: [PATCH 05/13] Naming anonymous iterator. ... in anticipation of the page size support. --- .../java/org/kohsuke/github/Requester.java | 153 +++++++++--------- 1 file changed, 78 insertions(+), 75 deletions(-) diff --git a/src/main/java/org/kohsuke/github/Requester.java b/src/main/java/org/kohsuke/github/Requester.java index b1e750ff74..14ebe5f3bb 100644 --- a/src/main/java/org/kohsuke/github/Requester.java +++ b/src/main/java/org/kohsuke/github/Requester.java @@ -42,13 +42,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; @@ -330,104 +328,109 @@ private boolean isMethodWithBody() { * * Every iterator call reports a new batch. */ - /*package*/ Iterator asIterator(final String _tailApiUrl, final Class type) { + /*package*/ PagingIterator asIterator(String tailApiUrl, Class type) { method("GET"); - final StringBuilder strBuilder = new StringBuilder(_tailApiUrl); + StringBuilder s = new StringBuilder(tailApiUrl); if (!args.isEmpty()) { - boolean first=true; + boolean first = true; try { for (Entry a : args) { - strBuilder.append(first ? '?' : '&'); + s.append(first ? '?' : '&'); first = false; - strBuilder.append(URLEncoder.encode(a.key, "UTF-8")); - strBuilder.append('='); - strBuilder.append(URLEncoder.encode(a.value.toString(), "UTF-8")); + s.append(URLEncoder.encode(a.key, "UTF-8")); + s.append('='); + s.append(URLEncoder.encode(a.value.toString(), "UTF-8")); } } catch (UnsupportedEncodingException e) { throw new AssertionError(e); // UTF-8 is mandatory } } - final String tailApiUrl = strBuilder.toString(); + try { + return new PagingIterator(type, root.getApiURL(s.toString())); + } catch (IOException e) { + throw new Error(e); + } + } - return new Iterator() { - /** - * The next batch to be returned from {@link #next()}. - */ - T next; - /** - * URL of the next resource to be retrieved, or null if no more data is available. - */ - URL url; + class PagingIterator implements Iterator { - { - try { - url = root.getApiURL(tailApiUrl); - } catch (IOException e) { - throw new Error(e); - } - } + private final Class type; - public boolean hasNext() { - fetch(); - return next!=null; - } + /** + * The next batch to be returned from {@link #next()}. + */ + private T next; - public T next() { - fetch(); - T r = next; - if (r==null) throw new NoSuchElementException(); - next = null; - return r; - } + /** + * URL of the next resource to be retrieved, or null if no more data is available. + */ + private URL url; - public void remove() { - throw new UnsupportedOperationException(); - } + PagingIterator(Class type, URL url) { + this.url = url; + this.type = type; + } - private void fetch() { - if (next!=null) return; // already fetched - if (url==null) return; // no more data to fetch + public boolean hasNext() { + fetch(); + return next!=null; + } - try { - while (true) {// loop while API rate limit is hit - setupConnection(url); - try { - next = parse(type,null); - assert next!=null; - findNextURL(); - return; - } catch (IOException e) { - handleApiError(e); - } - } - } catch (IOException e) { - throw new Error(e); - } - } + public T next() { + fetch(); + T r = next; + if (r==null) throw new NoSuchElementException(); + next = null; + return r; + } - /** - * Locate the next page from the pagination "Link" tag. - */ - private void findNextURL() throws MalformedURLException { - url = null; // start defensively - String link = uc.getHeaderField("Link"); - if (link==null) return; - - for (String token : link.split(", ")) { - if (token.endsWith("rel=\"next\"")) { - // found the next page. This should look something like - // ; rel="next" - int idx = token.indexOf('>'); - url = new URL(token.substring(1,idx)); + public void remove() { + throw new UnsupportedOperationException(); + } + + private void fetch() { + if (next!=null) return; // already fetched + if (url==null) return; // no more data to fetch + + try { + while (true) {// loop while API rate limit is hit + setupConnection(url); + try { + next = parse(type,null); + assert next!=null; + findNextURL(); return; + } catch (IOException e) { + handleApiError(e); } } + } catch (IOException e) { + throw new Error(e); + } + } - // no more "next" link. we are done. + /** + * Locate the next page from the pagination "Link" tag. + */ + private void findNextURL() throws MalformedURLException { + url = null; // start defensively + String link = uc.getHeaderField("Link"); + if (link==null) return; + + for (String token : link.split(", ")) { + if (token.endsWith("rel=\"next\"")) { + // found the next page. This should look something like + // ; rel="next" + int idx = token.indexOf('>'); + url = new URL(token.substring(1,idx)); + return; + } } - }; + + // no more "next" link. we are done. + } } From dbddf5b9eb500221a4c6be1e523a138de74261c4 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Thu, 3 Dec 2015 17:41:43 +0100 Subject: [PATCH 06/13] Implemented pagenation size support. --- .../java/org/kohsuke/github/GHCommit.java | 4 +- .../kohsuke/github/GHCommitQueryBuilder.java | 4 +- .../java/org/kohsuke/github/GHContent.java | 4 +- src/main/java/org/kohsuke/github/GHGist.java | 4 +- src/main/java/org/kohsuke/github/GHIssue.java | 4 +- .../java/org/kohsuke/github/GHMyself.java | 4 +- .../org/kohsuke/github/GHOrganization.java | 16 ++--- .../java/org/kohsuke/github/GHPerson.java | 6 +- .../org/kohsuke/github/GHPullRequest.java | 12 ++-- .../java/org/kohsuke/github/GHRepository.java | 65 +++++++++---------- .../org/kohsuke/github/GHSearchBuilder.java | 4 +- src/main/java/org/kohsuke/github/GHTeam.java | 8 +-- src/main/java/org/kohsuke/github/GHUser.java | 17 +++-- src/main/java/org/kohsuke/github/GitHub.java | 4 +- .../org/kohsuke/github/PagedIterable.java | 22 ++++++- .../java/org/kohsuke/github/Requester.java | 5 +- .../kohsuke/github/RepositoryMockTest.java | 2 +- 17 files changed, 103 insertions(+), 82 deletions(-) diff --git a/src/main/java/org/kohsuke/github/GHCommit.java b/src/main/java/org/kohsuke/github/GHCommit.java index b45683f2d2..2b68ff9a95 100644 --- a/src/main/java/org/kohsuke/github/GHCommit.java +++ b/src/main/java/org/kohsuke/github/GHCommit.java @@ -273,8 +273,8 @@ private GHUser resolveUser(User author) throws IOException { */ public PagedIterable listComments() { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(owner.root.retrieve().asIterator(String.format("/repos/%s/%s/commits/%s/comments", owner.getOwnerName(), owner.getName(), sha), GHCommitComment[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(owner.root.retrieve().asIterator(String.format("/repos/%s/%s/commits/%s/comments", owner.getOwnerName(), owner.getName(), sha), GHCommitComment[].class, pageSize)) { @Override protected void wrapUp(GHCommitComment[] page) { for (GHCommitComment c : page) diff --git a/src/main/java/org/kohsuke/github/GHCommitQueryBuilder.java b/src/main/java/org/kohsuke/github/GHCommitQueryBuilder.java index 6d0a7ae995..bac8c89ee6 100644 --- a/src/main/java/org/kohsuke/github/GHCommitQueryBuilder.java +++ b/src/main/java/org/kohsuke/github/GHCommitQueryBuilder.java @@ -92,8 +92,8 @@ public GHCommitQueryBuilder until(long timestamp) { */ public PagedIterable list() { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(req.asIterator(repo.getApiTailUrl("commits"), GHCommit[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(req.asIterator(repo.getApiTailUrl("commits"), GHCommit[].class, pageSize)) { protected void wrapUp(GHCommit[] page) { for (GHCommit c : page) c.wrapUp(repo); diff --git a/src/main/java/org/kohsuke/github/GHContent.java b/src/main/java/org/kohsuke/github/GHContent.java index c79e00e384..8955505219 100644 --- a/src/main/java/org/kohsuke/github/GHContent.java +++ b/src/main/java/org/kohsuke/github/GHContent.java @@ -152,8 +152,8 @@ public PagedIterable listDirectoryContent() throws IOException { throw new IllegalStateException(path+" is not a directory"); return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(url, GHContent[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(url, GHContent[].class, pageSize)) { @Override protected void wrapUp(GHContent[] page) { GHContent.wrap(page, repository); diff --git a/src/main/java/org/kohsuke/github/GHGist.java b/src/main/java/org/kohsuke/github/GHGist.java index f608af3da6..eb105b40c6 100644 --- a/src/main/java/org/kohsuke/github/GHGist.java +++ b/src/main/java/org/kohsuke/github/GHGist.java @@ -140,8 +140,8 @@ public GHGist fork() throws IOException { public PagedIterable listForks() { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("forks"), GHGist[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("forks"), GHGist[].class, pageSize)) { @Override protected void wrapUp(GHGist[] page) { try { diff --git a/src/main/java/org/kohsuke/github/GHIssue.java b/src/main/java/org/kohsuke/github/GHIssue.java index d45e49b935..39e5c24b70 100644 --- a/src/main/java/org/kohsuke/github/GHIssue.java +++ b/src/main/java/org/kohsuke/github/GHIssue.java @@ -205,8 +205,8 @@ public List getComments() throws IOException { */ public PagedIterable listComments() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getIssuesApiRoute() + "/comments", GHIssueComment[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getIssuesApiRoute() + "/comments", GHIssueComment[].class, pageSize)) { protected void wrapUp(GHIssueComment[] page) { for (GHIssueComment c : page) c.wrapUp(GHIssue.this); diff --git a/src/main/java/org/kohsuke/github/GHMyself.java b/src/main/java/org/kohsuke/github/GHMyself.java index 74ba96189d..c473af4806 100644 --- a/src/main/java/org/kohsuke/github/GHMyself.java +++ b/src/main/java/org/kohsuke/github/GHMyself.java @@ -158,9 +158,9 @@ public PagedIterable listRepositories(final int pageSize) { */ public PagedIterable listRepositories(final int pageSize, final RepositoryListFilter repoType) { return new PagedIterable() { - public PagedIterator iterator() { + public PagedIterator _iterator(int pageSize) { return new PagedIterator(root.retrieve().asIterator("/user/repos?per_page=" + pageSize + - "&type=" + repoType.name().toLowerCase(Locale.ENGLISH), GHRepository[].class)) { + "&type=" + repoType.name().toLowerCase(Locale.ENGLISH), GHRepository[].class, pageSize)) { @Override protected void wrapUp(GHRepository[] page) { for (GHRepository c : page) diff --git a/src/main/java/org/kohsuke/github/GHOrganization.java b/src/main/java/org/kohsuke/github/GHOrganization.java index b39cc6b98b..2f4a6c0f4f 100644 --- a/src/main/java/org/kohsuke/github/GHOrganization.java +++ b/src/main/java/org/kohsuke/github/GHOrganization.java @@ -57,8 +57,8 @@ public Map getTeams() throws IOException { */ public PagedIterable listTeams() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(String.format("/orgs/%s/teams", login), GHTeam[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(String.format("/orgs/%s/teams", login), GHTeam[].class, pageSize)) { @Override protected void wrapUp(GHTeam[] page) { for (GHTeam c : page) @@ -150,9 +150,9 @@ public PagedIterable listMembersWithFilter(String filter) throws IOExcep private PagedIterable listMembers(final String suffix, final String filter) throws IOException { return new PagedIterable() { - public PagedIterator iterator() { + public PagedIterator _iterator(int pageSize) { String filterParams = (filter == null) ? "" : ("?filter=" + filter); - return new PagedIterator(root.retrieve().asIterator(String.format("/orgs/%s/%s%s", login, suffix, filterParams), GHUser[].class)) { + return new PagedIterator(root.retrieve().asIterator(String.format("/orgs/%s/%s%s", login, suffix, filterParams), GHUser[].class, pageSize)) { @Override protected void wrapUp(GHUser[] users) { GHUser.wrap(users, root); @@ -222,8 +222,8 @@ public List getPullRequests() throws IOException { */ public PagedIterable listEvents() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(String.format("/orgs/%s/events", login), GHEventInfo[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(String.format("/orgs/%s/events", login), GHEventInfo[].class, pageSize)) { @Override protected void wrapUp(GHEventInfo[] page) { for (GHEventInfo c : page) @@ -244,8 +244,8 @@ protected void wrapUp(GHEventInfo[] page) { @Override public PagedIterable listRepositories(final int pageSize) { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator("/orgs/" + login + "/repos?per_page=" + pageSize, GHRepository[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator("/orgs/" + login + "/repos?per_page=" + pageSize, GHRepository[].class, pageSize)) { @Override protected void wrapUp(GHRepository[] page) { for (GHRepository c : page) diff --git a/src/main/java/org/kohsuke/github/GHPerson.java b/src/main/java/org/kohsuke/github/GHPerson.java index 1a9a0f1575..e9a750f2cc 100644 --- a/src/main/java/org/kohsuke/github/GHPerson.java +++ b/src/main/java/org/kohsuke/github/GHPerson.java @@ -76,8 +76,8 @@ public PagedIterable listRepositories() { */ public PagedIterable listRepositories(final int pageSize) { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator("/users/" + login + "/repos?per_page=" + pageSize, GHRepository[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator("/users/" + login + "/repos?per_page=" + pageSize, GHRepository[].class, pageSize)) { @Override protected void wrapUp(GHRepository[] page) { for (GHRepository c : page) @@ -104,7 +104,7 @@ protected void wrapUp(GHRepository[] page) { public synchronized Iterable> iterateRepositories(final int pageSize) { return new Iterable>() { public Iterator> iterator() { - final Iterator pager = root.retrieve().asIterator("/users/" + login + "/repos?per_page="+pageSize,GHRepository[].class); + final Iterator pager = root.retrieve().asIterator("/users/" + login + "/repos?per_page="+pageSize,GHRepository[].class, pageSize); return new Iterator>() { public boolean hasNext() { diff --git a/src/main/java/org/kohsuke/github/GHPullRequest.java b/src/main/java/org/kohsuke/github/GHPullRequest.java index 5604d2355e..dd3d93b758 100644 --- a/src/main/java/org/kohsuke/github/GHPullRequest.java +++ b/src/main/java/org/kohsuke/github/GHPullRequest.java @@ -210,9 +210,9 @@ private void populate() throws IOException { */ public PagedIterable listFiles() { return new PagedIterable() { - public PagedIterator iterator() { + public PagedIterator _iterator(int pageSize) { return new PagedIterator(root.retrieve().asIterator(String.format("%s/files", getApiURL()), - GHPullRequestFileDetail[].class)) { + GHPullRequestFileDetail[].class, pageSize)) { @Override protected void wrapUp(GHPullRequestFileDetail[] page) { } @@ -226,9 +226,9 @@ protected void wrapUp(GHPullRequestFileDetail[] page) { */ public PagedIterable listReviewComments() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { + public PagedIterator _iterator(int pageSize) { return new PagedIterator(root.retrieve().asIterator(getApiRoute() + "/comments", - GHPullRequestReviewComment[].class)) { + GHPullRequestReviewComment[].class, pageSize)) { protected void wrapUp(GHPullRequestReviewComment[] page) { for (GHPullRequestReviewComment c : page) c.wrapUp(GHPullRequest.this); @@ -243,10 +243,10 @@ protected void wrapUp(GHPullRequestReviewComment[] page) { */ public PagedIterable listCommits() { return new PagedIterable() { - public PagedIterator iterator() { + public PagedIterator _iterator(int pageSize) { return new PagedIterator(root.retrieve().asIterator( String.format("%s/commits", getApiURL()), - GHPullRequestCommitDetail[].class)) { + GHPullRequestCommitDetail[].class, pageSize)) { @Override protected void wrapUp(GHPullRequestCommitDetail[] page) { for (GHPullRequestCommitDetail c : page) diff --git a/src/main/java/org/kohsuke/github/GHRepository.java b/src/main/java/org/kohsuke/github/GHRepository.java index 917be54d7f..6300785ad1 100644 --- a/src/main/java/org/kohsuke/github/GHRepository.java +++ b/src/main/java/org/kohsuke/github/GHRepository.java @@ -36,7 +36,6 @@ import java.io.Reader; import java.io.UnsupportedEncodingException; import java.net.URL; -import java.nio.charset.Charset; import java.util.*; import static java.util.Arrays.asList; @@ -76,8 +75,8 @@ public GHDeploymentBuilder createDeployment(String ref) { public PagedIterable getDeploymentStatuses(final int id) { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("deployments")+"/"+id+"/statuses", GHDeploymentStatus[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("deployments")+"/"+id+"/statuses", GHDeploymentStatus[].class, pageSize)) { @Override protected void wrapUp(GHDeploymentStatus[] page) { for (GHDeploymentStatus c : page) @@ -92,8 +91,8 @@ public PagedIterable listDeployments(String sha,String ref,String List params = Arrays.asList(getParam("sha", sha), getParam("ref", ref), getParam("task", task), getParam("environment", environment)); final String deploymentsUrl = getApiTailUrl("deployments") + "?"+ join(params,"&"); return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(deploymentsUrl, GHDeployment[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(deploymentsUrl, GHDeployment[].class, pageSize)) { @Override protected void wrapUp(GHDeployment[] page) { for (GHDeployment c : page) @@ -241,8 +240,8 @@ public List getIssues(GHIssueState state, GHMilestone milestone) throws */ public PagedIterable listIssues(final GHIssueState state) { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("issues?state="+state.toString().toLowerCase(Locale.ENGLISH)), GHIssue[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("issues?state="+state.toString().toLowerCase(Locale.ENGLISH)), GHIssue[].class, pageSize)) { @Override protected void wrapUp(GHIssue[] page) { for (GHIssue c : page) @@ -281,8 +280,8 @@ public List getReleases() throws IOException { public PagedIterable listReleases() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("releases"), GHRelease[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("releases"), GHRelease[].class, pageSize)) { @Override protected void wrapUp(GHRelease[] page) { for (GHRelease c : page) @@ -295,8 +294,8 @@ protected void wrapUp(GHRelease[] page) { public PagedIterable listTags() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("tags"), GHTag[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("tags"), GHTag[].class, pageSize)) { @Override protected void wrapUp(GHTag[] page) { for (GHTag c : page) @@ -416,9 +415,9 @@ public GHPersonSet getCollaborators() throws IOException { */ public PagedIterable listCollaborators() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { + public PagedIterator _iterator(int pageSize) { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("collaborators"), GHUser[].class)) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("collaborators"), GHUser[].class, pageSize)) { @Override protected void wrapUp(GHUser[] users) { @@ -556,12 +555,12 @@ public PagedIterable listForks() { */ public PagedIterable listForks(final ForkSort sort) { return new PagedIterable() { - public PagedIterator iterator() { + public PagedIterator _iterator(int pageSize) { String sortParam = ""; if (sort != null) { sortParam = "?sort=" + sort.toString().toLowerCase(Locale.ENGLISH); } - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("forks" + sortParam), GHRepository[].class)) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("forks" + sortParam), GHRepository[].class, pageSize)) { @Override protected void wrapUp(GHRepository[] page) { for (GHRepository c : page) { @@ -626,8 +625,8 @@ public List getPullRequests(GHIssueState state) throws IOExceptio */ public PagedIterable listPullRequests(final GHIssueState state) { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("pulls?state="+state.name().toLowerCase(Locale.ENGLISH)), GHPullRequest[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("pulls?state="+state.name().toLowerCase(Locale.ENGLISH)), GHPullRequest[].class, pageSize)) { @Override protected void wrapUp(GHPullRequest[] page) { for (GHPullRequest pr : page) @@ -786,8 +785,8 @@ public GHCommit getCommit(String sha1) throws IOException { */ public PagedIterable listCommits() { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(String.format("/repos/%s/%s/commits", owner.login, name), GHCommit[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(String.format("/repos/%s/%s/commits", owner.login, name), GHCommit[].class, pageSize)) { protected void wrapUp(GHCommit[] page) { for (GHCommit c : page) c.wrapUp(GHRepository.this); @@ -809,8 +808,8 @@ public GHCommitQueryBuilder queryCommits() { */ public PagedIterable listCommitComments() { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(String.format("/repos/%s/%s/comments", owner.login, name), GHCommitComment[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(String.format("/repos/%s/%s/comments", owner.login, name), GHCommitComment[].class, pageSize)) { @Override protected void wrapUp(GHCommitComment[] page) { for (GHCommitComment c : page) @@ -826,8 +825,8 @@ protected void wrapUp(GHCommitComment[] page) { */ public PagedIterable listCommitStatuses(final String sha1) throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(String.format("/repos/%s/%s/statuses/%s", owner.login, name, sha1), GHCommitStatus[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(String.format("/repos/%s/%s/statuses/%s", owner.login, name, sha1), GHCommitStatus[].class, pageSize)) { @Override protected void wrapUp(GHCommitStatus[] page) { for (GHCommitStatus c : page) @@ -877,8 +876,8 @@ public GHCommitStatus createCommitStatus(String sha1, GHCommitState state, Strin */ public PagedIterable listEvents() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(String.format("/repos/%s/%s/events", owner.login, name), GHEventInfo[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(String.format("/repos/%s/%s/events", owner.login, name), GHEventInfo[].class, pageSize)) { @Override protected void wrapUp(GHEventInfo[] page) { for (GHEventInfo c : page) @@ -896,8 +895,8 @@ protected void wrapUp(GHEventInfo[] page) { */ public PagedIterable listLabels() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("labels"), GHLabel[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("labels"), GHLabel[].class, pageSize)) { @Override protected void wrapUp(GHLabel[] page) { for (GHLabel c : page) @@ -926,8 +925,8 @@ public GHLabel createLabel(String name, String color) throws IOException { */ public PagedIterable listSubscribers() { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("subscribers"), GHUser[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("subscribers"), GHUser[].class, pageSize)) { protected void wrapUp(GHUser[] page) { for (GHUser c : page) c.wrapUp(root); @@ -1078,8 +1077,8 @@ public Map getMilestones() throws IOException { */ public PagedIterable listMilestones(final GHIssueState state) { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("milestones?state="+state.toString().toLowerCase(Locale.ENGLISH)), GHMilestone[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("milestones?state="+state.toString().toLowerCase(Locale.ENGLISH)), GHMilestone[].class, pageSize)) { @Override protected void wrapUp(GHMilestone[] page) { for (GHMilestone c : page) @@ -1251,8 +1250,8 @@ public GHSubscription getSubscription() throws IOException { public PagedIterable listContributors() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("contributors"), Contributor[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("contributors"), Contributor[].class, pageSize)) { @Override protected void wrapUp(Contributor[] page) { for (Contributor c : page) diff --git a/src/main/java/org/kohsuke/github/GHSearchBuilder.java b/src/main/java/org/kohsuke/github/GHSearchBuilder.java index 766c6a319f..67d10bdb72 100644 --- a/src/main/java/org/kohsuke/github/GHSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHSearchBuilder.java @@ -39,9 +39,9 @@ public GHSearchBuilder q(String term) { */ public PagedSearchIterable list() { return new PagedSearchIterable(root) { - public PagedIterator iterator() { + public PagedIterator _iterator(int pageSize) { req.set("q", StringUtils.join(terms, " ")); - return new PagedIterator(adapt(req.asIterator(getApiUrl(), receiverType))) { + return new PagedIterator(adapt(req.asIterator(getApiUrl(), receiverType, pageSize))) { protected void wrapUp(T[] page) { // SearchResult.getItems() should do it } diff --git a/src/main/java/org/kohsuke/github/GHTeam.java b/src/main/java/org/kohsuke/github/GHTeam.java index 7213f327c9..cf7dd1fc91 100644 --- a/src/main/java/org/kohsuke/github/GHTeam.java +++ b/src/main/java/org/kohsuke/github/GHTeam.java @@ -52,8 +52,8 @@ public int getId() { */ public PagedIterable listMembers() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(org.root.retrieve().asIterator(api("/members"), GHUser[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(org.root.retrieve().asIterator(api("/members"), GHUser[].class, pageSize)) { @Override protected void wrapUp(GHUser[] page) { GHUser.wrap(page, org.root); @@ -89,8 +89,8 @@ public Map getRepositories() throws IOException { public PagedIterable listRepositories() { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(org.root.retrieve().asIterator(api("/repos"), GHRepository[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(org.root.retrieve().asIterator(api("/repos"), GHRepository[].class, pageSize)) { @Override protected void wrapUp(GHRepository[] page) { for (GHRepository r : page) diff --git a/src/main/java/org/kohsuke/github/GHUser.java b/src/main/java/org/kohsuke/github/GHUser.java index a3202c95cc..3e29395ebb 100644 --- a/src/main/java/org/kohsuke/github/GHUser.java +++ b/src/main/java/org/kohsuke/github/GHUser.java @@ -26,7 +26,6 @@ import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import java.io.IOException; -import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -83,8 +82,8 @@ public PagedIterable listFollowers() { private PagedIterable listUser(final String suffix) { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl(suffix), GHUser[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl(suffix), GHUser[].class, pageSize)) { protected void wrapUp(GHUser[] page) { GHUser.wrap(page,root); } @@ -100,8 +99,8 @@ protected void wrapUp(GHUser[] page) { */ public PagedIterable listSubscriptions() { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("subscriptions"), GHRepository[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("subscriptions"), GHRepository[].class, pageSize)) { protected void wrapUp(GHRepository[] page) { for (GHRepository c : page) c.wrap(root); @@ -157,8 +156,8 @@ public GHPersonSet getOrganizations() throws IOException { */ public PagedIterable listEvents() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(String.format("/users/%s/events", login), GHEventInfo[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(String.format("/users/%s/events", login), GHEventInfo[].class, pageSize)) { @Override protected void wrapUp(GHEventInfo[] page) { for (GHEventInfo c : page) @@ -174,8 +173,8 @@ protected void wrapUp(GHEventInfo[] page) { */ public PagedIterable listGists() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(String.format("/users/%s/gists", login), GHGist[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(String.format("/users/%s/gists", login), GHGist[].class, pageSize)) { @Override protected void wrapUp(GHGist[] page) { for (GHGist c : page) diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java index 2ef822680c..b6ddd6e965 100644 --- a/src/main/java/org/kohsuke/github/GitHub.java +++ b/src/main/java/org/kohsuke/github/GitHub.java @@ -526,8 +526,8 @@ public PagedIterable listAllPublicRepositories() { */ public PagedIterable listAllPublicRepositories(final String since) { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(retrieve().with("since",since).asIterator("/repositories", GHRepository[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(retrieve().with("since",since).asIterator("/repositories", GHRepository[].class, pageSize)) { @Override protected void wrapUp(GHRepository[] page) { for (GHRepository c : page) diff --git a/src/main/java/org/kohsuke/github/PagedIterable.java b/src/main/java/org/kohsuke/github/PagedIterable.java index 8085e1ed53..ae26b1abc6 100644 --- a/src/main/java/org/kohsuke/github/PagedIterable.java +++ b/src/main/java/org/kohsuke/github/PagedIterable.java @@ -1,5 +1,6 @@ package org.kohsuke.github; +import java.io.IOException; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; @@ -11,7 +12,26 @@ * @author Kohsuke Kawaguchi */ public abstract class PagedIterable implements Iterable { - public abstract PagedIterator iterator(); + /** + * Page size. 0 is default. + */ + private int size = 0; + + /** + * Sets the pagination size. + * + *

+ * When set to non-zero, each API call will retrieve this many entries. + */ + void setPageSize(int size) throws IOException { + this.size = size; + } + + public final PagedIterator iterator() { + return _iterator(size); + } + + public abstract PagedIterator _iterator(int pageSize); /** * Eagerly walk {@link Iterable} and return the result in a list. diff --git a/src/main/java/org/kohsuke/github/Requester.java b/src/main/java/org/kohsuke/github/Requester.java index 14ebe5f3bb..7c3052a817 100644 --- a/src/main/java/org/kohsuke/github/Requester.java +++ b/src/main/java/org/kohsuke/github/Requester.java @@ -328,9 +328,12 @@ private boolean isMethodWithBody() { * * Every iterator call reports a new batch. */ - /*package*/ PagingIterator asIterator(String tailApiUrl, Class type) { + /*package*/ Iterator asIterator(String tailApiUrl, Class type, int pageSize) { method("GET"); + if (pageSize!=0) + args.add(new Entry("per_page",pageSize)); + StringBuilder s = new StringBuilder(tailApiUrl); if (!args.isEmpty()) { boolean first = true; diff --git a/src/test/java/org/kohsuke/github/RepositoryMockTest.java b/src/test/java/org/kohsuke/github/RepositoryMockTest.java index 8c12ed640a..2ff984ef2c 100644 --- a/src/test/java/org/kohsuke/github/RepositoryMockTest.java +++ b/src/test/java/org/kohsuke/github/RepositoryMockTest.java @@ -48,7 +48,7 @@ public void listCollaborators() throws Exception { when(requester.asIterator("/repos/*/*/collaborators", - GHUser[].class)).thenReturn(iterator, iterator); + GHUser[].class, 0)).thenReturn(iterator, iterator); PagedIterable pagedIterable = Mockito.mock(PagedIterable.class); From 2603b5a402a3f0669f5828c316c4d589cf1d1e1b Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Thu, 3 Dec 2015 17:55:06 +0100 Subject: [PATCH 07/13] Added stargazers and stars --- .../java/org/kohsuke/github/GHRepository.java | 19 +++++++++++++++---- src/main/java/org/kohsuke/github/GHUser.java | 13 ++++++++++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/kohsuke/github/GHRepository.java b/src/main/java/org/kohsuke/github/GHRepository.java index 6300785ad1..dbc6a836e8 100644 --- a/src/main/java/org/kohsuke/github/GHRepository.java +++ b/src/main/java/org/kohsuke/github/GHRepository.java @@ -714,7 +714,7 @@ public GHCompare getCompare(GHBranch id1, GHBranch id2) throws IOException { * @throws IOException on failure communicating with GitHub */ public GHRef[] getRefs() throws IOException { - return GHRef.wrap(root.retrieve().to(String.format("/repos/%s/%s/git/refs", owner.login, name), GHRef[].class),root); + return GHRef.wrap(root.retrieve().to(String.format("/repos/%s/%s/git/refs", owner.login, name), GHRef[].class), root); } /** @@ -868,7 +868,7 @@ public GHCommitStatus createCommitStatus(String sha1, GHCommitState state, Strin * @see #createCommitStatus(String, GHCommitState,String,String,String) */ public GHCommitStatus createCommitStatus(String sha1, GHCommitState state, String targetUrl, String description) throws IOException { - return createCommitStatus(sha1, state, targetUrl, description,null); + return createCommitStatus(sha1, state, targetUrl, description, null); } /** @@ -914,7 +914,7 @@ public GHLabel getLabel(String name) throws IOException { public GHLabel createLabel(String name, String color) throws IOException { return root.retrieve().method("POST") .with("name",name) - .with("color",color) + .with("color", color) .to(getApiTailUrl("labels"), GHLabel.class).wrapUp(this); } @@ -924,9 +924,20 @@ public GHLabel createLabel(String name, String color) throws IOException { * https://developer.github.com/v3/activity/watching/ */ public PagedIterable listSubscribers() { + return listUsers("subscribers"); + } + + /** + * Lists all the users who have starred this repo. + */ + public PagedIterable listStargazers() { + return listUsers("stargazers"); + } + + private PagedIterable listUsers(final String suffix) { return new PagedIterable() { public PagedIterator _iterator(int pageSize) { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("subscribers"), GHUser[].class, pageSize)) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl(suffix), GHUser[].class, pageSize)) { protected void wrapUp(GHUser[] page) { for (GHUser c : page) c.wrapUp(root); diff --git a/src/main/java/org/kohsuke/github/GHUser.java b/src/main/java/org/kohsuke/github/GHUser.java index 3e29395ebb..319908cbae 100644 --- a/src/main/java/org/kohsuke/github/GHUser.java +++ b/src/main/java/org/kohsuke/github/GHUser.java @@ -98,9 +98,20 @@ protected void wrapUp(GHUser[] page) { * https://developer.github.com/v3/activity/watching/ */ public PagedIterable listSubscriptions() { + return listRepositories("subscriptions"); + } + + /** + * Lists all the repositories that this user has starred. + */ + public PagedIterable listStarredRepositories() { + return listRepositories("starred"); + } + + private PagedIterable listRepositories(final String suffix) { return new PagedIterable() { public PagedIterator _iterator(int pageSize) { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("subscriptions"), GHRepository[].class, pageSize)) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl(suffix), GHRepository[].class, pageSize)) { protected void wrapUp(GHRepository[] page) { for (GHRepository c : page) c.wrap(root); From 9149b6b998acdffab54d805fe22abdc95bf7de7a Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Thu, 10 Dec 2015 05:53:55 -0800 Subject: [PATCH 08/13] GHCommit might be only partially populated. This fixes issue #230 --- .../java/org/kohsuke/github/GHCommit.java | 22 ++++++++++++++----- .../java/org/kohsuke/github/CommitTest.java | 12 ++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/kohsuke/github/GHCommit.java b/src/main/java/org/kohsuke/github/GHCommit.java index 2b68ff9a95..a350eeb535 100644 --- a/src/main/java/org/kohsuke/github/GHCommit.java +++ b/src/main/java/org/kohsuke/github/GHCommit.java @@ -192,21 +192,24 @@ public GHRepository getOwner() { /** * Number of lines added + removed. */ - public int getLinesChanged() { + public int getLinesChanged() throws IOException { + populate(); return stats.total; } /** * Number of lines added. */ - public int getLinesAdded() { + public int getLinesAdded() throws IOException { + populate(); return stats.additions; } /** * Number of lines removed. */ - public int getLinesDeleted() { + public int getLinesDeleted() throws IOException { + populate(); return stats.deletions; } @@ -223,7 +226,8 @@ public String getSHA1() { * @return * Can be empty but never null. */ - public List getFiles() { + public List getFiles() throws IOException { + populate(); return files!=null ? Collections.unmodifiableList(files) : Collections.emptyList(); } @@ -301,7 +305,7 @@ public GHCommitComment createComment(String body, String path, Integer line, Int } public GHCommitComment createComment(String body) throws IOException { - return createComment(body,null,null,null); + return createComment(body, null, null, null); } /** @@ -318,6 +322,14 @@ public GHCommitStatus getLastStatus() throws IOException { return owner.getLastCommitStatus(sha); } + /** + * Some of the fields are not always filled in when this object is retrieved as a part of another API call. + */ + void populate() throws IOException { + if (files==null && stats==null) + owner.root.retrieve().to(owner.getApiTailUrl("commits/" + sha), this); + } + GHCommit wrapUp(GHRepository owner) { this.owner = owner; return this; diff --git a/src/test/java/org/kohsuke/github/CommitTest.java b/src/test/java/org/kohsuke/github/CommitTest.java index fff9084ebd..c42fceef41 100644 --- a/src/test/java/org/kohsuke/github/CommitTest.java +++ b/src/test/java/org/kohsuke/github/CommitTest.java @@ -1,5 +1,7 @@ package org.kohsuke.github; +import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; import org.junit.Test; import java.io.IOException; @@ -13,4 +15,14 @@ public void lastStatus() throws IOException { GHTag t = gitHub.getRepository("stapler/stapler").listTags().iterator().next(); t.getCommit().getLastStatus(); } + + @Test // issue 230 + public void listFiles() throws Exception { + GHRepository repo = gitHub.getRepository("stapler/stapler"); + PagedIterable commits = repo.queryCommits().path("pom.xml").list(); + for (GHCommit commit : Iterables.limit(commits, 10)) { + GHCommit expected = repo.getCommit( commit.getSHA1() ); + assertEquals(expected.getFiles().size(), commit.getFiles().size()); + } + } } From 1bbbcabae010c6d677b0c73d08c11d2b899b5831 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Thu, 10 Dec 2015 05:56:08 -0800 Subject: [PATCH 09/13] Making API flow better --- src/main/java/org/kohsuke/github/PagedIterable.java | 3 ++- src/main/java/org/kohsuke/github/PagedSearchIterable.java | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/kohsuke/github/PagedIterable.java b/src/main/java/org/kohsuke/github/PagedIterable.java index ae26b1abc6..af6406a8cd 100644 --- a/src/main/java/org/kohsuke/github/PagedIterable.java +++ b/src/main/java/org/kohsuke/github/PagedIterable.java @@ -23,8 +23,9 @@ public abstract class PagedIterable implements Iterable { *

* When set to non-zero, each API call will retrieve this many entries. */ - void setPageSize(int size) throws IOException { + public PagedIterable withPageSize(int size) throws IOException { this.size = size; + return this; } public final PagedIterator iterator() { diff --git a/src/main/java/org/kohsuke/github/PagedSearchIterable.java b/src/main/java/org/kohsuke/github/PagedSearchIterable.java index 222cedcd00..6cd26c3a94 100644 --- a/src/main/java/org/kohsuke/github/PagedSearchIterable.java +++ b/src/main/java/org/kohsuke/github/PagedSearchIterable.java @@ -1,6 +1,8 @@ package org.kohsuke.github; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.io.IOException; import java.util.Iterator; /** @@ -22,6 +24,11 @@ public abstract class PagedSearchIterable extends PagedIterable { this.root = root; } + @Override + public PagedSearchIterable withPageSize(int size) throws IOException { + return (PagedSearchIterable)super.withPageSize(size); + } + /** * Returns the total number of hit, including the results that's not yet fetched. */ From 03ac6c72e76a587f11fdd2b86be76619a81520ef Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Thu, 10 Dec 2015 06:01:34 -0800 Subject: [PATCH 10/13] Formatting change --- src/main/java/org/kohsuke/github/SearchResult.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/kohsuke/github/SearchResult.java b/src/main/java/org/kohsuke/github/SearchResult.java index 7b9f9a0c59..2fb2dca06e 100644 --- a/src/main/java/org/kohsuke/github/SearchResult.java +++ b/src/main/java/org/kohsuke/github/SearchResult.java @@ -7,7 +7,6 @@ * * @author Kohsuke Kawaguchi */ - abstract class SearchResult { @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") int total_count; From 2440a676bdb9e880c3752312bb45ff42ff3f8e6a Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Thu, 10 Dec 2015 06:24:51 -0800 Subject: [PATCH 11/13] Added more comprehensive API to list pull requests This fixes issue #234 --- .../java/org/kohsuke/github/GHDirection.java | 10 ++++ .../java/org/kohsuke/github/GHIssueState.java | 6 +- .../github/GHPullRequestQueryBuilder.java | 58 +++++++++++++++++++ .../org/kohsuke/github/GHQueryBuilder.java | 21 +++++++ .../java/org/kohsuke/github/GHRepository.java | 26 ++++----- .../org/kohsuke/github/GHSearchBuilder.java | 10 ++-- .../java/org/kohsuke/github/Requester.java | 9 +++ 7 files changed, 120 insertions(+), 20 deletions(-) create mode 100644 src/main/java/org/kohsuke/github/GHDirection.java create mode 100644 src/main/java/org/kohsuke/github/GHPullRequestQueryBuilder.java create mode 100644 src/main/java/org/kohsuke/github/GHQueryBuilder.java diff --git a/src/main/java/org/kohsuke/github/GHDirection.java b/src/main/java/org/kohsuke/github/GHDirection.java new file mode 100644 index 0000000000..0db172dccf --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHDirection.java @@ -0,0 +1,10 @@ +package org.kohsuke.github; + +/** + * Sort direction + * + * @author Kohsuke Kawaguchi + */ +public enum GHDirection { + ASC, DESC +} diff --git a/src/main/java/org/kohsuke/github/GHIssueState.java b/src/main/java/org/kohsuke/github/GHIssueState.java index d0af550ead..ec3cf10d24 100644 --- a/src/main/java/org/kohsuke/github/GHIssueState.java +++ b/src/main/java/org/kohsuke/github/GHIssueState.java @@ -24,7 +24,11 @@ package org.kohsuke.github; +/** + * @see GHPullRequestQueryBuilder#state(GHIssueState) + */ public enum GHIssueState { OPEN, - CLOSED + CLOSED, + ALL } \ No newline at end of file diff --git a/src/main/java/org/kohsuke/github/GHPullRequestQueryBuilder.java b/src/main/java/org/kohsuke/github/GHPullRequestQueryBuilder.java new file mode 100644 index 0000000000..141cf4d681 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHPullRequestQueryBuilder.java @@ -0,0 +1,58 @@ +package org.kohsuke.github; + +/** + * Lists up pull requests with some filtering and sorting. + * + * @author Kohsuke Kawaguchi + * @see GHRepository#queryPullRequests() + */ +public class GHPullRequestQueryBuilder extends GHQueryBuilder { + private final GHRepository repo; + + /*package*/ GHPullRequestQueryBuilder(GHRepository repo) { + super(repo.root); + this.repo = repo; + } + + public GHPullRequestQueryBuilder state(GHIssueState state) { + req.with("state",state); + return this; + } + + public GHPullRequestQueryBuilder head(String head) { + req.with("head",head); + return this; + } + + public GHPullRequestQueryBuilder base(String base) { + req.with("base",base); + return this; + } + + public GHPullRequestQueryBuilder sort(Sort sort) { + req.with("sort",sort); + return this; + } + + public enum Sort { CREATED, UPDATED, POPULARITY, LONG_RUNNING } + + public GHPullRequestQueryBuilder direction(GHDirection d) { + req.with("direction",d); + return this; + } + + @Override + public PagedIterable list() { + return new PagedIterable() { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(req.asIterator(repo.getApiTailUrl("pulls"), GHPullRequest[].class, pageSize)) { + @Override + protected void wrapUp(GHPullRequest[] page) { + for (GHPullRequest pr : page) + pr.wrapUp(repo); + } + }; + } + }; + } +} diff --git a/src/main/java/org/kohsuke/github/GHQueryBuilder.java b/src/main/java/org/kohsuke/github/GHQueryBuilder.java new file mode 100644 index 0000000000..bb85fbbe95 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHQueryBuilder.java @@ -0,0 +1,21 @@ +package org.kohsuke.github; + +/** + * Used to specify filters, sort order, etc for listing items in a collection. + * + * @author Kohsuke Kawaguchi + */ +public abstract class GHQueryBuilder { + protected final GitHub root; + protected final Requester req; + + /*package*/ GHQueryBuilder(GitHub root) { + this.root = root; + this.req = root.retrieve(); + } + + /** + * Start listing items by using the settings built up on this object. + */ + public abstract PagedIterable list(); +} diff --git a/src/main/java/org/kohsuke/github/GHRepository.java b/src/main/java/org/kohsuke/github/GHRepository.java index dbc6a836e8..38a821143a 100644 --- a/src/main/java/org/kohsuke/github/GHRepository.java +++ b/src/main/java/org/kohsuke/github/GHRepository.java @@ -617,24 +617,24 @@ public GHPullRequest getPullRequest(int i) throws IOException { * @see #listPullRequests(GHIssueState) */ public List getPullRequests(GHIssueState state) throws IOException { - return listPullRequests(state).asList(); + return queryPullRequests().state(state).list().asList(); } /** * Retrieves all the pull requests of a particular state. + * + * @deprecated + * Use {@link #queryPullRequests()} */ - public PagedIterable listPullRequests(final GHIssueState state) { - return new PagedIterable() { - public PagedIterator _iterator(int pageSize) { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("pulls?state="+state.name().toLowerCase(Locale.ENGLISH)), GHPullRequest[].class, pageSize)) { - @Override - protected void wrapUp(GHPullRequest[] page) { - for (GHPullRequest pr : page) - pr.wrapUp(GHRepository.this); - } - }; - } - }; + public PagedIterable listPullRequests(GHIssueState state) { + return queryPullRequests().state(state).list(); + } + + /** + * Retrieves pull requests. + */ + public GHPullRequestQueryBuilder queryPullRequests() { + return new GHPullRequestQueryBuilder(this); } /** diff --git a/src/main/java/org/kohsuke/github/GHSearchBuilder.java b/src/main/java/org/kohsuke/github/GHSearchBuilder.java index 67d10bdb72..17c2db85b8 100644 --- a/src/main/java/org/kohsuke/github/GHSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHSearchBuilder.java @@ -10,9 +10,7 @@ * * @author Kohsuke Kawaguchi */ -public abstract class GHSearchBuilder { - protected final GitHub root; - protected final Requester req; +public abstract class GHSearchBuilder extends GHQueryBuilder { protected final List terms = new ArrayList(); /** @@ -21,15 +19,14 @@ public abstract class GHSearchBuilder { private final Class> receiverType; /*package*/ GHSearchBuilder(GitHub root, Class> receiverType) { - this.root = root; - this.req = root.retrieve(); + super(root); this.receiverType = receiverType; } /** * Search terms. */ - public GHSearchBuilder q(String term) { + public GHQueryBuilder q(String term) { terms.add(term); return this; } @@ -37,6 +34,7 @@ public GHSearchBuilder q(String term) { /** * Performs the search. */ + @Override public PagedSearchIterable list() { return new PagedSearchIterable(root) { public PagedIterator _iterator(int pageSize) { diff --git a/src/main/java/org/kohsuke/github/Requester.java b/src/main/java/org/kohsuke/github/Requester.java index 7c3052a817..c9835677ea 100644 --- a/src/main/java/org/kohsuke/github/Requester.java +++ b/src/main/java/org/kohsuke/github/Requester.java @@ -45,6 +45,7 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.regex.Matcher; @@ -130,6 +131,14 @@ public Requester with(String key, Boolean value) { return _with(key, value); } + public Requester with(String key, Enum e) { + if (e==null) return _with(key, null); + + // by convention Java constant names are upper cases, but github uses + // lower-case constants. GitHub also uses '-', which in Java we always + // replace by '_' + return with(key, e.toString().toLowerCase(Locale.ENGLISH).replace('_','-')); + } public Requester with(String key, String value) { return _with(key, value); From d5809e375cdb033df869949b88d3af46ffd35710 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Thu, 10 Dec 2015 06:33:49 -0800 Subject: [PATCH 12/13] Simplification via enum handling in 'req.with' --- .../github/GHDeploymentStatusBuilder.java | 2 +- .../kohsuke/github/GHIssueSearchBuilder.java | 2 +- .../java/org/kohsuke/github/GHMyself.java | 5 ++-- .../org/kohsuke/github/GHOrganization.java | 2 +- .../java/org/kohsuke/github/GHRepository.java | 23 ++++++++----------- .../github/GHRepositorySearchBuilder.java | 2 +- .../kohsuke/github/GHUserSearchBuilder.java | 2 +- .../org/kohsuke/github/PagedIterable.java | 2 +- .../kohsuke/github/PagedSearchIterable.java | 2 +- 9 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/kohsuke/github/GHDeploymentStatusBuilder.java b/src/main/java/org/kohsuke/github/GHDeploymentStatusBuilder.java index b1ecf8918c..18cf5464ac 100644 --- a/src/main/java/org/kohsuke/github/GHDeploymentStatusBuilder.java +++ b/src/main/java/org/kohsuke/github/GHDeploymentStatusBuilder.java @@ -12,7 +12,7 @@ public GHDeploymentStatusBuilder(GHRepository repo, int deploymentId, GHDeployme this.repo = repo; this.deploymentId = deploymentId; this.builder = new Requester(repo.root); - this.builder.with("state",state.toString().toLowerCase(Locale.ENGLISH)); + this.builder.with("state",state); } public GHDeploymentStatusBuilder description(String description) { diff --git a/src/main/java/org/kohsuke/github/GHIssueSearchBuilder.java b/src/main/java/org/kohsuke/github/GHIssueSearchBuilder.java index c3054b1067..ff574025c0 100644 --- a/src/main/java/org/kohsuke/github/GHIssueSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHIssueSearchBuilder.java @@ -42,7 +42,7 @@ public GHIssueSearchBuilder isMerged() { } public GHIssueSearchBuilder sort(Sort sort) { - req.with("sort",sort.toString().toLowerCase(Locale.ENGLISH)); + req.with("sort",sort); return this; } diff --git a/src/main/java/org/kohsuke/github/GHMyself.java b/src/main/java/org/kohsuke/github/GHMyself.java index c473af4806..c7ae80dd79 100644 --- a/src/main/java/org/kohsuke/github/GHMyself.java +++ b/src/main/java/org/kohsuke/github/GHMyself.java @@ -159,8 +159,7 @@ public PagedIterable listRepositories(final int pageSize) { public PagedIterable listRepositories(final int pageSize, final RepositoryListFilter repoType) { return new PagedIterable() { public PagedIterator _iterator(int pageSize) { - return new PagedIterator(root.retrieve().asIterator("/user/repos?per_page=" + pageSize + - "&type=" + repoType.name().toLowerCase(Locale.ENGLISH), GHRepository[].class, pageSize)) { + return new PagedIterator(root.retrieve().with("type",repoType).asIterator("/user/repos", GHRepository[].class, pageSize)) { @Override protected void wrapUp(GHRepository[] page) { for (GHRepository c : page) @@ -168,7 +167,7 @@ protected void wrapUp(GHRepository[] page) { } }; } - }; + }.withPageSize(pageSize); } /** diff --git a/src/main/java/org/kohsuke/github/GHOrganization.java b/src/main/java/org/kohsuke/github/GHOrganization.java index 2f4a6c0f4f..83c8a0474a 100644 --- a/src/main/java/org/kohsuke/github/GHOrganization.java +++ b/src/main/java/org/kohsuke/github/GHOrganization.java @@ -175,7 +175,7 @@ public enum Permission { ADMIN, PUSH, PULL } * Creates a new team and assigns the repositories. */ public GHTeam createTeam(String name, Permission p, Collection repositories) throws IOException { - Requester post = new Requester(root).with("name", name).with("permission", p.name().toLowerCase(Locale.ENGLISH)); + Requester post = new Requester(root).with("name", name).with("permission", p); List repo_names = new ArrayList(); for (GHRepository r : repositories) { repo_names.add(r.getName()); diff --git a/src/main/java/org/kohsuke/github/GHRepository.java b/src/main/java/org/kohsuke/github/GHRepository.java index 38a821143a..b024814625 100644 --- a/src/main/java/org/kohsuke/github/GHRepository.java +++ b/src/main/java/org/kohsuke/github/GHRepository.java @@ -228,11 +228,10 @@ public List getIssues(GHIssueState state) throws IOException { public List getIssues(GHIssueState state, GHMilestone milestone) throws IOException { return Arrays.asList(GHIssue.wrap(root.retrieve() - .to(getApiTailUrl(String.format("issues?state=%s&milestone=%s", - state.toString().toLowerCase(Locale.ENGLISH), - milestone == null ? "none" : "" + milestone.getNumber())), - GHIssue[].class - ), this)); + .with("state", state) + .with("milestone", milestone == null ? "none" : "" + milestone.getNumber()) + .to(getApiTailUrl("issues"), + GHIssue[].class), this)); } /** @@ -241,7 +240,7 @@ public List getIssues(GHIssueState state, GHMilestone milestone) throws public PagedIterable listIssues(final GHIssueState state) { return new PagedIterable() { public PagedIterator _iterator(int pageSize) { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("issues?state="+state.toString().toLowerCase(Locale.ENGLISH)), GHIssue[].class, pageSize)) { + return new PagedIterator(root.retrieve().with("state",state).asIterator(getApiTailUrl("issues"), GHIssue[].class, pageSize)) { @Override protected void wrapUp(GHIssue[] page) { for (GHIssue c : page) @@ -538,7 +537,7 @@ public void delete() throws IOException { /** * Sort orders for listing forks */ - public static enum ForkSort { NEWEST, OLDEST, STARGAZERS } + public enum ForkSort { NEWEST, OLDEST, STARGAZERS } /** * Lists all the direct forks of this repository, sorted by @@ -556,11 +555,7 @@ public PagedIterable listForks() { public PagedIterable listForks(final ForkSort sort) { return new PagedIterable() { public PagedIterator _iterator(int pageSize) { - String sortParam = ""; - if (sort != null) { - sortParam = "?sort=" + sort.toString().toLowerCase(Locale.ENGLISH); - } - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("forks" + sortParam), GHRepository[].class, pageSize)) { + return new PagedIterator(root.retrieve().with("sort",sort).asIterator(getApiTailUrl("forks"), GHRepository[].class, pageSize)) { @Override protected void wrapUp(GHRepository[] page) { for (GHRepository c : page) { @@ -857,7 +852,7 @@ public GHCommitStatus getLastCommitStatus(String sha1) throws IOException { */ public GHCommitStatus createCommitStatus(String sha1, GHCommitState state, String targetUrl, String description, String context) throws IOException { return new Requester(root) - .with("state", state.name().toLowerCase(Locale.ENGLISH)) + .with("state", state) .with("target_url", targetUrl) .with("description", description) .with("context", context) @@ -1089,7 +1084,7 @@ public Map getMilestones() throws IOException { public PagedIterable listMilestones(final GHIssueState state) { return new PagedIterable() { public PagedIterator _iterator(int pageSize) { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("milestones?state="+state.toString().toLowerCase(Locale.ENGLISH)), GHMilestone[].class, pageSize)) { + return new PagedIterator(root.retrieve().with("state",state).asIterator(getApiTailUrl("milestones"), GHMilestone[].class, pageSize)) { @Override protected void wrapUp(GHMilestone[] page) { for (GHMilestone c : page) diff --git a/src/main/java/org/kohsuke/github/GHRepositorySearchBuilder.java b/src/main/java/org/kohsuke/github/GHRepositorySearchBuilder.java index cbcbb9e19d..8d30aaaf6b 100644 --- a/src/main/java/org/kohsuke/github/GHRepositorySearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHRepositorySearchBuilder.java @@ -58,7 +58,7 @@ public GHRepositorySearchBuilder stars(String v) { } public GHRepositorySearchBuilder sort(Sort sort) { - req.with("sort",sort.toString().toLowerCase(Locale.ENGLISH)); + req.with("sort",sort); return this; } diff --git a/src/main/java/org/kohsuke/github/GHUserSearchBuilder.java b/src/main/java/org/kohsuke/github/GHUserSearchBuilder.java index ee0c5f62a0..fa5161683f 100644 --- a/src/main/java/org/kohsuke/github/GHUserSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHUserSearchBuilder.java @@ -50,7 +50,7 @@ public GHUserSearchBuilder followers(String v) { } public GHUserSearchBuilder sort(Sort sort) { - req.with("sort",sort.toString().toLowerCase(Locale.ENGLISH)); + req.with("sort",sort); return this; } diff --git a/src/main/java/org/kohsuke/github/PagedIterable.java b/src/main/java/org/kohsuke/github/PagedIterable.java index af6406a8cd..41c5bfdb0c 100644 --- a/src/main/java/org/kohsuke/github/PagedIterable.java +++ b/src/main/java/org/kohsuke/github/PagedIterable.java @@ -23,7 +23,7 @@ public abstract class PagedIterable implements Iterable { *

* When set to non-zero, each API call will retrieve this many entries. */ - public PagedIterable withPageSize(int size) throws IOException { + public PagedIterable withPageSize(int size) { this.size = size; return this; } diff --git a/src/main/java/org/kohsuke/github/PagedSearchIterable.java b/src/main/java/org/kohsuke/github/PagedSearchIterable.java index 6cd26c3a94..1efe49a1ef 100644 --- a/src/main/java/org/kohsuke/github/PagedSearchIterable.java +++ b/src/main/java/org/kohsuke/github/PagedSearchIterable.java @@ -25,7 +25,7 @@ public abstract class PagedSearchIterable extends PagedIterable { } @Override - public PagedSearchIterable withPageSize(int size) throws IOException { + public PagedSearchIterable withPageSize(int size) { return (PagedSearchIterable)super.withPageSize(size); } From 733d78abddbec74af5e78288c390928b0fff03f3 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Thu, 10 Dec 2015 06:59:37 -0800 Subject: [PATCH 13/13] [maven-release-plugin] prepare release github-api-1.72 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0157b91a59..805080b06e 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ github-api - 1.72-SNAPSHOT + 1.72 GitHub API for Java http://github-api.kohsuke.org/ GitHub API for Java @@ -16,7 +16,7 @@ scm:git:git@github.com/kohsuke/${project.artifactId}.git scm:git:ssh://git@github.com/kohsuke/${project.artifactId}.git http://${project.artifactId}.kohsuke.org/ - HEAD + github-api-1.72