diff --git a/pom.xml b/pom.xml index 992c9ef281..3c04540ce0 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ github-api - 1.68 + 1.69 GitHub API for Java http://github-api.kohsuke.org/ GitHub API for Java @@ -16,7 +16,7 @@ scm:git:git@github.com/kohsuke/${project.artifactId}.git scm:git:ssh://git@github.com/kohsuke/${project.artifactId}.git http://${project.artifactId}.kohsuke.org/ - github-api-1.68 + github-api-1.69 @@ -44,6 +44,25 @@ + + org.codehaus.mojo + findbugs-maven-plugin + 3.0.1 + + true + true + false + + + + run-findbugs + verify + + check + + + + @@ -109,6 +128,12 @@ 1.9.5 test + + com.google.code.findbugs + annotations + 3.0.0 + provided + diff --git a/src/main/java/org/kohsuke/github/GHBranch.java b/src/main/java/org/kohsuke/github/GHBranch.java index 1c68bfa758..9c3d331adc 100644 --- a/src/main/java/org/kohsuke/github/GHBranch.java +++ b/src/main/java/org/kohsuke/github/GHBranch.java @@ -40,7 +40,8 @@ public String getSHA1() { @Override public String toString() { - return "Branch:" + name + " in " + owner.getUrl(); + final String url = owner != null ? owner.getUrl().toString() : "unknown"; + return "Branch:" + name + " in " + url; } /*package*/ GHBranch wrap(GHRepository repo) { diff --git a/src/main/java/org/kohsuke/github/GHCommit.java b/src/main/java/org/kohsuke/github/GHCommit.java index fbb7c24fdd..ce56cddd6b 100644 --- a/src/main/java/org/kohsuke/github/GHCommit.java +++ b/src/main/java/org/kohsuke/github/GHCommit.java @@ -70,7 +70,8 @@ public static class Stats { public static class File { String status; int changes,additions,deletions; - String raw_url, blob_url, filename, sha, patch; + String raw_url, blob_url, sha, patch; + String filename, previous_filename; /** * Number of lines added + removed. @@ -101,12 +102,19 @@ public String getStatus() { } /** - * Just the base name and the extension without any directory name. + * Full path in the repository. */ public String getFileName() { return filename; } + /** + * Previous path, in case file has moved. + */ + public String getPreviousFilename() { + return previous_filename; + } + /** * The actual change. */ diff --git a/src/main/java/org/kohsuke/github/GHEvent.java b/src/main/java/org/kohsuke/github/GHEvent.java index 13ef432f8f..9e172146ae 100644 --- a/src/main/java/org/kohsuke/github/GHEvent.java +++ b/src/main/java/org/kohsuke/github/GHEvent.java @@ -23,12 +23,15 @@ public enum GHEvent { ISSUE_COMMENT, ISSUES, MEMBER, + PAGE_BUILD, PUBLIC, PULL_REQUEST, PULL_REQUEST_REVIEW_COMMENT, PUSH, RELEASE, + REPOSITORY, // only valid for org hooks STATUS, TEAM_ADD, - WATCH + WATCH, + PING } diff --git a/src/main/java/org/kohsuke/github/GHEventPayload.java b/src/main/java/org/kohsuke/github/GHEventPayload.java index cd248c80ec..e60fa40193 100644 --- a/src/main/java/org/kohsuke/github/GHEventPayload.java +++ b/src/main/java/org/kohsuke/github/GHEventPayload.java @@ -104,8 +104,12 @@ public void setRepository(GHRepository repository) { @Override void wrapUp(GitHub root) { super.wrapUp(root); - repository.wrap(root); - issue.wrap(repository); + if (repository != null) { + repository.wrap(root); + issue.wrap(repository); + } else { + issue.wrap(root); + } comment.wrapUp(issue); } } diff --git a/src/main/java/org/kohsuke/github/GHHook.java b/src/main/java/org/kohsuke/github/GHHook.java index ffe4837509..ce8c8a4dfd 100644 --- a/src/main/java/org/kohsuke/github/GHHook.java +++ b/src/main/java/org/kohsuke/github/GHHook.java @@ -11,22 +11,12 @@ /** * @author Kohsuke Kawaguchi */ -public class GHHook extends GHObject { - /** - * Repository that the hook belongs to. - */ - /*package*/ transient GHRepository repository; - +public abstract class GHHook extends GHObject { String name; List events; boolean active; Map config; - /*package*/ GHHook wrap(GHRepository owner) { - this.repository = owner; - return this; - } - public String getName() { return name; } @@ -50,7 +40,7 @@ public Map getConfig() { * Deletes this hook. */ public void delete() throws IOException { - new Requester(repository.root).method("DELETE").to(String.format("/repos/%s/%s/hooks/%d", repository.getOwnerName(), repository.getName(), id)); + new Requester(getRoot()).method("DELETE").to(getApiRoute()); } /** @@ -60,4 +50,8 @@ public void delete() throws IOException { public URL getHtmlUrl() { return null; } + + abstract GitHub getRoot(); + + abstract String getApiRoute(); } diff --git a/src/main/java/org/kohsuke/github/GHHooks.java b/src/main/java/org/kohsuke/github/GHHooks.java new file mode 100644 index 0000000000..1a6154aa6d --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHHooks.java @@ -0,0 +1,130 @@ +package org.kohsuke.github; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * Utility class for creating and retrieving webhooks; removes duplication between GHOrganization and GHRepository + * functionality + */ +class GHHooks { + static abstract class Context { + private final GitHub root; + + private Context(GitHub root) { + this.root = root; + } + + public List getHooks() throws IOException { + List list = new ArrayList(Arrays.asList( + root.retrieve().to(collection(), collectionClass()))); + for (GHHook h : list) + wrap(h); + return list; + } + + public GHHook getHook(int id) throws IOException { + GHHook hook = root.retrieve().to(collection() + "/" + id, clazz()); + return wrap(hook); + } + + public GHHook createHook(String name, Map config, Collection events, boolean active) throws IOException { + List ea = null; + if (events!=null) { + ea = new ArrayList(); + for (GHEvent e : events) + ea.add(e.name().toLowerCase(Locale.ENGLISH)); + } + + GHHook hook = new Requester(root) + .with("name", name) + .with("active", active) + ._with("config", config) + ._with("events", ea) + .to(collection(), clazz()); + + return wrap(hook); + } + + abstract String collection(); + + abstract Class collectionClass(); + + abstract Class clazz(); + + abstract GHHook wrap(GHHook hook); + } + + private static class RepoContext extends Context { + private final GHRepository repository; + private final GHUser owner; + + private RepoContext(GHRepository repository, GHUser owner) { + super(repository.root); + this.repository = repository; + this.owner = owner; + } + + @Override + String collection() { + return String.format("/repos/%s/%s/hooks", owner.getLogin(), repository.getName()); + } + + @Override + Class collectionClass() { + return GHRepoHook[].class; + } + + @Override + Class clazz() { + return GHRepoHook.class; + } + + @Override + GHHook wrap(GHHook hook) { + return ((GHRepoHook)hook).wrap(repository); + } + } + + private static class OrgContext extends Context { + private final GHOrganization organization; + + private OrgContext(GHOrganization organization) { + super(organization.root); + this.organization = organization; + } + + @Override + String collection() { + return String.format("/orgs/%s/hooks", organization.getLogin()); + } + + @Override + Class collectionClass() { + return GHOrgHook[].class; + } + + @Override + Class clazz() { + return GHOrgHook.class; + } + + @Override + GHHook wrap(GHHook hook) { + return ((GHOrgHook)hook).wrap(organization); + } + } + + static Context repoContext(GHRepository repository, GHUser owner) { + return new RepoContext(repository, owner); + } + + static Context orgContext(GHOrganization organization) { + return new OrgContext(organization); + } +} diff --git a/src/main/java/org/kohsuke/github/GHIssue.java b/src/main/java/org/kohsuke/github/GHIssue.java index 8c071a93f8..e7864087dd 100644 --- a/src/main/java/org/kohsuke/github/GHIssue.java +++ b/src/main/java/org/kohsuke/github/GHIssue.java @@ -24,6 +24,8 @@ package org.kohsuke.github; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; + import java.io.IOException; import java.net.URL; import java.util.Collection; @@ -140,9 +142,14 @@ public URL getApiURL(){ /** * Updates the issue by adding a comment. + * + * @return + * Newly posted comment. */ - public void comment(String message) throws IOException { - new Requester(root).with("body",message).to(getIssuesApiRoute() + "/comments"); + @WithBridgeMethods(void.class) + public GHIssueComment comment(String message) throws IOException { + GHIssueComment r = new Requester(root).with("body",message).to(getIssuesApiRoute() + "/comments", GHIssueComment.class); + return r.wrapUp(this); } private void edit(String key, Object value) throws IOException { @@ -176,7 +183,7 @@ public void setBody(String body) throws IOException { } public void assignTo(GHUser user) throws IOException { - editIssue("assignee",user.getLogin()); + editIssue("assignee", user.getLogin()); } public void setLabels(String... labels) throws IOException { diff --git a/src/main/java/org/kohsuke/github/GHIssueComment.java b/src/main/java/org/kohsuke/github/GHIssueComment.java index 732c508a2d..3179fa93a6 100644 --- a/src/main/java/org/kohsuke/github/GHIssueComment.java +++ b/src/main/java/org/kohsuke/github/GHIssueComment.java @@ -25,7 +25,6 @@ import java.io.IOException; import java.net.URL; -import java.util.Date; /** * Comment to the issue @@ -79,4 +78,23 @@ public GHUser getUser() throws IOException { public URL getHtmlUrl() { return null; } + + /** + * Updates the body of the issue comment. + */ + public void update(String body) throws IOException { + new Requester(owner.root).with("body", body).method("PATCH").to(getApiRoute(), GHIssueComment.class); + this.body = body; + } + + /** + * Deletes this issue comment. + */ + public void delete() throws IOException { + new Requester(owner.root).method("DELETE").to(getApiRoute()); + } + + private String getApiRoute() { + return "/repos/"+owner.getRepository().getOwnerName()+"/"+owner.getRepository().getName()+"/issues/comments/" + id; + } } diff --git a/src/main/java/org/kohsuke/github/GHMyself.java b/src/main/java/org/kohsuke/github/GHMyself.java index 7b588e88fa..2041336367 100644 --- a/src/main/java/org/kohsuke/github/GHMyself.java +++ b/src/main/java/org/kohsuke/github/GHMyself.java @@ -5,7 +5,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -17,6 +16,33 @@ * @author Kohsuke Kawaguchi */ public class GHMyself extends GHUser { + + /** + * Type of repositories returned during listing. + */ + public enum RepositoryListFilter { + /** + * All public and private repositories that current user has access or collaborates to + */ + ALL, + /** + * Public and private repositories owned by current user + */ + OWNER, + /** + * Public repositories that current user has access or collaborates to + */ + PUBLIC, + /** + * Private repositories that current user has access or collaborates to + */ + PRIVATE, + /** + * Public and private repositories that current user is a member + */ + MEMBER; + } + /** * @deprecated * Use {@link #getEmails2()} @@ -109,16 +135,31 @@ public PagedIterable listRepositories() { } /** - * Lists up all the repositories this user owns (public and private) using the specified page size. + * List repositories that are accessible to the authenticated user (public and private) using the specified page size. + * + * This includes repositories owned by the authenticated user, repositories that belong to other users + * where the authenticated user is a collaborator, and other organizations' repositories that the authenticated + * user has access to through an organization membership. * * @param pageSize size for each page of items returned by GitHub. Maximum page size is 100. * * Unlike {@link #getRepositories()}, this does not wait until all the repositories are returned. */ public PagedIterable listRepositories(final int pageSize) { + return listRepositories(pageSize, RepositoryListFilter.ALL); + } + + /** + * List repositories of a certain type that are accessible by current authenticated user using the specified page size. + * + * @param pageSize size for each page of items returned by GitHub. Maximum page size is 100. + * @param repoType type of repository returned in the listing + */ + public PagedIterable listRepositories(final int pageSize, final RepositoryListFilter repoType) { return new PagedIterable() { public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator("/user/repos?per_page=" + pageSize, GHRepository[].class)) { + return new PagedIterator(root.retrieve().asIterator("/user/repos?per_page=" + pageSize + + "&type=" + repoType.name().toLowerCase(), GHRepository[].class)) { @Override protected void wrapUp(GHRepository[] page) { for (GHRepository c : page) diff --git a/src/main/java/org/kohsuke/github/GHOrgHook.java b/src/main/java/org/kohsuke/github/GHOrgHook.java new file mode 100644 index 0000000000..58404019bf --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHOrgHook.java @@ -0,0 +1,27 @@ +/* + * © Copyright 2015 - SourceClear Inc + */ + +package org.kohsuke.github; + +class GHOrgHook extends GHHook { + /** + * Organization that the hook belongs to. + */ + /*package*/ transient GHOrganization organization; + + /*package*/ GHOrgHook wrap(GHOrganization owner) { + this.organization = owner; + return this; + } + + @Override + GitHub getRoot() { + return organization.root; + } + + @Override + String getApiRoute() { + return String.format("/orgs/%s/hooks/%d", organization.getLogin(), id); + } +} diff --git a/src/main/java/org/kohsuke/github/GHOrganization.java b/src/main/java/org/kohsuke/github/GHOrganization.java index 0680ca39ca..4409dafaaa 100644 --- a/src/main/java/org/kohsuke/github/GHOrganization.java +++ b/src/main/java/org/kohsuke/github/GHOrganization.java @@ -1,10 +1,13 @@ package org.kohsuke.github; import java.io.IOException; +import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.TreeMap; @@ -26,7 +29,7 @@ public GHRepository createRepository(String name, String description, String hom GHTeam t = getTeams().get(team); if (t==null) throw new IllegalArgumentException("No such team: "+team); - return createRepository(name,description,homepage,t,isPublic); + return createRepository(name, description, homepage, t, isPublic); } public GHRepository createRepository(String name, String description, String homepage, GHTeam team, boolean isPublic) throws IOException { @@ -252,4 +255,39 @@ protected void wrapUp(GHRepository[] page) { } }; } + + /** + * Retrieves the currently configured hooks. + */ + public List getHooks() throws IOException { + return GHHooks.orgContext(this).getHooks(); + } + + public GHHook getHook(int id) throws IOException { + return GHHooks.orgContext(this).getHook(id); + } + + /** + * + * See https://api.github.com/hooks for possible names and their configuration scheme. + * TODO: produce type-safe binding + * + * @param name + * Type of the hook to be created. See https://api.github.com/hooks for possible names. + * @param config + * The configuration hash. + * @param events + * Can be null. Types of events to hook into. + */ + public GHHook createHook(String name, Map config, Collection events, boolean active) throws IOException { + return GHHooks.orgContext(this).createHook(name, config, events, active); + } + + public GHHook createWebHook(URL url, Collection events) throws IOException { + return createHook("web", Collections.singletonMap("url", url.toExternalForm()),events,true); + } + + public GHHook createWebHook(URL url) throws IOException { + return createWebHook(url, null); + } } diff --git a/src/main/java/org/kohsuke/github/GHPullRequestReviewComment.java b/src/main/java/org/kohsuke/github/GHPullRequestReviewComment.java index 09863f78cf..8cb497e16a 100644 --- a/src/main/java/org/kohsuke/github/GHPullRequestReviewComment.java +++ b/src/main/java/org/kohsuke/github/GHPullRequestReviewComment.java @@ -86,7 +86,7 @@ public URL getHtmlUrl() { } protected String getApiRoute() { - return "/repos/"+owner.getRepository().getFullName()+"/comments/"+id; + return "/repos/"+owner.getRepository().getFullName()+"/pulls/comments/"+id; } /** @@ -94,6 +94,7 @@ protected String getApiRoute() { */ public void update(String body) throws IOException { new Requester(owner.root).method("PATCH").with("body", body).to(getApiRoute(),this); + this.body = body; } /** diff --git a/src/main/java/org/kohsuke/github/GHRepoHook.java b/src/main/java/org/kohsuke/github/GHRepoHook.java new file mode 100644 index 0000000000..948438eb96 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHRepoHook.java @@ -0,0 +1,23 @@ +package org.kohsuke.github; + +class GHRepoHook extends GHHook { + /** + * Repository that the hook belongs to. + */ + /*package*/ transient GHRepository repository; + + /*package*/ GHRepoHook wrap(GHRepository owner) { + this.repository = owner; + return this; + } + + @Override + GitHub getRoot() { + return repository.root; + } + + @Override + String getApiRoute() { + return String.format("/repos/%s/%s/hooks/%d", repository.getOwnerName(), repository.getName(), id); + } +} diff --git a/src/main/java/org/kohsuke/github/GHRepository.java b/src/main/java/org/kohsuke/github/GHRepository.java index e4459e84d6..5acf0a648b 100644 --- a/src/main/java/org/kohsuke/github/GHRepository.java +++ b/src/main/java/org/kohsuke/github/GHRepository.java @@ -40,7 +40,7 @@ /** * A repository on GitHub. - * + * * @author Kohsuke Kawaguchi */ @SuppressWarnings({"UnusedDeclaration"}) @@ -57,14 +57,14 @@ public class GHRepository extends GHObject { private int watchers,forks,open_issues,size,network_count,subscribers_count; private String pushed_at; private Map milestones = new HashMap(); - + private String default_branch,language; private Map commits = new HashMap(); private GHRepoPermission permissions; private GHRepository source, parent; - + public GHDeploymentBuilder createDeployment(String ref) { return new GHDeploymentBuilder(this,ref); } @@ -164,7 +164,7 @@ public String getSshUrl() { public URL getHtmlUrl() { return GitHub.parseURL(html_url); } - + /** * Short repository name without the owner. For example 'jenkins' in case of http://github.com/jenkinsci/jenkins */ @@ -321,6 +321,10 @@ public boolean isFork() { return fork; } + /** + * Returns the number of all forks of this repository. + * This not only counts direct forks, but also forks of forks, and so on. + */ public int getForks() { return forks; } @@ -364,6 +368,14 @@ public Date getPushedAt() { * @return * This field is null until the user explicitly configures the master branch. */ + public String getDefaultBranch() { + return default_branch; + } + + /** + * @deprecated + * Renamed to {@link #getDefaultBranch()} + */ public String getMasterBranch() { return default_branch; } @@ -494,6 +506,10 @@ public void setHomepage(String value) throws IOException { edit("homepage",value); } + public void setDefaultBranch(String value) throws IOException { + edit("default_branch", value); + } + /** * Deletes this repository. */ @@ -505,6 +521,43 @@ public void delete() throws IOException { } } + /** + * Sort orders for listing forks + */ + public static enum ForkSort { NEWEST, OLDEST, STARGAZERS } + + /** + * Lists all the direct forks of this repository, sorted by + * github api default, currently {@link ForkSort#NEWEST ForkSort.NEWEST}. + */ + public PagedIterable listForks() { + return listForks(null); + } + + /** + * Lists all the direct forks of this repository, sorted by the given sort order. + * @param sort the sort order. If null, defaults to github api default, + * currently {@link ForkSort#NEWEST ForkSort.NEWEST}. + */ + public PagedIterable listForks(final ForkSort sort) { + return new PagedIterable() { + public PagedIterator iterator() { + String sortParam = ""; + if (sort != null) { + sortParam = "?sort=" + sort.toString().toLowerCase(Locale.ENGLISH); + } + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("forks" + sortParam), GHRepository[].class)) { + @Override + protected void wrapUp(GHRepository[] page) { + for (GHRepository c : page) { + c.wrap(root); + } + } + }; + } + }; + } + /** * Forks this repository as your repository. * @@ -597,15 +650,11 @@ public GHPullRequest createPullRequest(String title, String head, String base, S * Retrieves the currently configured hooks. */ public List getHooks() throws IOException { - List list = new ArrayList(Arrays.asList( - root.retrieve().to(getApiTailUrl("hooks"), GHHook[].class))); - for (GHHook h : list) - h.wrap(this); - return list; + return GHHooks.repoContext(this, owner).getHooks(); } public GHHook getHook(int id) throws IOException { - return root.retrieve().to(getApiTailUrl("hooks/" + id), GHHook.class).wrap(this); + return GHHooks.repoContext(this, owner).getHook(id); } /** @@ -649,7 +698,7 @@ public GHRef[] getRefs(String refType) throws IOException { } /** * Retrive a ref of the given type for the current GitHub repository. - * + * * @param refName * eg: heads/branch * @return refs matching the request type @@ -662,7 +711,7 @@ public GHRef getRef(String refName) throws IOException { } /** * Retrive a tree of the given type for the current GitHub repository. - * + * * @param sha - sha number or branch name ex: "master" * @return refs matching the request type * @throws IOException @@ -673,11 +722,11 @@ public GHTree getTree(String sha) throws IOException { String url = String.format("/repos/%s/%s/git/trees/%s", owner.login, name, sha); return root.retrieve().to(url, GHTree.class).wrap(root); } - + /** * Retrieves the tree for the current GitHub repository, recursively as described in here: * https://developer.github.com/v3/git/trees/#get-a-tree-recursively - * + * * @param sha - sha number or branch name ex: "master" * @param recursive use 1 * @throws IOException @@ -774,7 +823,7 @@ public GHCommitStatus getLastCommitStatus(String sha1) throws IOException { * @param description * Optional short description. * @param context - * Optinal commit status context. + * Optinal commit status context. */ public GHCommitStatus createCommitStatus(String sha1, GHCommitState state, String targetUrl, String description, String context) throws IOException { return new Requester(root) @@ -784,7 +833,7 @@ public GHCommitStatus createCommitStatus(String sha1, GHCommitState state, Strin .with("context", context) .to(String.format("/repos/%s/%s/statuses/%s",owner.login,this.name,sha1),GHCommitStatus.class).wrapUp(root); } - + /** * @see #createCommitStatus(String, GHCommitState,String,String,String) */ @@ -858,10 +907,10 @@ protected void wrapUp(GHUser[] page) { } /** - * + * * See https://api.github.com/hooks for possible names and their configuration scheme. * TODO: produce type-safe binding - * + * * @param name * Type of the hook to be created. See https://api.github.com/hooks for possible names. * @param config @@ -870,21 +919,9 @@ protected void wrapUp(GHUser[] page) { * Can be null. Types of events to hook into. */ public GHHook createHook(String name, Map config, Collection events, boolean active) throws IOException { - List ea = null; - if (events!=null) { - ea = new ArrayList(); - for (GHEvent e : events) - ea.add(e.name().toLowerCase(Locale.ENGLISH)); - } - - return new Requester(root) - .with("name", name) - .with("active", active) - ._with("config", config) - ._with("events",ea) - .to(String.format("/repos/%s/%s/hooks",owner.login,this.name),GHHook.class).wrap(this); + return GHHooks.repoContext(this, owner).createHook(name, config, events, active); } - + public GHHook createWebHook(URL url, Collection events) throws IOException { return createHook("web",Collections.singletonMap("url",url.toExternalForm()),events,true); } @@ -909,8 +946,8 @@ private void verifyMine() throws IOException { /** * Returns a set that represents the post-commit hook URLs. * The returned set is live, and changes made to them are reflected to GitHub. - * - * @deprecated + * + * @deprecated * Use {@link #getHooks()} and {@link #createHook(String, Map, Collection, boolean)} */ public Set getPostCommitHooks() { @@ -1098,19 +1135,19 @@ public GHMilestone createMilestone(String title, String description) throws IOEx return new Requester(root) .with("title", title).with("description", description).method("POST").to(getApiTailUrl("milestones"), GHMilestone.class).wrap(this); } - + public GHDeployKey addDeployKey(String title,String key) throws IOException { return new Requester(root) .with("title", title).with("key", key).method("POST").to(getApiTailUrl("keys"), GHDeployKey.class).wrap(this); - + } - + public List getDeployKeys() throws IOException{ List list = new ArrayList(Arrays.asList( root.retrieve().to(getApiTailUrl("keys"), GHDeployKey[].class))); for (GHDeployKey h : list) h.wrap(this); - return list; + return list; } /** @@ -1119,7 +1156,7 @@ public List getDeployKeys() throws IOException{ * @return * {@link GHRepository} that points to the root repository where this repository is forked * (indirectly or directly) from. Otherwise null. - * @see #getParent() + * @see #getParent() */ public GHRepository getSource() throws IOException { if (source == null) return null; @@ -1136,7 +1173,7 @@ public GHRepository getSource() throws IOException { * @return * {@link GHRepository} that points to the repository where this repository is forked * directly from. Otherwise null. - * @see #getSource() + * @see #getSource() */ public GHRepository getParent() throws IOException { if (parent == null) return null; @@ -1144,7 +1181,7 @@ public GHRepository getParent() throws IOException { parent = root.getRepository(parent.getFullName()); return parent; } - + /** * Subscribes to this repository to get notifications. */ @@ -1162,7 +1199,7 @@ public GHSubscription subscribe(boolean subscribed, boolean ignored) throws IOEx */ public GHSubscription getSubscription() throws IOException { try { - return new Requester(root).to(getApiTailUrl("subscription"), GHSubscription.class).wrapUp(this); + return root.retrieve().to(getApiTailUrl("subscription"), GHSubscription.class).wrapUp(this); } catch (FileNotFoundException e) { return null; } diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java index d196481bc2..d3853380e8 100644 --- a/src/main/java/org/kohsuke/github/GitHub.java +++ b/src/main/java/org/kohsuke/github/GitHub.java @@ -45,6 +45,7 @@ import java.util.Map; import java.util.Set; import java.util.TimeZone; +import java.util.concurrent.TimeUnit; import org.apache.commons.codec.binary.Base64; @@ -248,6 +249,7 @@ public GHRateLimit getRateLimit() throws IOException { // see issue #78 GHRateLimit r = new GHRateLimit(); r.limit = r.remaining = 1000000; + r.reset = new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1)); return r; } } diff --git a/src/main/java/org/kohsuke/github/Requester.java b/src/main/java/org/kohsuke/github/Requester.java index 960940e7cf..281426dd3e 100644 --- a/src/main/java/org/kohsuke/github/Requester.java +++ b/src/main/java/org/kohsuke/github/Requester.java @@ -53,6 +53,7 @@ import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; +import static java.util.Arrays.asList; import static org.kohsuke.github.GitHub.*; /** @@ -61,6 +62,8 @@ * @author Kohsuke Kawaguchi */ class Requester { + private static final List METHODS_WITHOUT_BODY = asList("GET", "DELETE"); + private final GitHub root; private final List args = new ArrayList(); private final Map headers = new LinkedHashMap(); @@ -208,7 +211,7 @@ public T to(String tailApiUrl, Class type, String method) throws IOExcept private T _to(String tailApiUrl, Class type, T instance) throws IOException { while (true) {// loop while API rate limit is hit - if (method.equals("GET") && !args.isEmpty()) { + if (METHODS_WITHOUT_BODY.contains(method) && !args.isEmpty()) { StringBuilder qs=new StringBuilder(); for (Entry arg : args) { qs.append(qs.length()==0 ? '?' : '&'); @@ -287,7 +290,7 @@ public String getResponseHeader(String header) { * Set up the request parameters or POST payload. */ private void buildRequest() throws IOException { - if (!method.equals("GET")) { + if (isMethodWithBody()) { uc.setDoOutput(true); uc.setRequestProperty("Content-type", contentType); @@ -311,6 +314,10 @@ private void buildRequest() throws IOException { } } + private boolean isMethodWithBody() { + return !METHODS_WITHOUT_BODY.contains(method); + } + /** * Loads pagenated resources. * diff --git a/src/test/java/org/kohsuke/github/AppTest.java b/src/test/java/org/kohsuke/github/AppTest.java index 5cdb17e987..d2b68e112c 100755 --- a/src/test/java/org/kohsuke/github/AppTest.java +++ b/src/test/java/org/kohsuke/github/AppTest.java @@ -351,7 +351,7 @@ public void testQueryCommits() throws Exception { sha1.add(c.getSHA1()); } assertEquals("1cccddb22e305397151b2b7b87b4b47d74ca337b",sha1.get(0)); - assertEquals(29,sha1.size()); + assertEquals(29, sha1.size()); } @Test @@ -618,7 +618,7 @@ public void directoryListing() throws IOException { @Test public void testAddDeployKey() throws IOException { - GHRepository myRepository = Iterables.get(gitHub.getMyself().getRepositories().values(),0); + GHRepository myRepository = getTestRepository(); final GHDeployKey newDeployKey = myRepository.addDeployKey("test", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDUt0RAycC5cS42JKh6SecfFZBR1RrF+2hYMctz4mk74/arBE+wFb7fnSHGzdGKX2h5CFOWODifRCJVhB7hlVxodxe+QkQQYAEL/x1WVCJnGgTGQGOrhOMj95V3UE5pQKhsKD608C+u5tSofcWXLToP1/wZ7U4/AHjqYi08OLsWToHCax55TZkvdt2jo0hbIoYU+XI9Q8Uv4ONDN1oabiOdgeKi8+crvHAuvNleiBhWVBzFh8KdfzaH5uNdw7ihhFjEd1vzqACsjCINCjdMfzl6jD9ExuWuE92nZJnucls2cEoNC6k2aPmrZDg9hA32FXVpyseY+bDUWFU6LO2LG6PB kohsuke@atlas"); try { assertNotNull(newDeployKey.getId()); @@ -636,7 +636,7 @@ public boolean apply(GHDeployKey deployKey) { @Test public void testCommitStatusContext() throws IOException { - GHRepository myRepository = Iterables.get(gitHub.getMyself().getRepositories().values(), 0); + GHRepository myRepository = getTestRepository(); GHRef masterRef = myRepository.getRef("heads/master"); GHCommitStatus commitStatus = myRepository.createCommitStatus(masterRef.getObject().getSha(), GHCommitState.SUCCESS, "http://www.example.com", "test", "test/context"); assertEquals("test/context", commitStatus.getContext()); diff --git a/src/test/java/org/kohsuke/github/GitHubTest.java b/src/test/java/org/kohsuke/github/GitHubTest.java index 578bbc13f3..54ce6e35d7 100644 --- a/src/test/java/org/kohsuke/github/GitHubTest.java +++ b/src/test/java/org/kohsuke/github/GitHubTest.java @@ -1,34 +1,40 @@ package org.kohsuke.github; +import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Field; import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.Properties; -import junit.framework.TestCase; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; /** * Unit test for {@link GitHub}. */ -public class GitHubTest extends TestCase { - +public class GitHubTest { + @Test public void testGitHubServerWithHttp() throws Exception { GitHub hub = GitHub.connectToEnterprise("http://enterprise.kohsuke.org/api/v3", "bogus","bogus"); assertEquals("http://enterprise.kohsuke.org/api/v3/test", hub.getApiURL("/test").toString()); } - + @Test public void testGitHubServerWithHttps() throws Exception { GitHub hub = GitHub.connectToEnterprise("https://enterprise.kohsuke.org/api/v3", "bogus","bogus"); assertEquals("https://enterprise.kohsuke.org/api/v3/test", hub.getApiURL("/test").toString()); } - + @Test public void testGitHubServerWithoutServer() throws Exception { GitHub hub = GitHub.connectUsingPassword("kohsuke", "bogus"); assertEquals("https://api.github.com/test", hub.getApiURL("/test").toString()); } - + @Test public void testGitHubBuilderFromEnvironment() throws IOException { Mapprops = new HashMap(); @@ -86,7 +92,7 @@ private void setupEnvironment(Map newenv) { e1.printStackTrace(); } } - + @Test public void testGitHubBuilderFromCustomEnvironment() throws IOException { Map props = new HashMap(); @@ -105,4 +111,12 @@ public void testGitHubBuilderFromCustomEnvironment() throws IOException { assertEquals("bogusEndpoint", builder.endpoint); } + @Test + public void testGitHubEnterpriseDoesNotHaveRateLimit() throws IOException { + GitHub github = spy(new GitHubBuilder().build()); + when(github.retrieve()).thenThrow(FileNotFoundException.class); + + GHRateLimit rateLimit = github.getRateLimit(); + assertThat(rateLimit.getResetDate(), notNullValue()); + } } diff --git a/src/test/java/org/kohsuke/github/PullRequestTest.java b/src/test/java/org/kohsuke/github/PullRequestTest.java index c513860eae..9fc55f7519 100644 --- a/src/test/java/org/kohsuke/github/PullRequestTest.java +++ b/src/test/java/org/kohsuke/github/PullRequestTest.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.util.Collection; +import java.util.List; /** * @author Kohsuke Kawaguchi @@ -18,7 +19,38 @@ public void createPullRequest() throws Exception { assertEquals(name, p.getTitle()); } - @Test // Requires push access to the test repo to pass + @Test + public void createPullRequestComment() throws Exception { + String name = rnd.next(); + GHPullRequest p = getRepository().createPullRequest(name, "stable", "master", "## test"); + p.comment("Some comment"); + } + + @Test + public void testPullRequestReviewComments() throws Exception { + String name = rnd.next(); + GHPullRequest p = getRepository().createPullRequest(name, "stable", "master", "## test"); + System.out.println(p.getUrl()); + assertTrue(p.listReviewComments().asList().isEmpty()); + p.createReviewComment("Sample review comment", p.getHead().getSha(), "cli/pom.xml", 5); + List comments = p.listReviewComments().asList(); + assertEquals(1, comments.size()); + GHPullRequestReviewComment comment = comments.get(0); + assertEquals("Sample review comment", comment.getBody()); + + comment.update("Updated review comment"); + comments = p.listReviewComments().asList(); + assertEquals(1, comments.size()); + comment = comments.get(0); + assertEquals("Updated review comment", comment.getBody()); + + comment.delete(); + comments = p.listReviewComments().asList(); + assertTrue(comments.isEmpty()); + } + + @Test + // Requires push access to the test repo to pass public void setLabels() throws Exception { GHPullRequest p = getRepository().createPullRequest(rnd.next(), "stable", "master", "## test"); String label = rnd.next(); @@ -29,7 +61,8 @@ public void setLabels() throws Exception { assertEquals(label, labels.iterator().next().getName()); } - @Test // Requires push access to the test repo to pass + @Test + // Requires push access to the test repo to pass public void setAssignee() throws Exception { GHPullRequest p = getRepository().createPullRequest(rnd.next(), "stable", "master", "## test"); GHMyself user = gitHub.getMyself(); @@ -49,7 +82,7 @@ public void testGetUser() throws IOException { PagedIterable ghPullRequests = getRepository().listPullRequests(GHIssueState.OPEN); for (GHPullRequest pr : ghPullRequests) { assertNotNull(pr.getUser().root); - assertFalse(pr.getMergeable()); + pr.getMergeable(); assertNotNull(pr.getUser().root); } }