Compare commits

...

74 Commits

Author SHA1 Message Date
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
Kohsuke Kawaguchi
f6aea47c2c [maven-release-plugin] prepare release github-api-1.19 2012-04-06 08:25:43 -07:00
Kohsuke Kawaguchi
23069ac2fc Using map to enable lookup by name. 2012-04-06 08:24:41 -07:00
Kohsuke Kawaguchi
0b2fdc598b cleaning up 2012-04-06 08:22:44 -07:00
Frederik Fix
aac30b923c implement listing of branches 2012-04-05 16:21:24 +02:00
Kohsuke Kawaguchi
318caf6123 [maven-release-plugin] prepare for next development iteration 2012-03-08 12:51:59 -08:00
Kohsuke Kawaguchi
6898893ffb [maven-release-plugin] prepare release github-api-1.18 2012-03-08 12:51:54 -08:00
Kohsuke Kawaguchi
cb34f4e91f Merge branch 'pull-6' 2012-03-08 12:50:09 -08:00
Kohsuke Kawaguchi
0da4b320f1 As per Sun coding convention, using upper case letters for enum constants 2012-03-08 12:49:36 -08:00
Kohsuke Kawaguchi
facb5bd13e Added API to handle emails. 2012-03-08 12:47:11 -08:00
Kohsuke Kawaguchi
22bc768868 [maven-release-plugin] prepare for next development iteration 2012-02-12 09:30:37 -08:00
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
y.kokubo
fbcf3a17b4 implmented milestone api via v3 2012-01-13 14:36:00 +09: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
Kohsuke Kawaguchi
3e2c9f86da [maven-release-plugin] prepare release github-api-1.16 2012-01-03 11:16:10 -08:00
Kohsuke Kawaguchi
44cc3c8556 added OAuth support to V3 API calls 2012-01-03 11:14:33 -08:00
Kohsuke Kawaguchi
bf65a746b0 inheriting javadoc and designating as overridden 2012-01-03 11:12:18 -08:00
Joshua Krall
4a4a469ca4 Fix for finding private repos on organizations,
requires authenticated API call
2012-01-02 19:45:58 -06:00
Kohsuke Kawaguchi
46b2e5dd32 bug fix 2012-01-02 10:46:54 -08:00
Kohsuke Kawaguchi
8a7adf876f added support for a hook 2012-01-02 10:39:16 -08:00
Kohsuke Kawaguchi
c16ebe4fec bug fix 2012-01-01 18:38:03 -08:00
Kohsuke Kawaguchi
4c61f79e5c bug fix 2012-01-01 18:07:27 -08:00
Kohsuke Kawaguchi
646dc17e5d typo 2012-01-01 18:07:07 -08:00
Kohsuke Kawaguchi
7977471458 Pulled up more properties to the base type 2012-01-01 17:43:44 -08:00
Kohsuke Kawaguchi
9ac2ad957b needs to send auth header 2012-01-01 13:11:24 -08:00
Kohsuke Kawaguchi
7fead8171d improving the usability by introducing a set with lookup-by-ID 2012-01-01 11:13:09 -08:00
Kohsuke Kawaguchi
70bc5c59e2 added a method to return organizations of the user 2012-01-01 09:01:33 -08:00
Kohsuke Kawaguchi
06ef6e841d [maven-release-plugin] prepare for next development iteration 2011-12-31 21:20:06 -08:00
Kohsuke Kawaguchi
2bee34da59 [maven-release-plugin] prepare release github-api-1.15 2011-12-31 21:20:00 -08:00
Kohsuke Kawaguchi
74415b14be bug fix in the error handling 2011-12-19 17:47:08 -08:00
Kohsuke Kawaguchi
1e9a68a16b fixed the behaviour in case the repository doesn't exist. 2011-12-19 17:07:56 -08:00
Kohsuke Kawaguchi
7a3127ed65 added a method to list all members 2011-12-19 17:07:56 -08:00
Kohsuke Kawaguchi
11b0ac19fd PUT apparently requires some payload. 2011-12-19 17:07:56 -08:00
Michael O'Cleirigh
c8eab1f53e OAuth related changes to getMyself() and getUser (username)
Changed how getMyself() works so that in the OAuth case it will use the username less url.

Changed how getUser (username) works in the OAuth case so that you can lookup the specific username using the OAuth token.

Previously getUser (username) would always return the 'myself' user.
2011-11-19 01:08:01 -05:00
Kohsuke Kawaguchi
1c613c5f07 added a method to publicize/conceal 2011-11-15 15:26:47 -08:00
Kohsuke Kawaguchi
b8a63541e8 v3 API changed the meaning of the 'url' field. 2011-11-10 14:30:27 -08:00
Kohsuke Kawaguchi
f25083fde1 more test 2011-11-10 14:25:59 -08:00
Kohsuke Kawaguchi
c86c974400 GHOrganization.createRepository broke, so I fixed that by using V3 API.
That didn't quite work because v3 API has owner as an object, whereas v2 API has owner as string.

So then I fixed all the other retrieval points for GHRepository to use v3.
2011-11-10 14:23:36 -08:00
Kohsuke Kawaguchi
0ad419b026 [maven-release-plugin] prepare for next development iteration 2011-10-26 17:39:15 -07:00
Kohsuke Kawaguchi
a395697980 [maven-release-plugin] prepare release github-api-1.14 2011-10-26 17:39:10 -07:00
Kohsuke Kawaguchi
834a6eb865 GitHub has changed the element ID 2011-10-26 17:38:51 -07:00
Kohsuke Kawaguchi
e1c6927fd8 [maven-release-plugin] prepare for next development iteration 2011-09-15 11:44:31 -07:00
Kohsuke Kawaguchi
f02f2d7585 [maven-release-plugin] prepare release github-api-1.13 2011-09-15 11:44:20 -07:00
Kohsuke Kawaguchi
d4c099d447 Merge pull request #2 from lacostej/lacostej-master
expose issue_updated_at. It looks like a better representation of update
2011-09-06 13:28:30 -07:00
Jerome Lacoste (f19)
5756133840 expose issue_updated_at. It looks like a better representation of update time for an pull request than updated_at 2011-09-06 13:07:50 +02:00
Kohsuke Kawaguchi
6cc966adee [maven-release-plugin] prepare for next development iteration 2011-08-26 18:43:22 -07:00
28 changed files with 1370 additions and 375 deletions

33
pom.xml
View File

@@ -3,11 +3,11 @@
<parent> <parent>
<groupId>org.kohsuke</groupId> <groupId>org.kohsuke</groupId>
<artifactId>pom</artifactId> <artifactId>pom</artifactId>
<version>1</version> <version>3</version>
</parent> </parent>
<artifactId>github-api</artifactId> <artifactId>github-api</artifactId>
<version>1.12</version> <version>1.21</version>
<name>GitHub API for Java</name> <name>GitHub API for Java</name>
<url>http://github-api.kohsuke.org/</url> <url>http://github-api.kohsuke.org/</url>
<description>GitHub API for Java</description> <description>GitHub API for Java</description>
@@ -25,6 +25,23 @@
</site> </site>
</distributionManagement> </distributionManagement>
<build>
<plugins>
<plugin>
<groupId>com.infradna.tool</groupId>
<artifactId>bridge-method-injector</artifactId>
<version>1.2</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.jvnet.hudson</groupId> <groupId>org.jvnet.hudson</groupId>
@@ -54,6 +71,18 @@
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>
<version>1.4</version> <version>1.4</version>
</dependency> </dependency>
<dependency>
<groupId>com.infradna.tool</groupId>
<artifactId>bridge-method-annotation</artifactId>
<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> </dependencies>
<reporting> <reporting>

View File

@@ -6,12 +6,19 @@ package org.kohsuke.github;
* @author Kohsuke Kawaguchi * @author Kohsuke Kawaguchi
*/ */
enum ApiVersion { 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) { ApiVersion(String templateUrl) {
this.url = url; this.templateUrl = templateUrl;
}
public String getApiVersionBaseUrl(String githubServer) {
return templateUrl.replaceFirst("\\?", githubServer);
} }
} }

View File

@@ -0,0 +1,51 @@
package org.kohsuke.github;
/**
* A branch in a repository.
*
* @author Yusuke Kokubo
*/
public class GHBranch {
private GitHub root;
private GHRepository owner;
private String name;
private Commit commit;
public static class Commit {
String sha,url;
}
public GitHub getRoot() {
return root;
}
/**
* Repository that this branch is in.
*/
public GHRepository getOwner() {
return owner;
}
public String getName() {
return name;
}
/**
* The commit that this branch currently points to.
*/
public String getSHA1() {
return commit.sha;
}
@Override
public String toString() {
return "Branch:" + name + " in " + owner.getUrl();
}
/*package*/ GHBranch wrap(GHRepository repo) {
this.owner = repo;
this.root = repo.root;
return this;
}
}

View File

@@ -31,7 +31,7 @@ package org.kohsuke.github;
public class GHCommitPointer { public class GHCommitPointer {
private String ref, sha, label; private String ref, sha, label;
private GHUser user; private GHUser user;
private GHRepository repository; private GHRepository repository/*V2*/,repo/*V3*/;
/** /**
* This points to the user who owns * This points to the user who owns
@@ -45,11 +45,11 @@ public class GHCommitPointer {
* The repository that contains the commit. * The repository that contains the commit.
*/ */
public GHRepository getRepository() { 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() { public String getRef() {
return ref; return ref;
@@ -68,4 +68,10 @@ public class GHCommitPointer {
public String getLabel() { public String getLabel() {
return label; 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,28 @@
package org.kohsuke.github;
/**
* Hook event type.
*
* See http://developer.github.com/v3/events/types/
*
* @author Kohsuke Kawaguchi
*/
public enum GHEvent {
COMMIT_COMMENT,
CREATE,
DELETE,
DOWNLOAD,
FOLLOW,
FORK,
FORK_APPLY,
GIST,
GOLLUM,
ISSUE_COMMENT,
ISSUES,
MEMBER,
PUBLIC,
PULL_REQUEST,
PUSH,
TEAM_ADD,
WATCH
}

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

@@ -0,0 +1,60 @@
package org.kohsuke.github;
import java.io.IOException;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* @author Kohsuke Kawaguchi
*/
public final class GHHook {
/**
* Repository that the hook belongs to.
*/
/*package*/ transient GHRepository repository;
String created_at, updated_at, name;
List<String> events;
boolean active;
Map<String,String> config;
int id;
/*package*/ GHHook wrap(GHRepository owner) {
this.repository = owner;
return this;
}
public String getName() {
return name;
}
public EnumSet<GHEvent> getEvents() {
EnumSet<GHEvent> s = EnumSet.noneOf(GHEvent.class);
for (String e : events)
Enum.valueOf(GHEvent.class,e.toUpperCase(Locale.ENGLISH));
return s;
}
public boolean isActive() {
return active;
}
public Map<String, String> getConfig() {
return Collections.unmodifiableMap(config);
}
public int getId() {
return id;
}
/**
* Deletes this hook.
*/
public void delete() throws IOException {
new Poster(repository.root,ApiVersion.V3).withCredential()
.to(String.format("/repos/%s/%s/hooks/%d",repository.getOwnerName(),repository.getName(),id),null,"DELETE");
}
}

View File

@@ -0,0 +1,73 @@
package org.kohsuke.github;
import java.util.Date;
import java.util.Locale;
/**
*
* @author Yusuke Kokubo
*
*/
public class GHMilestone {
GitHub root;
GHRepository owner;
GHUser creator;
private String state, due_on, title, url, created_at, description;
private int closed_issues, open_issues, number;
public GitHub getRoot() {
return root;
}
public GHRepository getOwner() {
return owner;
}
public GHUser getCreator() {
return creator;
}
public Date getDueOn() {
if (due_on == null) return null;
return GitHub.parseDate(due_on);
}
public String getTitle() {
return title;
}
public String getUrl() {
return url;
}
public Date getCreatedAt() {
return GitHub.parseDate(created_at);
}
public String getDescription() {
return description;
}
public int getClosedIssues() {
return closed_issues;
}
public int getOpenIssues() {
return open_issues;
}
public int getNumber() {
return number;
}
public GHMilestoneState getState() {
return Enum.valueOf(GHMilestoneState.class, state.toUpperCase(Locale.ENGLISH));
}
public GHMilestone wrap(GHRepository repo) {
this.owner = repo;
this.root = repo.root;
return this;
}
}

View File

@@ -0,0 +1,11 @@
package org.kohsuke.github;
/**
*
* @author Yusuke Kokubo
*
*/
public enum GHMilestoneState {
OPEN,
CLOSED
}

View File

@@ -0,0 +1,34 @@
package org.kohsuke.github;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Represents the account that's logging into GitHub.
*
* @author Kohsuke Kawaguchi
*/
public class GHMyself extends GHUser {
/**
* Returns the read-only list of e-mail addresses configured for you.
*
* This corresponds to the stuff you configure in https://github.com/settings/emails,
* and not to be confused with {@link #getEmail()} that shows your public e-mail address
* set in https://github.com/settings/profile
*
* @return
* Always non-null.
*/
public List<String> getEmails() throws IOException {
String[] addresses = root.retrieveWithAuth3("/user/emails",String[].class);
return Collections.unmodifiableList(Arrays.asList(addresses));
}
// public void addEmails(Collection<String> emails) throws IOException {
//// new Poster(root,ApiVersion.V3).withCredential().to("/user/emails");
// root.retrieveWithAuth3()
// }
}

View File

@@ -2,16 +2,19 @@ package org.kohsuke.github;
import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor; import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.gargoylesoftware.htmlunit.html.HtmlPage;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.AbstractList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static org.kohsuke.github.ApiVersion.*;
/** /**
* @author Kohsuke Kawaguchi * @author Kohsuke Kawaguchi
*/ */
@@ -23,23 +26,14 @@ public class GHOrganization extends GHPerson {
* Newly created repository. * Newly created repository.
*/ */
public GHRepository createRepository(String name, String description, String homepage, String team, boolean isPublic) throws IOException { public GHRepository createRepository(String name, String description, String homepage, String team, boolean isPublic) throws IOException {
return createRepository(name,description,homepage,getTeams().get(team),isPublic);
}
public GHRepository createRepository(String name, String description, String homepage, GHTeam team, boolean isPublic) throws IOException {
// such API doesn't exist, so fall back to HTML scraping // such API doesn't exist, so fall back to HTML scraping
WebClient wc = root.createWebClient(); return new Poster(root,V3).withCredential()
HtmlPage pg = (HtmlPage)wc.getPage("https://github.com/organizations/"+login+"/repositories/new"); .with("name", name).with("description", description).with("homepage", homepage)
HtmlForm f = pg.getForms().get(1); .with("public", isPublic).with("team_id",team.getId()).to("/orgs/"+login+"/repos", GHRepository.class).wrap(root);
f.getInputByName("repository[name]").setValueAttribute(name);
f.getInputByName("repository[description]").setValueAttribute(description);
f.getInputByName("repository[homepage]").setValueAttribute(homepage);
f.getSelectByName("team_id").getOptionByText(team).setSelected(true);
f.submit(f.getButtonByCaption("Create Repository"));
return getRepository(name);
// GHRepository r = new Poster(root).withCredential()
// .with("name", name).with("description", description).with("homepage", homepage)
// .with("public", isPublic ? 1 : 0).to(root.getApiURL("/organizations/"+login+"/repos/create"), JsonRepository.class).repository;
// r.root = root;
// return r;
} }
/** /**
@@ -49,6 +43,54 @@ public class GHOrganization extends GHPerson {
return root.retrieveWithAuth("/organizations/"+login+"/teams",JsonTeams.class).toMap(this); return root.retrieveWithAuth("/organizations/"+login+"/teams",JsonTeams.class).toMap(this);
} }
@Override
public GHRepository getRepository(String name) throws IOException {
try {
return root.retrieveWithAuth3("/repos/" + login + '/' + name, GHRepository.class).wrap(root);
} catch (FileNotFoundException e) {
return null;
}
}
/**
* Publicizes the membership.
*/
public void publicize(GHUser u) throws IOException {
root.retrieveWithAuth3("/orgs/" + login + "/public_members/" + u.getLogin(), null, "PUT");
}
/**
* All the members of this organization.
*/
public List<GHUser> getMembers() throws IOException {
return new AbstractList<GHUser>() {
// these are shallow objects with only some limited values filled out
// TODO: it's better to allow objects to fill themselves in later when missing values are requested
final GHUser[] shallow = root.retrieveWithAuth3("/orgs/" + login + "/members", GHUser[].class);
@Override
public GHUser get(int index) {
try {
return root.getUser(shallow[index].getLogin());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public int size() {
return shallow.length;
}
};
}
/**
* Conceals the membership.
*/
public void conceal(GHUser u) throws IOException {
root.retrieveWithAuth3("/orgs/" + login + "/public_members/" + u.getLogin(), null, "DELETE");
}
public enum Permission { ADMIN, PUSH, PULL } public enum Permission { ADMIN, PUSH, PULL }
/** /**

View File

@@ -1,10 +1,16 @@
package org.kohsuke.github; package org.kohsuke.github;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
import static org.kohsuke.github.ApiVersion.*;
/** /**
* Common part of {@link GHUser} and {@link GHOrganization}. * Common part of {@link GHUser} and {@link GHOrganization}.
* *
@@ -13,36 +19,167 @@ import java.util.TreeMap;
public abstract class GHPerson { public abstract class GHPerson {
/*package almost final*/ GitHub root; /*package almost final*/ GitHub root;
protected String gravatar_id,login; // common
protected String login,location,blog,email,name,created_at,company;
protected int id;
protected String gravatar_id; // appears in V3 as well but presumably subsumed by avatar_url?
protected int public_gist_count,public_repo_count,following_count,id; // V2
protected int public_gist_count,public_repo_count,followers_count,following_count;
// V3
protected String avatar_url,html_url;
protected int followers,following,public_repos,public_gists;
/** /**
* Gets the repositories this user owns. * Gets the repositories this user owns.
*/ */
public synchronized Map<String,GHRepository> getRepositories() throws IOException { public synchronized Map<String,GHRepository> getRepositories() throws IOException {
Map<String,GHRepository> repositories = new TreeMap<String, GHRepository>(); Map<String,GHRepository> repositories = new TreeMap<String, GHRepository>();
for (int i=1; ; i++) { for (List<GHRepository> batch : iterateRepositories(100)) {
Map<String, GHRepository> map = root.retrieve("/repos/show/" + login + "?page=" + i, JsonRepositories.class).wrap(root); for (GHRepository r : batch)
repositories.putAll(map); repositories.put(r.getName(),r);
if (map.isEmpty()) break;
} }
return Collections.unmodifiableMap(repositories); 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
* null if the repository was not found
*/
public GHRepository getRepository(String name) throws IOException { public GHRepository getRepository(String name) throws IOException {
return root.retrieve("/repos/show/" + login + '/' + name, JsonRepository.class).wrap(root); try {
return root.retrieve3("/repos/" + login + '/' + name, GHRepository.class).wrap(root);
} catch (FileNotFoundException e) {
return null;
}
} }
/** /**
* Gravatar ID of this user, like 0cb9832a01c22c083390f3c5dcb64105 * Gravatar ID of this user, like 0cb9832a01c22c083390f3c5dcb64105
*
* @deprecated
* No longer available in the v3 API.
*/ */
public String getGravatarId() { public String getGravatarId() {
return gravatar_id; 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'
*/
public String getLogin() { public String getLogin() {
return login; return login;
} }
/**
* Gets the human-readable name of the user, like "Kohsuke Kawaguchi"
*/
public String getName() {
return name;
}
/**
* Gets the company name of this user, like "Sun Microsystems, Inc."
*/
public String getCompany() {
return company;
}
/**
* Gets the location of this user, like "Santa Clara, California"
*/
public String getLocation() {
return location;
}
public String getCreatedAt() {
return created_at;
}
/**
* Gets the blog URL of this user.
*/
public String getBlog() {
return blog;
}
/**
* Gets the e-mail address of the user.
*/
public String getEmail() {
return email;
}
public int getPublicGistCount() {
return Math.max(public_gist_count,public_gists);
}
public int getPublicRepoCount() {
return Math.max(public_repo_count,public_repos);
}
public int getFollowingCount() {
return Math.max(following_count,following);
}
/**
* What appears to be a GitHub internal unique number that identifies this user.
*/
public int getId() {
return id;
}
public int getFollowersCount() {
return Math.max(followers_count,followers);
}
} }

View File

@@ -0,0 +1,36 @@
package org.kohsuke.github;
import java.util.Collection;
import java.util.HashSet;
/**
* Set of {@link GHPerson} with helper lookup methods.
*
* @author Kohsuke Kawaguchi
*/
public final class GHPersonSet<T extends GHPerson> extends HashSet<T> {
public GHPersonSet() {
}
public GHPersonSet(Collection<? extends T> c) {
super(c);
}
public GHPersonSet(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
}
public GHPersonSet(int initialCapacity) {
super(initialCapacity);
}
/**
* Finds the item by its login.
*/
public T byLogin(String login) {
for (T t : this)
if (t.getLogin().equals(login))
return t;
return null;
}
}

View File

@@ -25,7 +25,6 @@ package org.kohsuke.github;
import java.net.URL; import java.net.URL;
import java.util.Date; import java.util.Date;
import java.util.Locale;
/** /**
* A pull request. * A pull request.
@@ -64,12 +63,16 @@ 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() { public GHCommitPointer getHead() {
return head; return head;
} }
public Date getIssueUpdatedAt() {
return GitHub.parseDate(issue_updated_at);
}
/** /**
* The HTML page of this pull request, * The HTML page of this pull request,
* like https://github.com/jenkinsci/jenkins/pull/100 * like https://github.com/jenkinsci/jenkins/pull/100
@@ -89,4 +92,19 @@ public class GHPullRequest extends GHIssue {
public Date getClosedAt() { public Date getClosedAt() {
return GitHub.parseDate(closed_at); 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

@@ -30,22 +30,26 @@ import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput;
import com.gargoylesoftware.htmlunit.html.HtmlForm; import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlInput; import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.net.URL; import java.net.URL;
import java.util.AbstractSet; import java.util.AbstractSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeMap;
import static java.util.Arrays.*; import static java.util.Arrays.*;
import static org.kohsuke.github.ApiVersion.V3; import static org.kohsuke.github.ApiVersion.V3;
@@ -59,10 +63,16 @@ import static org.kohsuke.github.ApiVersion.V3;
public class GHRepository { public class GHRepository {
/*package almost final*/ GitHub root; /*package almost final*/ GitHub root;
private String description, homepage, url, name, owner; private String description, homepage, name;
private String url; // this is the API url
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 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 String created_at, pushed_at;
private Map<Integer,GHMilestone> milestones = new HashMap<Integer, GHMilestone>();
private String master_branch;
public String getDescription() { public String getDescription() {
return description; return description;
@@ -73,26 +83,45 @@ 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() { public String getUrl() {
return url; 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() { public String getName() {
return name; return name;
} }
public GHUser getOwner() throws IOException { public GHUser getOwner() throws IOException {
return root.getUser(owner); return root.getUser(owner.login); // because 'owner' isn't fully populated
} }
public List<GHIssue> getIssues(GHIssueState state) throws IOException { public List<GHIssue> getIssues(GHIssueState state) throws IOException {
return root.retrieve("/issues/list/" + owner + "/" + name + "/" + state.toString().toLowerCase(), JsonIssues.class).wrap(this); return root.retrieve("/issues/list/" + owner.login + "/" + name + "/" + state.toString().toLowerCase(), JsonIssues.class).wrap(this);
} }
protected String getOwnerName() { protected String getOwnerName() {
return owner; return owner.login;
} }
public boolean hasIssues() { public boolean hasIssues() {
@@ -135,16 +164,30 @@ public class GHRepository {
return GitHub.parseDate(created_at); 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. * Gets the collaborators on this repository.
* This set always appear to include the owner. * This set always appear to include the owner.
*/ */
public Set<GHUser> getCollaborators() throws IOException { @WithBridgeMethods(Set.class)
Set<GHUser> r = new HashSet<GHUser>(); public GHPersonSet<GHUser> getCollaborators() throws IOException {
for (String u : root.retrieve("/repos/show/"+owner+"/"+name+"/collaborators",JsonCollaborators.class).collaborators) GHPersonSet<GHUser> r = new GHPersonSet<GHUser>();
for (String u : root.retrieve("/repos/show/"+owner.login+"/"+name+"/collaborators",JsonCollaborators.class).collaborators)
r.add(root.getUser(u)); r.add(root.getUser(u));
return Collections.unmodifiableSet(r); return r;
} }
/** /**
@@ -152,7 +195,7 @@ public class GHRepository {
* This method deviates from the principle of this library but it works a lot faster than {@link #getCollaborators()}. * This method deviates from the principle of this library but it works a lot faster than {@link #getCollaborators()}.
*/ */
public Set<String> getCollaboratorNames() throws IOException { public Set<String> getCollaboratorNames() throws IOException {
Set<String> r = new HashSet<String>(root.retrieve("/repos/show/"+owner+"/"+name+"/collaborators",JsonCollaborators.class).collaborators); Set<String> r = new HashSet<String>(root.retrieve("/repos/show/"+owner.login+"/"+name+"/collaborators",JsonCollaborators.class).collaborators);
return Collections.unmodifiableSet(r); return Collections.unmodifiableSet(r);
} }
@@ -160,8 +203,8 @@ public class GHRepository {
* If this repository belongs to an organization, return a set of teams. * If this repository belongs to an organization, return a set of teams.
*/ */
public Set<GHTeam> getTeams() throws IOException { public Set<GHTeam> getTeams() throws IOException {
return Collections.unmodifiableSet(root.retrieveWithAuth("/repos/show/"+owner+"/"+name+"/teams",JsonTeams.class).toSet( return Collections.unmodifiableSet(root.retrieveWithAuth("/repos/show/"+owner.login+"/"+name+"/teams",JsonTeams.class).toSet(
root.getOrganization(owner))); root.getOrganization(owner.login)));
} }
public void addCollaborators(GHUser... users) throws IOException { public void addCollaborators(GHUser... users) throws IOException {
@@ -190,9 +233,9 @@ public class GHRepository {
public void setEmailServiceHook(String address) throws IOException { public void setEmailServiceHook(String address) throws IOException {
WebClient wc = root.createWebClient(); WebClient wc = root.createWebClient();
HtmlPage pg = (HtmlPage)wc.getPage(getUrl()+"/admin"); HtmlPage pg = (HtmlPage)wc.getPage(getUrl()+"/admin");
HtmlInput email = (HtmlInput)pg.getElementById("Email_address"); HtmlInput email = (HtmlInput)pg.getElementById("email_address");
email.setValueAttribute(address); email.setValueAttribute(address);
HtmlCheckBoxInput active = (HtmlCheckBoxInput)pg.getElementById("Email[active]"); HtmlCheckBoxInput active = (HtmlCheckBoxInput)pg.getElementById("email[active]");
active.setChecked(true); active.setChecked(true);
final HtmlForm f = email.getEnclosingFormOrDie(); final HtmlForm f = email.getEnclosingFormOrDie();
@@ -204,7 +247,7 @@ public class GHRepository {
*/ */
public void enableIssueTracker(boolean v) throws IOException { public void enableIssueTracker(boolean v) throws IOException {
new Poster(root).withCredential().with("values[has_issues]",String.valueOf(v)) new Poster(root).withCredential().with("values[has_issues]",String.valueOf(v))
.to("/repos/show/" + owner + "/" + name); .to("/repos/show/" + owner.login + "/" + name);
} }
/** /**
@@ -212,7 +255,7 @@ public class GHRepository {
*/ */
public void enableWiki(boolean v) throws IOException { public void enableWiki(boolean v) throws IOException {
new Poster(root).withCredential().with("values[has_wiki]",String.valueOf(v)) new Poster(root).withCredential().with("values[has_wiki]",String.valueOf(v))
.to("/repos/show/" + owner + "/" + name); .to("/repos/show/" + owner.login + "/" + name);
} }
/** /**
@@ -220,7 +263,7 @@ public class GHRepository {
*/ */
public void delete() throws IOException { public void delete() throws IOException {
Poster poster = new Poster(root).withCredential(); Poster poster = new Poster(root).withCredential();
String url = "/repos/delete/" + owner +"/"+name; String url = "/repos/delete/" + owner.login +"/"+name;
DeleteToken token = poster.to(url, DeleteToken.class); DeleteToken token = poster.to(url, DeleteToken.class);
poster.with("delete_token",token.delete_token).to(url); poster.with("delete_token",token.delete_token).to(url);
@@ -233,7 +276,7 @@ public class GHRepository {
* Newly forked repository that belong to you. * Newly forked repository that belong to you.
*/ */
public GHRepository fork() throws IOException { public GHRepository fork() throws IOException {
return new Poster(root).withCredential().to("/repos/fork/" + owner + "/" + name, JsonRepository.class).wrap(root); return new Poster(root,V3).withCredential().to("/repos/" + owner.login + "/" + name + "/forks", GHRepository.class, "POST").wrap(root);
} }
/** /**
@@ -243,7 +286,7 @@ public class GHRepository {
* Newly forked repository that belong to you. * Newly forked repository that belong to you.
*/ */
public GHRepository forkTo(GHOrganization org) throws IOException { public GHRepository forkTo(GHOrganization org) throws IOException {
new Poster(root, V3).withCredential().to(String.format("/repos/%s/%s/forks?org=%s",owner,name,org.getLogin())); new Poster(root, V3).withCredential().to(String.format("/repos/%s/%s/forks?org=%s",owner.login,name,org.getLogin()));
return org.getRepository(name); return org.getRepository(name);
} }
@@ -277,21 +320,76 @@ public class GHRepository {
} }
} }
throw new IllegalArgumentException("Either you don't have the privilege to rename "+owner+'/'+name+" or there's a bug in HTML scraping"); throw new IllegalArgumentException("Either you don't have the privilege to rename "+owner.login+'/'+name+" or there's a bug in HTML scraping");
} }
/** /**
* Retrieves a specified pull request. * Retrieves a specified pull request.
*/ */
public GHPullRequest getPullRequest(int i) throws IOException { public GHPullRequest getPullRequest(int i) throws IOException {
return root.retrieveWithAuth("/pulls/" + owner + '/' + 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. * Retrieves all the pull requests of a particular state.
*/ */
public List<GHPullRequest> getPullRequests(GHIssueState state) throws IOException { public List<GHPullRequest> getPullRequests(GHIssueState state) throws IOException {
return root.retrieveWithAuth("/pulls/"+owner+'/'+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));
}
/**
* Retrieves the currently configured hooks.
*/
public List<GHHook> getHooks() throws IOException {
List<GHHook> list = new ArrayList<GHHook>(Arrays.asList(
root.retrieveWithAuth3(String.format("/repos/%s/%s/hooks",owner.login,name),GHHook[].class)));
for (GHHook h : list)
h.wrap(this);
return list;
}
public GHHook getHook(int id) throws IOException {
return root.retrieveWithAuth3(String.format("/repos/%s/%s/hooks/%d",owner.login,name,id),GHHook.class).wrap(this);
}
/**
*
* See https://api.github.com/hooks for possible names and their configuration scheme.
* TODO: produce type-safe binding
*
* @param name
* Type of the hook to be created. See https://api.github.com/hooks for possible names.
* @param config
* The configuration hash.
* @param events
* Can be null. Types of events to hook into.
*/
public GHHook createHook(String name, Map<String,String> config, Collection<GHEvent> events, boolean active) throws IOException {
List<String> ea = null;
if (events!=null) {
ea = new ArrayList<String>();
for (GHEvent e : events)
ea.add(e.name().toLowerCase(Locale.ENGLISH));
}
return new Poster(root,ApiVersion.V3)
.withCredential()
.with("name",name)
.with("active", active)
._with("config", config)
._with("events",ea)
.to(String.format("/repos/%s/%s/hooks",owner.login,this.name),GHHook.class).wrap(this);
}
public GHHook createWebHook(URL url, Collection<GHEvent> events) throws IOException {
return createHook("web",Collections.singletonMap("url",url.toExternalForm()),events,true);
}
public GHHook createWebHook(URL url) throws IOException {
return createWebHook(url,null);
} }
// this is no different from getPullRequests(OPEN) // this is no different from getPullRequests(OPEN)
@@ -303,13 +401,16 @@ public class GHRepository {
// } // }
private void verifyMine() throws IOException { private void verifyMine() throws IOException {
if (!root.login.equals(owner)) if (!root.login.equals(owner.login))
throw new IOException("Operation not applicable to a repository owned by someone else: "+owner); throw new IOException("Operation not applicable to a repository owned by someone else: "+owner.login);
} }
/** /**
* Returns a set that represents the post-commit hook URLs. * Returns a set that represents the post-commit hook URLs.
* The returned set is live, and changes made to them are reflected to GitHub. * The returned set is live, and changes made to them are reflected to GitHub.
*
* @deprecated
* Use {@link #getHooks()} and {@link #createHook(String, Map, Collection, boolean)}
*/ */
public Set<URL> getPostCommitHooks() { public Set<URL> getPostCommitHooks() {
return postCommitHooks; return postCommitHooks;
@@ -321,15 +422,11 @@ public class GHRepository {
private final Set<URL> postCommitHooks = new AbstractSet<URL>() { private final Set<URL> postCommitHooks = new AbstractSet<URL>() {
private List<URL> getPostCommitHooks() { private List<URL> getPostCommitHooks() {
try { try {
verifyMine();
HtmlForm f = getForm();
List<URL> r = new ArrayList<URL>(); List<URL> r = new ArrayList<URL>();
for (HtmlInput i : f.getInputsByName("urls[]")) { for (GHHook h : getHooks()) {
String v = i.getValueAttribute(); if (h.getName().equals("web")) {
if (v.length()==0) continue; r.add(new URL(h.getConfig().get("url")));
r.add(new URL(v)); }
} }
return r; return r;
} catch (IOException e) { } catch (IOException e) {
@@ -350,22 +447,7 @@ public class GHRepository {
@Override @Override
public boolean add(URL url) { public boolean add(URL url) {
try { try {
String u = url.toExternalForm(); createWebHook(url);
verifyMine();
HtmlForm f = getForm();
List<HtmlInput> controls = f.getInputsByName("urls[]");
for (HtmlInput i : controls) {
String v = i.getValueAttribute();
if (v.length()==0) continue;
if (v.equals(u))
return false; // already there
}
controls.get(controls.size()-1).setValueAttribute(u);
f.submit(null);
return true; return true;
} catch (IOException e) { } catch (IOException e) {
throw new GHException("Failed to update post-commit hooks",e); throw new GHException("Failed to update post-commit hooks",e);
@@ -373,43 +455,70 @@ public class GHRepository {
} }
@Override @Override
public boolean remove(Object o) { public boolean remove(Object url) {
try { try {
String u = ((URL)o).toExternalForm(); String _url = ((URL)url).toExternalForm();
for (GHHook h : getHooks()) {
verifyMine(); if (h.getName().equals("web") && h.getConfig().get("url").equals(_url)) {
h.delete();
HtmlForm f = getForm();
List<HtmlInput> controls = f.getInputsByName("urls[]");
for (HtmlInput i : controls) {
String v = i.getValueAttribute();
if (v.length()==0) continue;
if (v.equals(u)) {
i.setValueAttribute("");
f.submit(null);
return true; return true;
} }
} }
return false; return false;
} catch (IOException e) { } catch (IOException e) {
throw new GHException("Failed to update post-commit hooks",e); throw new GHException("Failed to update post-commit hooks",e);
} }
} }
private HtmlForm getForm() throws IOException {
WebClient wc = root.createWebClient();
HtmlPage pg = (HtmlPage)wc.getPage(getUrl()+"/admin");
HtmlForm f = (HtmlForm) pg.getElementById("new_service");
return f;
}
}; };
/*package*/ GHRepository wrap(GitHub root) {
this.root = root;
return this;
}
/**
* Gets branches by {@linkplain GHBranch#getName() their names}.
*/
public Map<String,GHBranch> getBranches() throws IOException {
Map<String,GHBranch> r = new TreeMap<String,GHBranch>();
for (GHBranch p : root.retrieve3("/repos/"+owner.login+"/"+name+"/branches", GHBranch[].class)) {
p.wrap(this);
r.put(p.getName(),p);
}
return r;
}
public Map<Integer, GHMilestone> getMilestones() throws IOException {
Map<Integer,GHMilestone> milestones = new TreeMap<Integer, GHMilestone>();
GHMilestone[] ms = root.retrieve3("/repos/"+owner.login+"/"+name+"/milestones", GHMilestone[].class);
for (GHMilestone m : ms) {
m.owner = this;
m.root = root;
milestones.put(m.getNumber(), m);
}
return milestones;
}
public GHMilestone getMilestone(int number) throws IOException {
GHMilestone m = milestones.get(number);
if (m == null) {
m = root.retrieve3("/repos/"+owner.login+"/"+name+"/milestones/"+number, GHMilestone.class);
m.owner = this;
m.root = root;
milestones.put(m.getNumber(), m);
}
return m;
}
public GHMilestone createMilestone(String title, String description) throws IOException {
return new Poster(root,V3).withCredential()
.with("title", title).with("description", description)
.to("/repos/"+owner.login+"/"+name+"/milestones", GHMilestone.class,"POST").wrap(this);
}
@Override @Override
public String toString() { public String toString() {
return "Repository:"+owner+":"+name; return "Repository:"+owner.login+":"+name;
} }
@Override @Override
@@ -421,7 +530,7 @@ public class GHRepository {
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj instanceof GHRepository) { if (obj instanceof GHRepository) {
GHRepository that = (GHRepository) obj; GHRepository that = (GHRepository) obj;
return this.owner.equals(that.owner) return this.owner.login.equals(that.owner.login)
&& this.name.equals(that.name); && this.name.equals(that.name);
} }
return false; return false;

View File

@@ -35,7 +35,7 @@ public class GHTeam {
} }
public Map<String,GHRepository> getRepositories() throws IOException { public Map<String,GHRepository> getRepositories() throws IOException {
return org.root.retrieveWithAuth(api("/repositories"),JsonRepositories.class).wrap(org.root); return org.root.retrieveWithAuth3(api("/repos"),JsonRepositories.class).wrap(org.root);
} }
/** /**

View File

@@ -23,7 +23,12 @@
*/ */
package org.kohsuke.github; package org.kohsuke.github;
import com.infradna.tool.bridge_method_injector.BridgeMethodsAdded;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import java.io.IOException; import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
@@ -32,77 +37,6 @@ import java.util.Set;
* @author Kohsuke Kawaguchi * @author Kohsuke Kawaguchi
*/ */
public class GHUser extends GHPerson { public class GHUser extends GHPerson {
private String name,company,location,created_at,blog,email;
private int followers_count;
/**
* Gets the human-readable name of the user, like "Kohsuke Kawaguchi"
*/
public String getName() {
return name;
}
/**
* Gets the company name of this user, like "Sun Microsystems, Inc."
*/
public String getCompany() {
return company;
}
/**
* Gets the location of this user, like "Santa Clara, California"
*/
public String getLocation() {
return location;
}
public String getCreatedAt() {
return created_at;
}
/**
* Gets the blog URL of this user.
*/
public String getBlog() {
return blog;
}
/**
* Gets the login ID of this user, like 'kohsuke'
*/
public String getLogin() {
return login;
}
/**
* Gets the e-mail address of the user.
*/
public String getEmail() {
return email;
}
public int getPublicGistCount() {
return public_gist_count;
}
public int getPublicRepoCount() {
return public_repo_count;
}
public int getFollowingCount() {
return following_count;
}
/**
* What appears to be a GitHub internal unique number that identifies this user.
*/
public int getId() {
return id;
}
public int getFollowersCount() {
return followers_count;
}
/** /**
* Follow this user. * Follow this user.
@@ -121,17 +55,33 @@ public class GHUser extends GHPerson {
/** /**
* Lists the users that this user is following * Lists the users that this user is following
*/ */
public Set<GHUser> getFollows() throws IOException { @WithBridgeMethods(Set.class)
public GHPersonSet<GHUser> getFollows() throws IOException {
return root.retrieve("/user/show/"+login+"/following",JsonUsers.class).toSet(root); return root.retrieve("/user/show/"+login+"/following",JsonUsers.class).toSet(root);
} }
/** /**
* Lists the users who are following this user. * Lists the users who are following this user.
*/ */
public Set<GHUser> getFollowers() throws IOException { @WithBridgeMethods(Set.class)
public GHPersonSet<GHUser> getFollowers() throws IOException {
return root.retrieve("/user/show/"+login+"/followers",JsonUsers.class).toSet(root); return root.retrieve("/user/show/"+login+"/followers",JsonUsers.class).toSet(root);
} }
/**
* Gets the organization that this user belongs to publicly.
*/
@WithBridgeMethods(Set.class)
public GHPersonSet<GHOrganization> getOrganizations() throws IOException {
GHPersonSet<GHOrganization> orgs = new GHPersonSet<GHOrganization>();
Set<String> names = new HashSet<String>();
for (GHOrganization o : root.retrieve3("/users/"+login+"/orgs",GHOrganization[].class)) {
if (names.add(o.getLogin())) // I've seen some duplicates in the data
orgs.add(root.getOrganization(o.getLogin()));
}
return orgs;
}
@Override @Override
public String toString() { public String toString() {
return "User:"+login; return "User:"+login;

View File

@@ -23,34 +23,47 @@
*/ */
package org.kohsuke.github; package org.kohsuke.github;
import com.gargoylesoftware.htmlunit.WebClient; import static org.codehaus.jackson.annotate.JsonAutoDetect.Visibility.ANY;
import com.gargoylesoftware.htmlunit.html.HtmlForm; import static org.codehaus.jackson.annotate.JsonAutoDetect.Visibility.NONE;
import com.gargoylesoftware.htmlunit.html.HtmlPage; import static org.kohsuke.github.ApiVersion.V2;
import org.apache.commons.io.IOUtils; import static org.kohsuke.github.ApiVersion.V3;
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 java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.io.Reader;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Collections; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.Iterator;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.TimeZone;
import java.util.zip.GZIPInputStream;
import static org.codehaus.jackson.annotate.JsonAutoDetect.Visibility.*; 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;
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. * Root of the GitHub API.
@@ -65,10 +78,17 @@ public class GitHub {
private final Map<String,GHUser> users = new HashMap<String, GHUser>(); private final Map<String,GHUser> users = new HashMap<String, GHUser>();
private final Map<String,GHOrganization> orgs = new HashMap<String, GHOrganization>(); private final Map<String,GHOrganization> orgs = new HashMap<String, GHOrganization>();
private String oauthAccessToken; /*package*/ String oauthAccessToken;
private final String githubServer;
private GitHub(String login, String apiToken, String password) { private GitHub(String login, String apiToken, String password) {
this.login = login; 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.apiToken = apiToken;
this.password = password; this.password = password;
@@ -80,8 +100,9 @@ public class GitHub {
encodedAuthorization = null; encodedAuthorization = null;
} }
private GitHub (String oauthAccessToken) throws IOException { private GitHub (String githubServer, String oauthAccessToken) throws IOException {
this.githubServer = githubServer;
this.password = null; this.password = null;
this.encodedAuthorization = null; this.encodedAuthorization = null;
@@ -115,7 +136,11 @@ public class GitHub {
} }
public static GitHub connectUsingOAuth (String accessToken) throws IOException { 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. * Connects to GitHub anonymously.
@@ -134,48 +159,173 @@ public class GitHub {
/*package*/ URL getApiURL(ApiVersion v, String tailApiUrl) throws IOException { /*package*/ URL getApiURL(ApiVersion v, String tailApiUrl) throws IOException {
if (oauthAccessToken != null) { if (oauthAccessToken != null) {
// append the access token // append the access token
tailApiUrl = tailApiUrl + "?access_token=" + oauthAccessToken; 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 { /*package*/ <T> T retrieve(String tailApiUrl, Class<T> type) throws IOException {
return _retrieve(tailApiUrl, type, "GET", false); return _retrieve(tailApiUrl, type, "GET", false, V2);
}
/*package*/ <T> T retrieve3(String tailApiUrl, Class<T> type) throws IOException {
return _retrieve(tailApiUrl, type, "GET", false, V3);
} }
/*package*/ <T> T retrieveWithAuth(String tailApiUrl, Class<T> type) throws IOException { /*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 {
return _retrieve(tailApiUrl, type, "GET", true, V3);
} }
/*package*/ <T> T retrieveWithAuth(String tailApiUrl, Class<T> type, String method) throws IOException { /*package*/ <T> T retrieveWithAuth(String tailApiUrl, Class<T> type, String method) throws IOException {
return _retrieve(tailApiUrl, type, method, true); return _retrieve(tailApiUrl, type, method, true, V2);
} }
private <T> T _retrieve(String tailApiUrl, Class<T> type, String method, boolean withAuth) throws IOException { /*package*/ <T> T retrieveWithAuth3(String tailApiUrl, Class<T> type, String method) throws IOException {
return _retrieve(tailApiUrl, type, method, true, V3);
}
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 while (true) {// loop while API rate limit is hit
HttpURLConnection uc = setupConnection(method, withAuth, getApiURL(v, tailApiUrl));
HttpURLConnection uc = (HttpURLConnection) getApiURL(ApiVersion.V2,tailApiUrl).openConnection();
if (withAuth && this.oauthAccessToken == null)
uc.setRequestProperty("Authorization", "Basic " + encodedAuthorization);
uc.setRequestMethod(method);
try { try {
InputStreamReader r = new InputStreamReader(uc.getInputStream(), "UTF-8"); return parse(uc,type);
if (type==null) {
String data = IOUtils.toString(r);
return null;
}
return MAPPER.readValue(r,type);
} catch (IOException e) { } catch (IOException e) {
handleApiError(e,uc); 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. * If the error is because of the API limit, wait 10 sec and return normally.
* Otherwise throw an exception reporting an error. * Otherwise throw an exception reporting an error.
@@ -191,30 +341,47 @@ public class GitHub {
} }
} }
throw (IOException)new IOException(IOUtils.toString(uc.getErrorStream(),"UTF-8")).initCause(e); if (e instanceof FileNotFoundException)
throw e; // pass through 404 Not Found to allow the caller to handle it intelligently
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);
}
} }
/** /**
* Obtains the object that represents the named user. * Gets the {@link GHUser} that represents yourself.
*/ */
public GHUser getUser(String login) throws IOException { @WithBridgeMethods(GHUser.class)
GHUser u = users.get(login); public GHMyself getMyself() throws IOException {
if (u==null) { requireCredential();
if (oauthAccessToken != null) { GHMyself u = retrieveWithAuth3("/user", GHMyself.class);
u = retrieve("/user/show",JsonUser.class).user;
u.root = this; u.root = this;
users.put(u.getLogin(),u); users.put(u.getLogin(), u);
}
else {
u = retrieve("/user/show/"+login,JsonUser.class).user;
u.root = this;
users.put(login,u);
}
}
return u; return u;
} }
/**
* Obtains the object that represents the named user.
*/
public GHUser getUser(String login) throws IOException {
GHUser u = users.get(login);
if (u == null) {
u = retrieve3("/users/" + login, GHUser.class);
u.root = this;
users.put(u.getLogin(), u);
}
return u;
}
/** /**
* Interns the given {@link GHUser}. * Interns the given {@link GHUser}.
@@ -239,18 +406,44 @@ public class GitHub {
return o; return o;
} }
public Map<String, GHOrganization> getMyOrganizations() throws IOException {
return retrieveWithAuth("/organizations",JsonOrganizations.class).wrap(this);
}
/** /**
* Gets the {@link GHUser} that represents yourself. * Gets the repository object from 'user/reponame' string that GitHub calls as "repository name"
*
* @see GHRepository#getName()
*/ */
public GHUser getMyself() throws IOException { public GHRepository getRepository(String name) throws IOException {
requireCredential(); String[] tokens = name.split("/");
return getUser(login); 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;
}
/** /**
* Creates a new repository. * Creates a new repository.
* *
@@ -258,9 +451,9 @@ public class GitHub {
* Newly created repository. * Newly created repository.
*/ */
public GHRepository createRepository(String name, String description, String homepage, boolean isPublic) throws IOException { public GHRepository createRepository(String name, String description, String homepage, boolean isPublic) throws IOException {
return new Poster(this).withCredential() return new Poster(this,V3).withCredential()
.with("name", name).with("description", description).with("homepage", homepage) .with("name", name).with("description", description).with("homepage", homepage)
.with("public", isPublic ? 1 : 0).to("/repos/create", JsonRepository.class).wrap(this); .with("public", isPublic ? 1 : 0).to("/user/repos", GHRepository.class,"POST").wrap(this);
} }
/** /**
@@ -268,7 +461,7 @@ public class GitHub {
*/ */
public boolean isCredentialValid() throws IOException { public boolean isCredentialValid() throws IOException {
try { try {
retrieveWithAuth("/user/show",JsonUser.class); retrieveWithAuth3("/user",GHUser.class);
return true; return true;
} catch (IOException e) { } catch (IOException e) {
return false; return false;
@@ -298,7 +491,9 @@ public class GitHub {
/*package*/ static Date parseDate(String timestamp) { /*package*/ static Date parseDate(String timestamp) {
for (String f : TIME_FORMATS) { for (String f : TIME_FORMATS) {
try { try {
return new SimpleDateFormat(f).parse(timestamp); SimpleDateFormat df = new SimpleDateFormat(f);
df.setTimeZone(TimeZone.getTimeZone("GMT"));
return df.parse(timestamp);
} catch (ParseException e) { } catch (ParseException e) {
// try next // try next
} }

View File

@@ -1,7 +1,7 @@
/* /*
* The MIT License * The MIT License
* *
* Copyright (c) 2010, Kohsuke Kawaguchi * Copyright (c) 2011, Eric Maupin
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -21,11 +21,16 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package org.kohsuke.github; package org.kohsuke.github;
/** /**
* @author Kohsuke Kawaguchi * @author Eric Maupin
*/ */
class JsonUser { class JsonBranch {
public GHUser user; GHBranch branch;
GHBranch wrap(GHRepository r) {
return branch.wrap(r);
}
} }

View File

@@ -1,7 +1,7 @@
/* /*
* The MIT License * The MIT License
* *
* Copyright (c) 2010, Kohsuke Kawaguchi * Copyright (c) 2011, Eric Maupin
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -21,17 +21,20 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package org.kohsuke.github; package org.kohsuke.github;
/** import java.util.List;
* @author Kohsuke Kawaguchi
*/
class JsonPullRequest {
public GHPullRequest pull;
public GHPullRequest wrap(GHRepository owner) { /**
pull.owner = owner; * @author Eric Maupin
pull.root = owner.root; */
return pull; class JsonBranches {
List<GHBranch> branches;
public List<GHBranch> wrap(GHRepository owner) {
for (GHBranch branch : branches)
branch.wrap(owner);
return branches;
} }
} }

View File

@@ -0,0 +1,8 @@
package org.kohsuke.github;
/**
* @author Kohsuke Kawaguchi
*/
class JsonMyself {
GHMyself user;
}

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,36 +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 JsonRepository {
public GHRepository repository;
public GHRepository wrap(GitHub root) {
repository.root = root;
return repository;
}
}

View File

@@ -34,8 +34,8 @@ import java.util.Set;
class JsonUsers { class JsonUsers {
public List<String> users; public List<String> users;
public Set<GHUser> toSet(GitHub root) throws IOException { public GHPersonSet<GHUser> toSet(GitHub root) throws IOException {
Set<GHUser> r = new HashSet<GHUser>(); GHPersonSet<GHUser> r = new GHPersonSet<GHUser>();
for (String u : users) for (String u : users)
r.add(root.getUser(u)); r.add(root.getUser(u));
return r; return r;

View File

@@ -24,6 +24,9 @@
package org.kohsuke.github; package org.kohsuke.github;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.impl.WriterBasedGenerator;
import org.codehaus.jackson.node.ObjectNode;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@@ -33,7 +36,9 @@ import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import static org.kohsuke.github.GitHub.*; import static org.kohsuke.github.GitHub.*;
@@ -43,11 +48,25 @@ import static org.kohsuke.github.GitHub.*;
*/ */
class Poster { class Poster {
private final GitHub root; private final GitHub root;
private final List<String> args = new ArrayList<String>(); private final List<Entry> args = new ArrayList<Entry>();
private boolean authenticate; private boolean authenticate;
private final ApiVersion v; private final ApiVersion v;
private static class Entry {
String key;
Object value;
private Entry(String key, Object value) {
this.key = key;
this.value = value;
}
public String toFormArg() throws UnsupportedEncodingException {
return URLEncoder.encode(key,"UTF-8")+'='+URLEncoder.encode(value.toString(),"UTF-8");
}
}
Poster(GitHub root, ApiVersion v) { Poster(GitHub root, ApiVersion v) {
this.root = root; this.root = root;
this.v = v; this.v = v;
@@ -64,16 +83,20 @@ class Poster {
} }
public Poster with(String key, int value) { public Poster with(String key, int value) {
return with(key,String.valueOf(value)); return _with(key, value);
}
public Poster with(String key, boolean value) {
return _with(key, value);
} }
public Poster with(String key, String value) { public Poster with(String key, String value) {
return _with(key, value);
}
public Poster _with(String key, Object value) {
if (value!=null) { if (value!=null) {
try { args.add(new Entry(key,value));
args.add(URLEncoder.encode(key,"UTF-8")+'='+URLEncoder.encode(value,"UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new Error(e); // impossible
}
} }
return this; return this;
} }
@@ -102,9 +125,13 @@ class Poster {
uc.setRequestProperty("Content-type","application/x-www-form-urlencoded"); uc.setRequestProperty("Content-type","application/x-www-form-urlencoded");
if (authenticate) { if (authenticate) {
if (v==ApiVersion.V3) { if (v==ApiVersion.V3) {
if (root.password==null) if (root.oauthAccessToken!=null) {
throw new IllegalArgumentException("V3 API doesn't support API token"); uc.setRequestProperty("Authorization", "token " + root.oauthAccessToken);
uc.setRequestProperty("Authorization", "Basic " + root.encodedAuthorization); } else {
if (root.password==null)
throw new IllegalArgumentException("V3 API doesn't support API token");
uc.setRequestProperty("Authorization", "Basic " + root.encodedAuthorization);
}
} else { } else {
uc.setRequestProperty("Authorization", "Basic " + root.encodedAuthorization); uc.setRequestProperty("Authorization", "Basic " + root.encodedAuthorization);
} }
@@ -112,23 +139,31 @@ class Poster {
uc.setRequestMethod(method); uc.setRequestMethod(method);
StringBuilder body = new StringBuilder(); if (v==ApiVersion.V2) {
for (String e : args) { StringBuilder body = new StringBuilder();
if (body.length()>0) body.append('&'); for (Entry e : args) {
body.append(e); if (body.length()>0) body.append('&');
} body.append(e.toFormArg());
}
OutputStreamWriter o = new OutputStreamWriter(uc.getOutputStream(), "UTF-8"); OutputStreamWriter o = new OutputStreamWriter(uc.getOutputStream(), "UTF-8");
o.write(body.toString()); o.write(body.toString());
o.close(); o.close();
} else {
Map json = new HashMap();
for (Entry e : args) {
json.put(e.key, e.value);
}
MAPPER.writeValue(uc.getOutputStream(),json);
}
try { try {
InputStreamReader r = new InputStreamReader(uc.getInputStream(), "UTF-8"); InputStreamReader r = new InputStreamReader(uc.getInputStream(), "UTF-8");
String data = IOUtils.toString(r);
if (type==null) { if (type==null) {
String data = IOUtils.toString(r);
return null; return null;
} }
return MAPPER.readValue(r,type); return MAPPER.readValue(data,type);
} catch (IOException e) { } catch (IOException e) {
root.handleApiError(e,uc); root.handleApiError(e,uc);
} }

View File

@@ -1,9 +1,15 @@
package org.kohsuke; package org.kohsuke;
import junit.framework.TestCase; 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.GHBranch;
import org.kohsuke.github.GHIssueState;
import org.kohsuke.github.GHMyself;
import org.kohsuke.github.GHOrganization; import org.kohsuke.github.GHOrganization;
import org.kohsuke.github.GHOrganization.Permission; import org.kohsuke.github.GHOrganization.Permission;
import org.kohsuke.github.GHPullRequest;
import org.kohsuke.github.GHRepository; import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GHTeam; import org.kohsuke.github.GHTeam;
import org.kohsuke.github.GHUser; import org.kohsuke.github.GHUser;
@@ -11,7 +17,9 @@ import org.kohsuke.github.GitHub;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.List;
/** /**
* Unit test for simple App. * Unit test for simple App.
@@ -22,6 +30,22 @@ public class AppTest extends TestCase {
assertFalse(GitHub.connect("totally","bogus").isCredentialValid()); assertFalse(GitHub.connect("totally","bogus").isCredentialValid());
} }
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);
}
public void tryOrgFork() throws Exception { public void tryOrgFork() throws Exception {
GitHub gh = GitHub.connect(); GitHub gh = GitHub.connect();
gh.getUser("kohsuke").getRepository("rubywm").forkTo(gh.getOrganization("jenkinsci")); gh.getUser("kohsuke").getRepository("rubywm").forkTo(gh.getOrganization("jenkinsci"));
@@ -38,9 +62,48 @@ public class AppTest extends TestCase {
Set<String> members = gitHub.getOrganization("jenkinsci").getRepository("violations-plugin").getCollaboratorNames(); Set<String> members = gitHub.getOrganization("jenkinsci").getRepository("violations-plugin").getCollaboratorNames();
System.out.println(members.contains("kohsuke")); System.out.println(members.contains("kohsuke"));
} }
public void testMemberOrgs() throws Exception {
GitHub gitHub = GitHub.connect();
Set<GHOrganization> o = gitHub.getUser("kohsuke").getOrganizations();
System.out.println(o);
}
public void testBranches() throws Exception {
GitHub gitHub = GitHub.connect();
Map<String,GHBranch> b =
gitHub.getUser("jenkinsci").getRepository("jenkins").getBranches();
System.out.println(b);
}
public void tryHook() throws Exception {
GitHub gitHub = GitHub.connect();
GHRepository r = gitHub.getMyself().getRepository("test2");
GHHook hook = r.createWebHook(new URL("http://www.google.com/"));
System.out.println(hook);
for (GHHook h : r.getHooks())
h.delete();
}
public void testEventApi() throws Exception {
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());
}
}
}
public void testApp() throws IOException { public void testApp() throws IOException {
GitHub gitHub = GitHub.connect(); GitHub gitHub = GitHub.connect();
System.out.println(gitHub.getMyself().getEmails());
// GHRepository r = gitHub.connect().getOrganization("jenkinsci").createRepository("kktest4", "Kohsuke's test", "http://kohsuke.org/", "Everyone", true);
// r.fork();
// tryDisablingIssueTrackers(gitHub); // tryDisablingIssueTrackers(gitHub);
@@ -129,10 +192,22 @@ public class AppTest extends TestCase {
System.out.println(hooks); System.out.println(hooks);
} }
private void testOrganization(GitHub gitHub) throws IOException { public void testOrgRepositories() throws IOException {
GHOrganization labs = gitHub.getOrganization("HudsonLabs"); GitHub gitHub = GitHub.connect();
GHTeam t = labs.getTeams().get("Core Developers"); 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");
GHTeam t = j.getTeams().get("Core Developers");
t.add(labs.getRepository("xyz")); assertNotNull(j.getRepository("jenkins"));
// t.add(labs.getRepository("xyz"));
} }
} }

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);
}
}