Compare commits

...

16 Commits

Author SHA1 Message Date
Kohsuke Kawaguchi
a9b6f7bc9c [maven-release-plugin] prepare release github-api-1.17 2012-02-12 09:30:06 -08:00
Kohsuke Kawaguchi
830fb7192d added additional convenience method 2012-02-12 08:20:10 -08:00
Kohsuke Kawaguchi
e8ff7a4ae8 updated to use V3 API 2012-02-12 08:11:15 -08:00
Kohsuke Kawaguchi
dac2a56671 fully populate the root field upon deserialization 2012-02-12 07:43:06 -08:00
Kohsuke Kawaguchi
2718cf5ccb support both v2 and v3 2012-02-12 07:27:39 -08:00
Michael O'Cleirigh
202a5d435c Add original GitHub.connectUsingOAuth(token) back
This adds the original API back which defaults to the 'github.com' server.
2012-02-01 21:24:19 -05:00
Michael O'Cleirigh
58af8d0e90 Add support for github enterprise different base url
This changes how the ApiVersion enumeration works so that each of the V2 and V3  Url schemes is actually a template.

Then the serverName/hostname to use is passed in and used to generate the full url.

This will allow the github-api project to be used against users running the enterprise version of github.

This is needed to support github enterprise based authentication with the github-oauth-plugin.
2012-01-31 22:01:46 -05:00
Kohsuke Kawaguchi
b950be9626 bug fix in the event API. Turns out GHRepository isn't usable for this, because the name field is different.
/repos/:user/:repo returns just ":repo" portion in the name field, whereas in the event it has both ":user/:repo"
2012-01-14 11:53:53 -08:00
Kohsuke Kawaguchi
222277ae0a doc improvement 2012-01-14 11:42:09 -08:00
Kohsuke Kawaguchi
748cbdd03b doc improvement 2012-01-08 18:30:20 -08:00
Kohsuke Kawaguchi
57b26b6a21 doc improvement 2012-01-08 18:30:19 -08:00
Kohsuke Kawaguchi
a4026d46ce after an experiment, revisiting the design 2012-01-08 18:30:19 -08:00
Kohsuke Kawaguchi
696daf7387 adding the payload handling 2012-01-08 18:30:19 -08:00
Kohsuke Kawaguchi
4a507a5625 adding event support 2012-01-08 18:30:19 -08:00
Kohsuke Kawaguchi
ac1dd9a0ee 'Z' isn't parsed but it means GMT 2012-01-08 18:30:19 -08:00
Kohsuke Kawaguchi
44f1038516 [maven-release-plugin] prepare for next development iteration 2012-01-03 11:16:15 -08:00
12 changed files with 321 additions and 110 deletions

View File

@@ -7,7 +7,7 @@
</parent>
<artifactId>github-api</artifactId>
<version>1.16</version>
<version>1.17</version>
<name>GitHub API for Java</name>
<url>http://github-api.kohsuke.org/</url>
<description>GitHub API for Java</description>
@@ -77,6 +77,12 @@
<version>1.4</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.kohsuke.stapler</groupId>
<artifactId>stapler-jetty</artifactId>
<version>1.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<reporting>

View File

@@ -6,12 +6,19 @@ package org.kohsuke.github;
* @author Kohsuke Kawaguchi
*/
enum ApiVersion {
V2("https://github.com/api/v2/json"),
V3("https://api.github.com");
V2("https://?/api/v2/json"),
V3("https://api.?");
final String url;
final String templateUrl;
ApiVersion(String url) {
this.url = url;
ApiVersion(String templateUrl) {
this.templateUrl = templateUrl;
}
public String getApiVersionBaseUrl(String githubServer) {
return templateUrl.replaceFirst("\\?", githubServer);
}
}

View File

@@ -31,7 +31,7 @@ package org.kohsuke.github;
public class GHCommitPointer {
private String ref, sha, label;
private GHUser user;
private GHRepository repository;
private GHRepository repository/*V2*/,repo/*V3*/;
/**
* This points to the user who owns
@@ -45,11 +45,11 @@ public class GHCommitPointer {
* The repository that contains the commit.
*/
public GHRepository getRepository() {
return repository;
return repo!=null ? repo : repository;
}
/**
* Named ref to the commit.
* Named ref to the commit. This appears to be a "short ref" that doesn't include "refs/heads/" portion.
*/
public String getRef() {
return ref;
@@ -68,4 +68,10 @@ public class GHCommitPointer {
public String getLabel() {
return label;
}
void wrapUp(GitHub root) {
if (user!=null) user.root = root;
if (repo!=null) repo.wrap(root);
if (repository!=null) repository.wrap(root);
}
}

View File

@@ -0,0 +1,82 @@
package org.kohsuke.github;
import org.codehaus.jackson.node.ObjectNode;
import java.io.IOException;
import java.util.Date;
/**
* Represents an event.
*
* @author Kohsuke Kawaguchi
*/
public class GHEventInfo {
private GitHub root;
// we don't want to expose Jackson dependency to the user. This needs databinding
private ObjectNode payload;
private String created_at;
private String type;
// these are all shallow objects
private GHEventRepository repo;
private GHUser actor;
private GHOrganization org;
/**
* Inside the event JSON model, GitHub uses a slightly different format.
*/
public static class GHEventRepository {
private int id;
private String url; // repository API URL
private String name; // owner/repo
}
public GHEvent getType() {
String t = type;
if (t.endsWith("Event")) t=t.substring(0,t.length()-5);
for (GHEvent e : GHEvent.values()) {
if (e.name().replace("_","").equalsIgnoreCase(t))
return e;
}
return null; // unknown event type
}
/*package*/ GHEventInfo wrapUp(GitHub root) {
this.root = root;
return this;
}
public Date getCreatedAt() {
return GitHub.parseDate(created_at);
}
/**
* Repository where the change was made.
*/
public GHRepository getRepository() throws IOException {
return root.getRepository(repo.name);
}
public GHUser getActor() throws IOException {
return root.getUser(actor.getLogin());
}
public GHOrganization getOrganization() throws IOException {
return (org==null || org.getLogin()==null) ? null : root.getOrganization(org.getLogin());
}
/**
* Retrieves the payload.
*
* @param type
* Specify one of the {@link GHEventPayload} subtype that defines a type-safe access to the payload.
* This must match the {@linkplain #getType() event type}.
*/
public <T extends GHEventPayload> T getPayload(Class<T> type) throws IOException {
T v = GitHub.MAPPER.readValue(payload.traverse(), type);
v.wrapUp(root);
return v;
}
}

View File

@@ -0,0 +1,46 @@
package org.kohsuke.github;
import java.io.Reader;
/**
* Base type for types used in databinding of the event payload.
*
* @see GitHub#parseEventPayload(Reader, Class)
* @see GHEventInfo#getPayload(Class)
*/
public abstract class GHEventPayload {
protected GitHub root;
/*package*/ GHEventPayload() {
}
/*package*/ void wrapUp(GitHub root) {
this.root = root;
}
public static class PullRequest extends GHEventPayload {
private String action;
private int number;
GHPullRequest pull_request;
public String getAction() {
return action;
}
public int getNumber() {
return number;
}
public GHPullRequest getPullRequest() {
pull_request.root = root;
return pull_request;
}
@Override
void wrapUp(GitHub root) {
super.wrapUp(root);
pull_request.wrapUp(root);
}
}
}

View File

@@ -25,7 +25,6 @@ package org.kohsuke.github;
import java.net.URL;
import java.util.Date;
import java.util.Locale;
/**
* A pull request.
@@ -64,7 +63,7 @@ public class GHPullRequest extends GHIssue {
}
/**
* The change that should be pulled.
* The change that should be pulled. The tip of the commits to merge.
*/
public GHCommitPointer getHead() {
return head;
@@ -93,4 +92,19 @@ public class GHPullRequest extends GHIssue {
public Date getClosedAt() {
return GitHub.parseDate(closed_at);
}
GHPullRequest wrapUp(GHRepository owner) {
this.owner = owner;
return wrapUp(owner.root);
}
GHPullRequest wrapUp(GitHub root) {
this.root = root;
if (owner!=null) owner.wrap(root);
if (issue_user!=null) issue_user.root=root;
if (user!=null) user.root=root;
if (base!=null) base.wrapUp(root);
if (head!=null) head.wrapUp(root);
return this;
}
}

View File

@@ -78,12 +78,31 @@ public class GHRepository {
}
/**
* URL of this repository, like 'http://github.com/kohsuke/hudson'
* URL of this repository, like 'http://github.com/kohsuke/jenkins'
*/
public String getUrl() {
return html_url;
}
/**
* Gets the git:// URL to this repository, such as "git://github.com/kohsuke/jenkins.git"
* This URL is read-only.
*/
public String getGitTransportUrl() {
return "git://github.com/"+getOwnerName()+"/"+name+".git";
}
/**
* Gets the HTTPS URL to this repository, such as "https://github.com/kohsuke/jenkins.git"
* This URL is read-only.
*/
public String gitHttpTransportUrl() {
return "https://github.com/"+getOwnerName()+"/"+name+".git";
}
/**
* Short repository name without the owner. For example 'jenkins' in case of http://github.com/jenkinsci/jenkins
*/
public String getName() {
return name;
}
@@ -290,14 +309,17 @@ public class GHRepository {
* Retrieves a specified pull request.
*/
public GHPullRequest getPullRequest(int i) throws IOException {
return root.retrieveWithAuth("/pulls/" + owner.login + '/' + name + "/" + i, JsonPullRequest.class).wrap(this);
return root.retrieveWithAuth3("/repos/" + owner.login + '/' + name + "/pulls/" + i, GHPullRequest.class).wrapUp(this);
}
/**
* Retrieves all the pull requests of a particular state.
*/
public List<GHPullRequest> getPullRequests(GHIssueState state) throws IOException {
return root.retrieveWithAuth("/pulls/"+owner.login+'/'+name+"/"+state.name().toLowerCase(Locale.ENGLISH),JsonPullRequests.class).wrap(this);
GHPullRequest[] r = root.retrieveWithAuth3("/repos/" + owner.login + '/' + name + "/pulls?state=" + state.name().toLowerCase(Locale.ENGLISH), GHPullRequest[].class);
for (GHPullRequest p : r)
p.wrapUp(this);
return new ArrayList<GHPullRequest>(Arrays.asList(r));
}
/**
@@ -321,7 +343,7 @@ public class GHRepository {
* TODO: produce type-safe binding
*
* @param name
* Type of the hook to be created.
* Type of the hook to be created. See https://api.github.com/hooks for possible names.
* @param config
* The configuration hash.
* @param events

View File

@@ -23,14 +23,10 @@
*/
package org.kohsuke.github;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.apache.commons.io.IOUtils;
import org.codehaus.jackson.map.DeserializationConfig.Feature;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.introspect.VisibilityChecker.Std;
import sun.misc.BASE64Encoder;
import static org.codehaus.jackson.annotate.JsonAutoDetect.Visibility.ANY;
import static org.codehaus.jackson.annotate.JsonAutoDetect.Visibility.NONE;
import static org.kohsuke.github.ApiVersion.V2;
import static org.kohsuke.github.ApiVersion.V3;
import java.io.File;
import java.io.FileInputStream;
@@ -39,18 +35,30 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;
import static org.codehaus.jackson.annotate.JsonAutoDetect.Visibility.*;
import static org.kohsuke.github.ApiVersion.*;
import org.apache.commons.io.IOUtils;
import org.codehaus.jackson.map.DeserializationConfig.Feature;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.introspect.VisibilityChecker.Std;
import sun.misc.BASE64Encoder;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
/**
* Root of the GitHub API.
@@ -66,9 +74,16 @@ public class GitHub {
private final Map<String,GHUser> users = new HashMap<String, GHUser>();
private final Map<String,GHOrganization> orgs = new HashMap<String, GHOrganization>();
/*package*/ String oauthAccessToken;
private final String githubServer;
private GitHub(String login, String apiToken, String password) {
this.login = login;
private GitHub(String login, String apiToken, String password) {
this ("github.com", login, apiToken, password);
}
private GitHub(String githubServer, String login, String apiToken, String password) {
this.githubServer = githubServer;
this.login = login;
this.apiToken = apiToken;
this.password = password;
@@ -80,8 +95,9 @@ public class GitHub {
encodedAuthorization = null;
}
private GitHub (String oauthAccessToken) throws IOException {
private GitHub (String githubServer, String oauthAccessToken) throws IOException {
this.githubServer = githubServer;
this.password = null;
this.encodedAuthorization = null;
@@ -115,7 +131,11 @@ public class GitHub {
}
public static GitHub connectUsingOAuth (String accessToken) throws IOException {
return new GitHub(accessToken);
return connectUsingOAuth("github.com", accessToken);
}
public static GitHub connectUsingOAuth (String githubServer, String accessToken) throws IOException {
return new GitHub(githubServer, accessToken);
}
/**
* Connects to GitHub anonymously.
@@ -137,7 +157,7 @@ public class GitHub {
tailApiUrl = tailApiUrl + (tailApiUrl.indexOf('?')>=0 ?'&':'?') + "access_token=" + oauthAccessToken;
}
return new URL(v.url+tailApiUrl);
return new URL(v.getApiVersionBaseUrl(githubServer)+tailApiUrl);
}
/*package*/ <T> T retrieve(String tailApiUrl, Class<T> type) throws IOException {
@@ -279,9 +299,42 @@ public class GitHub {
return o;
}
/**
* Gets the repository object from 'user/reponame' string that GitHub calls as "repository name"
*
* @see GHRepository#getName()
*/
public GHRepository getRepository(String name) throws IOException {
String[] tokens = name.split("/");
return getUser(tokens[0]).getRepository(tokens[1]);
}
public Map<String, GHOrganization> getMyOrganizations() throws IOException {
return retrieveWithAuth("/organizations",JsonOrganizations.class).wrap(this);
}
/**
* Public events visible to you. Equivalent of what's displayed on https://github.com/
*/
public List<GHEventInfo> getEvents() throws IOException {
// TODO: pagenation
GHEventInfo[] events = retrieve3("/events", GHEventInfo[].class);
for (GHEventInfo e : events)
e.wrapUp(this);
return Arrays.asList(events);
}
/**
* Parses the GitHub event object.
*
* This is primarily intended for receiving a POST HTTP call from a hook.
* Unfortunately, hook script payloads aren't self-descriptive, so you need
* to know the type of the payload you are expecting.
*/
public <T extends GHEventPayload> T parseEventPayload(Reader r, Class<T> type) throws IOException {
T t = MAPPER.readValue(r, type);
t.wrapUp(this);
return t;
}
/**
@@ -331,7 +384,9 @@ public class GitHub {
/*package*/ static Date parseDate(String timestamp) {
for (String f : TIME_FORMATS) {
try {
return new SimpleDateFormat(f).parse(timestamp);
SimpleDateFormat df = new SimpleDateFormat(f);
df.setTimeZone(TimeZone.getTimeZone("GMT"));
return df.parse(timestamp);
} catch (ParseException e) {
// try next
}

View File

@@ -1,37 +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 JsonPullRequest {
public GHPullRequest pull;
public GHPullRequest wrap(GHRepository owner) {
pull.owner = owner;
pull.root = owner.root;
return pull;
}
}

View File

@@ -1,41 +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;
import java.util.List;
/**
* @author Kohsuke Kawaguchi
*/
class JsonPullRequests {
public List<GHPullRequest> pulls;
public List<GHPullRequest> wrap(GHRepository owner) {
for (GHPullRequest pull : pulls) {
pull.owner = owner;
pull.root = owner.root;
}
return pulls;
}
}

View File

@@ -1,7 +1,11 @@
package org.kohsuke;
import junit.framework.TestCase;
import org.kohsuke.github.GHEvent;
import org.kohsuke.github.GHEventInfo;
import org.kohsuke.github.GHEventPayload;
import org.kohsuke.github.GHHook;
import org.kohsuke.github.GHIssueState;
import org.kohsuke.github.GHOrganization;
import org.kohsuke.github.GHOrganization.Permission;
import org.kohsuke.github.GHRepository;
@@ -21,6 +25,13 @@ public class AppTest extends TestCase {
assertFalse(GitHub.connect("totally","bogus").isCredentialValid());
}
public void testFetchPullRequest() throws Exception {
GitHub gh = GitHub.connect();
GHRepository r = gh.getOrganization("jenkinsci").getRepository("jenkins");
r.getPullRequest(1);
r.getPullRequests(GHIssueState.OPEN);
}
public void tryOrgFork() throws Exception {
GitHub gh = GitHub.connect();
gh.getUser("kohsuke").getRepository("rubywm").forkTo(gh.getOrganization("jenkinsci"));
@@ -56,6 +67,14 @@ public class AppTest extends TestCase {
public void testApp() throws IOException {
GitHub gitHub = GitHub.connect();
for (GHEventInfo ev : gitHub.getEvents()) {
System.out.println(ev);
if (ev.getType()==GHEvent.PULL_REQUEST) {
GHEventPayload.PullRequest pr = ev.getPayload(GHEventPayload.PullRequest.class);
System.out.println(pr.getNumber());
System.out.println(pr.getPullRequest());
}
}
// GHRepository r = gitHub.connect().getOrganization("jenkinsci").createRepository("kktest4", "Kohsuke's test", "http://kohsuke.org/", "Everyone", true);
// r.fork();

View File

@@ -0,0 +1,32 @@
package org.kohsuke;
import org.kohsuke.github.GHEventPayload;
import org.kohsuke.github.GitHub;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.jetty.JettyRunner;
import java.io.IOException;
import java.io.StringReader;
/**
* App to test the hook script. You need some internet-facing server that can forward the request to you
* (typically via SSH reverse port forwarding.)
*
* @author Kohsuke Kawaguchi
*/
public class HookApp {
public static void main(String[] args) throws Exception {
// GitHub.connect().getMyself().getRepository("sandbox").createWebHook(
// new URL("http://173.203.118.45:18080/"), EnumSet.of(GHEvent.PULL_REQUEST));
JettyRunner jr = new JettyRunner(new HookApp());
jr.addHttpListener(8080);
jr.start();
}
public void doIndex(StaplerRequest req) throws IOException {
String str = req.getParameter("payload");
System.out.println(str);
GHEventPayload.PullRequest o = GitHub.connect().parseEventPayload(new StringReader(str),GHEventPayload.PullRequest.class);
System.out.println(o);
}
}