diff --git a/pom.xml b/pom.xml
index 97328ce89a..a2254c512d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
github-api
- 1.80
+ 1.81
GitHub API for Java
http://github-api.kohsuke.org/
GitHub API for Java
@@ -16,7 +16,7 @@
scm:git:git@github.com/kohsuke/${project.artifactId}.git
scm:git:ssh://git@github.com/kohsuke/${project.artifactId}.git
http://${project.artifactId}.kohsuke.org/
- github-api-1.80
+ github-api-1.81
diff --git a/src/main/java/org/kohsuke/github/GHIssue.java b/src/main/java/org/kohsuke/github/GHIssue.java
index f02439daa7..849d4831ff 100644
--- a/src/main/java/org/kohsuke/github/GHIssue.java
+++ b/src/main/java/org/kohsuke/github/GHIssue.java
@@ -29,13 +29,15 @@
import java.io.IOException;
import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
-import static org.kohsuke.github.Previews.SQUIRREL_GIRL;
+import static org.kohsuke.github.Previews.*;
/**
* Represents an issue on GitHub.
@@ -51,7 +53,8 @@ public class GHIssue extends GHObject implements Reactable{
GHRepository owner;
// API v3
- protected GHUser assignee;
+ protected GHUser assignee; // not sure what this field is now that 'assignees' exist
+ protected GHUser[] assignees;
protected String state;
protected int number;
protected String closed_at;
@@ -81,6 +84,7 @@ public static class Label extends GHLabel {
/*package*/ GHIssue wrap(GitHub root) {
this.root = root;
if(assignee != null) assignee.wrapUp(root);
+ if(assignees!=null) GHUser.wrap(assignees,root);
if(user != null) user.wrapUp(root);
if(closed_by != null) closed_by.wrapUp(root);
return this;
@@ -187,7 +191,7 @@ public void setBody(String body) throws IOException {
}
public void assignTo(GHUser user) throws IOException {
- editIssue("assignee", user.getLogin());
+ setAssignees(user);
}
public void setLabels(String... labels) throws IOException {
@@ -242,6 +246,40 @@ protected void wrapUp(GHReaction[] page) {
};
}
+ public void addAssignees(GHUser... assignees) throws IOException {
+ addAssignees(Arrays.asList(assignees));
+ }
+
+ public void addAssignees(Collection assignees) throws IOException {
+ List names = toLogins(assignees);
+ root.retrieve().method("POST").with("assignees",names).to(getIssuesApiRoute()+"/assignees",this);
+ }
+
+ public void setAssignees(GHUser... assignees) throws IOException {
+ setAssignees(Arrays.asList(assignees));
+ }
+
+ public void setAssignees(Collection assignees) throws IOException {
+ editIssue("assignees",toLogins(assignees));
+ }
+
+ public void removeAssignees(GHUser... assignees) throws IOException {
+ removeAssignees(Arrays.asList(assignees));
+ }
+
+ public void removeAssignees(Collection assignees) throws IOException {
+ List names = toLogins(assignees);
+ root.retrieve().method("DELETE").with("assignees",names).inBody().to(getIssuesApiRoute()+"/assignees",this);
+ }
+
+ private List toLogins(Collection assignees) {
+ List names = new ArrayList(assignees.size());
+ for (GHUser a : assignees) {
+ names.add(a.getLogin());
+ }
+ return names;
+ }
+
protected String getApiRoute() {
return getIssuesApiRoute();
}
@@ -253,7 +291,11 @@ protected String getIssuesApiRoute() {
public GHUser getAssignee() {
return assignee;
}
-
+
+ public List getAssignees() {
+ return Collections.unmodifiableList(Arrays.asList(assignees));
+ }
+
/**
* User who submitted the issue.
*/
diff --git a/src/main/java/org/kohsuke/github/GHIssueBuilder.java b/src/main/java/org/kohsuke/github/GHIssueBuilder.java
index 385fcd04a0..3a5a532308 100644
--- a/src/main/java/org/kohsuke/github/GHIssueBuilder.java
+++ b/src/main/java/org/kohsuke/github/GHIssueBuilder.java
@@ -11,6 +11,7 @@ public class GHIssueBuilder {
private final GHRepository repo;
private final Requester builder;
private List labels = new ArrayList();
+ private List assignees = new ArrayList();
GHIssueBuilder(GHRepository repo, String title) {
this.repo = repo;
@@ -28,13 +29,13 @@ public GHIssueBuilder body(String str) {
public GHIssueBuilder assignee(GHUser user) {
if (user!=null)
- builder.with("assignee",user.getLogin());
+ assignees.add(user.getLogin());
return this;
}
public GHIssueBuilder assignee(String user) {
if (user!=null)
- builder.with("assignee",user);
+ assignees.add(user);
return this;
}
@@ -54,6 +55,6 @@ public GHIssueBuilder label(String label) {
* Creates a new issue.
*/
public GHIssue create() throws IOException {
- return builder.with("labels",labels).to(repo.getApiTailUrl("issues"),GHIssue.class).wrap(repo);
+ return builder.with("labels",labels).with("assignees",assignees).to(repo.getApiTailUrl("issues"),GHIssue.class).wrap(repo);
}
}
diff --git a/src/main/java/org/kohsuke/github/GHMembership.java b/src/main/java/org/kohsuke/github/GHMembership.java
new file mode 100644
index 0000000000..2847e1891c
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GHMembership.java
@@ -0,0 +1,84 @@
+package org.kohsuke.github;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Locale;
+
+/**
+ * Represents a membership of a user in an organization.
+ *
+ * @author Kohsuke Kawaguchi
+ * @see GHMyself#listOrgMemberships()
+ */
+public class GHMembership /* extends GHObject --- but it doesn't have id, created_at, etc. */ {
+ GitHub root;
+
+ String url;
+ String state;
+ String role;
+ GHUser user;
+ GHOrganization organization;
+
+ public URL getUrl() {
+ return GitHub.parseURL(url);
+ }
+
+ public State getState() {
+ return Enum.valueOf(State.class, state.toUpperCase(Locale.ENGLISH));
+ }
+
+ public Role getRole() {
+ return Enum.valueOf(Role.class, role.toUpperCase(Locale.ENGLISH));
+ }
+
+ public GHUser getUser() {
+ return user;
+ }
+
+ public GHOrganization getOrganization() {
+ return organization;
+ }
+
+ /**
+ * Accepts a pending invitation to an organization.
+ *
+ * @see GHMyself#getMembership(GHOrganization)
+ */
+ public void activate() throws IOException {
+ root.retrieve().method("PATCH").with("state",State.ACTIVE).to(url,this);
+ }
+
+ /*package*/ GHMembership wrap(GitHub root) {
+ this.root = root;
+ if (user!=null) user = root.getUser(user.wrapUp(root));
+ if (organization!=null) organization.wrapUp(root);
+ return this;
+ }
+
+ /*package*/ static void wrap(GHMembership[] page, GitHub root) {
+ for (GHMembership m : page)
+ m.wrap(root);
+ }
+
+ /**
+ * Role of a user in an organization.
+ */
+ public enum Role {
+ /**
+ * Organization owner.
+ */
+ ADMIN,
+ /**
+ * Non-owner organization member.
+ */
+ MEMBER;
+ }
+
+ /**
+ * Whether a role is currently active or waiting for acceptance (pending)
+ */
+ public enum State {
+ ACTIVE,
+ PENDING;
+ }
+}
diff --git a/src/main/java/org/kohsuke/github/GHMyself.java b/src/main/java/org/kohsuke/github/GHMyself.java
index c7ae80dd79..cc05f03bc5 100644
--- a/src/main/java/org/kohsuke/github/GHMyself.java
+++ b/src/main/java/org/kohsuke/github/GHMyself.java
@@ -178,6 +178,39 @@ public PagedIterable listAllRepositories() {
return listRepositories();
}
+ /**
+ * List your organization memberships
+ */
+ public PagedIterable listOrgMemberships() {
+ return listOrgMemberships(null);
+ }
+
+ /**
+ * List your organization memberships
+ *
+ * @param state
+ * Filter by a specific state
+ */
+ public PagedIterable listOrgMemberships(final GHMembership.State state) {
+ return new PagedIterable() {
+ public PagedIterator _iterator(int pageSize) {
+ return new PagedIterator(root.retrieve().with("state",state).asIterator("/user/memberships/orgs", GHMembership[].class, pageSize)) {
+ @Override
+ protected void wrapUp(GHMembership[] page) {
+ GHMembership.wrap(page,root);
+ }
+ };
+ }
+ };
+ }
+
+ /**
+ * Gets your membership in a specific organization.
+ */
+ public GHMembership getMembership(GHOrganization o) throws IOException {
+ return root.retrieve().to("/user/memberships/orgs/"+o.getLogin(),GHMembership.class).wrap(root);
+ }
+
// public void addEmails(Collection emails) throws IOException {
//// new Requester(root,ApiVersion.V3).withCredential().to("/user/emails");
// root.retrieveWithAuth3()
diff --git a/src/main/java/org/kohsuke/github/GHRepository.java b/src/main/java/org/kohsuke/github/GHRepository.java
index fdd39b8d45..f34ea85004 100644
--- a/src/main/java/org/kohsuke/github/GHRepository.java
+++ b/src/main/java/org/kohsuke/github/GHRepository.java
@@ -50,8 +50,8 @@
import java.util.Set;
import java.util.TreeMap;
-import static java.util.Arrays.asList;
-import static org.kohsuke.github.Previews.DRAX;
+import static java.util.Arrays.*;
+import static org.kohsuke.github.Previews.*;
/**
* A repository on GitHub.
@@ -451,22 +451,22 @@ public GHPersonSet getCollaborators() throws IOException {
* @throws IOException
*/
public PagedIterable listCollaborators() throws IOException {
- return new PagedIterable() {
- public PagedIterator _iterator(int pageSize) {
-
- return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("collaborators"), GHUser[].class, pageSize)) {
-
- @Override
- protected void wrapUp(GHUser[] users) {
- for (GHUser user : users) {
- user.wrapUp(root);
- }
- }
- };
+ return listUsers("collaborators");
+ }
- }
- };
+ /**
+ * Lists all the available assignees
+ * to which issues may be assigned.
+ */
+ public PagedIterable listAssignees() throws IOException {
+ return listUsers("assignees");
+ }
+ /**
+ * Checks if the given user is an assignee for this repository.
+ */
+ public boolean hasAssignee(GHUser u) throws IOException {
+ return root.retrieve().asHttpStatusCode(getApiTailUrl("assignees/" + u.getLogin()))/100==2;
}
/**
diff --git a/src/main/java/org/kohsuke/github/GHUser.java b/src/main/java/org/kohsuke/github/GHUser.java
index 790f24ec20..77beb11b88 100644
--- a/src/main/java/org/kohsuke/github/GHUser.java
+++ b/src/main/java/org/kohsuke/github/GHUser.java
@@ -214,4 +214,9 @@ String getApiTailUrl(String tail) {
if (tail.length()>0 && !tail.startsWith("/")) tail='/'+tail;
return "/users/" + login + tail;
}
+
+ /*package*/ GHUser wrapUp(GitHub root) {
+ super.wrapUp(root);
+ return this;
+ }
}
diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java
index 2a54626f2d..b50b2234ea 100644
--- a/src/main/java/org/kohsuke/github/GitHub.java
+++ b/src/main/java/org/kohsuke/github/GitHub.java
@@ -340,7 +340,7 @@ public void refreshCache() {
/**
* Interns the given {@link GHUser}.
*/
- protected GHUser getUser(GHUser orig) throws IOException {
+ protected GHUser getUser(GHUser orig) {
GHUser u = users.get(orig.getLogin());
if (u==null) {
orig.root = this;
diff --git a/src/main/java/org/kohsuke/github/Requester.java b/src/main/java/org/kohsuke/github/Requester.java
index 6175191ea6..3d240abd65 100644
--- a/src/main/java/org/kohsuke/github/Requester.java
+++ b/src/main/java/org/kohsuke/github/Requester.java
@@ -81,6 +81,7 @@ class Requester {
* Current connection.
*/
private HttpURLConnection uc;
+ private boolean forceBody;
private static class Entry {
String key;
@@ -197,6 +198,16 @@ public Requester contentType(String contentType) {
return this;
}
+ /**
+ * Small number of GitHub APIs use HTTP methods somewhat inconsistently, and use a body where it's not expected.
+ * Normally whether parameters go as query parameters or a body depends on the HTTP verb in use,
+ * but this method forces the parameters to be sent as a body.
+ */
+ /*package*/ Requester inBody() {
+ forceBody = true;
+ return this;
+ }
+
public void to(String tailApiUrl) throws IOException {
to(tailApiUrl,null);
}
@@ -230,7 +241,7 @@ public T to(String tailApiUrl, Class type, String method) throws IOExcept
@SuppressFBWarnings("SBSC_USE_STRINGBUFFER_CONCATENATION")
private T _to(String tailApiUrl, Class type, T instance) throws IOException {
- if (METHODS_WITHOUT_BODY.contains(method) && !args.isEmpty()) {
+ if (!isMethodWithBody() && !args.isEmpty()) {
boolean questionMarkFound = tailApiUrl.indexOf('?') != -1;
tailApiUrl += questionMarkFound ? '&' : '?';
for (Iterator it = args.listIterator(); it.hasNext();) {
@@ -340,7 +351,7 @@ private void buildRequest() throws IOException {
}
private boolean isMethodWithBody() {
- return !METHODS_WITHOUT_BODY.contains(method);
+ return forceBody || !METHODS_WITHOUT_BODY.contains(method);
}
/**
diff --git a/src/test/java/org/kohsuke/github/AppTest.java b/src/test/java/org/kohsuke/github/AppTest.java
index 7246781127..0a958418be 100755
--- a/src/test/java/org/kohsuke/github/AppTest.java
+++ b/src/test/java/org/kohsuke/github/AppTest.java
@@ -888,6 +888,21 @@ public void reactions() throws Exception {
a.delete();
}
+ @Test
+ public void listOrgMemberships() throws Exception {
+ GHMyself me = gitHub.getMyself();
+ for (GHMembership m : me.listOrgMemberships()) {
+ assertThat(m.getUser(), is((GHUser)me));
+ assertNotNull(m.getState());
+ assertNotNull(m.getRole());
+
+ System.out.printf("%s %s %s\n",
+ m.getOrganization().getLogin(),
+ m.getState(),
+ m.getRole());
+ }
+ }
+
private void kohsuke() {
String login = getUser().getLogin();
Assume.assumeTrue(login.equals("kohsuke") || login.equals("kohsuke2"));