From c7f2228a44aaee340ac0796842cf80d3d66f7140 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 16 Nov 2016 22:52:14 -0800 Subject: [PATCH 1/7] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 97328ce89a..cd302f7cd8 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ github-api - 1.80 + 1.81-SNAPSHOT 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 + HEAD From 47fc813027055cc5b8b53c12828eb650d684e9f1 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sat, 19 Nov 2016 14:29:27 -0800 Subject: [PATCH 2/7] Assignees of the repository. (Personally this concept makes no sense for me, so I don't know what this API really does. I'm just following their API docs) --- .../java/org/kohsuke/github/GHRepository.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/org/kohsuke/github/GHRepository.java b/src/main/java/org/kohsuke/github/GHRepository.java index fdd39b8d45..cc25c86004 100644 --- a/src/main/java/org/kohsuke/github/GHRepository.java +++ b/src/main/java/org/kohsuke/github/GHRepository.java @@ -469,6 +469,21 @@ protected void wrapUp(GHUser[] users) { } + /** + * 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; + } + /** * Gets the names of the collaborators on this repository. * This method deviates from the principle of this library but it works a lot faster than {@link #getCollaborators()}. From b8bfddbf3a523557c97d3bafddca50102b53e982 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sat, 19 Nov 2016 14:30:35 -0800 Subject: [PATCH 3/7] Code simplification --- .../java/org/kohsuke/github/GHRepository.java | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/kohsuke/github/GHRepository.java b/src/main/java/org/kohsuke/github/GHRepository.java index cc25c86004..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,7 @@ 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"); } /** From a1528a1a63ced24f6f042a1e0b5bfba368f16950 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sat, 19 Nov 2016 14:48:43 -0800 Subject: [PATCH 4/7] API to add/set/remove assignees from an issue --- src/main/java/org/kohsuke/github/GHIssue.java | 50 +++++++++++++++++-- .../java/org/kohsuke/github/Requester.java | 15 +++++- 2 files changed, 59 insertions(+), 6 deletions(-) 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/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); } /** From 3f223b1ba079882d16303595a215457d08a5a3b6 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sat, 19 Nov 2016 14:50:47 -0800 Subject: [PATCH 5/7] Support assignees when creating a new issue --- src/main/java/org/kohsuke/github/GHIssueBuilder.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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); } } From 511f156603500f659e86343add60496ebd50fb11 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sat, 19 Nov 2016 15:26:04 -0800 Subject: [PATCH 6/7] Added the membership API for the authenticated user. --- .../java/org/kohsuke/github/GHMembership.java | 84 +++++++++++++++++++ .../java/org/kohsuke/github/GHMyself.java | 33 ++++++++ src/main/java/org/kohsuke/github/GHUser.java | 5 ++ src/main/java/org/kohsuke/github/GitHub.java | 2 +- src/test/java/org/kohsuke/github/AppTest.java | 15 ++++ 5 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/kohsuke/github/GHMembership.java 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/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/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")); From 0023ecefa4004180c86757042c96e92009022285 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Mon, 21 Nov 2016 08:53:38 -0800 Subject: [PATCH 7/7] [maven-release-plugin] prepare release github-api-1.81 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index cd302f7cd8..a2254c512d 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ github-api - 1.81-SNAPSHOT + 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/ - HEAD + github-api-1.81