Compare commits

..

58 Commits

Author SHA1 Message Date
Kohsuke Kawaguchi
255c993548 [maven-release-plugin] prepare release github-api-1.74 2016-03-18 19:22:34 -07:00
Kohsuke Kawaguchi
557ae4165c Not important 2016-03-18 19:19:51 -07:00
Kohsuke Kawaguchi
a31395ed80 Signature fix for Java5 2016-03-18 19:15:52 -07:00
Kohsuke Kawaguchi
397886d289 Excluding a flaky test 2016-03-18 19:08:51 -07:00
Kohsuke Kawaguchi
7307bec2ae Merge pull request #259 from Shredder121/animal-sniffer
Animal sniffer
2016-03-18 18:27:22 -07:00
Ruben Dijkstra
0cd5147e1a Java 5 doesn't have TimeUnit.HOURS
http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/TimeUnit.html
2016-03-12 22:01:25 +01:00
Ruben Dijkstra
36d5b092d7 Java 5 doesn't have new String(byte[], Charset)
http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/String.html
2016-03-12 21:59:51 +01:00
Ruben Dijkstra
f9014dbab3 Convert to legacy Throwable.initCause() 2016-03-12 21:58:27 +01:00
Ruben Dijkstra
755d5f77ea Java 5 doesn't have Arrays.copyOf()
http://docs.oracle.com/javase/1.5.0/docs/api/java/util/Arrays.html
2016-03-12 21:56:56 +01:00
Ruben Dijkstra
906d9af7b7 Include the animal sniffer plugin
7a78f9f5aa set the compiler level back to 5, but there are no guarantees that we don't accidentally use any features from newer class libraries.
2016-03-12 21:48:50 +01:00
Kohsuke Kawaguchi
14dcb37ee1 Not all caller wants GET 2016-03-11 23:29:58 -08:00
Kohsuke Kawaguchi
c1c2a27358 Doc improvement 2016-03-11 23:21:07 -08:00
Kohsuke Kawaguchi
5ab9657f9c Merge branch 'master' of github.com:kohsuke/github-api 2016-03-11 23:16:37 -08:00
Kohsuke Kawaguchi
7a78f9f5aa No need to require 1.6 2016-03-11 23:16:24 -08:00
Kohsuke Kawaguchi
ac8c65f062 Merge pull request #251
Conflicts:
	src/main/java/org/kohsuke/github/GitHub.java
2016-03-11 23:14:01 -08:00
Kohsuke Kawaguchi
1954a9f3f8 This isn't just about API URL but it also checks the valid credential 2016-03-11 23:12:50 -08:00
Kohsuke Kawaguchi
3b764f9c90 Don't lose the original problem 2016-03-11 23:11:22 -08:00
Kohsuke Kawaguchi
10f55cc549 Checking another header
I think it's better to pick a header that's unique to GitHub.
2016-03-11 23:10:41 -08:00
Kohsuke Kawaguchi
cd8d955646 Documenting what one gets 2016-03-11 23:06:21 -08:00
Kohsuke Kawaguchi
bba07c9080 Merge pull request #253 from cyrille-leclerc/fix-infinite-loop
Fix #252: infinite loop because the "hypertext engine" generates invalid URLs
2016-03-11 22:59:00 -08:00
Kohsuke Kawaguchi
dba84a33b9 Logger should be static 2016-03-11 22:55:58 -08:00
Kohsuke Kawaguchi
ae49166aa2 No such parameter exists on this method 2016-03-11 22:55:44 -08:00
Kohsuke Kawaguchi
e09185fd0e Logger should be static 2016-03-11 22:51:48 -08:00
Cyrille Le Clerc
56379bb3b9 Fix broken log message in GitHub.java and cleanup code as recommended by @jglick 2016-03-07 19:25:42 +01:00
Cyrille Le Clerc
027e4b4f25 Better error message: introduce HttpException, subclass of IOException with url, http responseCode and http responseMessage to help exception handling. 2016-03-06 18:34:27 +01:00
Cyrille Le Clerc
ba951cb6e3 Fix #252: infinite loop because the "hypertext engine" may duplicate '?' generating invalid "https://api.github.com/notifications?all=true&page=2?all=true" instead of "https://api.github.com/notifications?all=true&page=2&all=true". A better fix will be to prevent duplication of parameters ("all=true" in this case). 2016-03-06 18:27:05 +01:00
Manuel Recena
ae85cf4b6c Improve checkApiUrlValidity() method to support the private mode in GitHub Enterprise servers 2016-03-05 17:42:25 +01:00
Kohsuke Kawaguchi
dbc79f8c42 Fixing issue raised in https://github.com/kohsuke/github-api/pull/247
From Shredder121,
--------------------
Only the HttpURLConnection.method was set by that change. Not the
Requester.method.

This means that Requester.method is still set to POST, isMethodWithBody
will return true, and uc.setDoOutput(true) will be called.

I use Okhttp, and their HttpURLConnectionImpl's method changes to POST
if you tell it to open a stream to write to.
2016-03-01 19:46:50 -08:00
Kohsuke Kawaguchi
54c3070607 [maven-release-plugin] prepare for next development iteration 2016-02-29 21:03:34 -08:00
Kohsuke Kawaguchi
013eaa30b6 [maven-release-plugin] prepare release github-api-1.73 2016-02-29 21:03:31 -08:00
Kohsuke Kawaguchi
751043bf81 change in the markup generated 2016-02-29 21:01:18 -08:00
Kohsuke Kawaguchi
14f7198a07 Handle "all" webhook correctly
This fixes #250
2016-02-29 20:56:47 -08:00
Kohsuke Kawaguchi
94af819ae5 Merge pull request #249 from zapelin/master
Added getHtmlUrl() to GHCommit
2016-02-29 20:48:19 -08:00
Kohsuke Kawaguchi
dbcc9afbc7 Merge pull request #248 from daniel-beck/populate-commit
Populate commit with data for getCommitShortInfo
2016-02-29 20:47:52 -08:00
Kohsuke Kawaguchi
8556033ae6 Merge pull request #245 from daniel-beck/email-hook-error
Fix error when creating email service hook
2016-02-29 20:45:40 -08:00
Kohsuke Kawaguchi
650493f863 Merge pull request #244 from benbek/patch-1
Minor amendment to the documentation
2016-02-29 19:57:47 -08:00
Kohsuke Kawaguchi
d80ad77871 Use builder pattern to support all the other options 2016-02-29 19:57:16 -08:00
Artem Gubanov
f4b129b9f1 Added getHtmlUrl() to GHCommit 2016-02-25 10:46:17 +02:00
Daniel Beck
c0a05e0650 Populate commit with data for getCommitShortInfo 2016-02-21 01:26:43 +01:00
Daniel Beck
33d95d3e3a Fix error when creating email service hook 2016-01-20 23:30:10 +01:00
benbek
f573f83fb9 Amendment to the documentation
The status reported by GitHub for deleting a file is actually "removed", not "deleted".
2016-01-19 00:05:01 +02:00
Daniel Lovera
e94c36b7e6 clean: remove unused import 2015-12-13 21:05:36 +01:00
Daniel Lovera
c879e9e34d Support for auto_init parameter in organization
The GitHub api auto_init parameter allows to initialize created repository with a readme file.
Add createRepository methods in GHOrganization using auto_init parameter. Already existing createRepository methods use auto_init parameter as false for retro-compatibility.
2015-12-13 21:00:40 +01:00
Daniel Lovera
ac39b564a8 Support for auto_init parameter
The GitHub api auto_init parameter allows to initialize created repository with a readme file.
Add a createRepository method using auto_init parameter. Already existing createRepository method uses auto_init parameter as false for retro-compatibility.
2015-12-13 20:59:49 +01:00
Kohsuke Kawaguchi
d91388aba4 [maven-release-plugin] prepare for next development iteration 2015-12-10 07:00:06 -08:00
Kohsuke Kawaguchi
733d78abdd [maven-release-plugin] prepare release github-api-1.72 2015-12-10 06:59:37 -08:00
Kohsuke Kawaguchi
d5809e375c Simplification via enum handling in 'req.with' 2015-12-10 06:33:49 -08:00
Kohsuke Kawaguchi
2440a676bd Added more comprehensive API to list pull requests
This fixes issue #234
2015-12-10 06:26:04 -08:00
Kohsuke Kawaguchi
03ac6c72e7 Formatting change 2015-12-10 06:01:34 -08:00
Kohsuke Kawaguchi
1bbbcabae0 Making API flow better 2015-12-10 05:56:08 -08:00
Kohsuke Kawaguchi
9149b6b998 GHCommit might be only partially populated.
This fixes issue #230
2015-12-10 05:53:55 -08:00
Kohsuke Kawaguchi
2603b5a402 Added stargazers and stars 2015-12-03 17:55:06 +01:00
Kohsuke Kawaguchi
dbddf5b9eb Implemented pagenation size support. 2015-12-03 17:41:43 +01:00
Kohsuke Kawaguchi
841f77bac2 Naming anonymous iterator.
... in anticipation of the page size support.
2015-12-03 16:33:39 +01:00
Kohsuke Kawaguchi
83ffe75baa Fixed rate handling limit handling
Issue #220. If RateLimitHandler returns normally, it should retry.
2015-12-02 12:03:39 +01:00
Kohsuke Kawaguchi
8cb7094803 Added pagination for following & follower 2015-12-02 08:35:56 +01:00
Kohsuke Kawaguchi
ed8cd0ad19 Added getter for ID
And the actual value has already gone past 'int'

This fixes issue #199
2015-12-01 17:02:53 +01:00
Kohsuke Kawaguchi
90d8e65a3b [maven-release-plugin] prepare for next development iteration 2015-12-01 16:27:29 +01:00
44 changed files with 974 additions and 270 deletions

25
pom.xml
View File

@@ -7,7 +7,7 @@
</parent>
<artifactId>github-api</artifactId>
<version>1.71</version>
<version>1.74</version>
<name>GitHub API for Java</name>
<url>http://github-api.kohsuke.org/</url>
<description>GitHub API for Java</description>
@@ -16,7 +16,7 @@
<connection>scm:git:git@github.com/kohsuke/${project.artifactId}.git</connection>
<developerConnection>scm:git:ssh://git@github.com/kohsuke/${project.artifactId}.git</developerConnection>
<url>http://${project.artifactId}.kohsuke.org/</url>
<tag>github-api-1.71</tag>
<tag>github-api-1.74</tag>
</scm>
<distributionManagement>
@@ -34,6 +34,27 @@
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId>
<version>1.15</version>
<configuration>
<signature>
<groupId>org.codehaus.mojo.signature</groupId>
<artifactId>java15</artifactId>
<version>1.0</version>
</signature>
</configuration>
<executions>
<execution>
<id>ensure-java-1.5-class-library</id>
<phase>test</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.infradna.tool</groupId>
<artifactId>bridge-method-injector</artifactId>

View File

@@ -102,7 +102,7 @@ public class GHCommit {
}
/**
* "modified", "added", or "deleted"
* "modified", "added", or "removed"
*/
public String getStatus() {
return status;
@@ -171,14 +171,15 @@ public class GHCommit {
String login;
}
String url,sha;
String url,html_url,sha;
List<File> files;
Stats stats;
List<Parent> parents;
User author,committer;
public ShortInfo getCommitShortInfo() {
public ShortInfo getCommitShortInfo() throws IOException {
populate();
return commit;
}
@@ -192,24 +193,34 @@ public class GHCommit {
/**
* 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;
}
/**
* URL of this commit like "https://github.com/kohsuke/sandbox-ant/commit/8ae38db0ea5837313ab5f39d43a6f73de3bd9000"
*/
public URL getHtmlUrl() {
return GitHub.parseURL(html_url);
}
/**
* [0-9a-f]{40} SHA1 checksum.
*/
@@ -223,7 +234,8 @@ public class GHCommit {
* @return
* Can be empty but never null.
*/
public List<File> getFiles() {
public List<File> getFiles() throws IOException {
populate();
return files!=null ? Collections.unmodifiableList(files) : Collections.<File>emptyList();
}
@@ -273,8 +285,8 @@ public class GHCommit {
*/
public PagedIterable<GHCommitComment> listComments() {
return new PagedIterable<GHCommitComment>() {
public PagedIterator<GHCommitComment> iterator() {
return new PagedIterator<GHCommitComment>(owner.root.retrieve().asIterator(String.format("/repos/%s/%s/commits/%s/comments", owner.getOwnerName(), owner.getName(), sha), GHCommitComment[].class)) {
public PagedIterator<GHCommitComment> _iterator(int pageSize) {
return new PagedIterator<GHCommitComment>(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)
@@ -301,7 +313,7 @@ public class GHCommit {
}
public GHCommitComment createComment(String body) throws IOException {
return createComment(body,null,null,null);
return createComment(body, null, null, null);
}
/**
@@ -318,6 +330,14 @@ public class GHCommit {
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;

View File

@@ -92,8 +92,8 @@ public class GHCommitQueryBuilder {
*/
public PagedIterable<GHCommit> list() {
return new PagedIterable<GHCommit>() {
public PagedIterator<GHCommit> iterator() {
return new PagedIterator<GHCommit>(req.asIterator(repo.getApiTailUrl("commits"), GHCommit[].class)) {
public PagedIterator<GHCommit> _iterator(int pageSize) {
return new PagedIterator<GHCommit>(req.asIterator(repo.getApiTailUrl("commits"), GHCommit[].class, pageSize)) {
protected void wrapUp(GHCommit[] page) {
for (GHCommit c : page)
c.wrapUp(repo);

View File

@@ -4,8 +4,6 @@ import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.net.URL;
import java.util.Arrays;
import java.util.Date;
/**
* The model user for comparing 2 commits in the GitHub API.
@@ -72,7 +70,9 @@ public class GHCompare {
* @return A copy of the array being stored in the class.
*/
public Commit[] getCommits() {
return Arrays.copyOf(commits, commits.length);
Commit[] newValue = new Commit[commits.length];
System.arraycopy(commits, 0, newValue, 0, commits.length);
return newValue;
}
/**
@@ -80,7 +80,9 @@ public class GHCompare {
* @return A copy of the array being stored in the class.
*/
public GHCommit.File[] getFiles() {
return Arrays.copyOf(files, files.length);
GHCommit.File[] newValue = new GHCommit.File[files.length];
System.arraycopy(files, 0, newValue, 0, files.length);
return newValue;
}
public GHCompare wrap(GHRepository owner) {

View File

@@ -78,7 +78,7 @@ public class GHContent {
*/
@SuppressFBWarnings("DM_DEFAULT_ENCODING")
public String getContent() throws IOException {
return new String(DatatypeConverter.parseBase64Binary(getEncodedContent()));
return new String(Base64.decodeBase64(getEncodedContent()));
}
/**
@@ -115,7 +115,8 @@ public class GHContent {
* Retrieves the actual content stored here.
*/
public InputStream read() throws IOException {
return new Requester(root).asStream(getDownloadUrl());
// if the download link is encoded with a token on the query string, the default behavior of POST will fail
return new Requester(root).method("GET").asStream(getDownloadUrl());
}
/**
@@ -152,8 +153,8 @@ public class GHContent {
throw new IllegalStateException(path+" is not a directory");
return new PagedIterable<GHContent>() {
public PagedIterator<GHContent> iterator() {
return new PagedIterator<GHContent>(root.retrieve().asIterator(url, GHContent[].class)) {
public PagedIterator<GHContent> _iterator(int pageSize) {
return new PagedIterator<GHContent>(root.retrieve().asIterator(url, GHContent[].class, pageSize)) {
@Override
protected void wrapUp(GHContent[] page) {
GHContent.wrap(page, repository);
@@ -178,7 +179,7 @@ public class GHContent {
}
public GHContentUpdateResponse update(byte[] newContentBytes, String commitMessage, String branch) throws IOException {
String encodedContent = DatatypeConverter.printBase64Binary(newContentBytes);
String encodedContent = Base64.encodeBase64String(newContentBytes);
Requester requester = new Requester(root)
.with("path", path)

View File

@@ -0,0 +1,114 @@
package org.kohsuke.github;
import java.io.IOException;
import java.net.URL;
/**
* Creates a repository
*
* @author Kohsuke Kawaguchi
*/
public class GHCreateRepositoryBuilder {
private final GitHub root;
protected final Requester builder;
private final String apiUrlTail;
/*package*/ GHCreateRepositoryBuilder(GitHub root, String apiUrlTail, String name) {
this.root = root;
this.apiUrlTail = apiUrlTail;
this.builder = new Requester(root);
this.builder.with("name",name);
}
public GHCreateRepositoryBuilder description(String description) {
this.builder.with("description",description);
return this;
}
public GHCreateRepositoryBuilder homepage(URL homepage) {
return homepage(homepage.toExternalForm());
}
public GHCreateRepositoryBuilder homepage(String homepage) {
this.builder.with("homepage",homepage);
return this;
}
/**
* Creates a private repository
*/
public GHCreateRepositoryBuilder private_(boolean b) {
this.builder.with("private",b);
return this;
}
/**
* Enables issue tracker
*/
public GHCreateRepositoryBuilder issues(boolean b) {
this.builder.with("has_issues",b);
return this;
}
/**
* Enables wiki
*/
public GHCreateRepositoryBuilder wiki(boolean b) {
this.builder.with("has_wiki",b);
return this;
}
/**
* Enables downloads
*/
public GHCreateRepositoryBuilder downloads(boolean b) {
this.builder.with("has_downloads",b);
return this;
}
/**
* If true, create an initial commit with empty README.
*/
public GHCreateRepositoryBuilder autoInit(boolean b) {
this.builder.with("auto_init",b);
return this;
}
/**
* Creates a default .gitignore
*
* See https://developer.github.com/v3/repos/#create
*/
public GHCreateRepositoryBuilder gitignoreTemplate(String language) {
this.builder.with("gitignore_template",language);
return this;
}
/**
* Desired license template to apply
*
* See https://developer.github.com/v3/repos/#create
*/
public GHCreateRepositoryBuilder licenseTemplate(String license) {
this.builder.with("license_template",license);
return this;
}
/**
* The team that gets granted access to this repository. Only valid for creating a repository in
* an organization.
*/
public GHCreateRepositoryBuilder team(GHTeam team) {
if (team!=null)
this.builder.with("team_id",team.getId());
return this;
}
/**
* Creates a repository with all the parameters.
*/
public GHRepository create() throws IOException {
return builder.method("POST").to(apiUrlTail, GHRepository.class).wrap(root);
}
}

View File

@@ -12,7 +12,7 @@ public class GHDeploymentStatusBuilder {
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) {

View File

@@ -0,0 +1,10 @@
package org.kohsuke.github;
/**
* Sort direction
*
* @author Kohsuke Kawaguchi
*/
public enum GHDirection {
ASC, DESC
}

View File

@@ -1,5 +1,7 @@
package org.kohsuke.github;
import java.util.Locale;
/**
* Hook event type.
*
@@ -33,5 +35,18 @@ public enum GHEvent {
STATUS,
TEAM_ADD,
WATCH,
PING
PING,
/**
* Special event type that means "every possible event"
*/
ALL;
/**
* Returns GitHub's internal representation of this event.
*/
String symbol() {
if (this==ALL) return "*";
return name().toLowerCase(Locale.ENGLISH);
}
}

View File

@@ -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 class GHEventInfo {
return this;
}
public long getId() {
return id;
}
public Date getCreatedAt() {
return GitHub.parseDate(created_at);
}

View File

@@ -140,8 +140,8 @@ public class GHGist extends GHObject {
public PagedIterable<GHGist> listForks() {
return new PagedIterable<GHGist>() {
public PagedIterator<GHGist> iterator() {
return new PagedIterator<GHGist>(root.retrieve().asIterator(getApiTailUrl("forks"), GHGist[].class)) {
public PagedIterator<GHGist> _iterator(int pageSize) {
return new PagedIterator<GHGist>(root.retrieve().asIterator(getApiTailUrl("forks"), GHGist[].class, pageSize)) {
@Override
protected void wrapUp(GHGist[] page) {
try {

View File

@@ -26,8 +26,10 @@ public abstract class GHHook extends GHObject {
public EnumSet<GHEvent> getEvents() {
EnumSet<GHEvent> s = EnumSet.noneOf(GHEvent.class);
for (String e : events)
s.add(Enum.valueOf(GHEvent.class,e.toUpperCase(Locale.ENGLISH)));
for (String e : events) {
if (e.equals("*")) s.add(GHEvent.ALL);
else s.add(Enum.valueOf(GHEvent.class, e.toUpperCase(Locale.ENGLISH)));
}
return s;
}

View File

@@ -39,7 +39,7 @@ class GHHooks {
if (events!=null) {
ea = new ArrayList<String>();
for (GHEvent e : events)
ea.add(e.name().toLowerCase(Locale.ENGLISH));
ea.add(e.symbol());
}
GHHook hook = new Requester(root)

View File

@@ -205,8 +205,8 @@ public class GHIssue extends GHObject {
*/
public PagedIterable<GHIssueComment> listComments() throws IOException {
return new PagedIterable<GHIssueComment>() {
public PagedIterator<GHIssueComment> iterator() {
return new PagedIterator<GHIssueComment>(root.retrieve().asIterator(getIssuesApiRoute() + "/comments", GHIssueComment[].class)) {
public PagedIterator<GHIssueComment> _iterator(int pageSize) {
return new PagedIterator<GHIssueComment>(root.retrieve().asIterator(getIssuesApiRoute() + "/comments", GHIssueComment[].class, pageSize)) {
protected void wrapUp(GHIssueComment[] page) {
for (GHIssueComment c : page)
c.wrapUp(GHIssue.this);

View File

@@ -42,7 +42,7 @@ public class GHIssueSearchBuilder extends GHSearchBuilder<GHIssue> {
}
public GHIssueSearchBuilder sort(Sort sort) {
req.with("sort",sort.toString().toLowerCase(Locale.ENGLISH));
req.with("sort",sort);
return this;
}

View File

@@ -24,7 +24,11 @@
package org.kohsuke.github;
/**
* @see GHPullRequestQueryBuilder#state(GHIssueState)
*/
public enum GHIssueState {
OPEN,
CLOSED
CLOSED,
ALL
}

View File

@@ -158,9 +158,8 @@ public class GHMyself extends GHUser {
*/
public PagedIterable<GHRepository> listRepositories(final int pageSize, final RepositoryListFilter repoType) {
return new PagedIterable<GHRepository>() {
public PagedIterator<GHRepository> iterator() {
return new PagedIterator<GHRepository>(root.retrieve().asIterator("/user/repos?per_page=" + pageSize +
"&type=" + repoType.name().toLowerCase(Locale.ENGLISH), GHRepository[].class)) {
public PagedIterator<GHRepository> _iterator(int pageSize) {
return new PagedIterator<GHRepository>(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 @@ public class GHMyself extends GHUser {
}
};
}
};
}.withPageSize(pageSize);
}
/**

View File

@@ -7,7 +7,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
@@ -24,6 +23,8 @@ public class GHOrganization extends GHPerson {
*
* @return
* Newly created repository.
* @deprecated
* Use {@link #createRepository(String)} that uses a builder pattern to let you control every aspect.
*/
public GHRepository createRepository(String name, String description, String homepage, String team, boolean isPublic) throws IOException {
GHTeam t = getTeams().get(team);
@@ -32,13 +33,25 @@ public class GHOrganization extends GHPerson {
return createRepository(name, description, homepage, t, isPublic);
}
/**
* @deprecated
* Use {@link #createRepository(String)} that uses a builder pattern to let you control every aspect.
*/
public GHRepository createRepository(String name, String description, String homepage, GHTeam team, boolean isPublic) throws IOException {
if (team==null)
throw new IllegalArgumentException("Invalid team");
// such API doesn't exist, so fall back to HTML scraping
return new Requester(root)
.with("name", name).with("description", description).with("homepage", homepage)
.with("public", isPublic).with("team_id",team.getId()).to("/orgs/"+login+"/repos", GHRepository.class).wrap(root);
return createRepository(name).description(description).homepage(homepage).private_(!isPublic).team(team).create();
}
/**
* Starts a builder that creates a new repository.
*
* <p>
* You use the returned builder to set various properties, then call {@link GHCreateRepositoryBuilder#create()}
* to finally createa repository.
*/
public GHCreateRepositoryBuilder createRepository(String name) throws IOException {
return new GHCreateRepositoryBuilder(root,"/orgs/"+login+"/repos",name);
}
/**
@@ -57,8 +70,8 @@ public class GHOrganization extends GHPerson {
*/
public PagedIterable<GHTeam> listTeams() throws IOException {
return new PagedIterable<GHTeam>() {
public PagedIterator<GHTeam> iterator() {
return new PagedIterator<GHTeam>(root.retrieve().asIterator(String.format("/orgs/%s/teams", login), GHTeam[].class)) {
public PagedIterator<GHTeam> _iterator(int pageSize) {
return new PagedIterator<GHTeam>(root.retrieve().asIterator(String.format("/orgs/%s/teams", login), GHTeam[].class, pageSize)) {
@Override
protected void wrapUp(GHTeam[] page) {
for (GHTeam c : page)
@@ -150,9 +163,9 @@ public class GHOrganization extends GHPerson {
private PagedIterable<GHUser> listMembers(final String suffix, final String filter) throws IOException {
return new PagedIterable<GHUser>() {
public PagedIterator<GHUser> iterator() {
public PagedIterator<GHUser> _iterator(int pageSize) {
String filterParams = (filter == null) ? "" : ("?filter=" + filter);
return new PagedIterator<GHUser>(root.retrieve().asIterator(String.format("/orgs/%s/%s%s", login, suffix, filterParams), GHUser[].class)) {
return new PagedIterator<GHUser>(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);
@@ -175,7 +188,7 @@ public class GHOrganization extends GHPerson {
* Creates a new team and assigns the repositories.
*/
public GHTeam createTeam(String name, Permission p, Collection<GHRepository> 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<String> repo_names = new ArrayList<String>();
for (GHRepository r : repositories) {
repo_names.add(r.getName());
@@ -185,7 +198,7 @@ public class GHOrganization extends GHPerson {
}
public GHTeam createTeam(String name, Permission p, GHRepository... repositories) throws IOException {
return createTeam(name,p, Arrays.asList(repositories));
return createTeam(name, p, Arrays.asList(repositories));
}
/**
@@ -222,8 +235,8 @@ public class GHOrganization extends GHPerson {
*/
public PagedIterable<GHEventInfo> listEvents() throws IOException {
return new PagedIterable<GHEventInfo>() {
public PagedIterator<GHEventInfo> iterator() {
return new PagedIterator<GHEventInfo>(root.retrieve().asIterator(String.format("/orgs/%s/events", login), GHEventInfo[].class)) {
public PagedIterator<GHEventInfo> _iterator(int pageSize) {
return new PagedIterator<GHEventInfo>(root.retrieve().asIterator(String.format("/orgs/%s/events", login), GHEventInfo[].class, pageSize)) {
@Override
protected void wrapUp(GHEventInfo[] page) {
for (GHEventInfo c : page)
@@ -244,8 +257,8 @@ public class GHOrganization extends GHPerson {
@Override
public PagedIterable<GHRepository> listRepositories(final int pageSize) {
return new PagedIterable<GHRepository>() {
public PagedIterator<GHRepository> iterator() {
return new PagedIterator<GHRepository>(root.retrieve().asIterator("/orgs/" + login + "/repos?per_page=" + pageSize, GHRepository[].class)) {
public PagedIterator<GHRepository> _iterator(int pageSize) {
return new PagedIterator<GHRepository>(root.retrieve().asIterator("/orgs/" + login + "/repos?per_page=" + pageSize, GHRepository[].class, pageSize)) {
@Override
protected void wrapUp(GHRepository[] page) {
for (GHRepository c : page)

View File

@@ -76,8 +76,8 @@ public abstract class GHPerson extends GHObject {
*/
public PagedIterable<GHRepository> listRepositories(final int pageSize) {
return new PagedIterable<GHRepository>() {
public PagedIterator<GHRepository> iterator() {
return new PagedIterator<GHRepository>(root.retrieve().asIterator("/users/" + login + "/repos?per_page=" + pageSize, GHRepository[].class)) {
public PagedIterator<GHRepository> _iterator(int pageSize) {
return new PagedIterator<GHRepository>(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 @@ public abstract class GHPerson extends GHObject {
public synchronized Iterable<List<GHRepository>> iterateRepositories(final int pageSize) {
return new Iterable<List<GHRepository>>() {
public Iterator<List<GHRepository>> iterator() {
final Iterator<GHRepository[]> pager = root.retrieve().asIterator("/users/" + login + "/repos?per_page="+pageSize,GHRepository[].class);
final Iterator<GHRepository[]> pager = root.retrieve().asIterator("/users/" + login + "/repos?per_page="+pageSize,GHRepository[].class, pageSize);
return new Iterator<List<GHRepository>>() {
public boolean hasNext() {

View File

@@ -210,9 +210,9 @@ public class GHPullRequest extends GHIssue {
*/
public PagedIterable<GHPullRequestFileDetail> listFiles() {
return new PagedIterable<GHPullRequestFileDetail>() {
public PagedIterator<GHPullRequestFileDetail> iterator() {
public PagedIterator<GHPullRequestFileDetail> _iterator(int pageSize) {
return new PagedIterator<GHPullRequestFileDetail>(root.retrieve().asIterator(String.format("%s/files", getApiURL()),
GHPullRequestFileDetail[].class)) {
GHPullRequestFileDetail[].class, pageSize)) {
@Override
protected void wrapUp(GHPullRequestFileDetail[] page) {
}
@@ -226,9 +226,9 @@ public class GHPullRequest extends GHIssue {
*/
public PagedIterable<GHPullRequestReviewComment> listReviewComments() throws IOException {
return new PagedIterable<GHPullRequestReviewComment>() {
public PagedIterator<GHPullRequestReviewComment> iterator() {
public PagedIterator<GHPullRequestReviewComment> _iterator(int pageSize) {
return new PagedIterator<GHPullRequestReviewComment>(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 @@ public class GHPullRequest extends GHIssue {
*/
public PagedIterable<GHPullRequestCommitDetail> listCommits() {
return new PagedIterable<GHPullRequestCommitDetail>() {
public PagedIterator<GHPullRequestCommitDetail> iterator() {
public PagedIterator<GHPullRequestCommitDetail> _iterator(int pageSize) {
return new PagedIterator<GHPullRequestCommitDetail>(root.retrieve().asIterator(
String.format("%s/commits", getApiURL()),
GHPullRequestCommitDetail[].class)) {
GHPullRequestCommitDetail[].class, pageSize)) {
@Override
protected void wrapUp(GHPullRequestCommitDetail[] page) {
for (GHPullRequestCommitDetail c : page)

View File

@@ -27,7 +27,6 @@ import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.net.URL;
import java.util.Arrays;
/**
* Commit detail inside a {@link GHPullRequest}.
@@ -144,6 +143,8 @@ public class GHPullRequestCommitDetail {
}
public CommitPointer[] getParents() {
return Arrays.copyOf(parents, parents.length);
CommitPointer[] newValue = new CommitPointer[parents.length];
System.arraycopy(parents, 0, newValue, 0, parents.length);
return newValue;
}
}

View File

@@ -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<GHPullRequest> {
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<GHPullRequest> list() {
return new PagedIterable<GHPullRequest>() {
public PagedIterator<GHPullRequest> _iterator(int pageSize) {
return new PagedIterator<GHPullRequest>(req.asIterator(repo.getApiTailUrl("pulls"), GHPullRequest[].class, pageSize)) {
@Override
protected void wrapUp(GHPullRequest[] page) {
for (GHPullRequest pr : page)
pr.wrapUp(repo);
}
};
}
};
}
}

View File

@@ -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<T> {
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<T> list();
}

View File

@@ -26,9 +26,9 @@ package org.kohsuke.github;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import javax.xml.bind.DatatypeConverter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -36,8 +36,19 @@ import java.io.InterruptedIOException;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.*;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import static java.util.Arrays.asList;
@@ -76,8 +87,8 @@ public class GHRepository extends GHObject {
public PagedIterable<GHDeploymentStatus> getDeploymentStatuses(final int id) {
return new PagedIterable<GHDeploymentStatus>() {
public PagedIterator<GHDeploymentStatus> iterator() {
return new PagedIterator<GHDeploymentStatus>(root.retrieve().asIterator(getApiTailUrl("deployments")+"/"+id+"/statuses", GHDeploymentStatus[].class)) {
public PagedIterator<GHDeploymentStatus> _iterator(int pageSize) {
return new PagedIterator<GHDeploymentStatus>(root.retrieve().asIterator(getApiTailUrl("deployments")+"/"+id+"/statuses", GHDeploymentStatus[].class, pageSize)) {
@Override
protected void wrapUp(GHDeploymentStatus[] page) {
for (GHDeploymentStatus c : page)
@@ -92,8 +103,8 @@ public class GHRepository extends GHObject {
List<String> 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<GHDeployment>() {
public PagedIterator<GHDeployment> iterator() {
return new PagedIterator<GHDeployment>(root.retrieve().asIterator(deploymentsUrl, GHDeployment[].class)) {
public PagedIterator<GHDeployment> _iterator(int pageSize) {
return new PagedIterator<GHDeployment>(root.retrieve().asIterator(deploymentsUrl, GHDeployment[].class, pageSize)) {
@Override
protected void wrapUp(GHDeployment[] page) {
for (GHDeployment c : page)
@@ -229,11 +240,10 @@ public class GHRepository extends GHObject {
public List<GHIssue> 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,8 +251,8 @@ public class GHRepository extends GHObject {
*/
public PagedIterable<GHIssue> listIssues(final GHIssueState state) {
return new PagedIterable<GHIssue>() {
public PagedIterator<GHIssue> iterator() {
return new PagedIterator<GHIssue>(root.retrieve().asIterator(getApiTailUrl("issues?state="+state.toString().toLowerCase(Locale.ENGLISH)), GHIssue[].class)) {
public PagedIterator<GHIssue> _iterator(int pageSize) {
return new PagedIterator<GHIssue>(root.retrieve().with("state",state).asIterator(getApiTailUrl("issues"), GHIssue[].class, pageSize)) {
@Override
protected void wrapUp(GHIssue[] page) {
for (GHIssue c : page)
@@ -281,8 +291,8 @@ public class GHRepository extends GHObject {
public PagedIterable<GHRelease> listReleases() throws IOException {
return new PagedIterable<GHRelease>() {
public PagedIterator<GHRelease> iterator() {
return new PagedIterator<GHRelease>(root.retrieve().asIterator(getApiTailUrl("releases"), GHRelease[].class)) {
public PagedIterator<GHRelease> _iterator(int pageSize) {
return new PagedIterator<GHRelease>(root.retrieve().asIterator(getApiTailUrl("releases"), GHRelease[].class, pageSize)) {
@Override
protected void wrapUp(GHRelease[] page) {
for (GHRelease c : page)
@@ -295,8 +305,8 @@ public class GHRepository extends GHObject {
public PagedIterable<GHTag> listTags() throws IOException {
return new PagedIterable<GHTag>() {
public PagedIterator<GHTag> iterator() {
return new PagedIterator<GHTag>(root.retrieve().asIterator(getApiTailUrl("tags"), GHTag[].class)) {
public PagedIterator<GHTag> _iterator(int pageSize) {
return new PagedIterator<GHTag>(root.retrieve().asIterator(getApiTailUrl("tags"), GHTag[].class, pageSize)) {
@Override
protected void wrapUp(GHTag[] page) {
for (GHTag c : page)
@@ -416,9 +426,9 @@ public class GHRepository extends GHObject {
*/
public PagedIterable<GHUser> listCollaborators() throws IOException {
return new PagedIterable<GHUser>() {
public PagedIterator<GHUser> iterator() {
public PagedIterator<GHUser> _iterator(int pageSize) {
return new PagedIterator<GHUser>(root.retrieve().asIterator(getApiTailUrl("collaborators"), GHUser[].class)) {
return new PagedIterator<GHUser>(root.retrieve().asIterator(getApiTailUrl("collaborators"), GHUser[].class, pageSize)) {
@Override
protected void wrapUp(GHUser[] users) {
@@ -477,7 +487,7 @@ public class GHRepository extends GHObject {
public void setEmailServiceHook(String address) throws IOException {
Map<String, String> config = new HashMap<String, String>();
config.put("address", address);
new Requester(root).method("POST").with("name", "email").with("config", config).with("active", "true")
new Requester(root).method("POST").with("name", "email").with("config", config).with("active", true)
.to(getApiTailUrl("hooks"));
}
@@ -539,7 +549,7 @@ public class GHRepository extends GHObject {
/**
* 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,12 +566,8 @@ public class GHRepository extends GHObject {
*/
public PagedIterable<GHRepository> listForks(final ForkSort sort) {
return new PagedIterable<GHRepository>() {
public PagedIterator<GHRepository> iterator() {
String sortParam = "";
if (sort != null) {
sortParam = "?sort=" + sort.toString().toLowerCase(Locale.ENGLISH);
}
return new PagedIterator<GHRepository>(root.retrieve().asIterator(getApiTailUrl("forks" + sortParam), GHRepository[].class)) {
public PagedIterator<GHRepository> _iterator(int pageSize) {
return new PagedIterator<GHRepository>(root.retrieve().with("sort",sort).asIterator(getApiTailUrl("forks"), GHRepository[].class, pageSize)) {
@Override
protected void wrapUp(GHRepository[] page) {
for (GHRepository c : page) {
@@ -618,24 +624,24 @@ public class GHRepository extends GHObject {
* @see #listPullRequests(GHIssueState)
*/
public List<GHPullRequest> 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<GHPullRequest> listPullRequests(final GHIssueState state) {
return new PagedIterable<GHPullRequest>() {
public PagedIterator<GHPullRequest> iterator() {
return new PagedIterator<GHPullRequest>(root.retrieve().asIterator(getApiTailUrl("pulls?state="+state.name().toLowerCase(Locale.ENGLISH)), GHPullRequest[].class)) {
@Override
protected void wrapUp(GHPullRequest[] page) {
for (GHPullRequest pr : page)
pr.wrapUp(GHRepository.this);
}
};
}
};
public PagedIterable<GHPullRequest> listPullRequests(GHIssueState state) {
return queryPullRequests().state(state).list();
}
/**
* Retrieves pull requests.
*/
public GHPullRequestQueryBuilder queryPullRequests() {
return new GHPullRequestQueryBuilder(this);
}
/**
@@ -715,7 +721,7 @@ public class GHRepository extends GHObject {
* @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);
}
/**
@@ -786,8 +792,8 @@ public class GHRepository extends GHObject {
*/
public PagedIterable<GHCommit> listCommits() {
return new PagedIterable<GHCommit>() {
public PagedIterator<GHCommit> iterator() {
return new PagedIterator<GHCommit>(root.retrieve().asIterator(String.format("/repos/%s/%s/commits", owner.login, name), GHCommit[].class)) {
public PagedIterator<GHCommit> _iterator(int pageSize) {
return new PagedIterator<GHCommit>(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 +815,8 @@ public class GHRepository extends GHObject {
*/
public PagedIterable<GHCommitComment> listCommitComments() {
return new PagedIterable<GHCommitComment>() {
public PagedIterator<GHCommitComment> iterator() {
return new PagedIterator<GHCommitComment>(root.retrieve().asIterator(String.format("/repos/%s/%s/comments", owner.login, name), GHCommitComment[].class)) {
public PagedIterator<GHCommitComment> _iterator(int pageSize) {
return new PagedIterator<GHCommitComment>(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 +832,8 @@ public class GHRepository extends GHObject {
*/
public PagedIterable<GHCommitStatus> listCommitStatuses(final String sha1) throws IOException {
return new PagedIterable<GHCommitStatus>() {
public PagedIterator<GHCommitStatus> iterator() {
return new PagedIterator<GHCommitStatus>(root.retrieve().asIterator(String.format("/repos/%s/%s/statuses/%s", owner.login, name, sha1), GHCommitStatus[].class)) {
public PagedIterator<GHCommitStatus> _iterator(int pageSize) {
return new PagedIterator<GHCommitStatus>(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)
@@ -858,7 +864,7 @@ public class GHRepository extends GHObject {
*/
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)
@@ -869,7 +875,7 @@ public class GHRepository extends GHObject {
* @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);
}
/**
@@ -877,8 +883,8 @@ public class GHRepository extends GHObject {
*/
public PagedIterable<GHEventInfo> listEvents() throws IOException {
return new PagedIterable<GHEventInfo>() {
public PagedIterator<GHEventInfo> iterator() {
return new PagedIterator<GHEventInfo>(root.retrieve().asIterator(String.format("/repos/%s/%s/events", owner.login, name), GHEventInfo[].class)) {
public PagedIterator<GHEventInfo> _iterator(int pageSize) {
return new PagedIterator<GHEventInfo>(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 +902,8 @@ public class GHRepository extends GHObject {
*/
public PagedIterable<GHLabel> listLabels() throws IOException {
return new PagedIterable<GHLabel>() {
public PagedIterator<GHLabel> iterator() {
return new PagedIterator<GHLabel>(root.retrieve().asIterator(getApiTailUrl("labels"), GHLabel[].class)) {
public PagedIterator<GHLabel> _iterator(int pageSize) {
return new PagedIterator<GHLabel>(root.retrieve().asIterator(getApiTailUrl("labels"), GHLabel[].class, pageSize)) {
@Override
protected void wrapUp(GHLabel[] page) {
for (GHLabel c : page)
@@ -915,7 +921,7 @@ public class GHRepository extends GHObject {
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);
}
@@ -925,9 +931,20 @@ public class GHRepository extends GHObject {
* https://developer.github.com/v3/activity/watching/
*/
public PagedIterable<GHUser> listSubscribers() {
return listUsers("subscribers");
}
/**
* Lists all the users who have starred this repo.
*/
public PagedIterable<GHUser> listStargazers() {
return listUsers("stargazers");
}
private PagedIterable<GHUser> listUsers(final String suffix) {
return new PagedIterable<GHUser>() {
public PagedIterator<GHUser> iterator() {
return new PagedIterator<GHUser>(root.retrieve().asIterator(getApiTailUrl("subscribers"), GHUser[].class)) {
public PagedIterator<GHUser> _iterator(int pageSize) {
return new PagedIterator<GHUser>(root.retrieve().asIterator(getApiTailUrl(suffix), GHUser[].class, pageSize)) {
protected void wrapUp(GHUser[] page) {
for (GHUser c : page)
c.wrapUp(root);
@@ -1078,8 +1095,8 @@ public class GHRepository extends GHObject {
*/
public PagedIterable<GHMilestone> listMilestones(final GHIssueState state) {
return new PagedIterable<GHMilestone>() {
public PagedIterator<GHMilestone> iterator() {
return new PagedIterator<GHMilestone>(root.retrieve().asIterator(getApiTailUrl("milestones?state="+state.toString().toLowerCase(Locale.ENGLISH)), GHMilestone[].class)) {
public PagedIterator<GHMilestone> _iterator(int pageSize) {
return new PagedIterator<GHMilestone>(root.retrieve().with("state",state).asIterator(getApiTailUrl("milestones"), GHMilestone[].class, pageSize)) {
@Override
protected void wrapUp(GHMilestone[] page) {
for (GHMilestone c : page)
@@ -1147,7 +1164,7 @@ public class GHRepository extends GHObject {
try {
payload = content.getBytes("UTF-8");
} catch (UnsupportedEncodingException ex) {
throw new IOException("UTF-8 encoding is not supported", ex);
throw (IOException) new IOException("UTF-8 encoding is not supported").initCause(ex);
}
return createContent(payload, commitMessage, path, branch);
}
@@ -1160,7 +1177,7 @@ public class GHRepository extends GHObject {
Requester requester = new Requester(root)
.with("path", path)
.with("message", commitMessage)
.with("content", DatatypeConverter.printBase64Binary(contentBytes))
.with("content", Base64.encodeBase64String(contentBytes))
.method("PUT");
if (branch != null) {
@@ -1251,8 +1268,8 @@ public class GHRepository extends GHObject {
public PagedIterable<Contributor> listContributors() throws IOException {
return new PagedIterable<Contributor>() {
public PagedIterator<Contributor> iterator() {
return new PagedIterator<Contributor>(root.retrieve().asIterator(getApiTailUrl("contributors"), Contributor[].class)) {
public PagedIterator<Contributor> _iterator(int pageSize) {
return new PagedIterator<Contributor>(root.retrieve().asIterator(getApiTailUrl("contributors"), Contributor[].class, pageSize)) {
@Override
protected void wrapUp(Contributor[] page) {
for (Contributor c : page)

View File

@@ -58,7 +58,7 @@ public class GHRepositorySearchBuilder extends GHSearchBuilder<GHRepository> {
}
public GHRepositorySearchBuilder sort(Sort sort) {
req.with("sort",sort.toString().toLowerCase(Locale.ENGLISH));
req.with("sort",sort);
return this;
}

View File

@@ -10,9 +10,7 @@ import java.util.List;
*
* @author Kohsuke Kawaguchi
*/
public abstract class GHSearchBuilder<T> {
protected final GitHub root;
protected final Requester req;
public abstract class GHSearchBuilder<T> extends GHQueryBuilder<T> {
protected final List<String> terms = new ArrayList<String>();
/**
@@ -21,15 +19,14 @@ public abstract class GHSearchBuilder<T> {
private final Class<? extends SearchResult<T>> receiverType;
/*package*/ GHSearchBuilder(GitHub root, Class<? extends SearchResult<T>> receiverType) {
this.root = root;
this.req = root.retrieve();
super(root);
this.receiverType = receiverType;
}
/**
* Search terms.
*/
public GHSearchBuilder q(String term) {
public GHQueryBuilder<T> q(String term) {
terms.add(term);
return this;
}
@@ -37,11 +34,12 @@ public abstract class GHSearchBuilder<T> {
/**
* Performs the search.
*/
@Override
public PagedSearchIterable<T> list() {
return new PagedSearchIterable<T>(root) {
public PagedIterator<T> iterator() {
public PagedIterator<T> _iterator(int pageSize) {
req.set("q", StringUtils.join(terms, " "));
return new PagedIterator<T>(adapt(req.asIterator(getApiUrl(), receiverType))) {
return new PagedIterator<T>(adapt(req.asIterator(getApiUrl(), receiverType, pageSize))) {
protected void wrapUp(T[] page) {
// SearchResult.getItems() should do it
}

View File

@@ -52,8 +52,8 @@ public class GHTeam {
*/
public PagedIterable<GHUser> listMembers() throws IOException {
return new PagedIterable<GHUser>() {
public PagedIterator<GHUser> iterator() {
return new PagedIterator<GHUser>(org.root.retrieve().asIterator(api("/members"), GHUser[].class)) {
public PagedIterator<GHUser> _iterator(int pageSize) {
return new PagedIterator<GHUser>(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 class GHTeam {
public PagedIterable<GHRepository> listRepositories() {
return new PagedIterable<GHRepository>() {
public PagedIterator<GHRepository> iterator() {
return new PagedIterator<GHRepository>(org.root.retrieve().asIterator(api("/repos"), GHRepository[].class)) {
public PagedIterator<GHRepository> _iterator(int pageSize) {
return new PagedIterator<GHRepository>(org.root.retrieve().asIterator(api("/repos"), GHRepository[].class, pageSize)) {
@Override
protected void wrapUp(GHRepository[] page) {
for (GHRepository r : page)

View File

@@ -26,7 +26,6 @@ package org.kohsuke.github;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@@ -56,8 +55,14 @@ public class GHUser extends GHPerson {
*/
@WithBridgeMethods(Set.class)
public GHPersonSet<GHUser> getFollows() throws IOException {
GHUser[] followers = root.retrieve().to("/users/" + login + "/following", GHUser[].class);
return new GHPersonSet<GHUser>(Arrays.asList(wrap(followers,root)));
return new GHPersonSet<GHUser>(listFollows().asList());
}
/**
* Lists the users that this user is following
*/
public PagedIterable<GHUser> listFollows() {
return listUser("following");
}
/**
@@ -65,8 +70,26 @@ public class GHUser extends GHPerson {
*/
@WithBridgeMethods(Set.class)
public GHPersonSet<GHUser> getFollowers() throws IOException {
GHUser[] followers = root.retrieve().to("/users/" + login + "/followers", GHUser[].class);
return new GHPersonSet<GHUser>(Arrays.asList(wrap(followers,root)));
return new GHPersonSet<GHUser>(listFollowers().asList());
}
/**
* Lists the users who are following this user.
*/
public PagedIterable<GHUser> listFollowers() {
return listUser("followers");
}
private PagedIterable<GHUser> listUser(final String suffix) {
return new PagedIterable<GHUser>() {
public PagedIterator<GHUser> _iterator(int pageSize) {
return new PagedIterator<GHUser>(root.retrieve().asIterator(getApiTailUrl(suffix), GHUser[].class, pageSize)) {
protected void wrapUp(GHUser[] page) {
GHUser.wrap(page,root);
}
};
}
};
}
/**
@@ -75,9 +98,20 @@ public class GHUser extends GHPerson {
* https://developer.github.com/v3/activity/watching/
*/
public PagedIterable<GHRepository> listSubscriptions() {
return listRepositories("subscriptions");
}
/**
* Lists all the repositories that this user has starred.
*/
public PagedIterable<GHRepository> listStarredRepositories() {
return listRepositories("starred");
}
private PagedIterable<GHRepository> listRepositories(final String suffix) {
return new PagedIterable<GHRepository>() {
public PagedIterator<GHRepository> iterator() {
return new PagedIterator<GHRepository>(root.retrieve().asIterator(getApiTailUrl("subscriptions"), GHRepository[].class)) {
public PagedIterator<GHRepository> _iterator(int pageSize) {
return new PagedIterator<GHRepository>(root.retrieve().asIterator(getApiTailUrl(suffix), GHRepository[].class, pageSize)) {
protected void wrapUp(GHRepository[] page) {
for (GHRepository c : page)
c.wrap(root);
@@ -133,8 +167,8 @@ public class GHUser extends GHPerson {
*/
public PagedIterable<GHEventInfo> listEvents() throws IOException {
return new PagedIterable<GHEventInfo>() {
public PagedIterator<GHEventInfo> iterator() {
return new PagedIterator<GHEventInfo>(root.retrieve().asIterator(String.format("/users/%s/events", login), GHEventInfo[].class)) {
public PagedIterator<GHEventInfo> _iterator(int pageSize) {
return new PagedIterator<GHEventInfo>(root.retrieve().asIterator(String.format("/users/%s/events", login), GHEventInfo[].class, pageSize)) {
@Override
protected void wrapUp(GHEventInfo[] page) {
for (GHEventInfo c : page)
@@ -150,8 +184,8 @@ public class GHUser extends GHPerson {
*/
public PagedIterable<GHGist> listGists() throws IOException {
return new PagedIterable<GHGist>() {
public PagedIterator<GHGist> iterator() {
return new PagedIterator<GHGist>(root.retrieve().asIterator(String.format("/users/%s/gists", login), GHGist[].class)) {
public PagedIterator<GHGist> _iterator(int pageSize) {
return new PagedIterator<GHGist>(root.retrieve().asIterator(String.format("/users/%s/gists", login), GHGist[].class, pageSize)) {
@Override
protected void wrapUp(GHGist[] page) {
for (GHGist c : page)

View File

@@ -50,7 +50,7 @@ public class GHUserSearchBuilder extends GHSearchBuilder<GHUser> {
}
public GHUserSearchBuilder sort(Sort sort) {
req.with("sort",sort.toString().toLowerCase(Locale.ENGLISH));
req.with("sort",sort);
return this;
}

View File

@@ -25,12 +25,15 @@ package org.kohsuke.github;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE;
import static java.util.logging.Level.FINE;
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
@@ -45,7 +48,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import org.apache.commons.codec.Charsets;
import org.apache.commons.codec.binary.Base64;
@@ -54,7 +56,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.VisibilityChecker.Std;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import java.nio.charset.Charset;
import java.util.logging.Logger;
/**
* Root of the GitHub API.
@@ -130,8 +132,8 @@ public class GitHub {
} else {
if (password!=null) {
String authorization = (login + ':' + password);
Charset charset = Charsets.UTF_8;
encodedAuthorization = "Basic "+new String(Base64.encodeBase64(authorization.getBytes(charset)), charset);
String charsetName = Charsets.UTF_8.name();
encodedAuthorization = "Basic "+new String(Base64.encodeBase64(authorization.getBytes(charsetName)), charsetName);
} else {// anonymous access
encodedAuthorization = null;
}
@@ -261,7 +263,8 @@ public class GitHub {
// see issue #78
GHRateLimit r = new GHRateLimit();
r.limit = r.remaining = 1000000;
r.reset = new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1));
long hours = 1000L * 60 * 60;
r.reset = new Date(System.currentTimeMillis() + 1 * hours );
return r;
}
}
@@ -410,17 +413,28 @@ public class GitHub {
/**
* Creates a new repository.
*
* To create a repository in an organization, see
* {@link GHOrganization#createRepository(String, String, String, GHTeam, boolean)}
*
* @return
* Newly created repository.
* @deprecated
* Use {@link #createRepository(String)} that uses a builder pattern to let you control every aspect.
*/
public GHRepository createRepository(String name, String description, String homepage, boolean isPublic) throws IOException {
Requester requester = new Requester(this)
.with("name", name).with("description", description).with("homepage", homepage)
.with("public", isPublic ? 1 : 0);
return requester.method("POST").to("/user/repos", GHRepository.class).wrap(this);
return createRepository(name).description(description).homepage(homepage).private_(!isPublic).create();
}
/**
* Starts a builder that creates a new repository.
*
* <p>
* You use the returned builder to set various properties, then call {@link GHCreateRepositoryBuilder#create()}
* to finally createa repository.
*
* <p>
* To create a repository in an organization, see
* {@link GHOrganization#createRepository(String, String, String, GHTeam, boolean)}
*/
public GHCreateRepositoryBuilder createRepository(String name) {
return new GHCreateRepositoryBuilder(this,"/user/repos",name);
}
/**
@@ -447,6 +461,8 @@ public class GitHub {
retrieve().to("/user", GHUser.class);
return true;
} catch (IOException e) {
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, "Exception validating credentials on " + this.apiUrl + " with login '" + this.login + "' " + e, e);
return false;
}
}
@@ -464,14 +480,59 @@ public class GitHub {
}
/**
* Ensures that the API URL is valid.
* Tests the connection.
*
* <p>
* Verify that the API URL and credentials are valid to access this GitHub.
*
* <p>
* This method returns normally if the endpoint is reachable and verified to be GitHub API URL.
* Otherwise this method throws {@link IOException} to indicate the problem.
*/
public void checkApiUrlValidity() throws IOException {
retrieve().to("/", GHApiInfo.class).check(apiUrl);
try {
retrieve().to("/", GHApiInfo.class).check(apiUrl);
} catch (IOException e) {
if (isPrivateModeEnabled()) {
throw (IOException)new IOException("GitHub Enterprise server (" + apiUrl + ") with private mode enabled").initCause(e);
}
throw e;
}
}
/**
* Ensures if a GitHub Enterprise server is configured in private mode.
*
* @return {@code true} if private mode is enabled. If it tries to use this method with GitHub, returns {@code
* false}.
*/
private boolean isPrivateModeEnabled() {
try {
HttpURLConnection uc = getConnector().connect(getApiURL("/"));
/*
$ curl -i https://github.mycompany.com/api/v3/
HTTP/1.1 401 Unauthorized
Server: GitHub.com
Date: Sat, 05 Mar 2016 19:45:01 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 130
Status: 401 Unauthorized
X-GitHub-Media-Type: github.v3
X-XSS-Protection: 1; mode=block
X-Frame-Options: deny
Content-Security-Policy: default-src 'none'
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
Access-Control-Allow-Origin: *
X-GitHub-Request-Id: dbc70361-b11d-4131-9a7f-674b8edd0411
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
X-Content-Type-Options: nosniff
*/
return uc.getResponseCode() == HTTP_UNAUTHORIZED
&& uc.getHeaderField("X-GitHub-Media-Type") != null;
} catch (IOException e) {
return false;
}
}
/**
@@ -526,8 +587,8 @@ public class GitHub {
*/
public PagedIterable<GHRepository> listAllPublicRepositories(final String since) {
return new PagedIterable<GHRepository>() {
public PagedIterator<GHRepository> iterator() {
return new PagedIterator<GHRepository>(retrieve().with("since",since).asIterator("/repositories", GHRepository[].class)) {
public PagedIterator<GHRepository> _iterator(int pageSize) {
return new PagedIterator<GHRepository>(retrieve().with("since",since).asIterator("/repositories", GHRepository[].class, pageSize)) {
@Override
protected void wrapUp(GHRepository[] page) {
for (GHRepository c : page)
@@ -593,4 +654,6 @@ public class GitHub {
}
/* package */ static final String GITHUB_URL = "https://api.github.com";
private static final Logger LOGGER = Logger.getLogger(GitHub.class.getName());
}

View File

@@ -15,7 +15,7 @@ import java.util.Map.Entry;
import java.util.Properties;
/**
*
* Configures connection details and produces {@link GitHub}.
*
* @since 1.59
*/

View File

@@ -0,0 +1,118 @@
package org.kohsuke.github;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.annotation.CheckForNull;
/**
* {@link IOException} for http exceptions because {@link HttpURLConnection} throws un-discerned
* {@link IOException} and it can help to know the http response code to decide how to handle an
* http exceptions.
*
* @author <a href="mailto:cleclerc@cloudbees.com">Cyrille Le Clerc</a>
*/
public class HttpException extends IOException {
static final long serialVersionUID = 1L;
private final int responseCode;
private final String responseMessage;
private final String url;
/**
* @param message The detail message (which is saved for later retrieval
* by the {@link #getMessage()} method)
* @param responseCode Http response code. {@code -1} if no code can be discerned.
* @param responseMessage Http response message
* @param url The url that was invoked
* @see HttpURLConnection#getResponseCode()
* @see HttpURLConnection#getResponseMessage()
*/
public HttpException(String message, int responseCode, String responseMessage, String url) {
super(message);
this.responseCode = responseCode;
this.responseMessage = responseMessage;
this.url = url;
}
/**
* @param message The detail message (which is saved for later retrieval
* by the {@link #getMessage()} method)
* @param responseCode Http response code. {@code -1} if no code can be discerned.
* @param responseMessage Http response message
* @param url The url that was invoked
* @param cause The cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A null value is permitted,
* and indicates that the cause is nonexistent or unknown.)
* @see HttpURLConnection#getResponseCode()
* @see HttpURLConnection#getResponseMessage()
*/
public HttpException(String message, int responseCode, String responseMessage, String url, Throwable cause) {
super(message);
initCause(cause);
this.responseCode = responseCode;
this.responseMessage = responseMessage;
this.url = url;
}
/**
* @param responseCode Http response code. {@code -1} if no code can be discerned.
* @param responseMessage Http response message
* @param url The url that was invoked
* @param cause The cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A null value is permitted,
* and indicates that the cause is nonexistent or unknown.)
* @see HttpURLConnection#getResponseCode()
* @see HttpURLConnection#getResponseMessage()
*/
public HttpException(int responseCode, String responseMessage, String url, Throwable cause) {
super("Server returned HTTP response code: " + responseCode + ", message: '" + responseMessage + "'" +
" for URL: " + url);
initCause(cause);
this.responseCode = responseCode;
this.responseMessage = responseMessage;
this.url = url;
}
/**
* @param responseCode Http response code. {@code -1} if no code can be discerned.
* @param responseMessage Http response message
* @param url The url that was invoked
* @param cause The cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A null value is permitted,
* and indicates that the cause is nonexistent or unknown.)
* @see HttpURLConnection#getResponseCode()
* @see HttpURLConnection#getResponseMessage()
*/
public HttpException(int responseCode, String responseMessage, @CheckForNull URL url, Throwable cause) {
this(responseCode, responseMessage, url == null ? null : url.toString(), cause);
}
/**
* Http response code of the request that cause the exception
*
* @return {@code -1} if no code can be discerned.
*/
public int getResponseCode() {
return responseCode;
}
/**
* Http response message of the request that cause the exception
*
* @return {@code null} if no response message can be discerned.
*/
public String getResponseMessage() {
return responseMessage;
}
/**
* The http URL that caused the exception
*
* @return url
*/
public String getUrl() {
return url;
}
}

View File

@@ -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,27 @@ import java.util.Set;
* @author Kohsuke Kawaguchi
*/
public abstract class PagedIterable<T> implements Iterable<T> {
public abstract PagedIterator<T> iterator();
/**
* Page size. 0 is default.
*/
private int size = 0;
/**
* Sets the pagination size.
*
* <p>
* When set to non-zero, each API call will retrieve this many entries.
*/
public PagedIterable<T> withPageSize(int size) {
this.size = size;
return this;
}
public final PagedIterator<T> iterator() {
return _iterator(size);
}
public abstract PagedIterator<T> _iterator(int pageSize);
/**
* Eagerly walk {@link Iterable} and return the result in a list.

View File

@@ -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<T> extends PagedIterable<T> {
this.root = root;
}
@Override
public PagedSearchIterable<T> withPageSize(int size) {
return (PagedSearchIterable<T>)super.withPageSize(size);
}
/**
* Returns the total number of hit, including the results that's not yet fetched.
*/

View File

@@ -24,6 +24,7 @@
package org.kohsuke.github;
import com.fasterxml.jackson.databind.JsonMappingException;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.apache.commons.io.IOUtils;
import java.io.FileNotFoundException;
@@ -42,13 +43,14 @@ import java.net.URLEncoder;
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.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
@@ -56,6 +58,7 @@ import java.util.zip.GZIPInputStream;
import javax.annotation.WillClose;
import static java.util.Arrays.asList;
import static java.util.logging.Level.FINE;
import static org.kohsuke.github.GitHub.*;
/**
@@ -64,8 +67,6 @@ import static org.kohsuke.github.GitHub.*;
* @author Kohsuke Kawaguchi
*/
class Requester {
private static final List<String> METHODS_WITHOUT_BODY = asList("GET", "DELETE");
private final GitHub root;
private final List<Entry> args = new ArrayList<Entry>();
private final Map<String,String> headers = new LinkedHashMap<String, String>();
@@ -132,6 +133,14 @@ class Requester {
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);
@@ -208,19 +217,24 @@ class Requester {
*/
@Deprecated
public <T> T to(String tailApiUrl, Class<T> type, String method) throws IOException {
return method(method).to(tailApiUrl,type);
return method(method).to(tailApiUrl, type);
}
@SuppressFBWarnings("SBSC_USE_STRINGBUFFER_CONCATENATION")
private <T> T _to(String tailApiUrl, Class<T> type, T instance) throws IOException {
while (true) {// loop while API rate limit is hit
if (METHODS_WITHOUT_BODY.contains(method) && !args.isEmpty()) {
StringBuilder qs=new StringBuilder();
for (Entry arg : args) {
qs.append(qs.length()==0 ? '?' : '&');
qs.append(arg.key).append('=').append(URLEncoder.encode(arg.value.toString(),"UTF-8"));
if (METHODS_WITHOUT_BODY.contains(method) && !args.isEmpty()) {
boolean questionMarkFound = tailApiUrl.indexOf('?') != -1;
tailApiUrl += questionMarkFound ? '&' : '?';
for (Iterator<Entry> it = args.listIterator(); it.hasNext();) {
Entry arg = it.next();
tailApiUrl += arg.key + '=' + URLEncoder.encode(arg.value.toString(),"UTF-8");
if (it.hasNext()) {
tailApiUrl += '&';
}
tailApiUrl += qs.toString();
}
}
while (true) {// loop while API rate limit is hit
setupConnection(root.getApiURL(tailApiUrl));
buildRequest();
@@ -257,10 +271,9 @@ class Requester {
*/
public int asHttpStatusCode(String tailApiUrl) throws IOException {
while (true) {// loop while API rate limit is hit
method("GET");
setupConnection(root.getApiURL(tailApiUrl));
uc.setRequestMethod("GET");
buildRequest();
try {
@@ -275,10 +288,7 @@ class Requester {
while (true) {// loop while API rate limit is hit
setupConnection(root.getApiURL(tailApiUrl));
// if the download link is encoded with a token on the query string, the default behavior of POST will fail
uc.setRequestMethod("GET");
buildRequest();
buildRequest();
try {
return wrapStream(uc.getInputStream());
@@ -330,104 +340,112 @@ class Requester {
*
* Every iterator call reports a new batch.
*/
/*package*/ <T> Iterator<T> asIterator(final String _tailApiUrl, final Class<T> type) {
/*package*/ <T> Iterator<T> asIterator(String tailApiUrl, Class<T> type, int pageSize) {
method("GET");
final StringBuilder strBuilder = new StringBuilder(_tailApiUrl);
if (pageSize!=0)
args.add(new Entry("per_page",pageSize));
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<T>(type, root.getApiURL(s.toString()));
} catch (IOException e) {
throw new Error(e);
}
}
return new Iterator<T>() {
/**
* 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<T> implements Iterator<T> {
{
try {
url = root.getApiURL(tailApiUrl);
} catch (IOException e) {
throw new Error(e);
}
}
private final Class<T> 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<T> 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;
public void remove() {
throw new UnsupportedOperationException();
}
for (String token : link.split(", ")) {
if (token.endsWith("rel=\"next\"")) {
// found the next page. This should look something like
// <https://api.github.com/repos?page=3&per_page=100>; rel="next"
int idx = token.indexOf('>');
url = new URL(token.substring(1,idx));
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);
}
}
// no more "next" link. we are done.
} catch (IOException e) {
throw new Error(e);
}
};
}
/**
* 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
// <https://api.github.com/repos?page=3&per_page=100>; rel="next"
int idx = token.indexOf('>');
url = new URL(token.substring(1,idx));
return;
}
}
// no more "next" link. we are done.
}
}
@@ -461,21 +479,33 @@ class Requester {
}
private <T> T parse(Class<T> type, T instance) throws IOException {
if (uc.getResponseCode()==304)
return null; // special case handling for 304 unmodified, as the content will be ""
InputStreamReader r = null;
int responseCode = -1;
String responseMessage = null;
try {
responseCode = uc.getResponseCode();
responseMessage = uc.getResponseMessage();
if (responseCode == 304) {
return null; // special case handling for 304 unmodified, as the content will be ""
}
r = new InputStreamReader(wrapStream(uc.getInputStream()), "UTF-8");
String data = IOUtils.toString(r);
if (type!=null)
try {
return MAPPER.readValue(data,type);
} catch (JsonMappingException e) {
throw (IOException)new IOException("Failed to deserialize "+data).initCause(e);
throw (IOException)new IOException("Failed to deserialize " +data).initCause(e);
}
if (instance!=null)
return MAPPER.readerForUpdating(instance).<T>readValue(data);
return null;
} catch (FileNotFoundException e) {
// java.net.URLConnection handles 404 exception has FileNotFoundException, don't wrap exception in HttpException
// to preserve backward compatibility
throw e;
} catch (IOException e) {
throw new HttpException(responseCode, responseMessage, uc.getURL(), e);
} finally {
IOUtils.closeQuietly(r);
}
@@ -496,11 +526,23 @@ class Requester {
* Handle API error by either throwing it or by returning normally to retry.
*/
/*package*/ void handleApiError(IOException e) throws IOException {
if (uc.getResponseCode() == 401) // Unauthorized == bad creds
int responseCode;
try {
responseCode = uc.getResponseCode();
} catch (IOException e2) {
// likely to be a network exception (e.g. SSLHandshakeException),
// uc.getResponseCode() and any other getter on the response will cause an exception
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, "Silently ignore exception retrieving response code for '" + uc.getURL() + "'" +
" handling exception " + e, e);
throw e;
}
if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) // 401 / Unauthorized == bad creds
throw e;
if ("0".equals(uc.getHeaderField("X-RateLimit-Remaining"))) {
root.rateLimitHandler.onError(e,uc);
return;
}
InputStream es = wrapStream(uc.getErrorStream());
@@ -517,4 +559,7 @@ class Requester {
IOUtils.closeQuietly(es);
}
}
private static final List<String> METHODS_WITHOUT_BODY = asList("GET", "DELETE");
private static final Logger LOGGER = Logger.getLogger(Requester.class.getName());
}

View File

@@ -7,7 +7,6 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
*
* @author Kohsuke Kawaguchi
*/
abstract class SearchResult<T> {
@SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization")
int total_count;

View File

@@ -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 class Foo {
}
System.out.println(lst.size());
}
private static void testRateLimit() throws Exception {
GitHub g = GitHub.connectAnonymously();
for (GHUser u : g.getOrganization("jenkinsci").listMembers()) {
u.getFollowersCount();
}
}
}

View File

@@ -38,6 +38,21 @@ public class AppTest extends AbstractGitHubApiTestBase {
getUser().getRepository(targetName).delete();
}
@Test
public void testRepositoryWithAutoInitializationCRUD() throws IOException {
String name = "github-api-test-autoinit";
deleteRepository(name);
GHRepository r = gitHub.createRepository(name)
.description("a test repository for auto init")
.homepage("http://github-api.kohsuke.org/")
.autoInit(true).create();
r.enableIssueTracker(false);
r.enableDownloads(false);
r.enableWiki(false);
assertNotNull(r.getReadme());
getUser().getRepository(name).delete();
}
private void deleteRepository(final String name) throws IOException {
GHRepository repository = getUser().getRepository(name);
if(repository != null) {
@@ -326,6 +341,8 @@ public class AppTest extends AbstractGitHubApiTestBase {
System.out.println(commit);
assertEquals(1, commit.getParents().size());
assertEquals(1,commit.getFiles().size());
assertEquals("https://github.com/jenkinsci/jenkins/commit/08c1c9970af4d609ae754fbe803e06186e3206f7",
commit.getHtmlUrl().toString());
File f = commit.getFiles().get(0);
assertEquals(48,f.getLinesChanged());
@@ -774,7 +791,7 @@ public class AppTest extends AbstractGitHubApiTestBase {
assertTrue(actual.contains("href=\"https://github.com/kohsuke\""));
assertTrue(actual.contains("href=\"https://github.com/kohsuke/github-api/pull/1\""));
assertTrue(actual.contains("class=\"user-mention\""));
assertTrue(actual.contains("class=\"issue-link\""));
assertTrue(actual.contains("class=\"issue-link "));
assertTrue(actual.contains("to fix issue"));
}

View File

@@ -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 class CommitTest extends AbstractGitHubApiTestBase {
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<GHCommit> 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());
}
}
}

View File

@@ -66,7 +66,8 @@ public class GHContentIntegrationTest extends AbstractGitHubApiTestBase {
assertNotNull(updatedContentResponse.getCommit());
assertNotNull(updatedContentResponse.getContent());
assertEquals("this is some new content\n", updatedContent.getContent());
// due to what appears to be a cache propagation delay, this test is too flaky
// assertEquals("this is some new content\n", updatedContent.getContent());
GHContentUpdateResponse deleteResponse = updatedContent.delete("Enough of this foolishness!");

View File

@@ -0,0 +1,43 @@
package org.kohsuke.github;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
public class GHOrganizationTest extends AbstractGitHubApiTestBase {
public static final String GITHUB_API_TEST = "github-api-test";
private GHOrganization org;
@Override
public void setUp() throws Exception {
super.setUp();
org = gitHub.getOrganization("github-api-test-org");
}
@Test
public void testCreateRepository() throws IOException {
GHRepository repository = org.createRepository(GITHUB_API_TEST,
"a test repository used to test kohsuke's github-api", "http://github-api.kohsuke.org/", "Core Developers", true);
Assert.assertNotNull(repository);
}
@Test
public void testCreateRepositoryWithAutoInitialization() throws IOException {
GHRepository repository = org.createRepository(GITHUB_API_TEST)
.description("a test repository used to test kohsuke's github-api")
.homepage("http://github-api.kohsuke.org/")
.team(org.getTeamByName("Core Developers"))
.autoInit(true).create();
Assert.assertNotNull(repository);
Assert.assertNotNull(repository.getReadme());
}
@After
public void cleanUp() throws Exception {
GHRepository repository = org.getRepository(GITHUB_API_TEST);
repository.delete();
}
}

View File

@@ -124,6 +124,11 @@ public class GitHubTest {
@Test
public void testGitHubIsApiUrlValid() throws IOException {
GitHub github = GitHub.connectAnonymously();
github.checkApiUrlValidity();
//GitHub github = GitHub.connectToEnterpriseAnonymously("https://github.mycompany.com/api/v3/");
try {
github.checkApiUrlValidity();
} catch (IOException ioe) {
assertTrue(ioe.getMessage().contains("private mode enabled"));
}
}
}

View File

@@ -48,7 +48,7 @@ public class RepositoryMockTest {
when(requester.asIterator("/repos/*/*/collaborators",
GHUser[].class)).thenReturn(iterator, iterator);
GHUser[].class, 0)).thenReturn(iterator, iterator);
PagedIterable<GHUser> pagedIterable = Mockito.mock(PagedIterable.class);

View File

@@ -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<GHUser> count50(PagedIterable<GHUser> l) {
Set<GHUser> users = new HashSet<GHUser>();
PagedIterator<GHUser> itr = l.iterator();
for (int i=0; i<50 && itr.hasNext(); i++) {
users.add(itr.next());
}
assertEquals(50, users.size());
return users;
}
}