diff --git a/pom.xml b/pom.xml
index de5ce58c4f..fec4cefb66 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
org.kohsuke
github-api
jar
- 1.2
+ 1.3
GitHub API for Java
http://kohsuke.org/github-api/
GitHub API for Java
@@ -75,6 +75,18 @@
+
+ org.jvnet.hudson
+ htmlunit
+ 2.6-hudson-2
+
+
+
+ xml-apis
+ xml-apis
+
+
+
junit
junit
diff --git a/src/main/java/org/kohsuke/github/GHOrganization.java b/src/main/java/org/kohsuke/github/GHOrganization.java
new file mode 100644
index 0000000000..24405373b6
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GHOrganization.java
@@ -0,0 +1,46 @@
+package org.kohsuke.github;
+
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.html.HtmlForm;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * @author Kohsuke Kawaguchi
+ */
+public class GHOrganization extends GHPerson {
+ /**
+ * Creates a new repository.
+ *
+ * @return
+ * Newly created repository.
+ */
+ public GHRepository createRepository(String name, String description, String homepage, String team, boolean isPublic) throws IOException {
+ // such API doesn't exist, so fall back to HTML scraping
+ WebClient wc = root.createWebClient();
+ HtmlPage pg = (HtmlPage)wc.getPage("https://github.com/organizations/"+login+"/repositories/new");
+ HtmlForm f = pg.getForms().get(1);
+ 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 root.getUser(login).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;
+ }
+
+ /**
+ * Teams by their names.
+ */
+ public Map getTeams() throws IOException {
+ return root.retrieveWithAuth(root.getApiURL("/organizations/"+login+"/teams"),JsonTeams.class).toMap(this);
+ }
+}
diff --git a/src/main/java/org/kohsuke/github/GHPerson.java b/src/main/java/org/kohsuke/github/GHPerson.java
new file mode 100644
index 0000000000..db87442484
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GHPerson.java
@@ -0,0 +1,58 @@
+package org.kohsuke.github;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import static org.kohsuke.github.GitHub.*;
+
+/**
+ * Common part of {@link GHUser} and {@link GHOrganization}.
+ *
+ * @author Kohsuke Kawaguchi
+ */
+public abstract class GHPerson {
+ /*package almost final*/ GitHub root;
+
+ protected String gravatar_id,login;
+
+ protected int public_gist_count,public_repo_count,following_count,id;
+
+ /**
+ * Repositories that this user owns.
+ */
+ private transient Map repositories;
+
+ /**
+ * Gets the repositories this user owns.
+ */
+ public Map getRepositories() throws IOException {
+ if (repositories==null) {
+ repositories = new TreeMap();
+ URL url = new URL("http://github.com/api/v2/json/repos/show/" + login);
+ for (GHRepository r : MAPPER.readValue(url, JsonRepositories.class).repositories) {
+ r.root = root;
+ repositories.put(r.getName(),r);
+ }
+ }
+
+ return Collections.unmodifiableMap(repositories);
+ }
+
+ public GHRepository getRepository(String name) throws IOException {
+ return getRepositories().get(name);
+ }
+
+ /**
+ * Gravatar ID of this user, like 0cb9832a01c22c083390f3c5dcb64105
+ */
+ public String getGravatarId() {
+ return gravatar_id;
+ }
+
+ public String getLogin() {
+ return login;
+ }
+}
diff --git a/src/main/java/org/kohsuke/github/GHRepository.java b/src/main/java/org/kohsuke/github/GHRepository.java
index 69f7711a60..116b454b3e 100644
--- a/src/main/java/org/kohsuke/github/GHRepository.java
+++ b/src/main/java/org/kohsuke/github/GHRepository.java
@@ -64,6 +64,10 @@ public GHUser getOwner() throws IOException {
return root.getUser(owner);
}
+ protected String getOwnerName() {
+ return owner;
+ }
+
public boolean hasIssues() {
return has_issues;
}
@@ -142,9 +146,7 @@ public void delete() throws IOException {
* Forks this repository.
*/
public GHRepository fork() throws IOException {
- GHRepository r = new Poster(root).withCredential().to(root.getApiURL("/repos/fork/" + owner + "/" + name), JsonRepository.class).repository;
- r.root = root;
- return r;
+ return new Poster(root).withCredential().to(root.getApiURL("/repos/fork/" + owner + "/" + name), JsonRepository.class).wrap(root);
}
private void verifyMine() throws IOException {
diff --git a/src/main/java/org/kohsuke/github/GHTeam.java b/src/main/java/org/kohsuke/github/GHTeam.java
new file mode 100644
index 0000000000..84d5efc789
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GHTeam.java
@@ -0,0 +1,62 @@
+package org.kohsuke.github;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Set;
+
+/**
+ * A team in GitHub organization.
+ *
+ * @author Kohsuke Kawaguchi
+ */
+public class GHTeam {
+ private String name,permission;
+ private int id;
+
+ protected /*final*/ GHOrganization org;
+
+ public String getName() {
+ return name;
+ }
+
+ public String getPermission() {
+ return permission;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ /**
+ * Retrieves the current members.
+ */
+ public Set getMembers() throws IOException {
+ return org.root.retrieveWithAuth(getApiURL("/members"),JsonUsersWithDetails.class).toSet(org.root);
+ }
+
+ /**
+ * Adds a member to the team.
+ */
+ public void add(GHUser u) throws IOException {
+ org.root.retrieveWithAuth(getApiURL("/members?name="+u.getLogin()),null, "POST");
+ }
+
+ /**
+ * Removes a member to the team.
+ */
+ public void remove(GHUser u) throws IOException {
+ org.root.retrieveWithAuth(getApiURL("/members?name="+u.getLogin()),null, "DELETE");
+ }
+
+ public void add(GHRepository r) throws IOException {
+ org.root.retrieveWithAuth(getApiURL("/repositories?name="+r.getOwnerName()+'/'+r.getName()),null, "POST");
+ }
+
+ public void remove(GHRepository r) throws IOException {
+ org.root.retrieveWithAuth(getApiURL("/repositories?name="+r.getOwnerName()+'/'+r.getName()),null, "DELETE");
+ }
+
+ private URL getApiURL(String tail) throws IOException {
+ return org.root.getApiURL("/organizations/"+org.getLogin()+"/teams/"+id+tail);
+ }
+}
diff --git a/src/main/java/org/kohsuke/github/GHUser.java b/src/main/java/org/kohsuke/github/GHUser.java
index b439ca4ee8..205b466c0e 100644
--- a/src/main/java/org/kohsuke/github/GHUser.java
+++ b/src/main/java/org/kohsuke/github/GHUser.java
@@ -24,36 +24,16 @@
package org.kohsuke.github;
import java.io.IOException;
-import java.net.URL;
-import java.util.Collections;
-import java.util.Map;
import java.util.Set;
-import java.util.TreeMap;
-
-import static org.kohsuke.github.GitHub.MAPPER;
/**
* Represents an user of GitHub.
*
* @author Kohsuke Kawaguchi
*/
-public class GHUser {
- /*package almost final*/ GitHub root;
-
- private String gravatar_id,name,company,location,created_at,blog,login,email;
- private int public_gist_count,public_repo_count,following_count,id,followers_count;
-
- /**
- * Repositories that this user owns.
- */
- private transient Map repositories;
-
- /**
- * Gravatar ID of this user, like 0cb9832a01c22c083390f3c5dcb64105
- */
- public String getGravatarId() {
- return gravatar_id;
- }
+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"
@@ -124,26 +104,6 @@ public int getFollowersCount() {
return followers_count;
}
- /**
- * Gets the repositories this user owns.
- */
- public Map getRepositories() throws IOException {
- if (repositories==null) {
- repositories = new TreeMap();
- URL url = new URL("http://github.com/api/v2/json/repos/show/" + login);
- for (GHRepository r : MAPPER.readValue(url, JsonRepositories.class).repositories) {
- r.root = root;
- repositories.put(r.getName(),r);
- }
- }
-
- return Collections.unmodifiableMap(repositories);
- }
-
- public GHRepository getRepository(String name) throws IOException {
- return getRepositories().get(name);
- }
-
/**
* Follow this user.
*/
diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java
index cd3a1ed8f8..616c123908 100644
--- a/src/main/java/org/kohsuke/github/GitHub.java
+++ b/src/main/java/org/kohsuke/github/GitHub.java
@@ -23,15 +23,22 @@
*/
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 java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
@@ -48,12 +55,15 @@
public class GitHub {
/*package*/ final String login;
/*package*/ final String token;
+ /*package*/ final String password;
private final Map users = new HashMap();
+ private final Map orgs = new HashMap();
- private GitHub(String login, String apiToken) {
+ private GitHub(String login, String apiToken, String password) {
this.login = login;
this.token = apiToken;
+ this.password = password;
}
/**
@@ -68,11 +78,11 @@ public static GitHub connect() throws IOException {
} finally {
IOUtils.closeQuietly(in);
}
- return new GitHub(props.getProperty("login"),props.getProperty("token"));
+ return new GitHub(props.getProperty("login"),props.getProperty("token"),props.getProperty("password"));
}
public static GitHub connect(String login, String apiToken) throws IOException {
- return new GitHub(login,apiToken);
+ return new GitHub(login,apiToken,null);
}
/**
@@ -81,7 +91,7 @@ public static GitHub connect(String login, String apiToken) throws IOException {
* All operations that requires authentication will fail.
*/
public static GitHub connectAnonymously() {
- return new GitHub(null,null);
+ return new GitHub(null,null,null);
}
/*package*/ void requireCredential() {
@@ -97,6 +107,30 @@ public static GitHub connectAnonymously() {
return MAPPER.readValue(getApiURL(tail),type);
}
+ /*package*/ T retrieveWithAuth(URL url, Class type) throws IOException {
+ return retrieveWithAuth(url,type,"GET");
+ }
+ /*package*/ T retrieveWithAuth(URL url, Class type, String method) throws IOException {
+ HttpURLConnection uc = (HttpURLConnection) url.openConnection();
+
+ BASE64Encoder enc = new sun.misc.BASE64Encoder();
+ String userpassword = login + "/token" + ":" + token;
+ String encodedAuthorization = enc.encode(userpassword.getBytes());
+ uc.setRequestProperty("Authorization", "Basic " + encodedAuthorization);
+ uc.setRequestMethod(method);
+
+ try {
+ InputStreamReader r = new InputStreamReader(uc.getInputStream(), "UTF-8");
+ if (type==null) {
+ String data = IOUtils.toString(r);
+ return null;
+ }
+ return MAPPER.readValue(r,type);
+ } catch (IOException e) {
+ throw (IOException)new IOException(IOUtils.toString(uc.getErrorStream(),"UTF-8")).initCause(e);
+ }
+ }
+
/**
* Obtains the object that represents the named user.
*/
@@ -110,6 +144,29 @@ public GHUser getUser(String login) throws IOException {
return u;
}
+ /**
+ * Interns the given {@link GHUser}.
+ */
+ protected GHUser getUser(GHUser orig) throws IOException {
+ GHUser u = users.get(orig.getLogin());
+ if (u==null) {
+ orig.root = this;
+ users.put(login,orig);
+ return orig;
+ }
+ return u;
+ }
+
+ public GHOrganization getOrganization(String name) throws IOException {
+ GHOrganization o = orgs.get(name);
+ if (o==null) {
+ o = MAPPER.readValue(getApiURL("/organizations/"+name), JsonOrganization.class).organization;
+ o.root = this;
+ orgs.put(name,o);
+ }
+ return o;
+ }
+
/**
* Gets the {@link GHUser} that represents yourself.
*/
@@ -125,11 +182,21 @@ public GHUser getMyself() throws IOException {
* Newly created repository.
*/
public GHRepository createRepository(String name, String description, String homepage, boolean isPublic) throws IOException {
- GHRepository r = new Poster(this).withCredential()
+ return new Poster(this).withCredential()
.with("name", name).with("description", description).with("homepage", homepage)
- .with("public", isPublic ? 1 : 0).to(getApiURL("/repos/create"), JsonRepository.class).repository;
- r.root = this;
- return r;
+ .with("public", isPublic ? 1 : 0).to(getApiURL("/repos/create"), JsonRepository.class).wrap(this);
+ }
+
+ WebClient createWebClient() throws IOException {
+ WebClient wc = new WebClient();
+ wc.setJavaScriptEnabled(false);
+ wc.setCssEnabled(false);
+ HtmlPage pg = (HtmlPage)wc.getPage("https://github.com/login");
+ HtmlForm f = pg.getForms().get(0);
+ f.getInputByName("login").setValueAttribute(login);
+ f.getInputByName("password").setValueAttribute(password);
+ f.submit();
+ return wc;
}
diff --git a/src/main/java/org/kohsuke/github/JsonOrganization.java b/src/main/java/org/kohsuke/github/JsonOrganization.java
new file mode 100644
index 0000000000..02c7479b84
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/JsonOrganization.java
@@ -0,0 +1,8 @@
+package org.kohsuke.github;
+
+/**
+ * @author Kohsuke Kawaguchi
+ */
+class JsonOrganization {
+ public GHOrganization organization;
+}
diff --git a/src/main/java/org/kohsuke/github/JsonRepository.java b/src/main/java/org/kohsuke/github/JsonRepository.java
index 1f13a196b3..eb8eadd315 100644
--- a/src/main/java/org/kohsuke/github/JsonRepository.java
+++ b/src/main/java/org/kohsuke/github/JsonRepository.java
@@ -28,4 +28,9 @@
*/
class JsonRepository {
public GHRepository repository;
+
+ public GHRepository wrap(GitHub root) {
+ repository.root = root;
+ return repository;
+ }
}
diff --git a/src/main/java/org/kohsuke/github/JsonTeams.java b/src/main/java/org/kohsuke/github/JsonTeams.java
new file mode 100644
index 0000000000..1f1db72ba7
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/JsonTeams.java
@@ -0,0 +1,21 @@
+package org.kohsuke.github;
+
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * @author Kohsuke Kawaguchi
+ */
+class JsonTeams {
+ public List teams;
+
+ Map toMap(GHOrganization org) {
+ Map r = new TreeMap();
+ for (GHTeam t : teams) {
+ t.org = org;
+ r.put(t.getName(),t);
+ }
+ return r;
+ }
+}
diff --git a/src/main/java/org/kohsuke/github/JsonUsersWithDetails.java b/src/main/java/org/kohsuke/github/JsonUsersWithDetails.java
new file mode 100644
index 0000000000..424e53c9da
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/JsonUsersWithDetails.java
@@ -0,0 +1,20 @@
+package org.kohsuke.github;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Kohsuke Kawaguchi
+ */
+class JsonUsersWithDetails {
+ public List users;
+
+ public Set toSet(GitHub root) throws IOException {
+ Set r = new HashSet();
+ for (GHUser u : users)
+ r.add(root.getUser(u));
+ return r;
+ }
+}
diff --git a/src/main/java/org/kohsuke/github/Poster.java b/src/main/java/org/kohsuke/github/Poster.java
index a6eafdf823..0f4a939358 100644
--- a/src/main/java/org/kohsuke/github/Poster.java
+++ b/src/main/java/org/kohsuke/github/Poster.java
@@ -78,11 +78,15 @@ public void to(URL url) throws IOException {
* {@link Reader} that reads the response.
*/
public T to(URL url, Class type) throws IOException {
+ return to(url,type,"POST");
+ }
+
+ public T to(URL url, Class type, String method) throws IOException {
HttpURLConnection uc = (HttpURLConnection) url.openConnection();
uc.setDoOutput(true);
uc.setRequestProperty("Content-type","application/x-www-form-urlencoded");
- uc.setRequestMethod("POST");
+ uc.setRequestMethod(method);
StringBuilder body = new StringBuilder();
@@ -100,7 +104,10 @@ public T to(URL url, Class type) throws IOException {
try {
InputStreamReader r = new InputStreamReader(uc.getInputStream(), "UTF-8");
- if (type==null) return null;
+ if (type==null) {
+ String data = IOUtils.toString(r);
+ return null;
+ }
return MAPPER.readValue(r,type);
} catch (IOException e) {
throw (IOException)new IOException(IOUtils.toString(uc.getErrorStream(),"UTF-8")).initCause(e);
diff --git a/src/test/java/org/kohsuke/AppTest.java b/src/test/java/org/kohsuke/AppTest.java
index ebbce47e3a..84ba0d6d8d 100644
--- a/src/test/java/org/kohsuke/AppTest.java
+++ b/src/test/java/org/kohsuke/AppTest.java
@@ -3,6 +3,9 @@
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
+import org.kohsuke.github.GHOrganization;
+import org.kohsuke.github.GHRepository;
+import org.kohsuke.github.GHTeam;
import org.kohsuke.github.GitHub;
import java.io.IOException;
@@ -12,10 +15,24 @@
*/
public class AppTest extends TestCase {
public void testApp() throws IOException {
- GitHub hub = GitHub.connectAnonymously();
-// hub.createRepository("test","test repository",null,true);
-// hub.getUser("kohsuke").getRepository("test").delete();
+ GitHub gitHub = GitHub.connect();
+ GHOrganization labs = gitHub.getOrganization("HudsonLabs");
+ GHTeam t = labs.getTeams().get("Core Developers");
- System.out.println(hub.getUser("kohsuke").getRepository("hudson").getCollaborators());
+ t.add(labs.getRepository("xyz"));
+
+// t.add(gitHub.getMyself());
+// System.out.println(t.getMembers());
+// t.remove(gitHub.getMyself());
+// System.out.println(t.getMembers());
+
+// GHRepository r = GitHub.connect().getOrganization("HudsonLabs").createRepository("auto-test", "some description", "http://kohsuke.org/", "Plugin Developers", true);
+
+// r.
+// GitHub hub = GitHub.connectAnonymously();
+//// hub.createRepository("test","test repository",null,true);
+//// hub.getUser("kohsuke").getRepository("test").delete();
+//
+// System.out.println(hub.getUser("kohsuke").getRepository("hudson").getCollaborators());
}
}