mirror of
https://github.com/jlengrand/github-api.git
synced 2026-03-11 08:21:23 +00:00
Compare commits
33 Commits
github-api
...
github-api
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f71afca828 | ||
|
|
87f5231c9a | ||
|
|
b17f506c20 | ||
|
|
7f15f12668 | ||
|
|
e53e62bfa0 | ||
|
|
2e74517a4a | ||
|
|
aed888051e | ||
|
|
bd584124bb | ||
|
|
e658a7fa6b | ||
|
|
52108707bb | ||
|
|
0e226a8f78 | ||
|
|
1bf3e025b8 | ||
|
|
a0fdcca129 | ||
|
|
3e75e96718 | ||
|
|
1dd875adac | ||
|
|
2341f789ab | ||
|
|
8a95847b0a | ||
|
|
ea9f3eacbc | ||
|
|
435363a246 | ||
|
|
b6520cb6f9 | ||
|
|
d6d73f5165 | ||
|
|
f58dbceec7 | ||
|
|
3f1bb1a214 | ||
|
|
892d2acaa2 | ||
|
|
4e27d1b5a0 | ||
|
|
fff3272e42 | ||
|
|
803198620d | ||
|
|
9017fe70d5 | ||
|
|
ce47762fbf | ||
|
|
b40677a3ca | ||
|
|
c283c4e595 | ||
|
|
6aabaea96c | ||
|
|
65adb2f2b4 |
4
pom.xml
4
pom.xml
@@ -7,7 +7,7 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>github-api</artifactId>
|
||||
<version>1.31</version>
|
||||
<version>1.33</version>
|
||||
<name>GitHub API for Java</name>
|
||||
<url>http://github-api.kohsuke.org/</url>
|
||||
<description>GitHub API for Java</description>
|
||||
@@ -64,7 +64,7 @@
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
<version>1.5.0</version>
|
||||
<version>1.9.9</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
|
||||
@@ -203,7 +203,7 @@ public class GHCommit {
|
||||
public PagedIterable<GHCommitComment> listComments() {
|
||||
return new PagedIterable<GHCommitComment>() {
|
||||
public PagedIterator<GHCommitComment> iterator() {
|
||||
return new PagedIterator<GHCommitComment>(owner.root.retrievePaged(String.format("/repos/%s/%s/commits/%s/comments",owner.getOwnerName(),owner.getName(),sha),GHCommitComment[].class,false)) {
|
||||
return new PagedIterator<GHCommitComment>(owner.root.retrieve().asIterator(String.format("/repos/%s/%s/commits/%s/comments", owner.getOwnerName(), owner.getName(), sha), GHCommitComment[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHCommitComment[] page) {
|
||||
for (GHCommitComment c : page)
|
||||
@@ -220,12 +220,11 @@ public class GHCommit {
|
||||
* I'm not sure how path/line/position parameters interact with each other.
|
||||
*/
|
||||
public GHCommitComment createComment(String body, String path, Integer line, Integer position) throws IOException {
|
||||
GHCommitComment r = new Poster(owner.root)
|
||||
GHCommitComment r = new Requester(owner.root)
|
||||
.with("body",body)
|
||||
.with("path",path)
|
||||
.with("line",line)
|
||||
.with("position",position)
|
||||
.withCredential()
|
||||
.to(String.format("/repos/%s/%s/commits/%s/comments",owner.getOwnerName(),owner.getName(),sha),GHCommitComment.class);
|
||||
return r.wrap(owner);
|
||||
}
|
||||
@@ -234,6 +233,20 @@ public class GHCommit {
|
||||
return createComment(body,null,null,null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status of this commit, newer ones first.
|
||||
*/
|
||||
public PagedIterable<GHCommitStatus> listStatuses() throws IOException {
|
||||
return owner.listCommitStatuses(sha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last status of this commit, which is what gets shown in the UI.
|
||||
*/
|
||||
public GHCommitStatus getLastStatus() throws IOException {
|
||||
return owner.getLastCommitStatus(sha);
|
||||
}
|
||||
|
||||
GHCommit wrapUp(GHRepository owner) {
|
||||
this.owner = owner;
|
||||
return this;
|
||||
|
||||
@@ -97,10 +97,9 @@ public class GHCommitComment {
|
||||
* Updates the body of the commit message.
|
||||
*/
|
||||
public void update(String body) throws IOException {
|
||||
GHCommitComment r = new Poster(owner.root)
|
||||
.with("body",body)
|
||||
.withCredential()
|
||||
.to(getApiTail(),GHCommitComment.class,"PATCH");
|
||||
GHCommitComment r = new Requester(owner.root)
|
||||
.with("body", body)
|
||||
.method("PATCH").to(getApiTail(), GHCommitComment.class);
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@@ -108,7 +107,7 @@ public class GHCommitComment {
|
||||
* Deletes this comment.
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
new Poster(owner.root).withCredential().to(getApiTail(),null,"DELETE");
|
||||
new Requester(owner.root).method("DELETE").to(getApiTail());
|
||||
}
|
||||
|
||||
private String getApiTail() {
|
||||
|
||||
11
src/main/java/org/kohsuke/github/GHCommitState.java
Normal file
11
src/main/java/org/kohsuke/github/GHCommitState.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
/**
|
||||
* Represents the state of commit
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GHCommitStatus
|
||||
*/
|
||||
public enum GHCommitState {
|
||||
PENDING, SUCCESS, ERROR, FAILURE
|
||||
}
|
||||
72
src/main/java/org/kohsuke/github/GHCommitStatus.java
Normal file
72
src/main/java/org/kohsuke/github/GHCommitStatus.java
Normal file
@@ -0,0 +1,72 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Represents a status of a commit.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @see GHRepository#getCommitStatus(String)
|
||||
* @see GHCommit#getStatus()
|
||||
*/
|
||||
public class GHCommitStatus {
|
||||
String created_at, updated_at;
|
||||
String state;
|
||||
String target_url,description;
|
||||
int id;
|
||||
String url;
|
||||
GHUser creator;
|
||||
|
||||
private GitHub root;
|
||||
|
||||
/*package*/ GHCommitStatus wrapUp(GitHub root) {
|
||||
if (creator!=null) creator.wrapUp(root);
|
||||
this.root = root;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getCreatedAt() {
|
||||
return GitHub.parseDate(created_at);
|
||||
}
|
||||
|
||||
public Date getUpdatedAt() {
|
||||
return GitHub.parseDate(updated_at);
|
||||
}
|
||||
|
||||
public GHCommitState getState() {
|
||||
for (GHCommitState s : GHCommitState.values()) {
|
||||
if (s.name().equalsIgnoreCase(state))
|
||||
return s;
|
||||
}
|
||||
throw new IllegalStateException("Unexpected state: "+state);
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL that this status is linked to.
|
||||
*
|
||||
* This is the URL specified when creating a commit status.
|
||||
*/
|
||||
public String getTargetUrl() {
|
||||
return target_url;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* API URL of this commit status.
|
||||
*/
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public GHUser getCreator() {
|
||||
return creator;
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,6 @@ public final class GHHook {
|
||||
* Deletes this hook.
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
new Poster(repository.root).withCredential()
|
||||
.to(String.format("/repos/%s/%s/hooks/%d",repository.getOwnerName(),repository.getName(),id),null,"DELETE");
|
||||
new Requester(repository.root).method("DELETE").to(String.format("/repos/%s/%s/hooks/%d", repository.getOwnerName(), repository.getName(), id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
@@ -42,15 +41,30 @@ import java.util.Locale;
|
||||
public class GHIssue {
|
||||
GitHub root;
|
||||
GHRepository owner;
|
||||
|
||||
private String gravatar_id,body,title,state,created_at,updated_at,html_url;
|
||||
private List<String> labels;
|
||||
private int number,votes,comments;
|
||||
private int position;
|
||||
|
||||
// API v3
|
||||
protected GHUser assignee;
|
||||
protected String state;
|
||||
protected int number;
|
||||
protected String closed_at;
|
||||
protected int comments;
|
||||
protected String body;
|
||||
protected List<String> labels;
|
||||
protected GHUser user;
|
||||
protected String title, created_at, html_url;
|
||||
protected GHIssue.PullRequest pull_request;
|
||||
protected GHMilestone milestone;
|
||||
protected String url, updated_at;
|
||||
protected int id;
|
||||
protected GHUser closed_by;
|
||||
|
||||
/*package*/ GHIssue wrap(GHRepository owner) {
|
||||
this.owner = owner;
|
||||
this.root = owner.root;
|
||||
if(milestone != null) milestone.wrap(owner);
|
||||
if(assignee != null) assignee.wrapUp(root);
|
||||
if(user != null) user.wrapUp(root);
|
||||
if(closed_by != null) closed_by.wrapUp(root);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -112,16 +126,23 @@ public class GHIssue {
|
||||
return GitHub.parseDate(updated_at);
|
||||
}
|
||||
|
||||
public Date getClosedAt() {
|
||||
return GitHub.parseDate(closed_at);
|
||||
}
|
||||
|
||||
public URL getApiURL(){
|
||||
return GitHub.parseURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the issue by adding a comment.
|
||||
*/
|
||||
public void comment(String message) throws IOException {
|
||||
new Poster(root).withCredential().with("body",message).to(getApiRoute()+"/comments",null,"POST");
|
||||
new Requester(root).with("body",message).to(getApiRoute() + "/comments");
|
||||
}
|
||||
|
||||
private void edit(String key, Object value) throws IOException {
|
||||
new Poster(root).withCredential()._with(key, value)
|
||||
.to(getApiRoute(),null,"PATCH");
|
||||
new Requester(root)._with(key, value).method("PATCH").to(getApiRoute());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,7 +190,7 @@ public class GHIssue {
|
||||
public PagedIterable<GHIssueComment> listComments() throws IOException {
|
||||
return new PagedIterable<GHIssueComment>() {
|
||||
public PagedIterator<GHIssueComment> iterator() {
|
||||
return new PagedIterator<GHIssueComment>(root.retrievePaged(getApiRoute() + "/comments",GHIssueComment[].class,false)) {
|
||||
return new PagedIterator<GHIssueComment>(root.retrieve().asIterator(getApiRoute() + "/comments", GHIssueComment[].class)) {
|
||||
protected void wrapUp(GHIssueComment[] page) {
|
||||
for (GHIssueComment c : page)
|
||||
c.wrapUp(GHIssue.this);
|
||||
@@ -182,4 +203,51 @@ public class GHIssue {
|
||||
private String getApiRoute() {
|
||||
return "/repos/"+owner.getOwnerName()+"/"+owner.getName()+"/issues/"+number;
|
||||
}
|
||||
|
||||
public GHUser getAssignee() {
|
||||
return assignee;
|
||||
}
|
||||
|
||||
/**
|
||||
* User who submitted the issue.
|
||||
*/
|
||||
public GHUser getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public GHUser getClosedBy() {
|
||||
if(!"closed".equals(state)) return null;
|
||||
if(closed_by != null) return closed_by;
|
||||
|
||||
//TODO closed_by = owner.getIssue(number).getClosed_by();
|
||||
return closed_by;
|
||||
}
|
||||
|
||||
public int getCommentsCount(){
|
||||
return comments;
|
||||
}
|
||||
|
||||
public PullRequest getPullRequest() {
|
||||
return pull_request;
|
||||
}
|
||||
|
||||
public GHMilestone getMilestone() {
|
||||
return milestone;
|
||||
}
|
||||
|
||||
public static class PullRequest{
|
||||
private String diff_url, patch_url, html_url;
|
||||
|
||||
public URL getDiffUrl() {
|
||||
return GitHub.parseURL(diff_url);
|
||||
}
|
||||
|
||||
public URL getPatchUrl() {
|
||||
return GitHub.parseURL(patch_url);
|
||||
}
|
||||
|
||||
public URL getUrl() {
|
||||
return GitHub.parseURL(html_url);
|
||||
}
|
||||
}
|
||||
}
|
||||
59
src/main/java/org/kohsuke/github/GHIssueBuilder.java
Normal file
59
src/main/java/org/kohsuke/github/GHIssueBuilder.java
Normal file
@@ -0,0 +1,59 @@
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class GHIssueBuilder {
|
||||
private final GHRepository repo;
|
||||
private final Requester builder;
|
||||
private List<String> labels = new ArrayList<String>();
|
||||
|
||||
GHIssueBuilder(GHRepository repo, String title) {
|
||||
this.repo = repo;
|
||||
this.builder = new Requester(repo.root);
|
||||
builder.with("title",title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the main text of an issue, which is arbitrary multi-line text.
|
||||
*/
|
||||
public GHIssueBuilder body(String str) {
|
||||
builder.with("body",str);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHIssueBuilder assignee(GHUser user) {
|
||||
if (user!=null)
|
||||
builder.with("assignee",user.getLogin());
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHIssueBuilder assignee(String user) {
|
||||
if (user!=null)
|
||||
builder.with("assignee",user);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHIssueBuilder milestone(GHMilestone milestone) {
|
||||
if (milestone!=null)
|
||||
builder.with("milestone",milestone.getNumber());
|
||||
return this;
|
||||
}
|
||||
|
||||
public GHIssueBuilder label(String label) {
|
||||
if (label!=null)
|
||||
labels.add(label);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new issue.
|
||||
*/
|
||||
public GHIssue create() throws IOException {
|
||||
return builder.with("labels",labels).to(repo.getApiTailUrl("issues"),GHIssue.class).wrap(repo);
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public class GHMyself extends GHUser {
|
||||
* Always non-null.
|
||||
*/
|
||||
public List<String> getEmails() throws IOException {
|
||||
String[] addresses = root.retrieveWithAuth("/user/emails", String[].class);
|
||||
String[] addresses = root.retrieve().to("/user/emails", String[].class);
|
||||
return Collections.unmodifiableList(Arrays.asList(addresses));
|
||||
}
|
||||
|
||||
@@ -33,11 +33,11 @@ public class GHMyself extends GHUser {
|
||||
* Always non-null.
|
||||
*/
|
||||
public List<GHKey> getPublicKeys() throws IOException {
|
||||
return Collections.unmodifiableList(Arrays.asList(root.retrieveWithAuth("/user/keys", GHKey[].class)));
|
||||
return Collections.unmodifiableList(Arrays.asList(root.retrieve().to("/user/keys", GHKey[].class)));
|
||||
}
|
||||
|
||||
// public void addEmails(Collection<String> emails) throws IOException {
|
||||
//// new Poster(root,ApiVersion.V3).withCredential().to("/user/emails");
|
||||
//// new Requester(root,ApiVersion.V3).withCredential().to("/user/emails");
|
||||
// root.retrieveWithAuth3()
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public class GHOrganization extends GHPerson {
|
||||
|
||||
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
|
||||
return new Poster(root).withCredential()
|
||||
return new Requester(root)
|
||||
.with("name", name).with("description", description).with("homepage", homepage)
|
||||
.with("public", isPublic).with("team_id",team.getId()).to("/orgs/"+login+"/repos", GHRepository.class).wrap(root);
|
||||
}
|
||||
@@ -42,7 +42,7 @@ public class GHOrganization extends GHPerson {
|
||||
* Teams by their names.
|
||||
*/
|
||||
public Map<String,GHTeam> getTeams() throws IOException {
|
||||
GHTeam[] teams = root.retrieveWithAuth("/orgs/" + login + "/teams", GHTeam[].class);
|
||||
GHTeam[] teams = root.retrieve().to("/orgs/" + login + "/teams", GHTeam[].class);
|
||||
Map<String,GHTeam> r = new TreeMap<String, GHTeam>();
|
||||
for (GHTeam t : teams) {
|
||||
r.put(t.getName(),t.wrapUp(this));
|
||||
@@ -54,7 +54,7 @@ public class GHOrganization extends GHPerson {
|
||||
* Publicizes the membership.
|
||||
*/
|
||||
public void publicize(GHUser u) throws IOException {
|
||||
root.retrieveWithAuth("/orgs/" + login + "/public_members/" + u.getLogin(), null, "PUT");
|
||||
root.retrieve().method("PUT").to("/orgs/" + login + "/public_members/" + u.getLogin(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,7 +64,7 @@ public class GHOrganization extends GHPerson {
|
||||
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.retrieveWithAuth("/orgs/" + login + "/members", GHUser[].class);
|
||||
final GHUser[] shallow = root.retrieve().to("/orgs/" + login + "/members", GHUser[].class);
|
||||
|
||||
@Override
|
||||
public GHUser get(int index) {
|
||||
@@ -86,7 +86,7 @@ public class GHOrganization extends GHPerson {
|
||||
* Conceals the membership.
|
||||
*/
|
||||
public void conceal(GHUser u) throws IOException {
|
||||
root.retrieveWithAuth("/orgs/" + login + "/public_members/" + u.getLogin(), null, "DELETE");
|
||||
root.retrieve().method("DELETE").to("/orgs/" + login + "/public_members/" + u.getLogin(), null);
|
||||
}
|
||||
|
||||
public enum Permission { ADMIN, PUSH, PULL }
|
||||
@@ -95,13 +95,13 @@ public class GHOrganization extends GHPerson {
|
||||
* Creates a new team and assigns the repositories.
|
||||
*/
|
||||
public GHTeam createTeam(String name, Permission p, Collection<GHRepository> repositories) throws IOException {
|
||||
Poster post = new Poster(root).withCredential().with("name", name).with("permission", p.name().toLowerCase());
|
||||
Requester post = new Requester(root).with("name", name).with("permission", p.name().toLowerCase());
|
||||
List<String> repo_names = new ArrayList<String>();
|
||||
for (GHRepository r : repositories) {
|
||||
repo_names.add(r.getName());
|
||||
}
|
||||
post.with("repo_names",repo_names);
|
||||
return post.to("/orgs/"+login+"/teams",GHTeam.class,"POST").wrapUp(this);
|
||||
return post.method("POST").to("/orgs/" + login + "/teams", GHTeam.class).wrapUp(this);
|
||||
}
|
||||
|
||||
public GHTeam createTeam(String name, Permission p, GHRepository... repositories) throws IOException {
|
||||
|
||||
@@ -17,13 +17,13 @@ import java.util.TreeMap;
|
||||
public abstract class GHPerson {
|
||||
/*package almost final*/ GitHub root;
|
||||
|
||||
// common
|
||||
protected String login,location,blog,email,name,created_at,company;
|
||||
// core data fields that exist even for "small" user data (such as the user info in pull request)
|
||||
protected String login, avatar_url, url, gravatar_id;
|
||||
protected int id;
|
||||
protected String gravatar_id; // appears in V3 as well but presumably subsumed by avatar_url?
|
||||
|
||||
// V3
|
||||
protected String avatar_url,html_url;
|
||||
// other fields (that only show up in full data)
|
||||
protected String location,blog,email,name,created_at,company;
|
||||
protected String html_url;
|
||||
protected int followers,following,public_repos,public_gists;
|
||||
|
||||
/*package*/ GHPerson wrapUp(GitHub root) {
|
||||
@@ -31,6 +31,17 @@ public abstract class GHPerson {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully populate the data by retrieving missing data.
|
||||
*
|
||||
* Depending on the original API call where this object is created, it may not contain everything.
|
||||
*/
|
||||
protected void populate() throws IOException {
|
||||
if (created_at!=null) return; // already populated
|
||||
|
||||
root.retrieve().to(url, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the repositories this user owns.
|
||||
*/
|
||||
@@ -56,7 +67,7 @@ public abstract class GHPerson {
|
||||
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);
|
||||
final Iterator<GHRepository[]> pager = root.retrieve().asIterator("/users/" + login + "/repos?per_page="+pageSize,GHRepository[].class);
|
||||
|
||||
return new Iterator<List<GHRepository>>() {
|
||||
public boolean hasNext() {
|
||||
@@ -85,7 +96,7 @@ public abstract class GHPerson {
|
||||
*/
|
||||
public GHRepository getRepository(String name) throws IOException {
|
||||
try {
|
||||
return root.retrieveWithAuth("/repos/" + login + '/' + name, GHRepository.class).wrap(root);
|
||||
return root.retrieve().to("/repos/" + login + '/' + name, GHRepository.class).wrap(root);
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
@@ -123,51 +134,60 @@ public abstract class GHPerson {
|
||||
/**
|
||||
* Gets the human-readable name of the user, like "Kohsuke Kawaguchi"
|
||||
*/
|
||||
public String getName() {
|
||||
public String getName() throws IOException {
|
||||
populate();
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the company name of this user, like "Sun Microsystems, Inc."
|
||||
*/
|
||||
public String getCompany() {
|
||||
public String getCompany() throws IOException {
|
||||
populate();
|
||||
return company;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the location of this user, like "Santa Clara, California"
|
||||
*/
|
||||
public String getLocation() {
|
||||
public String getLocation() throws IOException {
|
||||
populate();
|
||||
return location;
|
||||
}
|
||||
|
||||
public String getCreatedAt() {
|
||||
public String getCreatedAt() throws IOException {
|
||||
populate();
|
||||
return created_at;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the blog URL of this user.
|
||||
*/
|
||||
public String getBlog() {
|
||||
public String getBlog() throws IOException {
|
||||
populate();
|
||||
return blog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the e-mail address of the user.
|
||||
*/
|
||||
public String getEmail() {
|
||||
public String getEmail() throws IOException {
|
||||
populate();
|
||||
return email;
|
||||
}
|
||||
|
||||
public int getPublicGistCount() {
|
||||
public int getPublicGistCount() throws IOException {
|
||||
populate();
|
||||
return public_gists;
|
||||
}
|
||||
|
||||
public int getPublicRepoCount() {
|
||||
public int getPublicRepoCount() throws IOException {
|
||||
populate();
|
||||
return public_repos;
|
||||
}
|
||||
|
||||
public int getFollowingCount() {
|
||||
public int getFollowingCount() throws IOException {
|
||||
populate();
|
||||
return following;
|
||||
}
|
||||
|
||||
@@ -178,7 +198,8 @@ public abstract class GHPerson {
|
||||
return id;
|
||||
}
|
||||
|
||||
public int getFollowersCount() {
|
||||
public int getFollowersCount() throws IOException {
|
||||
populate();
|
||||
return followers;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
*/
|
||||
package org.kohsuke.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@@ -33,12 +35,35 @@ import java.util.Date;
|
||||
*/
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
public class GHPullRequest extends GHIssue {
|
||||
private String closed_at, patch_url, issue_updated_at;
|
||||
private GHUser issue_user, user;
|
||||
// labels??
|
||||
private GHCommitPointer base, head;
|
||||
private String mergeable, diff_url;
|
||||
|
||||
private String patch_url, diff_url, issue_url;
|
||||
private GHCommitPointer base;
|
||||
private String merged_at;
|
||||
private GHCommitPointer head;
|
||||
|
||||
// details that are only available when obtained from ID
|
||||
private GHUser merged_by;
|
||||
private int review_comments, additions;
|
||||
private boolean merged;
|
||||
private Boolean mergeable;
|
||||
private int deletions;
|
||||
private String mergeable_state;
|
||||
private int changed_files;
|
||||
|
||||
|
||||
GHPullRequest wrapUp(GHRepository owner) {
|
||||
this.wrap(owner);
|
||||
return wrapUp(owner.root);
|
||||
}
|
||||
|
||||
GHPullRequest wrapUp(GitHub root) {
|
||||
if (owner!=null) owner.wrap(root);
|
||||
if (base!=null) base.wrapUp(root);
|
||||
if (head!=null) head.wrapUp(root);
|
||||
if (merged_by != null) merged_by.wrapUp(root);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL of the patch file.
|
||||
* like https://github.com/jenkinsci/jenkins/pull/100.patch
|
||||
@@ -46,12 +71,13 @@ public class GHPullRequest extends GHIssue {
|
||||
public URL getPatchUrl() {
|
||||
return GitHub.parseURL(patch_url);
|
||||
}
|
||||
|
||||
/**
|
||||
* User who submitted a pull request.
|
||||
|
||||
/**
|
||||
* The URL of the patch file.
|
||||
* like https://github.com/jenkinsci/jenkins/pull/100.patch
|
||||
*/
|
||||
public GHUser getUser() {
|
||||
return user;
|
||||
public URL getIssueUrl() {
|
||||
return GitHub.parseURL(issue_url);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,16 +95,9 @@ public class GHPullRequest extends GHIssue {
|
||||
return head;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Date getIssueUpdatedAt() {
|
||||
return GitHub.parseDate(issue_updated_at);
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTML page of this pull request,
|
||||
* like https://github.com/jenkinsci/jenkins/pull/100
|
||||
*/
|
||||
public URL getUrl() {
|
||||
return super.getUrl();
|
||||
return super.getUpdatedAt();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,22 +108,77 @@ public class GHPullRequest extends GHIssue {
|
||||
return GitHub.parseURL(diff_url);
|
||||
}
|
||||
|
||||
public Date getClosedAt() {
|
||||
return GitHub.parseDate(closed_at);
|
||||
public Date getMergedAt() {
|
||||
return GitHub.parseDate(merged_at);
|
||||
}
|
||||
|
||||
GHPullRequest wrapUp(GHRepository owner) {
|
||||
this.owner = owner;
|
||||
return wrapUp(owner.root);
|
||||
}
|
||||
@Override
|
||||
public Collection<String> getLabels() {
|
||||
return super.getLabels();
|
||||
}
|
||||
|
||||
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;
|
||||
@Override
|
||||
public GHUser getClosedBy() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PullRequest getPullRequest() {
|
||||
return null;
|
||||
}
|
||||
|
||||
//
|
||||
// details that are only available via get with ID
|
||||
//
|
||||
//
|
||||
public GHUser getMergedBy() throws IOException {
|
||||
populate();
|
||||
return merged_by;
|
||||
}
|
||||
|
||||
public int getReviewComments() throws IOException {
|
||||
populate();
|
||||
return review_comments;
|
||||
}
|
||||
|
||||
public int getAdditions() throws IOException {
|
||||
populate();
|
||||
return additions;
|
||||
}
|
||||
|
||||
public boolean isMerged() throws IOException {
|
||||
populate();
|
||||
return merged;
|
||||
}
|
||||
|
||||
public Boolean getMergeable() throws IOException {
|
||||
populate();
|
||||
return mergeable;
|
||||
}
|
||||
|
||||
public int getDeletions() throws IOException {
|
||||
populate();
|
||||
return deletions;
|
||||
}
|
||||
|
||||
public String getMergeableState() throws IOException {
|
||||
populate();
|
||||
return mergeable_state;
|
||||
}
|
||||
|
||||
public int getChangedFiles() throws IOException {
|
||||
populate();
|
||||
return changed_files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully populate the data by retrieving missing data.
|
||||
*
|
||||
* Depending on the original API call where this object is created, it may not contain everything.
|
||||
*/
|
||||
private void populate() throws IOException {
|
||||
if (merged_by!=null) return; // already populated
|
||||
|
||||
root.retrieve().to(url, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,8 +140,16 @@ public class GHRepository {
|
||||
return root.getUser(owner.login); // because 'owner' isn't fully populated
|
||||
}
|
||||
|
||||
public GHIssue getIssue(int id) throws IOException {
|
||||
return root.retrieve().to("/repos/" + owner.login + "/" + name + "/issues/" + id, GHIssue.class).wrap(this);
|
||||
}
|
||||
|
||||
public GHIssueBuilder createIssue(String title) {
|
||||
return new GHIssueBuilder(this,title);
|
||||
}
|
||||
|
||||
public List<GHIssue> getIssues(GHIssueState state) throws IOException {
|
||||
return Arrays.asList(GHIssue.wrap(root.retrieve("/repos/" + owner.login + "/" + name + "/issues?state=" + state.toString().toLowerCase(), GHIssue[].class), this));
|
||||
return Arrays.asList(GHIssue.wrap(root.retrieve().to("/repos/" + owner.login + "/" + name + "/issues?state=" + state.toString().toLowerCase(), GHIssue[].class), this));
|
||||
}
|
||||
|
||||
protected String getOwnerName() {
|
||||
@@ -213,7 +221,7 @@ public class GHRepository {
|
||||
*/
|
||||
@WithBridgeMethods(Set.class)
|
||||
public GHPersonSet<GHUser> getCollaborators() throws IOException {
|
||||
return new GHPersonSet<GHUser>(GHUser.wrap(root.retrieve("/repos/" + owner.login + "/" + name + "/collaborators", GHUser[].class),root));
|
||||
return new GHPersonSet<GHUser>(GHUser.wrap(root.retrieve().to("/repos/" + owner.login + "/" + name + "/collaborators", GHUser[].class),root));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,7 +230,7 @@ public class GHRepository {
|
||||
*/
|
||||
public Set<String> getCollaboratorNames() throws IOException {
|
||||
Set<String> r = new HashSet<String>();
|
||||
for (GHUser u : GHUser.wrap(root.retrieve("/repos/" + owner.login + "/" + name + "/collaborators", GHUser[].class),root))
|
||||
for (GHUser u : GHUser.wrap(root.retrieve().to("/repos/" + owner.login + "/" + name + "/collaborators", GHUser[].class),root))
|
||||
r.add(u.login);
|
||||
return r;
|
||||
}
|
||||
@@ -231,7 +239,7 @@ public class GHRepository {
|
||||
* If this repository belongs to an organization, return a set of teams.
|
||||
*/
|
||||
public Set<GHTeam> getTeams() throws IOException {
|
||||
return Collections.unmodifiableSet(new HashSet<GHTeam>(Arrays.asList(GHTeam.wrapUp(root.retrieveWithAuth("/repos/" + owner.login + "/" + name + "/teams", GHTeam[].class), root.getOrganization(owner.login)))));
|
||||
return Collections.unmodifiableSet(new HashSet<GHTeam>(Arrays.asList(GHTeam.wrapUp(root.retrieve().to("/repos/" + owner.login + "/" + name + "/teams", GHTeam[].class), root.getOrganization(owner.login)))));
|
||||
}
|
||||
|
||||
public void addCollaborators(GHUser... users) throws IOException {
|
||||
@@ -253,7 +261,7 @@ public class GHRepository {
|
||||
private void modifyCollaborators(Collection<GHUser> users, String method) throws IOException {
|
||||
verifyMine();
|
||||
for (GHUser user : users) {
|
||||
new Poster(root).withCredential().to("/repos/"+owner.login+"/"+name+"/collaborators/"+user.getLogin(),null,method);
|
||||
new Requester(root).method(method).to("/repos/" + owner.login + "/" + name + "/collaborators/" + user.getLogin());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,11 +278,10 @@ public class GHRepository {
|
||||
}
|
||||
|
||||
private void edit(String key, String value) throws IOException {
|
||||
Poster poster = new Poster(root).withCredential();
|
||||
Requester requester = new Requester(root);
|
||||
if (!key.equals("name"))
|
||||
poster.with("name", name); // even when we don't change the name, we need to send it in
|
||||
poster.with(key, value)
|
||||
.to("/repos/" + owner.login + "/" + name, null, "PATCH");
|
||||
requester.with("name", name); // even when we don't change the name, we need to send it in
|
||||
requester.with(key, value).method("PATCH").to("/repos/" + owner.login + "/" + name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -314,7 +321,7 @@ public class GHRepository {
|
||||
* Deletes this repository.
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
new Poster(root).withCredential().to("/repos/" + owner.login +"/"+name, null, "DELETE");
|
||||
new Requester(root).method("DELETE").to("/repos/" + owner.login + "/" + name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -324,7 +331,7 @@ public class GHRepository {
|
||||
* Newly forked repository that belong to you.
|
||||
*/
|
||||
public GHRepository fork() throws IOException {
|
||||
return new Poster(root).withCredential().to("/repos/" + owner.login + "/" + name + "/forks", GHRepository.class, "POST").wrap(root);
|
||||
return new Requester(root).method("POST").to("/repos/" + owner.login + "/" + name + "/forks", GHRepository.class).wrap(root);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,7 +341,7 @@ public class GHRepository {
|
||||
* Newly forked repository that belong to you.
|
||||
*/
|
||||
public GHRepository forkTo(GHOrganization org) throws IOException {
|
||||
new Poster(root).withCredential().to(String.format("/repos/%s/%s/forks?org=%s",owner.login,name,org.getLogin()));
|
||||
new Requester(root).to(String.format("/repos/%s/%s/forks?org=%s", owner.login, name, org.getLogin()));
|
||||
|
||||
// this API is asynchronous. we need to wait for a bit
|
||||
for (int i=0; i<10; i++) {
|
||||
@@ -353,7 +360,7 @@ public class GHRepository {
|
||||
* Retrieves a specified pull request.
|
||||
*/
|
||||
public GHPullRequest getPullRequest(int i) throws IOException {
|
||||
return root.retrieveWithAuth("/repos/" + owner.login + '/' + name + "/pulls/" + i, GHPullRequest.class).wrapUp(this);
|
||||
return root.retrieve().to("/repos/" + owner.login + '/' + name + "/pulls/" + i, GHPullRequest.class).wrapUp(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -371,7 +378,7 @@ public class GHRepository {
|
||||
public PagedIterable<GHPullRequest> listPullRequests(final GHIssueState state) {
|
||||
return new PagedIterable<GHPullRequest>() {
|
||||
public PagedIterator<GHPullRequest> iterator() {
|
||||
return new PagedIterator<GHPullRequest>(root.retrievePaged(String.format("/repos/%s/%s/pulls?state=%s", owner.login,name,state.name().toLowerCase(Locale.ENGLISH)), GHPullRequest[].class, false)) {
|
||||
return new PagedIterator<GHPullRequest>(root.retrieve().asIterator(String.format("/repos/%s/%s/pulls?state=%s", owner.login, name, state.name().toLowerCase(Locale.ENGLISH)), GHPullRequest[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHPullRequest[] page) {
|
||||
for (GHPullRequest pr : page)
|
||||
@@ -387,14 +394,14 @@ public class GHRepository {
|
||||
*/
|
||||
public List<GHHook> getHooks() throws IOException {
|
||||
List<GHHook> list = new ArrayList<GHHook>(Arrays.asList(
|
||||
root.retrieveWithAuth(String.format("/repos/%s/%s/hooks", owner.login, name), GHHook[].class)));
|
||||
root.retrieve().to(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.retrieveWithAuth(String.format("/repos/%s/%s/hooks/%d", owner.login, name, id), GHHook.class).wrap(this);
|
||||
return root.retrieve().to(String.format("/repos/%s/%s/hooks/%d", owner.login, name, id), GHHook.class).wrap(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -403,7 +410,7 @@ public class GHRepository {
|
||||
public GHCommit getCommit(String sha1) throws IOException {
|
||||
GHCommit c = commits.get(sha1);
|
||||
if (c==null) {
|
||||
c = root.retrieve(String.format("/repos/%s/%s/commits/%s", owner.login, name, sha1), GHCommit.class).wrapUp(this);
|
||||
c = root.retrieve().to(String.format("/repos/%s/%s/commits/%s", owner.login, name, sha1), GHCommit.class).wrapUp(this);
|
||||
commits.put(sha1,c);
|
||||
}
|
||||
return c;
|
||||
@@ -415,7 +422,7 @@ public class GHRepository {
|
||||
public PagedIterable<GHCommit> listCommits() {
|
||||
return new PagedIterable<GHCommit>() {
|
||||
public PagedIterator<GHCommit> iterator() {
|
||||
return new PagedIterator<GHCommit>(root.retrievePaged(String.format("/repos/%s/%s/commits",owner.login,name),GHCommit[].class,false)) {
|
||||
return new PagedIterator<GHCommit>(root.retrieve().asIterator(String.format("/repos/%s/%s/commits", owner.login, name), GHCommit[].class)) {
|
||||
protected void wrapUp(GHCommit[] page) {
|
||||
for (GHCommit c : page)
|
||||
c.wrapUp(GHRepository.this);
|
||||
@@ -431,7 +438,7 @@ public class GHRepository {
|
||||
public PagedIterable<GHCommitComment> listCommitComments() {
|
||||
return new PagedIterable<GHCommitComment>() {
|
||||
public PagedIterator<GHCommitComment> iterator() {
|
||||
return new PagedIterator<GHCommitComment>(root.retrievePaged(String.format("/repos/%s/%s/comments",owner.login,name),GHCommitComment[].class,false)) {
|
||||
return new PagedIterator<GHCommitComment>(root.retrieve().asIterator(String.format("/repos/%s/%s/comments", owner.login, name), GHCommitComment[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHCommitComment[] page) {
|
||||
for (GHCommitComment c : page)
|
||||
@@ -442,6 +449,48 @@ public class GHRepository {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all the commit statues attached to the given commit, newer ones first.
|
||||
*/
|
||||
public PagedIterable<GHCommitStatus> listCommitStatuses(final String sha1) throws IOException {
|
||||
return new PagedIterable<GHCommitStatus>() {
|
||||
public PagedIterator<GHCommitStatus> iterator() {
|
||||
return new PagedIterator<GHCommitStatus>(root.retrieve().asIterator(String.format("/repos/%s/%s/statuses/%s", owner.login, name, sha1), GHCommitStatus[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHCommitStatus[] page) {
|
||||
for (GHCommitStatus c : page)
|
||||
c.wrapUp(root);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last status of this commit, which is what gets shown in the UI.
|
||||
*/
|
||||
public GHCommitStatus getLastCommitStatus(String sha1) throws IOException {
|
||||
List<GHCommitStatus> v = listCommitStatuses(sha1).asList();
|
||||
return v.isEmpty() ? null : v.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a commit status
|
||||
*
|
||||
* @param targetUrl
|
||||
* Optional parameter that points to the URL that has more details.
|
||||
* @param description
|
||||
* Optional short description.
|
||||
*/
|
||||
public GHCommitStatus createCommitStatus(String sha1, GHCommitState state, String targetUrl, String description) throws IOException {
|
||||
return new Requester(root)
|
||||
.with("state", state.name().toLowerCase(Locale.ENGLISH))
|
||||
.with("target_url", targetUrl)
|
||||
.with("description", description)
|
||||
.to(String.format("/repos/%s/%s/statuses/%s",owner.login,this.name,sha1),GHCommitStatus.class).wrapUp(root);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* See https://api.github.com/hooks for possible names and their configuration scheme.
|
||||
@@ -462,9 +511,8 @@ public class GHRepository {
|
||||
ea.add(e.name().toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
|
||||
return new Poster(root)
|
||||
.withCredential()
|
||||
.with("name",name)
|
||||
return new Requester(root)
|
||||
.with("name", name)
|
||||
.with("active", active)
|
||||
._with("config", config)
|
||||
._with("events",ea)
|
||||
@@ -568,28 +616,46 @@ public class GHRepository {
|
||||
*/
|
||||
public Map<String,GHBranch> getBranches() throws IOException {
|
||||
Map<String,GHBranch> r = new TreeMap<String,GHBranch>();
|
||||
for (GHBranch p : root.retrieve("/repos/" + owner.login + "/" + name + "/branches", GHBranch[].class)) {
|
||||
for (GHBranch p : root.retrieve().to(getApiTailUrl("branches"), GHBranch[].class)) {
|
||||
p.wrap(this);
|
||||
r.put(p.getName(),p);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Use {@link #listMilestones(GHIssueState)}
|
||||
*/
|
||||
public Map<Integer, GHMilestone> getMilestones() throws IOException {
|
||||
Map<Integer,GHMilestone> milestones = new TreeMap<Integer, GHMilestone>();
|
||||
GHMilestone[] ms = root.retrieve("/repos/" + owner.login + "/" + name + "/milestones", GHMilestone[].class);
|
||||
for (GHMilestone m : ms) {
|
||||
m.owner = this;
|
||||
m.root = root;
|
||||
for (GHMilestone m : listMilestones(GHIssueState.OPEN)) {
|
||||
milestones.put(m.getNumber(), m);
|
||||
}
|
||||
return milestones;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists up all the milestones in this repository.
|
||||
*/
|
||||
public PagedIterable<GHMilestone> listMilestones(final GHIssueState state) {
|
||||
return new PagedIterable<GHMilestone>() {
|
||||
public PagedIterator<GHMilestone> iterator() {
|
||||
return new PagedIterator<GHMilestone>(root.retrieve().asIterator(getApiTailUrl("milestones?state="+state.toString().toLowerCase(Locale.ENGLISH)), GHMilestone[].class)) {
|
||||
@Override
|
||||
protected void wrapUp(GHMilestone[] page) {
|
||||
for (GHMilestone c : page)
|
||||
c.wrap(GHRepository.this);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public GHMilestone getMilestone(int number) throws IOException {
|
||||
GHMilestone m = milestones.get(number);
|
||||
if (m == null) {
|
||||
m = root.retrieve("/repos/" + owner.login + "/" + name + "/milestones/" + number, GHMilestone.class);
|
||||
m = root.retrieve().to(getApiTailUrl("milestones/" + number), GHMilestone.class);
|
||||
m.owner = this;
|
||||
m.root = root;
|
||||
milestones.put(m.getNumber(), m);
|
||||
@@ -598,9 +664,8 @@ public class GHRepository {
|
||||
}
|
||||
|
||||
public GHMilestone createMilestone(String title, String description) throws IOException {
|
||||
return new Poster(root).withCredential()
|
||||
.with("title", title).with("description", description)
|
||||
.to("/repos/"+owner.login+"/"+name+"/milestones", GHMilestone.class,"POST").wrap(this);
|
||||
return new Requester(root)
|
||||
.with("title", title).with("description", description).method("POST").to(getApiTailUrl("milestones"), GHMilestone.class).wrap(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -622,4 +687,8 @@ public class GHRepository {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String getApiTailUrl(String tail) {
|
||||
return "/repos/" + owner.login + "/" + name +'/'+tail;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,11 +46,11 @@ public class GHTeam {
|
||||
* Retrieves the current members.
|
||||
*/
|
||||
public Set<GHUser> getMembers() throws IOException {
|
||||
return new HashSet<GHUser>(Arrays.asList(GHUser.wrap(org.root.retrieveWithAuth(api("/members"), GHUser[].class), org.root)));
|
||||
return new HashSet<GHUser>(Arrays.asList(GHUser.wrap(org.root.retrieve().to(api("/members"), GHUser[].class), org.root)));
|
||||
}
|
||||
|
||||
public Map<String,GHRepository> getRepositories() throws IOException {
|
||||
GHRepository[] repos = org.root.retrieveWithAuth(api("/repos"), GHRepository[].class);
|
||||
GHRepository[] repos = org.root.retrieve().to(api("/repos"), GHRepository[].class);
|
||||
Map<String,GHRepository> m = new TreeMap<String, GHRepository>();
|
||||
for (GHRepository r : repos) {
|
||||
m.put(r.getName(),r.wrap(org.root));
|
||||
@@ -62,22 +62,22 @@ public class GHTeam {
|
||||
* Adds a member to the team.
|
||||
*/
|
||||
public void add(GHUser u) throws IOException {
|
||||
org.root.retrieveWithAuth(api("/members/" + u.getLogin()), null, "PUT");
|
||||
org.root.retrieve().method("PUT").to(api("/members/" + u.getLogin()), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a member to the team.
|
||||
*/
|
||||
public void remove(GHUser u) throws IOException {
|
||||
org.root.retrieveWithAuth(api("/members/" + u.getLogin()), null, "DELETE");
|
||||
org.root.retrieve().method("DELETE").to(api("/members/" + u.getLogin()), null);
|
||||
}
|
||||
|
||||
public void add(GHRepository r) throws IOException {
|
||||
org.root.retrieveWithAuth(api("/repos/" + r.getOwnerName() + '/' + r.getName()), null, "PUT");
|
||||
org.root.retrieve().method("PUT").to(api("/repos/" + r.getOwnerName() + '/' + r.getName()), null);
|
||||
}
|
||||
|
||||
public void remove(GHRepository r) throws IOException {
|
||||
org.root.retrieveWithAuth(api("/repos/" + r.getOwnerName() + '/' + r.getName()), null, "DELETE");
|
||||
org.root.retrieve().method("DELETE").to(api("/repos/" + r.getOwnerName() + '/' + r.getName()), null);
|
||||
}
|
||||
|
||||
private String api(String tail) {
|
||||
|
||||
@@ -41,14 +41,14 @@ public class GHUser extends GHPerson {
|
||||
* Follow this user.
|
||||
*/
|
||||
public void follow() throws IOException {
|
||||
new Poster(root).withCredential().to("/user/following/"+login,null,"PUT");
|
||||
new Requester(root).method("PUT").to("/user/following/" + login);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfollow this user.
|
||||
*/
|
||||
public void unfollow() throws IOException {
|
||||
new Poster(root).withCredential().to("/user/following/"+login,null,"DELETE");
|
||||
new Requester(root).method("DELETE").to("/user/following/" + login);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,7 +56,7 @@ public class GHUser extends GHPerson {
|
||||
*/
|
||||
@WithBridgeMethods(Set.class)
|
||||
public GHPersonSet<GHUser> getFollows() throws IOException {
|
||||
GHUser[] followers = root.retrieve("/users/" + login + "/following", GHUser[].class);
|
||||
GHUser[] followers = root.retrieve().to("/users/" + login + "/following", GHUser[].class);
|
||||
return new GHPersonSet<GHUser>(Arrays.asList(wrap(followers,root)));
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public class GHUser extends GHPerson {
|
||||
*/
|
||||
@WithBridgeMethods(Set.class)
|
||||
public GHPersonSet<GHUser> getFollowers() throws IOException {
|
||||
GHUser[] followers = root.retrieve("/users/" + login + "/followers", GHUser[].class);
|
||||
GHUser[] followers = root.retrieve().to("/users/" + login + "/followers", GHUser[].class);
|
||||
return new GHPersonSet<GHUser>(Arrays.asList(wrap(followers,root)));
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ public class GHUser extends GHPerson {
|
||||
public GHPersonSet<GHOrganization> getOrganizations() throws IOException {
|
||||
GHPersonSet<GHOrganization> orgs = new GHPersonSet<GHOrganization>();
|
||||
Set<String> names = new HashSet<String>();
|
||||
for (GHOrganization o : root.retrieve("/users/" + login + "/orgs", GHOrganization[].class)) {
|
||||
for (GHOrganization o : root.retrieve().to("/users/" + login + "/orgs", GHOrganization[].class)) {
|
||||
if (names.add(o.getLogin())) // I've seen some duplicates in the data
|
||||
orgs.add(root.getOrganization(o.getLogin()));
|
||||
}
|
||||
|
||||
@@ -23,18 +23,20 @@
|
||||
*/
|
||||
package org.kohsuke.github;
|
||||
|
||||
import static org.codehaus.jackson.annotate.JsonAutoDetect.Visibility.ANY;
|
||||
import static org.codehaus.jackson.annotate.JsonAutoDetect.Visibility.NONE;
|
||||
import com.gargoylesoftware.htmlunit.WebClient;
|
||||
import com.gargoylesoftware.htmlunit.html.HtmlForm;
|
||||
import com.gargoylesoftware.htmlunit.html.HtmlPage;
|
||||
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
|
||||
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.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.Reader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.text.ParseException;
|
||||
@@ -42,25 +44,12 @@ import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Properties;
|
||||
import java.util.TimeZone;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.codehaus.jackson.map.DeserializationConfig.Feature;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.codehaus.jackson.map.introspect.VisibilityChecker.Std;
|
||||
|
||||
import sun.misc.BASE64Encoder;
|
||||
|
||||
import com.gargoylesoftware.htmlunit.WebClient;
|
||||
import com.gargoylesoftware.htmlunit.html.HtmlForm;
|
||||
import com.gargoylesoftware.htmlunit.html.HtmlPage;
|
||||
import static org.codehaus.jackson.annotate.JsonAutoDetect.Visibility.*;
|
||||
|
||||
/**
|
||||
* Root of the GitHub API.
|
||||
@@ -163,196 +152,22 @@ public class GitHub {
|
||||
// append the access token
|
||||
tailApiUrl = tailApiUrl + (tailApiUrl.indexOf('?')>=0 ?'&':'?') + "access_token=" + oauthAccessToken;
|
||||
}
|
||||
|
||||
return new URL("https://api."+githubServer+tailApiUrl);
|
||||
|
||||
if (tailApiUrl.startsWith("/"))
|
||||
return new URL("https://api."+githubServer+tailApiUrl);
|
||||
else
|
||||
return new URL(tailApiUrl);
|
||||
}
|
||||
|
||||
/*package*/ <T> T retrieve(String tailApiUrl, Class<T> type) throws IOException {
|
||||
return _retrieve(tailApiUrl, type, "GET", false);
|
||||
}
|
||||
|
||||
/*package*/ <T> T retrieveWithAuth(String tailApiUrl, Class<T> type) throws IOException {
|
||||
return _retrieve(tailApiUrl, type, "GET", true);
|
||||
}
|
||||
|
||||
/*package*/ <T> T retrieveWithAuth(String tailApiUrl, Class<T> type, String method) throws IOException {
|
||||
return _retrieve(tailApiUrl, type, method, true);
|
||||
}
|
||||
|
||||
private <T> T _retrieve(String tailApiUrl, Class<T> type, String method, boolean withAuth) throws IOException {
|
||||
while (true) {// loop while API rate limit is hit
|
||||
HttpURLConnection uc = setupConnection(method, withAuth, getApiURL(tailApiUrl));
|
||||
try {
|
||||
return parse(uc,type);
|
||||
} catch (IOException e) {
|
||||
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) {
|
||||
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(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 the authentication is needed but no credential is given, try it anyway (so that some calls
|
||||
// that do work with anonymous access in the reduced form should still work.)
|
||||
// if OAuth token is present, it'll be set in the URL, so need to set the Authorization header
|
||||
if (withAuth && encodedAuthorization!=null && this.oauthAccessToken == null)
|
||||
uc.setRequestProperty("Authorization", "Basic " + encodedAuthorization);
|
||||
|
||||
uc.setRequestMethod(method);
|
||||
uc.setRequestProperty("Accept-Encoding", "gzip");
|
||||
if (method.equals("PUT")) {
|
||||
uc.setDoOutput(true);
|
||||
uc.setRequestProperty("Content-Length","0");
|
||||
uc.getOutputStream().close();
|
||||
}
|
||||
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 || in==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.
|
||||
* Otherwise throw an exception reporting an error.
|
||||
*/
|
||||
/*package*/ void handleApiError(IOException e, HttpURLConnection uc) throws IOException {
|
||||
if ("0".equals(uc.getHeaderField("X-RateLimit-Remaining"))) {
|
||||
// API limit reached. wait 10 secs and return normally
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
return;
|
||||
} catch (InterruptedException _) {
|
||||
throw (InterruptedIOException)new InterruptedIOException().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);
|
||||
}
|
||||
/*package*/ Requester retrieve() {
|
||||
return new Requester(this).method("GET");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current rate limit.
|
||||
*/
|
||||
public GHRateLimit getRateLimit() throws IOException {
|
||||
return retrieveWithAuth("/rate_limit", JsonRateLimit.class).rate;
|
||||
return retrieve().to("/rate_limit", JsonRateLimit.class).rate;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -362,7 +177,7 @@ public class GitHub {
|
||||
public GHMyself getMyself() throws IOException {
|
||||
requireCredential();
|
||||
|
||||
GHMyself u = retrieveWithAuth("/user", GHMyself.class);
|
||||
GHMyself u = retrieve().to("/user", GHMyself.class);
|
||||
|
||||
u.root = this;
|
||||
users.put(u.getLogin(), u);
|
||||
@@ -376,7 +191,7 @@ public class GitHub {
|
||||
public GHUser getUser(String login) throws IOException {
|
||||
GHUser u = users.get(login);
|
||||
if (u == null) {
|
||||
u = retrieve("/users/" + login, GHUser.class);
|
||||
u = retrieve().to("/users/" + login, GHUser.class);
|
||||
u.root = this;
|
||||
users.put(u.getLogin(), u);
|
||||
}
|
||||
@@ -399,7 +214,7 @@ public class GitHub {
|
||||
public GHOrganization getOrganization(String name) throws IOException {
|
||||
GHOrganization o = orgs.get(name);
|
||||
if (o==null) {
|
||||
o = retrieve("/orgs/" + name, GHOrganization.class).wrapUp(this);
|
||||
o = retrieve().to("/orgs/" + name, GHOrganization.class).wrapUp(this);
|
||||
orgs.put(name,o);
|
||||
}
|
||||
return o;
|
||||
@@ -422,7 +237,7 @@ public class GitHub {
|
||||
* TODO: make this automatic.
|
||||
*/
|
||||
public Map<String, GHOrganization> getMyOrganizations() throws IOException {
|
||||
GHOrganization[] orgs = retrieveWithAuth("/user/orgs", GHOrganization[].class);
|
||||
GHOrganization[] orgs = retrieve().to("/user/orgs", GHOrganization[].class);
|
||||
Map<String, GHOrganization> r = new HashMap<String, GHOrganization>();
|
||||
for (GHOrganization o : orgs) {
|
||||
// don't put 'o' into orgs because they are shallow
|
||||
@@ -436,7 +251,7 @@ public class GitHub {
|
||||
*/
|
||||
public List<GHEventInfo> getEvents() throws IOException {
|
||||
// TODO: pagenation
|
||||
GHEventInfo[] events = retrieve("/events", GHEventInfo[].class);
|
||||
GHEventInfo[] events = retrieve().to("/events", GHEventInfo[].class);
|
||||
for (GHEventInfo e : events)
|
||||
e.wrapUp(this);
|
||||
return Arrays.asList(events);
|
||||
@@ -462,9 +277,10 @@ public class GitHub {
|
||||
* Newly created repository.
|
||||
*/
|
||||
public GHRepository createRepository(String name, String description, String homepage, boolean isPublic) throws IOException {
|
||||
return new Poster(this).withCredential()
|
||||
Requester requester = new Requester(this)
|
||||
.with("name", name).with("description", description).with("homepage", homepage)
|
||||
.with("public", isPublic ? 1 : 0).to("/user/repos", GHRepository.class,"POST").wrap(this);
|
||||
.with("public", isPublic ? 1 : 0);
|
||||
return requester.method("POST").to("/user/repos", GHRepository.class).wrap(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -472,7 +288,7 @@ public class GitHub {
|
||||
*/
|
||||
public boolean isCredentialValid() throws IOException {
|
||||
try {
|
||||
retrieveWithAuth("/user", GHUser.class);
|
||||
retrieve().to("/user", GHUser.class);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.kohsuke.github;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* Iterator over a pagenated data source.
|
||||
@@ -28,17 +29,24 @@ public abstract class PagedIterator<T> implements Iterator<T> {
|
||||
protected abstract void wrapUp(T[] page);
|
||||
|
||||
public boolean hasNext() {
|
||||
return (current!=null && pos<current.length) || base.hasNext();
|
||||
fetch();
|
||||
return current!=null;
|
||||
}
|
||||
|
||||
public T next() {
|
||||
fetch();
|
||||
|
||||
if (current==null) throw new NoSuchElementException();
|
||||
return current[pos++];
|
||||
}
|
||||
|
||||
private void fetch() {
|
||||
while (current==null || current.length<=pos) {
|
||||
if (!base.hasNext()) {// no more to retrieve
|
||||
current = null;
|
||||
pos = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
current = base.next();
|
||||
wrapUp(current);
|
||||
pos = 0;
|
||||
|
||||
@@ -1,163 +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 org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.ProtocolException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.kohsuke.github.GitHub.*;
|
||||
|
||||
/**
|
||||
* Handles HTTP POST.
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
class Poster {
|
||||
private final GitHub root;
|
||||
private final List<Entry> args = new ArrayList<Entry>();
|
||||
private boolean authenticate;
|
||||
|
||||
private static class Entry {
|
||||
String key;
|
||||
Object value;
|
||||
|
||||
private Entry(String key, Object value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
Poster(GitHub root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
public Poster withCredential() {
|
||||
root.requireCredential();
|
||||
authenticate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Poster with(String key, int value) {
|
||||
return _with(key, value);
|
||||
}
|
||||
|
||||
public Poster with(String key, Integer value) {
|
||||
if (value!=null)
|
||||
_with(key, value.intValue());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Poster with(String key, boolean value) {
|
||||
return _with(key, value);
|
||||
}
|
||||
|
||||
public Poster with(String key, String value) {
|
||||
return _with(key, value);
|
||||
}
|
||||
|
||||
public Poster with(String key, Collection<String> value) {
|
||||
return _with(key, value);
|
||||
}
|
||||
|
||||
public Poster _with(String key, Object value) {
|
||||
if (value!=null) {
|
||||
args.add(new Entry(key,value));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void to(String tailApiUrl) throws IOException {
|
||||
to(tailApiUrl,null);
|
||||
}
|
||||
|
||||
/**
|
||||
* POSTs the form to the specified URL.
|
||||
*
|
||||
* @throws IOException
|
||||
* if the server returns 4xx/5xx responses.
|
||||
* @return
|
||||
* {@link Reader} that reads the response.
|
||||
*/
|
||||
public <T> T to(String tailApiUrl, Class<T> type) throws IOException {
|
||||
return to(tailApiUrl,type,"POST");
|
||||
}
|
||||
|
||||
public <T> T to(String tailApiUrl, Class<T> type, String method) throws IOException {
|
||||
while (true) {// loop while API rate limit is hit
|
||||
HttpURLConnection uc = (HttpURLConnection) root.getApiURL(tailApiUrl).openConnection();
|
||||
|
||||
uc.setDoOutput(true);
|
||||
uc.setRequestProperty("Content-type","application/x-www-form-urlencoded");
|
||||
if (authenticate) {
|
||||
if (root.oauthAccessToken!=null) {
|
||||
uc.setRequestProperty("Authorization", "token " + root.oauthAccessToken);
|
||||
} else {
|
||||
if (root.password==null)
|
||||
throw new IllegalArgumentException("V3 API doesn't support API token");
|
||||
uc.setRequestProperty("Authorization", "Basic " + root.encodedAuthorization);
|
||||
}
|
||||
}
|
||||
try {
|
||||
uc.setRequestMethod(method);
|
||||
} catch (ProtocolException e) {
|
||||
// JDK only allows one of the fixed set of verbs. Try to override that
|
||||
try {
|
||||
Field $method = HttpURLConnection.class.getDeclaredField("method");
|
||||
$method.setAccessible(true);
|
||||
$method.set(uc,method);
|
||||
} catch (Exception x) {
|
||||
throw (IOException)new IOException("Failed to set the custom verb").initCause(x);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Map json = new HashMap();
|
||||
for (Entry e : args) {
|
||||
json.put(e.key, e.value);
|
||||
}
|
||||
MAPPER.writeValue(uc.getOutputStream(),json);
|
||||
|
||||
try {
|
||||
InputStreamReader r = new InputStreamReader(uc.getInputStream(), "UTF-8");
|
||||
String data = IOUtils.toString(r);
|
||||
if (type==null) {
|
||||
return null;
|
||||
}
|
||||
return MAPPER.readValue(data,type);
|
||||
} catch (IOException e) {
|
||||
root.handleApiError(e,uc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
344
src/main/java/org/kohsuke/github/Requester.java
Normal file
344
src/main/java/org/kohsuke/github/Requester.java
Normal file
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
* 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 org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import static org.kohsuke.github.GitHub.*;
|
||||
|
||||
/**
|
||||
* A builder pattern for making HTTP call and parsing its output.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
class Requester {
|
||||
private final GitHub root;
|
||||
private final List<Entry> args = new ArrayList<Entry>();
|
||||
|
||||
/**
|
||||
* Request method.
|
||||
*/
|
||||
private String method = "POST";
|
||||
|
||||
private static class Entry {
|
||||
String key;
|
||||
Object value;
|
||||
|
||||
private Entry(String key, Object value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
Requester(GitHub root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request with authentication credential.
|
||||
*/
|
||||
@Deprecated
|
||||
public Requester withCredential() {
|
||||
// keeping it inline with retrieveWithAuth not to enforce the check
|
||||
// root.requireCredential();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Requester with(String key, int value) {
|
||||
return _with(key, value);
|
||||
}
|
||||
|
||||
public Requester with(String key, Integer value) {
|
||||
if (value!=null)
|
||||
_with(key, value.intValue());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Requester with(String key, boolean value) {
|
||||
return _with(key, value);
|
||||
}
|
||||
|
||||
public Requester with(String key, String value) {
|
||||
return _with(key, value);
|
||||
}
|
||||
|
||||
public Requester with(String key, Collection<String> value) {
|
||||
return _with(key, value);
|
||||
}
|
||||
|
||||
public Requester _with(String key, Object value) {
|
||||
if (value!=null) {
|
||||
args.add(new Entry(key,value));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Requester method(String method) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void to(String tailApiUrl) throws IOException {
|
||||
to(tailApiUrl,null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the specified URL, and parses the response into the given type via databinding.
|
||||
*
|
||||
* @throws IOException
|
||||
* if the server returns 4xx/5xx responses.
|
||||
* @return
|
||||
* {@link Reader} that reads the response.
|
||||
*/
|
||||
public <T> T to(String tailApiUrl, Class<T> type) throws IOException {
|
||||
return _to(tailApiUrl, type, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #to(String, Class)} but updates an existing object instead of creating a new instance.
|
||||
*/
|
||||
public <T> T to(String tailApiUrl, T existingInstance) throws IOException {
|
||||
return _to(tailApiUrl, null, existingInstance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Short for {@code method(method).to(tailApiUrl,type)}
|
||||
*/
|
||||
@Deprecated
|
||||
public <T> T to(String tailApiUrl, Class<T> type, String method) throws IOException {
|
||||
return method(method).to(tailApiUrl,type);
|
||||
}
|
||||
|
||||
private <T> T _to(String tailApiUrl, Class<T> type, T instance) throws IOException {
|
||||
while (true) {// loop while API rate limit is hit
|
||||
HttpURLConnection uc = setupConnection(root.getApiURL(tailApiUrl));
|
||||
|
||||
if (!method.equals("GET")) {
|
||||
uc.setDoOutput(true);
|
||||
uc.setRequestProperty("Content-type","application/x-www-form-urlencoded");
|
||||
|
||||
Map json = new HashMap();
|
||||
for (Entry e : args) {
|
||||
json.put(e.key, e.value);
|
||||
}
|
||||
MAPPER.writeValue(uc.getOutputStream(),json);
|
||||
}
|
||||
|
||||
try {
|
||||
return parse(uc,type,instance);
|
||||
} catch (IOException e) {
|
||||
handleApiError(e,uc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads pagenated resources.
|
||||
*
|
||||
* Every iterator call reports a new batch.
|
||||
*/
|
||||
/*package*/ <T> Iterator<T> asIterator(final String tailApiUrl, final Class<T> type) {
|
||||
method("GET");
|
||||
if (!args.isEmpty()) throw new IllegalStateException();
|
||||
|
||||
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 = root.getApiURL(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(url);
|
||||
try {
|
||||
next = parse(uc,type,null);
|
||||
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(URL url) throws IOException {
|
||||
HttpURLConnection uc = (HttpURLConnection) url.openConnection();
|
||||
|
||||
// if the authentication is needed but no credential is given, try it anyway (so that some calls
|
||||
// that do work with anonymous access in the reduced form should still work.)
|
||||
// if OAuth token is present, it'll be set in the URL, so need to set the Authorization header
|
||||
if (root.encodedAuthorization!=null && root.oauthAccessToken == null)
|
||||
uc.setRequestProperty("Authorization", "Basic " + root.encodedAuthorization);
|
||||
|
||||
try {
|
||||
uc.setRequestMethod(method);
|
||||
} catch (ProtocolException e) {
|
||||
// JDK only allows one of the fixed set of verbs. Try to override that
|
||||
try {
|
||||
Field $method = HttpURLConnection.class.getDeclaredField("method");
|
||||
$method.setAccessible(true);
|
||||
$method.set(uc,method);
|
||||
} catch (Exception x) {
|
||||
throw (IOException)new IOException("Failed to set the custom verb").initCause(x);
|
||||
}
|
||||
}
|
||||
uc.setRequestProperty("Accept-Encoding", "gzip");
|
||||
return uc;
|
||||
}
|
||||
|
||||
private <T> T parse(HttpURLConnection uc, Class<T> type, T instance) throws IOException {
|
||||
InputStreamReader r = null;
|
||||
try {
|
||||
r = new InputStreamReader(wrapStream(uc, uc.getInputStream()), "UTF-8");
|
||||
String data = IOUtils.toString(r);
|
||||
if (type!=null)
|
||||
return MAPPER.readValue(data,type);
|
||||
if (instance!=null)
|
||||
return MAPPER.readerForUpdating(instance).<T>readValue(data);
|
||||
return null;
|
||||
} 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 || in==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.
|
||||
* Otherwise throw an exception reporting an error.
|
||||
*/
|
||||
/*package*/ void handleApiError(IOException e, HttpURLConnection uc) throws IOException {
|
||||
if ("0".equals(uc.getHeaderField("X-RateLimit-Remaining"))) {
|
||||
// API limit reached. wait 10 secs and return normally
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
return;
|
||||
} catch (InterruptedException _) {
|
||||
throw (InterruptedIOException)new InterruptedIOException().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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,17 @@ import junit.framework.TestCase;
|
||||
import org.kohsuke.github.GHCommit;
|
||||
import org.kohsuke.github.GHCommit.File;
|
||||
import org.kohsuke.github.GHCommitComment;
|
||||
import org.kohsuke.github.GHCommitStatus;
|
||||
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.GHIssue;
|
||||
import org.kohsuke.github.GHIssueComment;
|
||||
import org.kohsuke.github.GHIssueState;
|
||||
import org.kohsuke.github.GHKey;
|
||||
import org.kohsuke.github.GHMilestone;
|
||||
import org.kohsuke.github.GHMyself;
|
||||
import org.kohsuke.github.GHOrganization;
|
||||
import org.kohsuke.github.GHOrganization.Permission;
|
||||
@@ -20,7 +24,6 @@ import org.kohsuke.github.GHTeam;
|
||||
import org.kohsuke.github.GHUser;
|
||||
import org.kohsuke.github.GitHub;
|
||||
import org.kohsuke.github.PagedIterable;
|
||||
import org.kohsuke.github.PagedIterator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
@@ -45,7 +48,27 @@ public class AppTest extends TestCase {
|
||||
|
||||
public void testCredentialValid() throws IOException {
|
||||
assertTrue(GitHub.connect().isCredentialValid());
|
||||
assertFalse(GitHub.connect("totally","bogus").isCredentialValid());
|
||||
assertFalse(GitHub.connect("totally", "bogus").isCredentialValid());
|
||||
}
|
||||
|
||||
public void testIssueWithNoComment() throws IOException {
|
||||
GHRepository repository = GitHub.connect().getRepository("kohsuke/test");
|
||||
List<GHIssueComment> v = repository.getIssue(4).getComments();
|
||||
System.out.println(v);
|
||||
assertTrue(v.isEmpty());
|
||||
|
||||
v = repository.getIssue(3).getComments();
|
||||
System.out.println(v);
|
||||
assertTrue(v.size()==3);
|
||||
}
|
||||
|
||||
public void testCreateIssue() throws IOException {
|
||||
GHUser u = GitHub.connect().getUser("kohsuke");
|
||||
GHRepository r = u.getRepository("test");
|
||||
GHMilestone someMilestone = r.listMilestones(GHIssueState.CLOSED).iterator().next();
|
||||
GHIssue o = r.createIssue("testing").body("this is body").assignee(u).label("bug").label("question").milestone(someMilestone).create();
|
||||
System.out.println(o.getUrl());
|
||||
o.close();
|
||||
}
|
||||
|
||||
public void testRateLimit() throws IOException {
|
||||
@@ -85,7 +108,7 @@ public class AppTest extends TestCase {
|
||||
assertFalse(r.hasAdminAccess());
|
||||
}
|
||||
|
||||
public void tryGetMyself() throws Exception {
|
||||
public void testGetMyself() throws Exception {
|
||||
GitHub hub = GitHub.connect();
|
||||
GHMyself me = hub.getMyself();
|
||||
System.out.println(me);
|
||||
@@ -308,4 +331,25 @@ public class AppTest extends TestCase {
|
||||
|
||||
// t.add(labs.getRepository("xyz"));
|
||||
}
|
||||
|
||||
public void testCommitStatus() throws Exception {
|
||||
GitHub gitHub = GitHub.connect();
|
||||
GHRepository r = gitHub.getUser("kohsuke").getRepository("test");
|
||||
GHCommitStatus state;
|
||||
// state = r.createCommitStatus("edacdd76b06c5f3f0697a22ca75803169f25f296", GHCommitState.FAILURE, "http://jenkins-ci.org/", "oops!");
|
||||
|
||||
List<GHCommitStatus> lst = r.listCommitStatuses("edacdd76b06c5f3f0697a22ca75803169f25f296").asList();
|
||||
state = lst.get(0);
|
||||
System.out.println(state);
|
||||
assertEquals("oops!",state.getDescription());
|
||||
assertEquals("http://jenkins-ci.org/",state.getTargetUrl());
|
||||
}
|
||||
|
||||
public void testPullRequestPopulate() throws Exception {
|
||||
GitHub gitHub = GitHub.connect();
|
||||
GHRepository r = gitHub.getUser("kohsuke").getRepository("github-api");
|
||||
GHPullRequest p = r.getPullRequest(17);
|
||||
GHUser u = p.getUser();
|
||||
assertNotNull(u.getName());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user