diff --git a/src/main/java/org/kohsuke/github/AbuseLimitHandler.java b/src/main/java/org/kohsuke/github/AbuseLimitHandler.java
new file mode 100644
index 0000000000..4cb6bfe6c5
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/AbuseLimitHandler.java
@@ -0,0 +1,63 @@
+package org.kohsuke.github;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.HttpURLConnection;
+
+/**
+ * Pluggable strategy to determine what to do when the API abuse limit is hit.
+ *
+ * @author Kohsuke Kawaguchi
+ * @see GitHubBuilder#withAbuseLimitHandler(AbuseLimitHandler)
+ * @see documentation
+ * @see RateLimitHandler
+ */
+public abstract class AbuseLimitHandler {
+ /**
+ * Called when the library encounters HTTP error indicating that the API abuse limit is reached.
+ *
+ *
+ * Any exception thrown from this method will cause the request to fail, and the caller of github-api
+ * will receive an exception. If this method returns normally, another request will be attempted.
+ * For that to make sense, the implementation needs to wait for some time.
+ *
+ * @see API documentation from GitHub
+ * @param e
+ * Exception from Java I/O layer. If you decide to fail the processing, you can throw
+ * this exception (or wrap this exception into another exception and throw it.)
+ * @param uc
+ * Connection that resulted in an error. Useful for accessing other response headers.
+ */
+ public abstract void onError(IOException e, HttpURLConnection uc) throws IOException;
+
+ /**
+ * Wait until the API abuse "wait time" is passed.
+ */
+ public static final AbuseLimitHandler WAIT = new AbuseLimitHandler() {
+ @Override
+ public void onError(IOException e, HttpURLConnection uc) throws IOException {
+ try {
+ Thread.sleep(parseWaitTime(uc));
+ } catch (InterruptedException _) {
+ throw (InterruptedIOException)new InterruptedIOException().initCause(e);
+ }
+ }
+
+ private long parseWaitTime(HttpURLConnection uc) {
+ String v = uc.getHeaderField("Retry-After");
+ if (v==null) return 60 * 1000; // can't tell, return 1 min
+
+ return Math.max(1000, Long.parseLong(v)*1000);
+ }
+ };
+
+ /**
+ * Fail immediately.
+ */
+ public static final AbuseLimitHandler FAIL = new AbuseLimitHandler() {
+ @Override
+ public void onError(IOException e, HttpURLConnection uc) throws IOException {
+ throw (IOException)new IOException("Abust limit reached").initCause(e);
+ }
+ };
+}
diff --git a/src/main/java/org/kohsuke/github/GHBranch.java b/src/main/java/org/kohsuke/github/GHBranch.java
index 1de47a156e..4c80db9ef4 100644
--- a/src/main/java/org/kohsuke/github/GHBranch.java
+++ b/src/main/java/org/kohsuke/github/GHBranch.java
@@ -7,6 +7,8 @@
import java.util.Arrays;
import java.util.Collection;
+import static org.kohsuke.github.Previews.LOKI;
+
/**
* A branch in a repository.
*
@@ -53,6 +55,7 @@ public String getSHA1() {
/**
* Disables branch protection and allows anyone with push access to push changes.
*/
+ @Preview @Deprecated
public void disableProtection() throws IOException {
BranchProtection bp = new BranchProtection();
bp.enabled = false;
@@ -64,6 +67,7 @@ public void disableProtection() throws IOException {
*
* @see GHCommitStatus#getContext()
*/
+ @Preview @Deprecated
public void enableProtection(EnforcementLevel level, Collection contexts) throws IOException {
BranchProtection bp = new BranchProtection();
bp.enabled = true;
@@ -73,14 +77,13 @@ public void enableProtection(EnforcementLevel level, Collection contexts
setProtection(bp);
}
+ @Preview @Deprecated
public void enableProtection(EnforcementLevel level, String... contexts) throws IOException {
enableProtection(level, Arrays.asList(contexts));
}
private void setProtection(BranchProtection bp) throws IOException {
- new Requester(root).method("PATCH")
- .withHeader("Accept","application/vnd.github.loki-preview+json")
- ._with("protection",bp).to(getApiRoute());
+ new Requester(root).method("PATCH").withPreview(LOKI)._with("protection",bp).to(getApiRoute());
}
String getApiRoute() {
diff --git a/src/main/java/org/kohsuke/github/GHCommit.java b/src/main/java/org/kohsuke/github/GHCommit.java
index 91510fc88c..6e606ef603 100644
--- a/src/main/java/org/kohsuke/github/GHCommit.java
+++ b/src/main/java/org/kohsuke/github/GHCommit.java
@@ -179,7 +179,8 @@ static class User {
public ShortInfo getCommitShortInfo() throws IOException {
- populate();
+ if (commit==null)
+ populate();
return commit;
}
diff --git a/src/main/java/org/kohsuke/github/GHContentWithLicense.java b/src/main/java/org/kohsuke/github/GHContentWithLicense.java
new file mode 100644
index 0000000000..bbd115e649
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GHContentWithLicense.java
@@ -0,0 +1,21 @@
+package org.kohsuke.github;
+
+/**
+ * {@link GHContent} with license information.
+ *
+ * @author Kohsuke Kawaguchi
+ * @see documentation
+ * @see GHRepository#getLicense()
+ */
+@Preview @Deprecated
+class GHContentWithLicense extends GHContent {
+ GHLicense license;
+
+ @Override
+ GHContentWithLicense wrap(GHRepository owner) {
+ super.wrap(owner);
+ if (license!=null)
+ license.wrap(owner.root);
+ return this;
+ }
+}
diff --git a/src/main/java/org/kohsuke/github/GHLicense.java b/src/main/java/org/kohsuke/github/GHLicense.java
new file mode 100644
index 0000000000..2c5c4299c1
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GHLicense.java
@@ -0,0 +1,168 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2016, Duncan Dickinson
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.kohsuke.github;
+
+import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.kohsuke.github.Previews.DRAX;
+
+/**
+ * The GitHub Preview API's license information
+ *
+ * WARNING: This uses a PREVIEW API - subject to change.
+ *
+ * @author Duncan Dickinson
+ * @see GitHub#getLicense(String)
+ * @see GHRepository#getLicense()
+ * @see https://developer.github.com/v3/licenses/
+ */
+@Preview @Deprecated
+@SuppressWarnings({"UnusedDeclaration"})
+@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD",
+ "NP_UNWRITTEN_FIELD"}, justification = "JSON API")
+public class GHLicense extends GHObject {
+ @SuppressFBWarnings("IS2_INCONSISTENT_SYNC") // root is set before the object is returned to the app
+ /*package almost final*/ GitHub root;
+
+ // these fields are always present, even in the short form
+ protected String key, name;
+
+ // the rest is only after populated
+ protected Boolean featured;
+
+ protected String html_url, description, category, implementation, body;
+
+ protected List required = new ArrayList();
+ protected List permitted = new ArrayList();
+ protected List forbidden = new ArrayList();
+
+ /**
+ * @return a mnemonic for the license
+ */
+ public String getKey() {
+ return key;
+ }
+
+ /**
+ * @return the license name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return API URL of this object.
+ */
+ @WithBridgeMethods(value = String.class, adapterMethod = "urlToString")
+ public URL getUrl() {
+ return GitHub.parseURL(url);
+ }
+
+ /**
+ * Featured licenses are bold in the new repository drop-down
+ *
+ * @return True if the license is featured, false otherwise
+ */
+ public Boolean isFeatured() throws IOException {
+ populate();
+ return featured;
+ }
+
+ public URL getHtmlUrl() throws IOException {
+ populate();
+ return GitHub.parseURL(html_url);
+ }
+
+ public String getDescription() throws IOException {
+ populate();
+ return description;
+ }
+
+ public String getCategory() throws IOException {
+ populate();
+ return category;
+ }
+
+ public String getImplementation() throws IOException {
+ populate();
+ return implementation;
+ }
+
+ public List getRequired() throws IOException {
+ populate();
+ return required;
+ }
+
+ public List getPermitted() throws IOException {
+ populate();
+ return permitted;
+ }
+
+ public List getForbidden() throws IOException {
+ populate();
+ return forbidden;
+ }
+
+ public String getBody() throws IOException {
+ populate();
+ return body;
+ }
+
+ /**
+ * Fully populate the data by retrieving missing data.
+ *
+ * Depending on the original API call where this object is created, it may not contain everything.
+ */
+ protected synchronized void populate() throws IOException {
+ if (description!=null) return; // already populated
+
+ root.retrieve().withPreview(DRAX).to(url, this);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof GHLicense)) return false;
+
+ GHLicense that = (GHLicense) o;
+ return this.url.equals(that.url);
+ }
+
+ @Override
+ public int hashCode() {
+ return url.hashCode();
+ }
+
+ /*package*/ GHLicense wrap(GitHub root) {
+ this.root = root;
+ return this;
+ }
+}
diff --git a/src/main/java/org/kohsuke/github/GHObject.java b/src/main/java/org/kohsuke/github/GHObject.java
index 323e72cd29..a81d913a5c 100644
--- a/src/main/java/org/kohsuke/github/GHObject.java
+++ b/src/main/java/org/kohsuke/github/GHObject.java
@@ -51,7 +51,7 @@ public URL getUrl() {
* URL of this object for humans, which renders some HTML.
*/
@WithBridgeMethods(value=String.class, adapterMethod="urlToString")
- public abstract URL getHtmlUrl();
+ public abstract URL getHtmlUrl() throws IOException;
/**
* When was this resource last updated?
diff --git a/src/main/java/org/kohsuke/github/GHRepository.java b/src/main/java/org/kohsuke/github/GHRepository.java
index 41a44e9dab..472adf41eb 100644
--- a/src/main/java/org/kohsuke/github/GHRepository.java
+++ b/src/main/java/org/kohsuke/github/GHRepository.java
@@ -51,6 +51,7 @@
import java.util.TreeMap;
import static java.util.Arrays.asList;
+import static org.kohsuke.github.Previews.DRAX;
/**
* A repository on GitHub.
@@ -65,6 +66,13 @@ public class GHRepository extends GHObject {
private String description, homepage, name, full_name;
private String html_url; // this is the UI
+ /*
+ * The license information makes use of the preview API.
+ *
+ * See: https://developer.github.com/v3/licenses/
+ */
+ private GHLicense license;
+
private String git_url, ssh_url, clone_url, svn_url, mirror_url;
private GHUser owner; // not fully populated. beware.
private boolean has_issues, has_wiki, fork, has_downloads;
@@ -840,6 +848,46 @@ protected void wrapUp(GHCommitComment[] page) {
};
}
+ /**
+ * Gets the basic license details for the repository.
+ *
+ * This is a preview item and subject to change.
+ *
+ * @throws IOException as usual but also if you don't use the preview connector
+ * @return null if there's no license.
+ */
+ @Preview @Deprecated
+ public GHLicense getLicense() throws IOException{
+ GHContentWithLicense lic = getLicenseContent_();
+ return lic!=null ? lic.license : null;
+ }
+
+ /**
+ * Retrieves the contents of the repository's license file - makes an additional API call
+ *
+ * This is a preview item and subject to change.
+ *
+ * @return details regarding the license contents, or null if there's no license.
+ * @throws IOException as usual but also if you don't use the preview connector
+ */
+ @Preview @Deprecated
+ public GHContent getLicenseContent() throws IOException {
+ return getLicenseContent_();
+ }
+
+ @Preview @Deprecated
+ private GHContentWithLicense getLicenseContent_() throws IOException {
+ try {
+ return root.retrieve()
+ .withPreview(DRAX)
+ .to(getApiTailUrl("license"), GHContentWithLicense.class).wrap(this);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+
/**
* Lists all the commit statues attached to the given commit, newer ones first.
*/
diff --git a/src/main/java/org/kohsuke/github/GHTeam.java b/src/main/java/org/kohsuke/github/GHTeam.java
index 172f859657..ae950aed70 100644
--- a/src/main/java/org/kohsuke/github/GHTeam.java
+++ b/src/main/java/org/kohsuke/github/GHTeam.java
@@ -124,7 +124,13 @@ public void remove(GHUser u) throws IOException {
}
public void add(GHRepository r) throws IOException {
- org.root.retrieve().method("PUT").to(api("/repos/" + r.getOwnerName() + '/' + r.getName()), null);
+ add(r,null);
+ }
+
+ public void add(GHRepository r, GHOrganization.Permission permission) throws IOException {
+ org.root.retrieve().method("PUT")
+ .with("permission",permission)
+ .to(api("/repos/" + r.getOwnerName() + '/' + r.getName()), null);
}
public void remove(GHRepository r) throws IOException {
diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java
index 22792a8798..e5bf16b1de 100644
--- a/src/main/java/org/kohsuke/github/GitHub.java
+++ b/src/main/java/org/kohsuke/github/GitHub.java
@@ -27,6 +27,7 @@
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE;
import static java.util.logging.Level.FINE;
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
+import static org.kohsuke.github.Previews.DRAX;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
@@ -56,6 +57,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.VisibilityChecker.Std;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
+
import java.util.logging.Logger;
/**
@@ -83,6 +85,7 @@ public class GitHub {
private final String apiUrl;
/*package*/ final RateLimitHandler rateLimitHandler;
+ /*package*/ final AbuseLimitHandler abuseLimitHandler;
private HttpConnector connector = HttpConnector.DEFAULT;
@@ -122,7 +125,7 @@ public class GitHub {
* @param connector
* HttpConnector to use. Pass null to use default connector.
*/
- /* package */ GitHub(String apiUrl, String login, String oauthAccessToken, String password, HttpConnector connector, RateLimitHandler rateLimitHandler) throws IOException {
+ /* package */ GitHub(String apiUrl, String login, String oauthAccessToken, String password, HttpConnector connector, RateLimitHandler rateLimitHandler, AbuseLimitHandler abuseLimitHandler) throws IOException {
if (apiUrl.endsWith("/")) apiUrl = apiUrl.substring(0, apiUrl.length()-1); // normalize
this.apiUrl = apiUrl;
if (null != connector) this.connector = connector;
@@ -140,6 +143,7 @@ public class GitHub {
}
this.rateLimitHandler = rateLimitHandler;
+ this.abuseLimitHandler = abuseLimitHandler;
if (login==null && encodedAuthorization!=null)
login = getMyself().getLogin();
@@ -337,6 +341,46 @@ public GHRepository getRepository(String name) throws IOException {
String[] tokens = name.split("/");
return retrieve().to("/repos/" + tokens[0] + '/' + tokens[1], GHRepository.class).wrap(this);
}
+ /**
+ * Returns a list of popular open source licenses
+ *
+ * WARNING: This uses a PREVIEW API.
+ *
+ * @see GitHub API - Licenses
+ *
+ * @return a list of popular open source licenses
+ */
+ @Preview @Deprecated
+ public PagedIterable listLicenses() throws IOException {
+ return new PagedIterable() {
+ public PagedIterator _iterator(int pageSize) {
+ return new PagedIterator(retrieve().withPreview(DRAX).asIterator("/licenses", GHLicense[].class, pageSize)) {
+ @Override
+ protected void wrapUp(GHLicense[] page) {
+ for (GHLicense c : page)
+ c.wrap(GitHub.this);
+ }
+ };
+ }
+ };
+ }
+
+ /**
+ * Returns the full details for a license
+ *
+ * WARNING: This uses a PREVIEW API.
+ *
+ * @param key The license key provided from the API
+ * @return The license details
+ * @throws IOException
+ * @see GHLicense#getKey()
+ */
+ @Preview @Deprecated
+ public GHLicense getLicense(String key) throws IOException {
+ return retrieve().withPreview(DRAX).to("/licenses/" + key, GHLicense.class);
+ }
+
+ /**
/**
* This method returns a shallowly populated organizations.
diff --git a/src/main/java/org/kohsuke/github/GitHubBuilder.java b/src/main/java/org/kohsuke/github/GitHubBuilder.java
index 2abe16f9c1..62a99a6272 100644
--- a/src/main/java/org/kohsuke/github/GitHubBuilder.java
+++ b/src/main/java/org/kohsuke/github/GitHubBuilder.java
@@ -30,6 +30,7 @@ public class GitHubBuilder {
private HttpConnector connector;
private RateLimitHandler rateLimitHandler = RateLimitHandler.WAIT;
+ private AbuseLimitHandler abuseLimitHandler = AbuseLimitHandler.WAIT;
public GitHubBuilder() {
}
@@ -178,6 +179,10 @@ public GitHubBuilder withRateLimitHandler(RateLimitHandler handler) {
this.rateLimitHandler = handler;
return this;
}
+ public GitHubBuilder withAbuseLimitHandler(AbuseLimitHandler handler) {
+ this.abuseLimitHandler = handler;
+ return this;
+ }
/**
* Configures {@linkplain #withConnector(HttpConnector) connector}
@@ -193,6 +198,6 @@ public HttpURLConnection connect(URL url) throws IOException {
}
public GitHub build() throws IOException {
- return new GitHub(endpoint, user, oauthToken, password, connector, rateLimitHandler);
+ return new GitHub(endpoint, user, oauthToken, password, connector, rateLimitHandler, abuseLimitHandler);
}
}
diff --git a/src/main/java/org/kohsuke/github/Preview.java b/src/main/java/org/kohsuke/github/Preview.java
new file mode 100644
index 0000000000..2eeed60456
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/Preview.java
@@ -0,0 +1,18 @@
+package org.kohsuke.github;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Indicates that the method/class/etc marked maps to GitHub API in the preview period.
+ *
+ * These APIs are subject to change and not a part of the backward compatibility commitment.
+ * Always used in conjunction with 'deprecated' to raise awareness to clients.
+ *
+ * @author Kohsuke Kawaguchi
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Preview {
+}
diff --git a/src/main/java/org/kohsuke/github/Previews.java b/src/main/java/org/kohsuke/github/Previews.java
new file mode 100644
index 0000000000..a5d061cbd4
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/Previews.java
@@ -0,0 +1,9 @@
+package org.kohsuke.github;
+
+/**
+ * @author Kohsuke Kawaguchi
+ */
+/*package*/ class Previews {
+ static final String LOKI = "application/vnd.github.loki-preview+json";
+ static final String DRAX = "application/vnd.github.drax-preview+json";
+}
diff --git a/src/main/java/org/kohsuke/github/RateLimitHandler.java b/src/main/java/org/kohsuke/github/RateLimitHandler.java
index b00624ce2b..053fd783e0 100644
--- a/src/main/java/org/kohsuke/github/RateLimitHandler.java
+++ b/src/main/java/org/kohsuke/github/RateLimitHandler.java
@@ -9,6 +9,7 @@
*
* @author Kohsuke Kawaguchi
* @see GitHubBuilder#withRateLimitHandler(RateLimitHandler)
+ * @see AbuseLimitHandler
*/
public abstract class RateLimitHandler {
/**
diff --git a/src/main/java/org/kohsuke/github/Requester.java b/src/main/java/org/kohsuke/github/Requester.java
index dfbee6256f..6175191ea6 100644
--- a/src/main/java/org/kohsuke/github/Requester.java
+++ b/src/main/java/org/kohsuke/github/Requester.java
@@ -110,6 +110,10 @@ public Requester withHeader(String name, String value) {
return this;
}
+ /*package*/ Requester withPreview(String name) {
+ return withHeader("Accept",name);
+ }
+
/**
* Makes a request with authentication credential.
*/
@@ -512,6 +516,10 @@ private T parse(Class type, T instance) throws IOException {
if (responseCode == 304) {
return null; // special case handling for 304 unmodified, as the content will be ""
}
+ if (responseCode == 204 && type!=null && type.isArray()) {
+ // no content
+ return type.cast(Array.newInstance(type.getComponentType(),0));
+ }
r = new InputStreamReader(wrapStream(uc.getInputStream()), "UTF-8");
String data = IOUtils.toString(r);
@@ -569,6 +577,13 @@ private InputStream wrapStream(InputStream in) throws IOException {
return;
}
+ // Retry-After is not documented but apparently that field exists
+ if (responseCode == HttpURLConnection.HTTP_FORBIDDEN &&
+ uc.getHeaderField("Retry-After") != null) {
+ this.root.abuseLimitHandler.onError(e,uc);
+ return;
+ }
+
InputStream es = wrapStream(uc.getErrorStream());
try {
if (es!=null) {
diff --git a/src/test/java/Foo.java b/src/test/java/Foo.java
index a29baac4b4..35e5eb5955 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.GHRepository.Contributor;
import org.kohsuke.github.GHUser;
import org.kohsuke.github.GitHub;
@@ -9,11 +10,10 @@
*/
public class Foo {
public static void main(String[] args) throws Exception {
- Collection lst = GitHub.connect().getUser("kohsuke").getRepositories().values();
- for (GHRepository r : lst) {
- System.out.println(r.getName());
+ GitHub gh = GitHub.connect();
+ for (Contributor c : gh.getRepository("kohsuke/yo").listContributors()) {
+ System.out.println(c);
}
- System.out.println(lst.size());
}
private static void testRateLimit() throws Exception {
diff --git a/src/test/java/org/kohsuke/github/GHLicenseTest.java b/src/test/java/org/kohsuke/github/GHLicenseTest.java
new file mode 100644
index 0000000000..b2cc73f928
--- /dev/null
+++ b/src/test/java/org/kohsuke/github/GHLicenseTest.java
@@ -0,0 +1,193 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2016, Duncan Dickinson
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.kohsuke.github;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * @author Duncan Dickinson
+ */
+public class GHLicenseTest extends Assert {
+ private GitHub gitHub;
+
+ @Before
+ public void setUp() throws Exception {
+ gitHub = new GitHubBuilder()
+ .fromCredentials()
+ .build();
+ }
+
+ /**
+ * Basic test to ensure that the list of licenses from {@link GitHub#listLicenses()} is returned
+ *
+ * @throws IOException
+ */
+ @Test
+ public void listLicenses() throws IOException {
+ Iterable licenses = gitHub.listLicenses();
+ assertTrue(licenses.iterator().hasNext());
+ }
+
+ /**
+ * Tests that {@link GitHub#listLicenses()} returns the MIT license
+ * in the expected manner.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void listLicensesCheckIndividualLicense() throws IOException {
+ PagedIterable licenses = gitHub.listLicenses();
+ for (GHLicense lic : licenses) {
+ if (lic.getKey().equals("mit")) {
+ assertTrue(lic.getUrl().equals(new URL("https://api.github.com/licenses/mit")));
+ return;
+ }
+ }
+ fail("The MIT license was not found");
+ }
+
+ /**
+ * Checks that the request for an individual license using {@link GitHub#getLicense(String)}
+ * returns expected values (not all properties are checked)
+ *
+ * @throws IOException
+ */
+ @Test
+ public void getLicense() throws IOException {
+ String key = "mit";
+ GHLicense license = gitHub.getLicense(key);
+ assertNotNull(license);
+ assertTrue("The name is correct", license.getName().equals("MIT License"));
+ assertTrue("The HTML URL is correct", license.getHtmlUrl().equals(new URL("http://choosealicense.com/licenses/mit/")));
+ }
+
+ /**
+ * Accesses the 'kohsuke/github-api' repo using {@link GitHub#getRepository(String)}
+ * and checks that the license is correct
+ *
+ * @throws IOException
+ */
+ @Test
+ public void checkRepositoryLicense() throws IOException {
+ GHRepository repo = gitHub.getRepository("kohsuke/github-api");
+ GHLicense license = repo.getLicense();
+ assertNotNull("The license is populated", license);
+ assertTrue("The key is correct", license.getKey().equals("mit"));
+ assertTrue("The name is correct", license.getName().equals("MIT License"));
+ assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/mit")));
+ }
+
+ /**
+ * Accesses the 'atom/atom' repo using {@link GitHub#getRepository(String)}
+ * and checks that the license is correct
+ *
+ * @throws IOException
+ */
+ @Test
+ public void checkRepositoryLicenseAtom() throws IOException {
+ GHRepository repo = gitHub.getRepository("atom/atom");
+ GHLicense license = repo.getLicense();
+ assertNotNull("The license is populated", license);
+ assertTrue("The key is correct", license.getKey().equals("mit"));
+ assertTrue("The name is correct", license.getName().equals("MIT License"));
+ assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/mit")));
+ }
+
+ /**
+ * Accesses the 'pomes/pomes' repo using {@link GitHub#getRepository(String)}
+ * and checks that the license is correct
+ *
+ * @throws IOException
+ */
+ @Test
+ public void checkRepositoryLicensePomes() throws IOException {
+ GHRepository repo = gitHub.getRepository("pomes/pomes");
+ GHLicense license = repo.getLicense();
+ assertNotNull("The license is populated", license);
+ assertTrue("The key is correct", license.getKey().equals("apache-2.0"));
+ assertTrue("The name is correct", license.getName().equals("Apache License 2.0"));
+ assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/apache-2.0")));
+ }
+
+ /**
+ * Accesses the 'dedickinson/test-repo' repo using {@link GitHub#getRepository(String)}
+ * and checks that *no* license is returned as the repo doesn't have one
+ *
+ * @throws IOException
+ */
+ @Test
+ public void checkRepositoryWithoutLicense() throws IOException {
+ GHRepository repo = gitHub.getRepository("dedickinson/test-repo");
+ GHLicense license = repo.getLicense();
+ assertNull("There is no license", license);
+ }
+
+ /**
+ * Accesses the 'kohsuke/github-api' repo using {@link GitHub#getRepository(String)}
+ * and then calls {@link GHRepository#getLicense()} and checks that certain
+ * properties are correct
+ *
+ * @throws IOException
+ */
+ @Test
+ public void checkRepositoryFullLicense() throws IOException {
+ GHRepository repo = gitHub.getRepository("kohsuke/github-api");
+ GHLicense license = repo.getLicense();
+ assertNotNull("The license is populated", license);
+ assertTrue("The key is correct", license.getKey().equals("mit"));
+ assertTrue("The name is correct", license.getName().equals("MIT License"));
+ assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/mit")));
+ assertTrue("The HTML URL is correct", license.getHtmlUrl().equals(new URL("http://choosealicense.com/licenses/mit/")));
+ }
+
+ /**
+ * Accesses the 'pomes/pomes' repo using {@link GitHub#getRepository(String)}
+ * and then calls {@link GHRepository#getLicenseContent()} and checks that certain
+ * properties are correct
+ *
+ * @throws IOException
+ */
+ @Test
+ public void checkRepositoryLicenseContent() throws IOException {
+ GHRepository repo = gitHub.getRepository("pomes/pomes");
+ GHContent content = repo.getLicenseContent();
+ assertNotNull("The license content is populated", content);
+ assertTrue("The type is 'file'", content.getType().equals("file"));
+ assertTrue("The license file is 'LICENSE'", content.getName().equals("LICENSE"));
+
+ if (content.getEncoding().equals("base64")) {
+ String licenseText = new String(IOUtils.toByteArray(content.read()));
+ assertTrue("The license appears to be an Apache License", licenseText.contains("Apache License"));
+ } else {
+ fail("Expected the license to be Base64 encoded but instead it was " + content.getEncoding());
+ }
+ }
+}
diff --git a/src/test/java/org/kohsuke/github/RepositoryTest.java b/src/test/java/org/kohsuke/github/RepositoryTest.java
index 61cdc09dca..dae2892a45 100644
--- a/src/test/java/org/kohsuke/github/RepositoryTest.java
+++ b/src/test/java/org/kohsuke/github/RepositoryTest.java
@@ -50,4 +50,12 @@ public void listLanguages() throws IOException {
String mainLanguage = r.getLanguage();
assertTrue(r.listLanguages().containsKey(mainLanguage));
}
+
+ @Test // Issue #261
+ public void listEmptyContributors() throws IOException {
+ GitHub gh = GitHub.connect();
+ for (Contributor c : gh.getRepository("github-api-test-org/empty").listContributors()) {
+ System.out.println(c);
+ }
+ }
}