diff --git a/pom.xml b/pom.xml
index 7d5af32649..f8ac35a431 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
github-api
- 1.31
+ 1.32
GitHub API for Java
http://github-api.kohsuke.org/
GitHub API for Java
@@ -64,7 +64,7 @@
org.codehaus.jackson
jackson-mapper-asl
- 1.5.0
+ 1.9.9
commons-io
diff --git a/src/main/java/org/kohsuke/github/GHCommit.java b/src/main/java/org/kohsuke/github/GHCommit.java
index 3cf7f1437c..8069066780 100644
--- a/src/main/java/org/kohsuke/github/GHCommit.java
+++ b/src/main/java/org/kohsuke/github/GHCommit.java
@@ -203,7 +203,7 @@ private GHUser resolveUser(User author) throws IOException {
public PagedIterable listComments() {
return new PagedIterable() {
public PagedIterator iterator() {
- return new PagedIterator(owner.root.retrievePaged(String.format("/repos/%s/%s/commits/%s/comments",owner.getOwnerName(),owner.getName(),sha),GHCommitComment[].class,false)) {
+ return new PagedIterator(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,7 +220,7 @@ protected void wrapUp(GHCommitComment[] page) {
* 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)
@@ -234,6 +234,20 @@ public GHCommitComment createComment(String body) throws IOException {
return createComment(body,null,null,null);
}
+ /**
+ * Gets the status of this commit, newer ones first.
+ */
+ public PagedIterable 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;
diff --git a/src/main/java/org/kohsuke/github/GHCommitComment.java b/src/main/java/org/kohsuke/github/GHCommitComment.java
index f4621c2b01..9ff07304eb 100644
--- a/src/main/java/org/kohsuke/github/GHCommitComment.java
+++ b/src/main/java/org/kohsuke/github/GHCommitComment.java
@@ -97,10 +97,9 @@ public GHCommit getCommit() throws IOException {
* 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)
+ .withCredential().method("PATCH").to(getApiTail(), GHCommitComment.class);
this.body = body;
}
@@ -108,7 +107,7 @@ public void update(String body) throws IOException {
* Deletes this comment.
*/
public void delete() throws IOException {
- new Poster(owner.root).withCredential().to(getApiTail(),null,"DELETE");
+ new Requester(owner.root).withCredential().method("DELETE").to(getApiTail());
}
private String getApiTail() {
diff --git a/src/main/java/org/kohsuke/github/GHCommitState.java b/src/main/java/org/kohsuke/github/GHCommitState.java
new file mode 100644
index 0000000000..e716ea02e1
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GHCommitState.java
@@ -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
+}
diff --git a/src/main/java/org/kohsuke/github/GHCommitStatus.java b/src/main/java/org/kohsuke/github/GHCommitStatus.java
new file mode 100644
index 0000000000..319e13cc64
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GHCommitStatus.java
@@ -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;
+ }
+}
diff --git a/src/main/java/org/kohsuke/github/GHHook.java b/src/main/java/org/kohsuke/github/GHHook.java
index 94144e6f07..8d1e5da027 100644
--- a/src/main/java/org/kohsuke/github/GHHook.java
+++ b/src/main/java/org/kohsuke/github/GHHook.java
@@ -54,7 +54,6 @@ public int getId() {
* 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).withCredential().method("DELETE").to(String.format("/repos/%s/%s/hooks/%d", repository.getOwnerName(), repository.getName(), id));
}
}
diff --git a/src/main/java/org/kohsuke/github/GHIssue.java b/src/main/java/org/kohsuke/github/GHIssue.java
index 3224e76f29..18f2594e3a 100644
--- a/src/main/java/org/kohsuke/github/GHIssue.java
+++ b/src/main/java/org/kohsuke/github/GHIssue.java
@@ -26,7 +26,6 @@
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 @@
public class GHIssue {
GitHub root;
GHRepository owner;
-
- private String gravatar_id,body,title,state,created_at,updated_at,html_url;
- private List 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 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 Date getUpdatedAt() {
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).withCredential().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).withCredential()._with(key, value).method("PATCH").to(getApiRoute());
}
/**
@@ -169,7 +190,7 @@ public List getComments() throws IOException {
public PagedIterable listComments() throws IOException {
return new PagedIterable() {
public PagedIterator iterator() {
- return new PagedIterator(root.retrievePaged(getApiRoute() + "/comments",GHIssueComment[].class,false)) {
+ return new PagedIterator(root.retrieve().asIterator(getApiRoute() + "/comments", GHIssueComment[].class)) {
protected void wrapUp(GHIssueComment[] page) {
for (GHIssueComment c : page)
c.wrapUp(GHIssue.this);
@@ -182,4 +203,52 @@ protected void wrapUp(GHIssueComment[] page) {
private String getApiRoute() {
return "/repos/"+owner.getOwnerName()+"/"+owner.getName()+"/issues/"+number;
}
+
+ public GHUser getAssignee() {
+ return assignee;
+ }
+
+ /**
+ * User who submitted the issue.
+ */
+ @Deprecated
+ 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);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/java/org/kohsuke/github/GHMyself.java b/src/main/java/org/kohsuke/github/GHMyself.java
index ddceccd70f..2a16abcdad 100644
--- a/src/main/java/org/kohsuke/github/GHMyself.java
+++ b/src/main/java/org/kohsuke/github/GHMyself.java
@@ -22,7 +22,7 @@ public class GHMyself extends GHUser {
* Always non-null.
*/
public List getEmails() throws IOException {
- String[] addresses = root.retrieveWithAuth("/user/emails", String[].class);
+ String[] addresses = root.retrieve().withCredential().to("/user/emails", String[].class);
return Collections.unmodifiableList(Arrays.asList(addresses));
}
@@ -33,11 +33,11 @@ public List getEmails() throws IOException {
* Always non-null.
*/
public List getPublicKeys() throws IOException {
- return Collections.unmodifiableList(Arrays.asList(root.retrieveWithAuth("/user/keys", GHKey[].class)));
+ return Collections.unmodifiableList(Arrays.asList(root.retrieve().withCredential().to("/user/keys", GHKey[].class)));
}
// public void addEmails(Collection emails) throws IOException {
-//// new Poster(root,ApiVersion.V3).withCredential().to("/user/emails");
+//// new Requester(root,ApiVersion.V3).withCredential().to("/user/emails");
// root.retrieveWithAuth3()
// }
}
diff --git a/src/main/java/org/kohsuke/github/GHOrganization.java b/src/main/java/org/kohsuke/github/GHOrganization.java
index f15626370c..8328c72235 100644
--- a/src/main/java/org/kohsuke/github/GHOrganization.java
+++ b/src/main/java/org/kohsuke/github/GHOrganization.java
@@ -33,7 +33,7 @@ public GHRepository createRepository(String name, String description, String hom
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).withCredential()
.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 GHRepository createRepository(String name, String description, String hom
* Teams by their names.
*/
public Map getTeams() throws IOException {
- GHTeam[] teams = root.retrieveWithAuth("/orgs/" + login + "/teams", GHTeam[].class);
+ GHTeam[] teams = root.retrieve().withCredential().to("/orgs/" + login + "/teams", GHTeam[].class);
Map r = new TreeMap();
for (GHTeam t : teams) {
r.put(t.getName(),t.wrapUp(this));
@@ -54,7 +54,7 @@ public Map getTeams() throws IOException {
* Publicizes the membership.
*/
public void publicize(GHUser u) throws IOException {
- root.retrieveWithAuth("/orgs/" + login + "/public_members/" + u.getLogin(), null, "PUT");
+ root.retrieve().withCredential().method("PUT").to("/orgs/" + login + "/public_members/" + u.getLogin(), null);
}
/**
@@ -64,7 +64,7 @@ public List getMembers() throws IOException {
return new AbstractList() {
// 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().withCredential().to("/orgs/" + login + "/members", GHUser[].class);
@Override
public GHUser get(int index) {
@@ -86,7 +86,7 @@ public int size() {
* Conceals the membership.
*/
public void conceal(GHUser u) throws IOException {
- root.retrieveWithAuth("/orgs/" + login + "/public_members/" + u.getLogin(), null, "DELETE");
+ root.retrieve().withCredential().method("DELETE").to("/orgs/" + login + "/public_members/" + u.getLogin(), null);
}
public enum Permission { ADMIN, PUSH, PULL }
@@ -95,13 +95,13 @@ public enum Permission { ADMIN, PUSH, PULL }
* Creates a new team and assigns the repositories.
*/
public GHTeam createTeam(String name, Permission p, Collection repositories) throws IOException {
- Poster post = new Poster(root).withCredential().with("name", name).with("permission", p.name().toLowerCase());
+ Requester post = new Requester(root).withCredential().with("name", name).with("permission", p.name().toLowerCase());
List repo_names = new ArrayList();
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 {
diff --git a/src/main/java/org/kohsuke/github/GHPerson.java b/src/main/java/org/kohsuke/github/GHPerson.java
index 4811839c0a..71c7c54c6f 100644
--- a/src/main/java/org/kohsuke/github/GHPerson.java
+++ b/src/main/java/org/kohsuke/github/GHPerson.java
@@ -17,13 +17,13 @@
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 synchronized Map getRepositories() throws IOExceptio
public synchronized Iterable> iterateRepositories(final int pageSize) {
return new Iterable>() {
public Iterator> iterator() {
- final Iterator pager = root.retrievePaged("/users/" + login + "/repos?per_page="+pageSize,GHRepository[].class,false);
+ final Iterator pager = root.retrieve().asIterator("/users/" + login + "/repos?per_page="+pageSize,GHRepository[].class);
return new Iterator>() {
public boolean hasNext() {
@@ -85,7 +96,7 @@ public void remove() {
*/
public GHRepository getRepository(String name) throws IOException {
try {
- return root.retrieveWithAuth("/repos/" + login + '/' + name, GHRepository.class).wrap(root);
+ return root.retrieve().withCredential().to("/repos/" + login + '/' + name, GHRepository.class).wrap(root);
} catch (FileNotFoundException e) {
return null;
}
@@ -123,51 +134,60 @@ public String getLogin() {
/**
* 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 int getId() {
return id;
}
- public int getFollowersCount() {
+ public int getFollowersCount() throws IOException {
+ populate();
return followers;
}
diff --git a/src/main/java/org/kohsuke/github/GHPullRequest.java b/src/main/java/org/kohsuke/github/GHPullRequest.java
index 1f86981e0f..32523005e2 100644
--- a/src/main/java/org/kohsuke/github/GHPullRequest.java
+++ b/src/main/java/org/kohsuke/github/GHPullRequest.java
@@ -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 @@
*/
@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 GHCommitPointer getHead() {
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 URL getDiffUrl() {
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 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);
}
}
diff --git a/src/main/java/org/kohsuke/github/GHRepository.java b/src/main/java/org/kohsuke/github/GHRepository.java
index 15877e2bd7..291b616c3a 100644
--- a/src/main/java/org/kohsuke/github/GHRepository.java
+++ b/src/main/java/org/kohsuke/github/GHRepository.java
@@ -141,7 +141,7 @@ public GHUser getOwner() throws IOException {
}
public List 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 +213,7 @@ public int getSize() {
*/
@WithBridgeMethods(Set.class)
public GHPersonSet getCollaborators() throws IOException {
- return new GHPersonSet(GHUser.wrap(root.retrieve("/repos/" + owner.login + "/" + name + "/collaborators", GHUser[].class),root));
+ return new GHPersonSet(GHUser.wrap(root.retrieve().to("/repos/" + owner.login + "/" + name + "/collaborators", GHUser[].class),root));
}
/**
@@ -222,7 +222,7 @@ public GHPersonSet getCollaborators() throws IOException {
*/
public Set getCollaboratorNames() throws IOException {
Set r = new HashSet();
- 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 +231,7 @@ public Set getCollaboratorNames() throws IOException {
* If this repository belongs to an organization, return a set of teams.
*/
public Set getTeams() throws IOException {
- return Collections.unmodifiableSet(new HashSet(Arrays.asList(GHTeam.wrapUp(root.retrieveWithAuth("/repos/" + owner.login + "/" + name + "/teams", GHTeam[].class), root.getOrganization(owner.login)))));
+ return Collections.unmodifiableSet(new HashSet(Arrays.asList(GHTeam.wrapUp(root.retrieve().withCredential().to("/repos/" + owner.login + "/" + name + "/teams", GHTeam[].class), root.getOrganization(owner.login)))));
}
public void addCollaborators(GHUser... users) throws IOException {
@@ -253,7 +253,7 @@ public void removeCollaborators(Collection users) throws IOException {
private void modifyCollaborators(Collection 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).withCredential().method(method).to("/repos/" + owner.login + "/" + name + "/collaborators/" + user.getLogin());
}
}
@@ -270,11 +270,10 @@ public void setEmailServiceHook(String address) throws IOException {
}
private void edit(String key, String value) throws IOException {
- Poster poster = new Poster(root).withCredential();
+ Requester requester = new Requester(root).withCredential();
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 +313,7 @@ public void setHomepage(String value) throws IOException {
* Deletes this repository.
*/
public void delete() throws IOException {
- new Poster(root).withCredential().to("/repos/" + owner.login +"/"+name, null, "DELETE");
+ new Requester(root).withCredential().method("DELETE").to("/repos/" + owner.login + "/" + name);
}
/**
@@ -324,7 +323,7 @@ public void delete() throws IOException {
* 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).withCredential().method("POST").to("/repos/" + owner.login + "/" + name + "/forks", GHRepository.class).wrap(root);
}
/**
@@ -334,7 +333,7 @@ public GHRepository fork() throws IOException {
* 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).withCredential().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 +352,7 @@ public GHRepository forkTo(GHOrganization org) throws IOException {
* 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().withCredential().to("/repos/" + owner.login + '/' + name + "/pulls/" + i, GHPullRequest.class).wrapUp(this);
}
/**
@@ -371,7 +370,7 @@ public List getPullRequests(GHIssueState state) throws IOExceptio
public PagedIterable listPullRequests(final GHIssueState state) {
return new PagedIterable() {
public PagedIterator iterator() {
- return new PagedIterator(root.retrievePaged(String.format("/repos/%s/%s/pulls?state=%s", owner.login,name,state.name().toLowerCase(Locale.ENGLISH)), GHPullRequest[].class, false)) {
+ return new PagedIterator(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 +386,14 @@ protected void wrapUp(GHPullRequest[] page) {
*/
public List getHooks() throws IOException {
List list = new ArrayList(Arrays.asList(
- root.retrieveWithAuth(String.format("/repos/%s/%s/hooks", owner.login, name), GHHook[].class)));
+ root.retrieve().withCredential().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().withCredential().to(String.format("/repos/%s/%s/hooks/%d", owner.login, name, id), GHHook.class).wrap(this);
}
/**
@@ -403,7 +402,7 @@ public GHHook getHook(int id) throws IOException {
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 +414,7 @@ public GHCommit getCommit(String sha1) throws IOException {
public PagedIterable listCommits() {
return new PagedIterable() {
public PagedIterator iterator() {
- return new PagedIterator(root.retrievePaged(String.format("/repos/%s/%s/commits",owner.login,name),GHCommit[].class,false)) {
+ return new PagedIterator(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 +430,7 @@ protected void wrapUp(GHCommit[] page) {
public PagedIterable listCommitComments() {
return new PagedIterable() {
public PagedIterator iterator() {
- return new PagedIterator(root.retrievePaged(String.format("/repos/%s/%s/comments",owner.login,name),GHCommitComment[].class,false)) {
+ return new PagedIterator(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 +441,49 @@ protected void wrapUp(GHCommitComment[] page) {
};
}
+ /**
+ * Lists all the commit statues attached to the given commit, newer ones first.
+ */
+ public PagedIterable listCommitStatuses(final String sha1) throws IOException {
+ return new PagedIterable() {
+ public PagedIterator iterator() {
+ return new PagedIterator(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 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)
+ .withCredential()
+ .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,7 +504,7 @@ public GHHook createHook(String name, Map config, Collection getBranches() throws IOException {
Map r = new TreeMap();
- for (GHBranch p : root.retrieve("/repos/" + owner.login + "/" + name + "/branches", GHBranch[].class)) {
+ for (GHBranch p : root.retrieve().to("/repos/" + owner.login + "/" + name + "/branches", GHBranch[].class)) {
p.wrap(this);
r.put(p.getName(),p);
}
@@ -577,7 +619,7 @@ public Map getBranches() throws IOException {
public Map getMilestones() throws IOException {
Map milestones = new TreeMap();
- GHMilestone[] ms = root.retrieve("/repos/" + owner.login + "/" + name + "/milestones", GHMilestone[].class);
+ GHMilestone[] ms = root.retrieve().to("/repos/" + owner.login + "/" + name + "/milestones", GHMilestone[].class);
for (GHMilestone m : ms) {
m.owner = this;
m.root = root;
@@ -589,7 +631,7 @@ public Map getMilestones() throws IOException {
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("/repos/" + owner.login + "/" + name + "/milestones/" + number, GHMilestone.class);
m.owner = this;
m.root = root;
milestones.put(m.getNumber(), m);
@@ -598,9 +640,8 @@ public GHMilestone getMilestone(int number) throws IOException {
}
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).withCredential()
+ .with("title", title).with("description", description).method("POST").to("/repos/" + owner.login + "/" + name + "/milestones", GHMilestone.class).wrap(this);
}
@Override
diff --git a/src/main/java/org/kohsuke/github/GHTeam.java b/src/main/java/org/kohsuke/github/GHTeam.java
index d4bf99d582..dca37e3313 100644
--- a/src/main/java/org/kohsuke/github/GHTeam.java
+++ b/src/main/java/org/kohsuke/github/GHTeam.java
@@ -46,11 +46,11 @@ public int getId() {
* Retrieves the current members.
*/
public Set getMembers() throws IOException {
- return new HashSet(Arrays.asList(GHUser.wrap(org.root.retrieveWithAuth(api("/members"), GHUser[].class), org.root)));
+ return new HashSet(Arrays.asList(GHUser.wrap(org.root.retrieve().withCredential().to(api("/members"), GHUser[].class), org.root)));
}
public Map getRepositories() throws IOException {
- GHRepository[] repos = org.root.retrieveWithAuth(api("/repos"), GHRepository[].class);
+ GHRepository[] repos = org.root.retrieve().withCredential().to(api("/repos"), GHRepository[].class);
Map m = new TreeMap();
for (GHRepository r : repos) {
m.put(r.getName(),r.wrap(org.root));
@@ -62,22 +62,22 @@ public Map getRepositories() throws IOException {
* 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().withCredential().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().withCredential().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().withCredential().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().withCredential().method("DELETE").to(api("/repos/" + r.getOwnerName() + '/' + r.getName()), null);
}
private String api(String tail) {
diff --git a/src/main/java/org/kohsuke/github/GHUser.java b/src/main/java/org/kohsuke/github/GHUser.java
index 07f44f71c4..329434f38c 100644
--- a/src/main/java/org/kohsuke/github/GHUser.java
+++ b/src/main/java/org/kohsuke/github/GHUser.java
@@ -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).withCredential().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).withCredential().method("DELETE").to("/user/following/" + login);
}
/**
@@ -56,7 +56,7 @@ public void unfollow() throws IOException {
*/
@WithBridgeMethods(Set.class)
public GHPersonSet 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(Arrays.asList(wrap(followers,root)));
}
@@ -65,7 +65,7 @@ public GHPersonSet getFollows() throws IOException {
*/
@WithBridgeMethods(Set.class)
public GHPersonSet 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(Arrays.asList(wrap(followers,root)));
}
@@ -82,7 +82,7 @@ public GHPersonSet getFollowers() throws IOException {
public GHPersonSet getOrganizations() throws IOException {
GHPersonSet orgs = new GHPersonSet();
Set names = new HashSet();
- 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()));
}
diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java
index 5893de9c41..886239820b 100644
--- a/src/main/java/org/kohsuke/github/GitHub.java
+++ b/src/main/java/org/kohsuke/github/GitHub.java
@@ -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.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 static GitHub connectAnonymously() {
// append the access token
tailApiUrl = tailApiUrl + (tailApiUrl.indexOf('?')>=0 ?'&':'?') + "access_token=" + oauthAccessToken;
}
-
- return new URL("https://api."+githubServer+tailApiUrl);
- }
-
- /*package*/ T retrieve(String tailApiUrl, Class type) throws IOException {
- return _retrieve(tailApiUrl, type, "GET", false);
- }
-
- /*package*/ T retrieveWithAuth(String tailApiUrl, Class type) throws IOException {
- return _retrieve(tailApiUrl, type, "GET", true);
- }
-
- /*package*/ T retrieveWithAuth(String tailApiUrl, Class type, String method) throws IOException {
- return _retrieve(tailApiUrl, type, method, true);
- }
-
- private T _retrieve(String tailApiUrl, Class 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*/ Iterator retrievePaged(final String tailApiUrl, final Class type, final boolean withAuth) {
- return new Iterator() {
- /**
- * 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
- // ; rel="next"
- int idx = token.indexOf('>');
- url = new URL(token.substring(1,idx));
- return;
- }
- }
-
- // no more "next" link. we are done.
- }
- };
+ if (tailApiUrl.startsWith("/"))
+ return new URL("https://api."+githubServer+tailApiUrl);
+ else
+ return new URL(tailApiUrl);
}
- 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 parse(HttpURLConnection uc, Class 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().withCredential().to("/rate_limit", JsonRateLimit.class).rate;
}
/**
@@ -362,7 +177,7 @@ public GHRateLimit getRateLimit() throws IOException {
public GHMyself getMyself() throws IOException {
requireCredential();
- GHMyself u = retrieveWithAuth("/user", GHMyself.class);
+ GHMyself u = retrieve().withCredential().to("/user", GHMyself.class);
u.root = this;
users.put(u.getLogin(), u);
@@ -376,7 +191,7 @@ public GHMyself getMyself() throws IOException {
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 @@ protected GHUser getUser(GHUser orig) throws IOException {
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 GHRepository getRepository(String name) throws IOException {
* TODO: make this automatic.
*/
public Map getMyOrganizations() throws IOException {
- GHOrganization[] orgs = retrieveWithAuth("/user/orgs", GHOrganization[].class);
+ GHOrganization[] orgs = retrieve().withCredential().to("/user/orgs", GHOrganization[].class);
Map r = new HashMap();
for (GHOrganization o : orgs) {
// don't put 'o' into orgs because they are shallow
@@ -436,7 +251,7 @@ public Map getMyOrganizations() throws IOException {
*/
public List 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 T parseEventPayload(Reader r, Class type) t
* 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).withCredential()
.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 GHRepository createRepository(String name, String description, String hom
*/
public boolean isCredentialValid() throws IOException {
try {
- retrieveWithAuth("/user", GHUser.class);
+ retrieve().withCredential().to("/user", GHUser.class);
return true;
} catch (IOException e) {
return false;
diff --git a/src/main/java/org/kohsuke/github/Poster.java b/src/main/java/org/kohsuke/github/Poster.java
deleted file mode 100644
index d18c054b62..0000000000
--- a/src/main/java/org/kohsuke/github/Poster.java
+++ /dev/null
@@ -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 args = new ArrayList();
- 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 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 to(String tailApiUrl, Class type) throws IOException {
- return to(tailApiUrl,type,"POST");
- }
-
- public T to(String tailApiUrl, Class 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);
- }
- }
- }
-}
diff --git a/src/main/java/org/kohsuke/github/Requester.java b/src/main/java/org/kohsuke/github/Requester.java
new file mode 100644
index 0000000000..be8bb3ffd6
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/Requester.java
@@ -0,0 +1,345 @@
+/*
+ * 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 args = new ArrayList();
+ private boolean authenticate;
+
+ /**
+ * 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.
+ */
+ public Requester withCredential() {
+ // keeping it inline with retrieveWithAuth not to enforce the check
+ // root.requireCredential();
+ authenticate = true;
+ 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 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 to(String tailApiUrl, Class 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 to(String tailApiUrl, T existingInstance) throws IOException {
+ return _to(tailApiUrl, null, existingInstance);
+ }
+
+ /**
+ * Short for {@code method(method).to(tailApiUrl,type)}
+ */
+ @Deprecated
+ public T to(String tailApiUrl, Class type, String method) throws IOException {
+ return method(method).to(tailApiUrl,type);
+ }
+
+ private T _to(String tailApiUrl, Class 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*/ Iterator asIterator(final String tailApiUrl, final Class type) {
+ method("GET");
+ if (!args.isEmpty()) throw new IllegalStateException();
+
+ return new Iterator() {
+ /**
+ * 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
+ // ; 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 (authenticate && 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 parse(HttpURLConnection uc, Class 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).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);
+ }
+ }
+}
diff --git a/src/test/java/org/kohsuke/AppTest.java b/src/test/java/org/kohsuke/AppTest.java
index 1b93cee1d1..ba4a4a4804 100644
--- a/src/test/java/org/kohsuke/AppTest.java
+++ b/src/test/java/org/kohsuke/AppTest.java
@@ -4,11 +4,14 @@
import org.kohsuke.github.GHCommit;
import org.kohsuke.github.GHCommit.File;
import org.kohsuke.github.GHCommitComment;
+import org.kohsuke.github.GHCommitState;
+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.GHIssueState;
import org.kohsuke.github.GHKey;
import org.kohsuke.github.GHMyself;
@@ -45,7 +48,7 @@ public void testRepoCRUD() throws Exception {
public void testCredentialValid() throws IOException {
assertTrue(GitHub.connect().isCredentialValid());
- assertFalse(GitHub.connect("totally","bogus").isCredentialValid());
+ assertFalse(GitHub.connect("totally", "bogus").isCredentialValid());
}
public void testRateLimit() throws IOException {
@@ -85,7 +88,7 @@ public void testRepoPermissions() throws Exception {
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 +311,25 @@ public void testOrganization() throws IOException {
// 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 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());
+ }
}