Compare commits

..

43 Commits

Author SHA1 Message Date
Kohsuke Kawaguchi
3dd738b0db [maven-release-plugin] prepare release github-api-1.34 2013-01-05 17:18:11 -08:00
Kohsuke Kawaguchi
6480dde247 oops test failures 2013-01-05 17:15:46 -08:00
Kohsuke Kawaguchi
555dab7403 Merge branch 'pull-22' 2013-01-05 17:11:58 -08:00
Kohsuke Kawaguchi
13158a28e1 turns out we never exposed the ability to specify the custom URL.
So no backward compatibility provision is needed.
Also in this change, I stopped exposin the password. See the code comment for more details
2013-01-05 17:10:24 -08:00
Kohsuke Kawaguchi
cbaca87bbc massaging this a bit to accept the full URL 2013-01-05 16:08:42 -08:00
Kohsuke Kawaguchi
5166202f67 follow-up fix and documenting the expected value 2013-01-05 15:59:02 -08:00
johnou
35d45ca47d JENKINS-13726: Github plugin should work with Guthub enterprise by allowing for overriding the github URL. 2013-01-06 00:47:44 +01:00
Honza Brázdil
b66ede98c7 Retrieve repository directly. 2012-10-20 23:24:56 +02:00
Kohsuke Kawaguchi
1ba8f2ccbf Update pom.xml
Added <repository> definition
2012-10-17 07:45:15 -07:00
Kohsuke Kawaguchi
82133c117a [maven-release-plugin] prepare for next development iteration 2012-09-13 16:31:58 -07:00
Kohsuke Kawaguchi
f71afca828 [maven-release-plugin] prepare release github-api-1.33 2012-09-13 16:31:52 -07:00
Kohsuke Kawaguchi
87f5231c9a updated a test 2012-09-13 16:28:50 -07:00
Kohsuke Kawaguchi
b17f506c20 clean up 2012-09-13 16:23:57 -07:00
Kohsuke Kawaguchi
7f15f12668 completed code to create a new issue 2012-09-13 16:23:06 -07:00
Kohsuke Kawaguchi
e53e62bfa0 added code to create a new issue 2012-09-13 15:56:05 -07:00
Kohsuke Kawaguchi
2e74517a4a fixed issue #20
PagedIterator is updated to cope with the base iterator returning array of length 0
2012-09-13 15:46:25 -07:00
Kohsuke Kawaguchi
aed888051e added a method to retrieve a single issue 2012-09-13 15:36:32 -07:00
Kohsuke Kawaguchi
bd584124bb Merge pull request #19 from janinko/Issues-19-PagedIterable_dosnt_use_authentication
PagedIterable dosn't use authentication
2012-09-13 15:32:56 -07:00
Honza Brázdil
e658a7fa6b send authentication header on all requests 2012-09-12 18:04:59 +02:00
Kohsuke Kawaguchi
52108707bb Merge pull request #18 from janinko/patch-1
When using lazy population, this is not deprecated
2012-09-07 09:52:12 -07:00
Honza Brázdil
0e226a8f78 When using lazy population, this is not deprecated 2012-09-06 14:23:51 +03:00
Kohsuke Kawaguchi
1bf3e025b8 [maven-release-plugin] prepare for next development iteration 2012-09-05 19:30:18 -07:00
Kohsuke Kawaguchi
a0fdcca129 [maven-release-plugin] prepare release github-api-1.32 2012-09-05 19:30:04 -07:00
Kohsuke Kawaguchi
3e75e96718 adding an assertion 2012-09-05 19:26:25 -07:00
Kohsuke Kawaguchi
1dd875adac adding lazy population to GHUser and got rid of GHSmallUser 2012-09-05 19:25:56 -07:00
Kohsuke Kawaguchi
2341f789ab Merge branch 'master' into pull-17 2012-09-05 19:13:32 -07:00
Kohsuke Kawaguchi
8a95847b0a Renaming to better represent what it does. 2012-09-05 19:13:10 -07:00
Kohsuke Kawaguchi
ea9f3eacbc bug fix 2012-09-05 19:12:18 -07:00
Kohsuke Kawaguchi
435363a246 moved the pagenation API over to Poster 2012-09-05 19:08:29 -07:00
Kohsuke Kawaguchi
b6520cb6f9 got rid of all retrieveXYZ methods in favor of Poster 2012-09-05 18:55:44 -07:00
Kohsuke Kawaguchi
d6d73f5165 A step toward using Poster in place of the retrieve* methods.
I was trying to add  the flavor of the retrieve method that reads into an existing instance, when I realized that there are just too many orthogonal axes here to rely on overloaded methods.

That calls for a builder pattern, which we already have --- it's called Poster, but it can actually already handle GET and other HTTP requests.

So I'm retiring the retrieveXYZ methods and moving the code into Poster. This is the first step.
2012-09-05 18:53:06 -07:00
Kohsuke Kawaguchi
f58dbceec7 Massaging the pull request.
In various places of the GitHub API, there's often a full object and
then there's a shallow object. I think the client API would be a lot
easier to use if a single class represents both and retrieve additional
fields on the fly as needed.
2012-09-05 18:15:29 -07:00
Kohsuke Kawaguchi
3f1bb1a214 completed the commit status API 2012-09-05 18:00:50 -07:00
Kohsuke Kawaguchi
892d2acaa2 Added the commit status API, first cut. 2012-09-04 10:40:32 -07:00
Honza Brázdil
4e27d1b5a0 method names should by camelCase 2012-08-31 15:31:41 +02:00
Honza Brázdil
fff3272e42 make url methods return URL and add getApiUrl 2012-08-31 15:27:06 +02:00
Honza Brázdil
803198620d Nested GHIssue.PullRequest must be static 2012-08-31 15:24:14 +02:00
Honza Brázdil
9017fe70d5 We need to preserve api 2012-08-31 14:50:08 +02:00
Honza Brázdil
ce47762fbf Added milestone attribute to GHIssue 2012-08-31 14:37:02 +02:00
Honza Brázdil
b40677a3ca changed return value of GHRepository.getPullRequest()
I'm not sure whether this influence something or not, but I think it should be OK.
2012-08-31 14:30:00 +02:00
Honza Brázdil
c283c4e595 added method retrieving detailed pull request 2012-08-31 14:23:48 +02:00
Honza Brázdil
6aabaea96c Clean up GHIssue and GHPullRequest and make them relevant to api v3.
Added SmallUser representing reference to user.
Added DetailedPullRequest - when retrieving pull request by id, it has more attributes then pull requests obtained by GHRepository.getPullRequests()
2012-08-31 12:37:00 +02:00
Kohsuke Kawaguchi
65adb2f2b4 [maven-release-plugin] prepare for next development iteration 2012-08-28 11:03:11 -07:00
21 changed files with 997 additions and 509 deletions

11
pom.xml
View File

@@ -7,7 +7,7 @@
</parent>
<artifactId>github-api</artifactId>
<version>1.31</version>
<version>1.34</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>
@@ -85,6 +85,13 @@
</dependency>
</dependencies>
<repositories>
<repository>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
</repository>
</repositories>
<reporting>
<plugins>
<plugin>

View File

@@ -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;

View File

@@ -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() {

View 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
}

View 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;
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}
}

View 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);
}
}

View File

@@ -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()
// }
}

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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) {

View File

@@ -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()));
}

View File

@@ -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.
@@ -77,19 +66,22 @@ public class GitHub {
private final Map<String,GHOrganization> orgs = new HashMap<String, GHOrganization>();
/*package*/ String oauthAccessToken;
private final String githubServer;
private final String apiUrl;
private GitHub(String login, String apiToken, String password) {
this ("github.com", login, apiToken, password);
this ("https://api.github.com", login, apiToken, password);
}
/**
*
* @param githubServer
* The host name of the GitHub (or GitHub enterprise) server, such as "github.com".
* @param apiUrl
* The URL of GitHub (or GitHub enterprise) API endpoint, such as "https://api.github.com" or
* "http://ghe.acme.com/api/v3". Note that GitHub Enterprise has <tt>/api/v3</tt> in the URL.
* For historical reasons, this parameter still accepts the bare domain name, but that's considered deprecated.
*/
private GitHub(String githubServer, String login, String apiToken, String password) {
this.githubServer = githubServer;
private GitHub(String apiUrl, String login, String apiToken, String password) {
if (apiUrl.endsWith("/")) apiUrl = apiUrl.substring(0, apiUrl.length()-1); // normalize
this.apiUrl = apiUrl;
this.login = login;
this.apiToken = apiToken;
this.password = password;
@@ -102,9 +94,9 @@ public class GitHub {
encodedAuthorization = null;
}
private GitHub (String githubServer, String oauthAccessToken) throws IOException {
private GitHub (String apiUrl, String oauthAccessToken) throws IOException {
this.githubServer = githubServer;
this.apiUrl = apiUrl;
this.password = null;
this.encodedAuthorization = null;
@@ -129,6 +121,21 @@ public class GitHub {
return new GitHub(props.getProperty("login"),props.getProperty("token"),props.getProperty("password"));
}
/**
* Version that connects to GitHub Enterprise.
*
* @param apiUrl
* The URL of GitHub (or GitHub enterprise) API endpoint, such as "https://api.github.com" or
* "http://ghe.acme.com/api/v3". Note that GitHub Enterprise has <tt>/api/v3</tt> in the URL.
* For historical reasons, this parameter still accepts the bare domain name, but that's considered deprecated.
*/
public static GitHub connectToEnterprise(String apiUrl, String login, String apiToken) {
// not exposing password because the login process still assumes https://github.com/
// if we are to fix this, fix that by getting rid of createWebClient() and replace the e-mail service hook
// with GitHub API.
return new GitHub(apiUrl,login,apiToken,null);
}
public static GitHub connect(String login, String apiToken){
return new GitHub(login,apiToken,null);
}
@@ -163,196 +170,27 @@ public class GitHub {
// append the access token
tailApiUrl = tailApiUrl + (tailApiUrl.indexOf('?')>=0 ?'&':'?') + "access_token=" + oauthAccessToken;
}
return new URL("https://api."+githubServer+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);
if (tailApiUrl.startsWith("/")) {
if ("github.com".equals(apiUrl)) {// backward compatibility
return new URL("https://api.github.com" + tailApiUrl);
} else {
return new URL(apiUrl + tailApiUrl);
}
} else {
return new URL(tailApiUrl);
}
}
/**
* 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 +200,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 +214,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 +237,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;
@@ -412,7 +250,7 @@ public class GitHub {
*/
public GHRepository getRepository(String name) throws IOException {
String[] tokens = name.split("/");
return getUser(tokens[0]).getRepository(tokens[1]);
return retrieve().to("/repos/" + tokens[0] + '/' + tokens[1], GHRepository.class).wrap(this);
}
/**
@@ -422,7 +260,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 +274,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 +300,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 +311,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;

View File

@@ -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;

View File

@@ -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);
}
}
}
}

View 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);
}
}
}

View File

@@ -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());
}
}

View File

@@ -0,0 +1,24 @@
package org.kohsuke.github;
import junit.framework.TestCase;
/**
* Unit test for {@link GitHub}.
*/
public class GitHubTest extends TestCase {
public void testGitHubServerWithHttp() throws Exception {
GitHub hub = GitHub.connectToEnterprise("http://enterprise.kohsuke.org/api/v3", "kohsuke", "token");
assertEquals("http://enterprise.kohsuke.org/api/v3/test", hub.getApiURL("/test").toString());
}
public void testGitHubServerWithHttps() throws Exception {
GitHub hub = GitHub.connectToEnterprise("https://enterprise.kohsuke.org/api/v3", "kohsuke", "token");
assertEquals("https://enterprise.kohsuke.org/api/v3/test", hub.getApiURL("/test").toString());
}
public void testGitHubServerWithoutServer() throws Exception {
GitHub hub = GitHub.connect("kohsuke", "token", "password");
assertEquals("https://api.github.com/test", hub.getApiURL("/test").toString());
}
}