Compare commits

...

20 Commits

Author SHA1 Message Date
Kohsuke Kawaguchi
74b3902d5f [maven-release-plugin] prepare release github-api-1.22 2012-04-12 11:36:49 -07:00
Kohsuke Kawaguchi
7433ed968e monkey test code 2012-04-12 11:05:18 -07:00
Kohsuke Kawaguchi
ddf2d69a68 this method can return null 2012-04-12 11:05:04 -07:00
Kohsuke Kawaguchi
f5b34861bd avoid NPE 2012-04-12 11:01:43 -07:00
Kohsuke Kawaguchi
f0ff31a1af [maven-release-plugin] prepare for next development iteration 2012-04-11 18:16:16 -07:00
Kohsuke Kawaguchi
8ce76fba62 [maven-release-plugin] prepare release github-api-1.21 2012-04-11 18:16:10 -07:00
Kohsuke Kawaguchi
a947672320 doc improvement 2012-04-11 16:34:37 -07:00
Kohsuke Kawaguchi
5496e2b553 added pagenation support 2012-04-11 16:30:31 -07:00
Kohsuke Kawaguchi
2f9edc65dd support gzip for better performance 2012-04-11 15:45:08 -07:00
Kohsuke Kawaguchi
6372337456 Use gzip to improve transfer performance 2012-04-11 15:44:04 -07:00
Kohsuke Kawaguchi
b843606ebe [maven-release-plugin] prepare for next development iteration 2012-04-11 09:32:51 -07:00
Kohsuke Kawaguchi
7cda083b31 [maven-release-plugin] prepare release github-api-1.20 2012-04-11 09:32:46 -07:00
Kohsuke Kawaguchi
095604d4ca updated the parent version 2012-04-11 09:32:22 -07:00
Kohsuke Kawaguchi
7e799d02e3 Added test-ish 2012-04-11 09:29:28 -07:00
Kohsuke Kawaguchi
7f4612f872 exposed the size 2012-04-11 09:27:47 -07:00
Kohsuke Kawaguchi
6f6ff56338 exposed master_branch 2012-04-11 09:26:26 -07:00
Kohsuke Kawaguchi
27701a75d9 Migrating to the V3 API 2012-04-10 13:03:55 -07:00
Kohsuke Kawaguchi
3717d2ed32 emulate the value for V2 API 2012-04-10 12:57:25 -07:00
Kohsuke Kawaguchi
3a9b6665ee exposed avatar_url 2012-04-10 12:43:59 -07:00
Kohsuke Kawaguchi
bbc42f8898 [maven-release-plugin] prepare for next development iteration 2012-04-06 08:25:49 -07:00
6 changed files with 253 additions and 79 deletions

View File

@@ -3,11 +3,11 @@
<parent>
<groupId>org.kohsuke</groupId>
<artifactId>pom</artifactId>
<version>1</version>
<version>3</version>
</parent>
<artifactId>github-api</artifactId>
<version>1.19</version>
<version>1.22</version>
<name>GitHub API for Java</name>
<url>http://github-api.kohsuke.org/</url>
<description>GitHub API for Java</description>

View File

@@ -2,10 +2,15 @@ package org.kohsuke.github;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import static org.kohsuke.github.ApiVersion.*;
/**
* Common part of {@link GHUser} and {@link GHOrganization}.
*
@@ -31,18 +36,48 @@ public abstract class GHPerson {
*/
public synchronized Map<String,GHRepository> getRepositories() throws IOException {
Map<String,GHRepository> repositories = new TreeMap<String, GHRepository>();
for (int i=1; ; i++) {
GHRepository[] array = root.retrieve3("/users/" + login + "/repos?per_page=100&page=" + i, GHRepository[].class);
for (GHRepository r : array) {
r.root = root;
for (List<GHRepository> batch : iterateRepositories(100)) {
for (GHRepository r : batch)
repositories.put(r.getName(),r);
}
if (array.length==0) break;
}
return Collections.unmodifiableMap(repositories);
}
/**
* Loads repository list in a pagenated fashion.
*
* <p>
* For a person with a lot of repositories, GitHub returns the list of repositories in a pagenated fashion.
* Unlike {@link #getRepositories()}, this method allows the caller to start processing data as it arrives.
*
* Every {@link Iterator#next()} call results in I/O. Exceptions that occur during the processing is wrapped
* into {@link Error}.
*/
public synchronized Iterable<List<GHRepository>> iterateRepositories(final int pageSize) {
return new Iterable<List<GHRepository>>() {
public Iterator<List<GHRepository>> iterator() {
final Iterator<GHRepository[]> pager = root.retrievePaged("/users/" + login + "/repos?per_page="+pageSize,GHRepository[].class,false, V3);
return new Iterator<List<GHRepository>>() {
public boolean hasNext() {
return pager.hasNext();
}
public List<GHRepository> next() {
GHRepository[] batch = pager.next();
for (GHRepository r : batch)
r.root = root;
return Arrays.asList(batch);
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
/**
*
* @return
@@ -58,11 +93,26 @@ public abstract class GHPerson {
/**
* Gravatar ID of this user, like 0cb9832a01c22c083390f3c5dcb64105
*
* @deprecated
* No longer available in the v3 API.
*/
public String getGravatarId() {
return gravatar_id;
}
/**
* Returns a string like 'https://secure.gravatar.com/avatar/0cb9832a01c22c083390f3c5dcb64105'
* that indicates the avatar image URL.
*/
public String getAvatarUrl() {
if (avatar_url!=null)
return avatar_url;
if (gravatar_id!=null)
return "https://secure.gravatar.com/avatar/"+gravatar_id;
return null;
}
/**
* Gets the login ID of this user, like 'kohsuke'
*/

View File

@@ -68,9 +68,11 @@ public class GHRepository {
private String html_url; // this is the UI
private GHUser owner; // not fully populated. beware.
private boolean has_issues, has_wiki, fork, _private, has_downloads;
private int watchers,forks,open_issues;
private int watchers,forks,open_issues,size;
private String created_at, pushed_at;
private Map<Integer,GHMilestone> milestones = new HashMap<Integer, GHMilestone>();
private String master_branch;
public String getDescription() {
return description;
@@ -154,6 +156,11 @@ public class GHRepository {
return open_issues;
}
/**
*
* @return
* null if the repository was never pushed at.
*/
public Date getPushedAt() {
return GitHub.parseDate(pushed_at);
}
@@ -162,6 +169,19 @@ public class GHRepository {
return GitHub.parseDate(created_at);
}
/**
* Returns the primary branch you'll configure in the "Admin > Options" config page.
*
* @return
* This field is null until the user explicitly configures the master branch.
*/
public String getMasterBranch() {
return master_branch;
}
public int getSize() {
return size;
}
/**
* Gets the collaborators on this repository.

View File

@@ -44,13 +44,17 @@ import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.TimeZone;
import java.util.zip.GZIPInputStream;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import org.apache.commons.io.IOUtils;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.DeserializationConfig.Feature;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.introspect.VisibilityChecker.Std;
@@ -170,7 +174,7 @@ public class GitHub {
}
/*package*/ <T> T retrieveWithAuth(String tailApiUrl, Class<T> type) throws IOException {
return retrieveWithAuth(tailApiUrl,type,"GET");
return retrieveWithAuth(tailApiUrl, type, "GET");
}
/*package*/ <T> T retrieveWithAuth3(String tailApiUrl, Class<T> type) throws IOException {
@@ -187,32 +191,141 @@ public class GitHub {
private <T> T _retrieve(String tailApiUrl, Class<T> type, String method, boolean withAuth, ApiVersion v) throws IOException {
while (true) {// loop while API rate limit is hit
HttpURLConnection uc = (HttpURLConnection) getApiURL(v,tailApiUrl).openConnection();
if (withAuth && this.oauthAccessToken == null)
uc.setRequestProperty("Authorization", "Basic " + encodedAuthorization);
uc.setRequestMethod(method);
if (method.equals("PUT")) {
uc.setDoOutput(true);
uc.setRequestProperty("Content-Length","0");
uc.getOutputStream().close();
}
HttpURLConnection uc = setupConnection(method, withAuth, getApiURL(v, tailApiUrl));
try {
InputStreamReader r = new InputStreamReader(uc.getInputStream(), "UTF-8");
if (type==null) {
String data = IOUtils.toString(r);
return null;
}
return MAPPER.readValue(r,type);
return parse(uc,type);
} catch (IOException e) {
handleApiError(e,uc);
}
}
}
/**
* Loads pagenated resources.
*
* Every iterator call reports a new batch.
*/
/*package*/ <T> Iterator<T> retrievePaged(final String tailApiUrl, final Class<T> type, final boolean withAuth, final ApiVersion v) {
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;
{
try {
url = getApiURL(v, tailApiUrl);
} catch (IOException e) {
throw new Error(e);
}
}
public boolean hasNext() {
fetch();
return next!=null;
}
public T next() {
fetch();
T r = next;
if (r==null) throw new NoSuchElementException();
next = null;
return r;
}
public void remove() {
throw new UnsupportedOperationException();
}
private void fetch() {
if (next!=null) return; // already fetched
if (url==null) return; // no more data to fetch
try {
while (true) {// loop while API rate limit is hit
HttpURLConnection uc = setupConnection("GET", withAuth, url);
try {
next = parse(uc,type);
assert next!=null;
findNextURL(uc);
return;
} catch (IOException e) {
handleApiError(e,uc);
}
}
} catch (IOException e) {
throw new Error(e);
}
}
/**
* Locate the next page from the pagination "Link" tag.
*/
private void findNextURL(HttpURLConnection uc) 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.
}
};
}
private HttpURLConnection setupConnection(String method, boolean withAuth, URL url) throws IOException {
HttpURLConnection uc = (HttpURLConnection) url.openConnection();
if (withAuth && this.oauthAccessToken == null)
uc.setRequestProperty("Authorization", "Basic " + encodedAuthorization);
uc.setRequestMethod(method);
if (method.equals("PUT")) {
uc.setDoOutput(true);
uc.setRequestProperty("Content-Length","0");
uc.getOutputStream().close();
}
uc.setRequestProperty("Accept-Encoding", "gzip");
return uc;
}
private <T> T parse(HttpURLConnection uc, Class<T> type) throws IOException {
InputStreamReader r = null;
try {
r = new InputStreamReader(wrapStream(uc, uc.getInputStream()), "UTF-8");
if (type==null) {
String data = IOUtils.toString(r);
return null;
}
return MAPPER.readValue(r,type);
} finally {
IOUtils.closeQuietly(r);
}
}
/**
* Handles the "Content-Encoding" header.
*/
private InputStream wrapStream(HttpURLConnection uc, InputStream in) throws IOException {
String encoding = uc.getContentEncoding();
if (encoding==null) return in;
if (encoding.equals("gzip")) return new GZIPInputStream(in);
throw new UnsupportedOperationException("Unexpected Content-Encoding: "+encoding);
}
/**
* If the error is because of the API limit, wait 10 sec and return normally.
* Otherwise throw an exception reporting an error.
@@ -231,11 +344,15 @@ public class GitHub {
if (e instanceof FileNotFoundException)
throw e; // pass through 404 Not Found to allow the caller to handle it intelligently
InputStream es = uc.getErrorStream();
if (es!=null)
throw (IOException)new IOException(IOUtils.toString(es,"UTF-8")).initCause(e);
else
throw e;
InputStream es = wrapStream(uc, uc.getErrorStream());
try {
if (es!=null)
throw (IOException)new IOException(IOUtils.toString(es,"UTF-8")).initCause(e);
else
throw e;
} finally {
IOUtils.closeQuietly(es);
}
}
/**
@@ -245,7 +362,7 @@ public class GitHub {
public GHMyself getMyself() throws IOException {
requireCredential();
GHMyself u = retrieveWithAuth("/user/show", JsonMyself.class).user;
GHMyself u = retrieveWithAuth3("/user", GHMyself.class);
u.root = this;
users.put(u.getLogin(), u);
@@ -259,17 +376,9 @@ public class GitHub {
public GHUser getUser(String login) throws IOException {
GHUser u = users.get(login);
if (u == null) {
if (oauthAccessToken != null) {
u = retrieve("/user/show/" + login, JsonUser.class).user;
u.root = this;
users.put(u.getLogin(), u);
} else {
u = retrieve("/user/show/" + login, JsonUser.class).user;
u.root = this;
users.put(login, u);
}
u = retrieve3("/users/" + login, GHUser.class);
u.root = this;
users.put(u.getLogin(), u);
}
return u;
}
@@ -352,7 +461,7 @@ public class GitHub {
*/
public boolean isCredentialValid() throws IOException {
try {
retrieveWithAuth("/user/show",JsonUser.class);
retrieveWithAuth3("/user",GHUser.class);
return true;
} catch (IOException e) {
return false;
@@ -380,6 +489,7 @@ public class GitHub {
}
/*package*/ static Date parseDate(String timestamp) {
if (timestamp==null) return null;
for (String f : TIME_FORMATS) {
try {
SimpleDateFormat df = new SimpleDateFormat(f);

View File

@@ -1,31 +0,0 @@
/*
* The MIT License
*
* Copyright (c) 2010, Kohsuke Kawaguchi
*
* 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;
/**
* @author Kohsuke Kawaguchi
*/
class JsonUser {
public GHUser user;
}

View File

@@ -7,10 +7,12 @@ import org.kohsuke.github.GHEventPayload;
import org.kohsuke.github.GHHook;
import org.kohsuke.github.GHBranch;
import org.kohsuke.github.GHIssueState;
import org.kohsuke.github.GHMyself;
import org.kohsuke.github.GHOrganization;
import org.kohsuke.github.GHOrganization.Permission;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GHTeam;
import org.kohsuke.github.GHUser;
import org.kohsuke.github.GitHub;
import java.io.IOException;
@@ -31,9 +33,23 @@ public class AppTest extends TestCase {
public void testFetchPullRequest() throws Exception {
GitHub gh = GitHub.connect();
GHRepository r = gh.getOrganization("jenkinsci").getRepository("jenkins");
assertEquals("master",r.getMasterBranch());
r.getPullRequest(1);
r.getPullRequests(GHIssueState.OPEN);
}
public void tryGetMyself() throws Exception {
GitHub hub = GitHub.connect();
GHMyself me = hub.getMyself();
System.out.println(me);
GHUser u = hub.getUser("kohsuke2");
System.out.println(u);
for (List<GHRepository> lst : me.iterateRepositories(100)) {
for (GHRepository r : lst) {
System.out.println(r.getPushedAt());
}
}
}
public void tryOrgFork() throws Exception {
GitHub gh = GitHub.connect();
@@ -181,6 +197,15 @@ public class AppTest extends TestCase {
System.out.println(hooks);
}
public void testOrgRepositories() throws IOException {
GitHub gitHub = GitHub.connect();
GHOrganization j = gitHub.getOrganization("jenkinsci");
long start = System.currentTimeMillis();
Map<String, GHRepository> repos = j.getRepositories();
long end = System.currentTimeMillis();
System.out.printf("%d repositories in %dms\n",repos.size(),end-start);
}
public void testOrganization() throws IOException {
GitHub gitHub = GitHub.connect();
GHOrganization j = gitHub.getOrganization("jenkinsci");