diff --git a/.gitignore b/.gitignore index cb9b4d6a4a..3c2c3c6427 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ target *.iml *.ipr *.iws +.classpath +.project +.settings/ +.DS_Store diff --git a/pom.xml b/pom.xml index b54c58bc47..15c85ed319 100644 --- a/pom.xml +++ b/pom.xml @@ -3,11 +3,11 @@ org.kohsuke pom - 14 + 20 github-api - 1.67 + 1.96-SNAPSHOT GitHub API for Java http://github-api.kohsuke.org/ GitHub API for Java @@ -16,7 +16,7 @@ scm:git:git@github.com/kohsuke/${project.artifactId}.git scm:git:ssh://git@github.com/kohsuke/${project.artifactId}.git http://${project.artifactId}.kohsuke.org/ - github-api-1.67 + HEAD @@ -28,14 +28,44 @@ UTF-8 + 3.0.2 + true + + maven-surefire-plugin + 2.22.1 + + 2 + + + + org.codehaus.mojo + animal-sniffer-maven-plugin + 1.15 + + + org.codehaus.mojo.signature + java15 + 1.0 + + + + + ensure-java-1.5-class-library + test + + check + + + + com.infradna.tool bridge-method-injector - 1.14 + 1.18 @@ -44,14 +74,32 @@ + + org.codehaus.mojo + findbugs-maven-plugin + ${findbugs-maven-plugin.version} + + true + ${findbugs-maven-plugin.failOnError} + + + + run-findbugs + verify + + check + + + + - commons-lang - commons-lang - 2.6 + org.apache.commons + commons-lang3 + 3.7 commons-codec @@ -61,13 +109,19 @@ junit junit - 4.11 + 4.12 test + + org.hamcrest + hamcrest-all + 1.3 + test + com.fasterxml.jackson.core jackson-databind - 2.2.3 + 2.9.2 commons-io @@ -77,7 +131,8 @@ com.infradna.tool bridge-method-annotation - 1.14 + 1.17 + true org.kohsuke.stapler @@ -88,27 +143,39 @@ org.eclipse.jgit org.eclipse.jgit - 3.1.0.201310021548-r + 4.9.0.201710071750-r test com.squareup.okhttp okhttp-urlconnection - 2.0.0 + 2.7.5 + true + + + com.squareup.okhttp3 + okhttp-urlconnection + 3.9.0 true org.kohsuke wordnet-random-name - 1.2 + 1.3 test org.mockito mockito-all - 1.9.5 + 1.10.19 test + + com.google.code.findbugs + annotations + 3.0.1 + provided + diff --git a/src/main/java/org/kohsuke/github/AbuseLimitHandler.java b/src/main/java/org/kohsuke/github/AbuseLimitHandler.java new file mode 100644 index 0000000000..4cb6bfe6c5 --- /dev/null +++ b/src/main/java/org/kohsuke/github/AbuseLimitHandler.java @@ -0,0 +1,63 @@ +package org.kohsuke.github; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.HttpURLConnection; + +/** + * Pluggable strategy to determine what to do when the API abuse limit is hit. + * + * @author Kohsuke Kawaguchi + * @see GitHubBuilder#withAbuseLimitHandler(AbuseLimitHandler) + * @see documentation + * @see RateLimitHandler + */ +public abstract class AbuseLimitHandler { + /** + * Called when the library encounters HTTP error indicating that the API abuse limit is reached. + * + *

+ * Any exception thrown from this method will cause the request to fail, and the caller of github-api + * will receive an exception. If this method returns normally, another request will be attempted. + * For that to make sense, the implementation needs to wait for some time. + * + * @see API documentation from GitHub + * @param e + * Exception from Java I/O layer. If you decide to fail the processing, you can throw + * this exception (or wrap this exception into another exception and throw it.) + * @param uc + * Connection that resulted in an error. Useful for accessing other response headers. + */ + public abstract void onError(IOException e, HttpURLConnection uc) throws IOException; + + /** + * Wait until the API abuse "wait time" is passed. + */ + public static final AbuseLimitHandler WAIT = new AbuseLimitHandler() { + @Override + public void onError(IOException e, HttpURLConnection uc) throws IOException { + try { + Thread.sleep(parseWaitTime(uc)); + } catch (InterruptedException _) { + throw (InterruptedIOException)new InterruptedIOException().initCause(e); + } + } + + private long parseWaitTime(HttpURLConnection uc) { + String v = uc.getHeaderField("Retry-After"); + if (v==null) return 60 * 1000; // can't tell, return 1 min + + return Math.max(1000, Long.parseLong(v)*1000); + } + }; + + /** + * Fail immediately. + */ + public static final AbuseLimitHandler FAIL = new AbuseLimitHandler() { + @Override + public void onError(IOException e, HttpURLConnection uc) throws IOException { + throw (IOException)new IOException("Abust limit reached").initCause(e); + } + }; +} diff --git a/src/main/java/org/kohsuke/github/DeleteToken.java b/src/main/java/org/kohsuke/github/DeleteToken.java index eb61011920..d9d4724eb9 100644 --- a/src/main/java/org/kohsuke/github/DeleteToken.java +++ b/src/main/java/org/kohsuke/github/DeleteToken.java @@ -23,9 +23,13 @@ */ package org.kohsuke.github; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + /** * @author Kohsuke Kawaguchi */ +@SuppressFBWarnings(value = "UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD", + justification = "Being constructed by JSON deserialization") class DeleteToken { public String delete_token; } diff --git a/src/main/java/org/kohsuke/github/EnforcementLevel.java b/src/main/java/org/kohsuke/github/EnforcementLevel.java new file mode 100644 index 0000000000..81c86428c4 --- /dev/null +++ b/src/main/java/org/kohsuke/github/EnforcementLevel.java @@ -0,0 +1,17 @@ +package org.kohsuke.github; + +import java.util.Locale; + +/** + * This was added during preview API period but it has changed since then. + * + * @author Kohsuke Kawaguchi + */ +@Deprecated +public enum EnforcementLevel { + OFF, NON_ADMINS, EVERYONE; + + public String toString() { + return name().toLowerCase(Locale.ENGLISH); + } +} diff --git a/src/main/java/org/kohsuke/github/GHAuthorization.java b/src/main/java/org/kohsuke/github/GHAuthorization.java index f990cbefb3..bf7e24c7f2 100644 --- a/src/main/java/org/kohsuke/github/GHAuthorization.java +++ b/src/main/java/org/kohsuke/github/GHAuthorization.java @@ -1,8 +1,9 @@ package org.kohsuke.github; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import java.net.URL; import java.util.Collection; -import java.util.Date; import java.util.List; /** @@ -35,9 +36,14 @@ public class GHAuthorization extends GHObject { private GitHub root; private List scopes; private String token; + private String token_last_eight; + private String hashed_token; private App app; private String note; private String note_url; + private String fingerprint; + //TODO add some user class for https://developer.github.com/v3/oauth_authorizations/#check-an-authorization ? + //private GHUser user; public GitHub getRoot() { return root; @@ -51,6 +57,14 @@ public String getToken() { return token; } + public String getTokenLastEight() { + return token_last_eight; + } + + public String getHashedToken() { + return hashed_token; + } + public URL getAppUrl() { return GitHub.parseURL(app.url); } @@ -59,6 +73,8 @@ public String getAppName() { return app.name; } + @SuppressFBWarnings(value = "NM_CONFUSING", + justification = "It's a part of the library API, cannot be changed") public URL getApiURL() { return GitHub.parseURL(url); } @@ -79,14 +95,20 @@ public URL getNoteUrl() { return GitHub.parseURL(note_url); } + public String getFingerprint() { + return fingerprint; + } + /*package*/ GHAuthorization wrap(GitHub root) { this.root = root; return this; } - + @SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD"}, + justification = "JSON API") private static class App { private String url; private String name; + // private String client_id; not yet used } } diff --git a/src/main/java/org/kohsuke/github/GHBlob.java b/src/main/java/org/kohsuke/github/GHBlob.java new file mode 100644 index 0000000000..a38a18219c --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHBlob.java @@ -0,0 +1,64 @@ +package org.kohsuke.github; + +import org.apache.commons.codec.binary.Base64InputStream; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URL; + +/** + * @author Kanstantsin Shautsou + * @author Kohsuke Kawaguchi + * @see GHTreeEntry#asBlob() + * @see GHRepository#getBlob(String) + * @see Get a blob + */ +public class GHBlob { + private String content, encoding, url, sha; + private long size; + + /** + * API URL of this blob. + */ + public URL getUrl() { + return GitHub.parseURL(url); + } + + public String getSha() { + return sha; + } + + /** + * Number of bytes in this blob. + */ + public long getSize() { + return size; + } + + public String getEncoding() { + return encoding; + } + + /** + * Encoded content. You probably want {@link #read()} + */ + public String getContent() { + return content; + } + + /** + * Retrieves the actual bytes of the blob. + */ + public InputStream read() { + if (encoding.equals("base64")) { + try { + return new Base64InputStream(new ByteArrayInputStream(content.getBytes("US-ASCII")), false); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); // US-ASCII is mandatory + } + } + + throw new UnsupportedOperationException("Unrecognized encoding: "+encoding); + } +} diff --git a/src/main/java/org/kohsuke/github/GHBlobBuilder.java b/src/main/java/org/kohsuke/github/GHBlobBuilder.java new file mode 100644 index 0000000000..a6259e5b6d --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHBlobBuilder.java @@ -0,0 +1,49 @@ +package org.kohsuke.github; + +import org.apache.commons.codec.binary.Base64; + +import java.io.IOException; + +/** + * Builder pattern for creating a new blob. + * Based on https://developer.github.com/v3/git/blobs/#create-a-blob + */ +public class GHBlobBuilder { + private final GHRepository repo; + private final Requester req; + + GHBlobBuilder(GHRepository repo) { + this.repo = repo; + req = new Requester(repo.root); + } + + /** + * Configures a blob with the specified text {@code content}. + */ + public GHBlobBuilder textContent(String content) { + req.with("content", content); + req.with("encoding", "utf-8"); + return this; + } + + /** + * Configures a blob with the specified binary {@code content}. + */ + public GHBlobBuilder binaryContent(byte[] content) { + String base64Content = Base64.encodeBase64String(content); + req.with("content", base64Content); + req.with("encoding", "base64"); + return this; + } + + private String getApiTail() { + return String.format("/repos/%s/%s/git/blobs", repo.getOwnerName(), repo.getName()); + } + + /** + * Creates a blob based on the parameters specified thus far. + */ + public GHBlob create() throws IOException { + return req.method("POST").to(getApiTail(), GHBlob.class); + } +} diff --git a/src/main/java/org/kohsuke/github/GHBranch.java b/src/main/java/org/kohsuke/github/GHBranch.java index 1c68bfa758..e457fc243d 100644 --- a/src/main/java/org/kohsuke/github/GHBranch.java +++ b/src/main/java/org/kohsuke/github/GHBranch.java @@ -1,19 +1,37 @@ package org.kohsuke.github; +import com.fasterxml.jackson.annotation.JsonProperty; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.io.IOException; +import java.net.URL; +import java.util.Collection; + +import static org.kohsuke.github.Previews.*; + /** * A branch in a repository. - * + * * @author Yusuke Kokubo */ +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "NP_UNWRITTEN_FIELD", "URF_UNREAD_FIELD"}, justification = "JSON API") public class GHBranch { private GitHub root; private GHRepository owner; private String name; private Commit commit; + @JsonProperty("protected") + private boolean protection; + private String protection_url; + public static class Commit { - String sha,url; + String sha; + + @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") + String url; } public GitHub getRoot() { @@ -31,6 +49,26 @@ public String getName() { return name; } + /** + * Returns true if the push to this branch is restricted via branch protection. + */ + @Preview @Deprecated + public boolean isProtected() { + return protection; + } + + /** + * Returns API URL that deals with the protection of this branch. + */ + @Preview @Deprecated + public URL getProtectionUrl() { + return GitHub.parseURL(protection_url); + } + + public GHBranchProtection getProtection() throws IOException { + return root.retrieve().to(protection_url, GHBranchProtection.class).wrap(this); + } + /** * The commit that this branch currently points to. */ @@ -38,9 +76,48 @@ public String getSHA1() { return commit.sha; } + /** + * Disables branch protection and allows anyone with push access to push changes. + */ + public void disableProtection() throws IOException { + new Requester(root).method("DELETE").to(protection_url); + } + + /** + * Enables branch protection to control what commit statuses are required to push. + * + * @see GHCommitStatus#getContext() + */ + @Preview @Deprecated + public GHBranchProtectionBuilder enableProtection() { + return new GHBranchProtectionBuilder(this); + } + + // backward compatibility with previous signature + @Deprecated + public void enableProtection(EnforcementLevel level, Collection contexts) throws IOException { + switch (level) { + case OFF: + disableProtection(); + break; + case NON_ADMINS: + case EVERYONE: + enableProtection() + .addRequiredChecks(contexts) + .includeAdmins(level==EnforcementLevel.EVERYONE) + .enable(); + break; + } + } + + String getApiRoute() { + return owner.getApiTailUrl("/branches/"+name); + } + @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/GHBranchProtection.java b/src/main/java/org/kohsuke/github/GHBranchProtection.java new file mode 100644 index 0000000000..8420a531ad --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHBranchProtection.java @@ -0,0 +1,208 @@ +package org.kohsuke.github; + +import com.fasterxml.jackson.annotation.JsonProperty; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import static org.kohsuke.github.Previews.ZZZAX; + +import java.io.IOException; +import java.util.Collection; + +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD", + "URF_UNREAD_FIELD"}, justification = "JSON API") +public class GHBranchProtection { + private static final String REQUIRE_SIGNATURES_URI = "/required_signatures"; + + @JsonProperty("enforce_admins") + private EnforceAdmins enforceAdmins; + + private GitHub root; + + @JsonProperty("required_pull_request_reviews") + private RequiredReviews requiredReviews; + + @JsonProperty("required_status_checks") + private RequiredStatusChecks requiredStatusChecks; + + @JsonProperty + private Restrictions restrictions; + + @JsonProperty + private String url; + + @Preview @Deprecated + public void enabledSignedCommits() throws IOException { + requester().method("POST") + .to(url + REQUIRE_SIGNATURES_URI, RequiredSignatures.class); + } + + @Preview @Deprecated + public void disableSignedCommits() throws IOException { + requester().method("DELETE") + .to(url + REQUIRE_SIGNATURES_URI); + } + + public EnforceAdmins getEnforceAdmins() { + return enforceAdmins; + } + + public RequiredReviews getRequiredReviews() { + return requiredReviews; + } + + @Preview @Deprecated + public boolean getRequiredSignatures() throws IOException { + return requester().method("GET") + .to(url + REQUIRE_SIGNATURES_URI, RequiredSignatures.class).enabled; + } + + public RequiredStatusChecks getRequiredStatusChecks() { + return requiredStatusChecks; + } + + public Restrictions getRestrictions() { + return restrictions; + } + + public String getUrl() { + return url; + } + + GHBranchProtection wrap(GHBranch branch) { + this.root = branch.getRoot(); + return this; + } + + private Requester requester() { + return new Requester(root).withPreview(ZZZAX); + } + + public static class EnforceAdmins { + @JsonProperty + private boolean enabled; + + @JsonProperty + private String url; + + public String getUrl() { + return url; + } + + public boolean isEnabled() { + return enabled; + } + } + + public static class RequiredReviews { + @JsonProperty("dismissal_restrictions") + private Restrictions dismissalRestriction; + + @JsonProperty("dismiss_stale_reviews") + private boolean dismissStaleReviews; + + @JsonProperty("require_code_owner_reviews") + private boolean requireCodeOwnerReviews; + + @JsonProperty("required_approving_review_count") + private int requiredReviewers; + + @JsonProperty + private String url; + + public Restrictions getDismissalRestrictions() { + return dismissalRestriction; + } + + public String getUrl() { + return url; + } + + public boolean isDismissStaleReviews() { + return dismissStaleReviews; + } + + public boolean isRequireCodeOwnerReviews() { + return requireCodeOwnerReviews; + } + + public int getRequiredReviewers() { + return requiredReviewers; + } + } + + private static class RequiredSignatures { + @JsonProperty + private boolean enabled; + + @JsonProperty + private String url; + + public String getUrl() { + return url; + } + + public boolean isEnabled() { + return enabled; + } + } + + public static class RequiredStatusChecks { + @JsonProperty + private Collection contexts; + + @JsonProperty + private boolean strict; + + @JsonProperty + private String url; + + public Collection getContexts() { + return contexts; + } + + public String getUrl() { + return url; + } + + public boolean isRequiresBranchUpToDate() { + return strict; + } + } + + public static class Restrictions { + @JsonProperty + private Collection teams; + + @JsonProperty("teams_url") + private String teamsUrl; + + @JsonProperty + private String url; + + @JsonProperty + private Collection users; + + @JsonProperty("users_url") + private String usersUrl; + + public Collection getTeams() { + return teams; + } + + public String getTeamsUrl() { + return teamsUrl; + } + + public String getUrl() { + return url; + } + + public Collection getUsers() { + return users; + } + + public String getUsersUrl() { + return usersUrl; + } + } +} diff --git a/src/main/java/org/kohsuke/github/GHBranchProtectionBuilder.java b/src/main/java/org/kohsuke/github/GHBranchProtectionBuilder.java new file mode 100644 index 0000000000..822541a14d --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHBranchProtectionBuilder.java @@ -0,0 +1,218 @@ +package org.kohsuke.github; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.kohsuke.github.Previews.*; + +/** + * Builder to configure the branch protection settings. + * + * @see GHBranch#enableProtection() + */ +@SuppressFBWarnings(value = { "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD", + "URF_UNREAD_FIELD" }, justification = "JSON API") +public class GHBranchProtectionBuilder { + private final GHBranch branch; + + private boolean enforceAdmins; + private Map prReviews; + private Restrictions restrictions; + private StatusChecks statusChecks; + + GHBranchProtectionBuilder(GHBranch branch) { + this.branch = branch; + } + + public GHBranchProtectionBuilder addRequiredChecks(Collection checks) { + getStatusChecks().contexts.addAll(checks); + return this; + } + + public GHBranchProtectionBuilder addRequiredChecks(String... checks) { + addRequiredChecks(Arrays.asList(checks)); + return this; + } + + public GHBranchProtectionBuilder dismissStaleReviews() { + return dismissStaleReviews(true); + } + + public GHBranchProtectionBuilder dismissStaleReviews(boolean v) { + getPrReviews().put("dismiss_stale_reviews", v); + return this; + } + + public GHBranchProtection enable() throws IOException { + return requester().method("PUT") + .withNullable("required_status_checks", statusChecks) + .withNullable("required_pull_request_reviews", prReviews) + .withNullable("restrictions", restrictions) + .withNullable("enforce_admins", enforceAdmins) + .to(branch.getProtectionUrl().toString(), GHBranchProtection.class) + .wrap(branch); + } + + public GHBranchProtectionBuilder includeAdmins() { + return includeAdmins(true); + } + + public GHBranchProtectionBuilder includeAdmins(boolean v) { + enforceAdmins = v; + return this; + } + + public GHBranchProtectionBuilder requiredReviewers(int v) { + getPrReviews().put("required_approving_review_count", v); + return this; + } + + public GHBranchProtectionBuilder requireBranchIsUpToDate() { + return requireBranchIsUpToDate(true); + } + + public GHBranchProtectionBuilder requireBranchIsUpToDate(boolean v) { + getStatusChecks().strict = v; + return this; + } + + public GHBranchProtectionBuilder requireCodeOwnReviews() { + return requireCodeOwnReviews(true); + } + + public GHBranchProtectionBuilder requireCodeOwnReviews(boolean v) { + getPrReviews().put("require_code_owner_reviews", v); + return this; + } + + public GHBranchProtectionBuilder requireReviews() { + getPrReviews(); + return this; + } + + public GHBranchProtectionBuilder restrictReviewDismissals() { + getPrReviews(); + + if (!prReviews.containsKey("dismissal_restrictions")) { + prReviews.put("dismissal_restrictions", new Restrictions()); + } + + return this; + } + + public GHBranchProtectionBuilder restrictPushAccess() { + getRestrictions(); + return this; + } + + public GHBranchProtectionBuilder teamPushAccess(Collection teams) { + for (GHTeam team : teams) { + teamPushAccess(team); + } + return this; + } + + public GHBranchProtectionBuilder teamPushAccess(GHTeam... teams) { + for (GHTeam team : teams) { + getRestrictions().teams.add(team.getSlug()); + } + return this; + } + + public GHBranchProtectionBuilder teamReviewDismissals(Collection teams) { + for (GHTeam team : teams) { + teamReviewDismissals(team); + } + return this; + } + + public GHBranchProtectionBuilder teamReviewDismissals(GHTeam... teams) { + for (GHTeam team : teams) { + addReviewRestriction(team.getSlug(), true); + } + return this; + } + + public GHBranchProtectionBuilder userPushAccess(Collection users) { + for (GHUser user : users) { + userPushAccess(user); + } + return this; + } + + public GHBranchProtectionBuilder userPushAccess(GHUser... users) { + for (GHUser user : users) { + getRestrictions().users.add(user.getLogin()); + } + return this; + } + + public GHBranchProtectionBuilder userReviewDismissals(Collection users) { + for (GHUser team : users) { + userReviewDismissals(team); + } + return this; + } + + public GHBranchProtectionBuilder userReviewDismissals(GHUser... users) { + for (GHUser user : users) { + addReviewRestriction(user.getLogin(), false); + } + return this; + } + + private void addReviewRestriction(String restriction, boolean isTeam) { + restrictReviewDismissals(); + Restrictions restrictions = (Restrictions) prReviews.get("dismissal_restrictions"); + + if (isTeam) { + restrictions.teams.add(restriction); + } else { + restrictions.users.add(restriction); + } + } + + private Map getPrReviews() { + if (prReviews == null) { + prReviews = new HashMap(); + } + return prReviews; + } + + private Restrictions getRestrictions() { + if (restrictions == null) { + restrictions = new Restrictions(); + } + return restrictions; + } + + private StatusChecks getStatusChecks() { + if (statusChecks == null) { + statusChecks = new StatusChecks(); + } + return statusChecks; + } + + private Requester requester() { + return new Requester(branch.getRoot()).withPreview(LUKE_CAGE); + } + + private static class Restrictions { + private Set teams = new HashSet(); + private Set users = new HashSet(); + } + + private static class StatusChecks { + final List contexts = new ArrayList(); + boolean strict; + } +} diff --git a/src/main/java/org/kohsuke/github/GHCommentAuthorAssociation.java b/src/main/java/org/kohsuke/github/GHCommentAuthorAssociation.java new file mode 100644 index 0000000000..d66b8d8550 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHCommentAuthorAssociation.java @@ -0,0 +1,37 @@ +package org.kohsuke.github; + +/** + * How is an user associated with a repository? + * + * @author Kohsuke Kawaguchi + */ +public enum GHCommentAuthorAssociation { + /** + * Author has been invited to collaborate on the repository. + */ + COLLABORATOR, + /** + * Author has previously committed to the repository. + */ + CONTRIBUTOR, + /** + * Author has not previously committed to GitHub. + */ + FIRST_TIMER, + /** + * Author has not previously committed to the repository. + */ + FIRST_TIME_CONTRIBUTOR, + /** + * Author is a member of the organization that owns the repository. + */ + MEMBER, + /** + * Author has no association with the repository. + */ + NONE, + /** + * Author is the owner of the repository. + */ + OWNER +} diff --git a/src/main/java/org/kohsuke/github/GHCommit.java b/src/main/java/org/kohsuke/github/GHCommit.java index fbb7c24fdd..a817da2fd6 100644 --- a/src/main/java/org/kohsuke/github/GHCommit.java +++ b/src/main/java/org/kohsuke/github/GHCommit.java @@ -1,12 +1,14 @@ package org.kohsuke.github; import com.infradna.tool.bridge_method_injector.WithBridgeMethods; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.net.URL; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.List; /** @@ -16,6 +18,8 @@ * @see GHRepository#getCommit(String) * @see GHCommitComment#getCommit() */ +@SuppressFBWarnings(value = {"NP_UNWRITTEN_FIELD", "UWF_UNWRITTEN_FIELD"}, + justification = "JSON API") public class GHCommit { private GHRepository owner; @@ -24,6 +28,8 @@ public class GHCommit { /** * Short summary of this commit. */ + @SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "NP_UNWRITTEN_FIELD", "UWF_UNWRITTEN_FIELD"}, justification = "JSON API") public static class ShortInfo { private GHAuthor author; private GHAuthor committer; @@ -32,16 +38,30 @@ public static class ShortInfo { private int comment_count; + static class Tree { + String sha; + } + + private Tree tree; + @WithBridgeMethods(value = GHAuthor.class, castRequired = true) public GitUser getAuthor() { return author; } + public Date getAuthoredDate() { + return GitHub.parseDate(author.date); + } + @WithBridgeMethods(value = GHAuthor.class, castRequired = true) public GitUser getCommitter() { return committer; } + public Date getCommitDate() { + return GitHub.parseDate(committer.date); + } + /** * Commit message. */ @@ -58,6 +78,7 @@ public int getCommentCount() { * @deprecated Use {@link GitUser} instead. */ public static class GHAuthor extends GitUser { + private String date; } public static class Stats { @@ -67,10 +88,13 @@ public static class Stats { /** * A file that was modified. */ + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", + justification = "It's being initilized by JSON deserialization") 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. @@ -94,19 +118,28 @@ public int getLinesDeleted() { } /** - * "modified", "added", or "deleted" + * "modified", "added", or "removed" */ public String getStatus() { return status; } /** - * Just the base name and the extension without any directory name. + * Full path in the repository. */ + @SuppressFBWarnings(value = "NM_CONFUSING", + justification = "It's a part of the library's API and cannot be renamed") public String getFileName() { return filename; } + /** + * Previous path, in case file has moved. + */ + public String getPreviousFilename() { + return previous_filename; + } + /** * The actual change. */ @@ -139,23 +172,31 @@ public String getSha() { } public static class Parent { - String url,sha; + @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") + String url; + String sha; } static class User { // TODO: what if someone who doesn't have an account on GitHub makes a commit? - String url,avatar_url,login,gravatar_id; + @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") + String url,avatar_url,gravatar_id; + @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") int id; + + String login; } - String url,sha; + String url,html_url,sha; List files; Stats stats; List parents; User author,committer; - public ShortInfo getCommitShortInfo() { + public ShortInfo getCommitShortInfo() throws IOException { + if (commit==null) + populate(); return commit; } @@ -169,24 +210,41 @@ public GHRepository getOwner() { /** * Number of lines added + removed. */ - public int getLinesChanged() { + public int getLinesChanged() throws IOException { + populate(); return stats.total; } /** * Number of lines added. */ - public int getLinesAdded() { + public int getLinesAdded() throws IOException { + populate(); return stats.additions; } /** * Number of lines removed. */ - public int getLinesDeleted() { + public int getLinesDeleted() throws IOException { + populate(); return stats.deletions; } + /** + * Use this method to walk the tree + */ + public GHTree getTree() throws IOException { + return owner.getTree(getCommitShortInfo().tree.sha); + } + + /** + * URL of this commit like "https://github.com/kohsuke/sandbox-ant/commit/8ae38db0ea5837313ab5f39d43a6f73de3bd9000" + */ + public URL getHtmlUrl() { + return GitHub.parseURL(html_url); + } + /** * [0-9a-f]{40} SHA1 checksum. */ @@ -200,7 +258,8 @@ public String getSHA1() { * @return * Can be empty but never null. */ - public List getFiles() { + public List getFiles() throws IOException { + populate(); return files!=null ? Collections.unmodifiableList(files) : Collections.emptyList(); } @@ -236,10 +295,29 @@ public GHUser getAuthor() throws IOException { return resolveUser(author); } + /** + * Gets the date the change was authored on. + * @return the date the change was authored on. + * @throws IOException if the information was not already fetched and an attempt at fetching the information failed. + */ + public Date getAuthoredDate() throws IOException { + return getCommitShortInfo().getAuthoredDate(); + } + public GHUser getCommitter() throws IOException { return resolveUser(committer); } + /** + * Gets the date the change was committed on. + * + * @return the date the change was committed on. + * @throws IOException if the information was not already fetched and an attempt at fetching the information failed. + */ + public Date getCommitDate() throws IOException { + return getCommitShortInfo().getCommitDate(); + } + private GHUser resolveUser(User author) throws IOException { if (author==null || author.login==null) return null; return owner.root.getUser(author.login); @@ -250,8 +328,8 @@ private GHUser resolveUser(User author) throws IOException { */ public PagedIterable listComments() { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(owner.root.retrieve().asIterator(String.format("/repos/%s/%s/commits/%s/comments", owner.getOwnerName(), owner.getName(), sha), GHCommitComment[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(owner.root.retrieve().asIterator(String.format("/repos/%s/%s/commits/%s/comments", owner.getOwnerName(), owner.getName(), sha), GHCommitComment[].class, pageSize)) { @Override protected void wrapUp(GHCommitComment[] page) { for (GHCommitComment c : page) @@ -278,7 +356,7 @@ public GHCommitComment createComment(String body, String path, Integer line, Int } public GHCommitComment createComment(String body) throws IOException { - return createComment(body,null,null,null); + return createComment(body, null, null, null); } /** @@ -295,6 +373,14 @@ public GHCommitStatus getLastStatus() throws IOException { return owner.getLastCommitStatus(sha); } + /** + * Some of the fields are not always filled in when this object is retrieved as a part of another API call. + */ + void populate() throws IOException { + if (files==null && stats==null) + owner.root.retrieve().to(owner.getApiTailUrl("commits/" + sha), this); + } + GHCommit wrapUp(GHRepository owner) { this.owner = owner; return this; diff --git a/src/main/java/org/kohsuke/github/GHCommitBuilder.java b/src/main/java/org/kohsuke/github/GHCommitBuilder.java new file mode 100644 index 0000000000..76e846a7d7 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHCommitBuilder.java @@ -0,0 +1,92 @@ +package org.kohsuke.github; + +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +/** + * Builder pattern for creating a new commit. + * Based on https://developer.github.com/v3/git/commits/#create-a-commit + */ +public class GHCommitBuilder { + private final GHRepository repo; + private final Requester req; + + private final List parents = new ArrayList(); + + private static final class UserInfo { + private final String name; + private final String email; + private final String date; + + private UserInfo(String name, String email, Date date) { + this.name = name; + this.email = email; + TimeZone tz = TimeZone.getTimeZone("UTC"); + DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + df.setTimeZone(tz); + this.date = df.format((date != null) ? date : new Date()); + } + } + + GHCommitBuilder(GHRepository repo) { + this.repo = repo; + req = new Requester(repo.root); + } + + /** + * @param message the commit message + */ + public GHCommitBuilder message(String message) { + req.with("message", message); + return this; + } + + /** + * @param tree the SHA of the tree object this commit points to + */ + public GHCommitBuilder tree(String tree) { + req.with("tree", tree); + return this; + } + + /** + * @param parent the SHA of a parent commit. + */ + public GHCommitBuilder parent(String parent) { + parents.add(parent); + return this; + } + + /** + * Configures the author of this commit. + */ + public GHCommitBuilder author(String name, String email, Date date) { + req._with("author", new UserInfo(name, email, date)); + return this; + } + + /** + * Configures the committer of this commit. + */ + public GHCommitBuilder committer(String name, String email, Date date) { + req._with("committer", new UserInfo(name, email, date)); + return this; + } + + private String getApiTail() { + return String.format("/repos/%s/%s/git/commits", repo.getOwnerName(), repo.getName()); + } + + /** + * Creates a blob based on the parameters specified thus far. + */ + public GHCommit create() throws IOException { + req._with("parents", parents); + return req.method("POST").to(getApiTail(), GHCommit.class).wrapUp(repo); + } +} diff --git a/src/main/java/org/kohsuke/github/GHCommitComment.java b/src/main/java/org/kohsuke/github/GHCommitComment.java index 7deebbc6ad..f5ee76e478 100644 --- a/src/main/java/org/kohsuke/github/GHCommitComment.java +++ b/src/main/java/org/kohsuke/github/GHCommitComment.java @@ -1,8 +1,11 @@ package org.kohsuke.github; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import java.io.IOException; import java.net.URL; -import java.util.Date; + +import static org.kohsuke.github.Previews.*; /** * A comment attached to a commit (or a specific line in a specific file of a commit.) @@ -12,19 +15,15 @@ * @see GHCommit#listComments() * @see GHCommit#createComment(String, String, Integer, Integer) */ -public class GHCommitComment extends GHObject { +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "NP_UNWRITTEN_FIELD"}, justification = "JSON API") +public class GHCommitComment extends GHObject implements Reactable { private GHRepository owner; String body, html_url, commit_id; Integer line; String path; - User user; - - static class User { - // TODO: what if someone who doesn't have an account on GitHub makes a commit? - String url,avatar_url,login,gravatar_id; - int id; - } + GHUser user; // not fully populated. beware. public GHRepository getOwner() { return owner; @@ -69,7 +68,7 @@ public int getLine() { * Gets the user who put this comment. */ public GHUser getUser() throws IOException { - return owner.root.getUser(user.login); + return owner == null || owner.root.isOffline() ? user : owner.root.getUser(user.login); } /** @@ -83,12 +82,35 @@ public GHCommit getCommit() throws IOException { * Updates the body of the commit message. */ public void update(String body) throws IOException { - GHCommitComment r = new Requester(owner.root) + new Requester(owner.root) .with("body", body) .method("PATCH").to(getApiTail(), GHCommitComment.class); this.body = body; } + @Preview @Deprecated + public GHReaction createReaction(ReactionContent content) throws IOException { + return new Requester(owner.root) + .withPreview(SQUIRREL_GIRL) + .with("content", content.getContent()) + .to(getApiTail()+"/reactions", GHReaction.class).wrap(owner.root); + } + + @Preview @Deprecated + public PagedIterable listReactions() { + return new PagedIterable() { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(owner.root.retrieve().withPreview(SQUIRREL_GIRL).asIterator(getApiTail()+"/reactions", GHReaction[].class, pageSize)) { + @Override + protected void wrapUp(GHReaction[] page) { + for (GHReaction c : page) + c.wrap(owner.root); + } + }; + } + }; + } + /** * Deletes this comment. */ @@ -103,6 +125,9 @@ private String getApiTail() { GHCommitComment wrap(GHRepository owner) { this.owner = owner; + if (owner.root.isOffline()) { + user.wrapUp(owner.root); + } return this; } } diff --git a/src/main/java/org/kohsuke/github/GHCommitPointer.java b/src/main/java/org/kohsuke/github/GHCommitPointer.java index 7aaf4f01e1..b6c347864e 100644 --- a/src/main/java/org/kohsuke/github/GHCommitPointer.java +++ b/src/main/java/org/kohsuke/github/GHCommitPointer.java @@ -23,6 +23,8 @@ */ package org.kohsuke.github; +import java.io.IOException; + /** * Identifies a commit in {@link GHPullRequest}. * @@ -37,7 +39,8 @@ public class GHCommitPointer { * This points to the user who owns * the {@link #getRepository()}. */ - public GHUser getUser() { + public GHUser getUser() throws IOException { + if (user != null) return user.root.intern(user); return user; } @@ -69,6 +72,13 @@ public String getLabel() { return label; } + /** + * Obtains the commit that this pointer is referring to. + */ + public GHCommit getCommit() throws IOException { + return getRepository().getCommit(getSha()); + } + void wrapUp(GitHub root) { if (user!=null) user.root = root; if (repo!=null) repo.wrap(root); diff --git a/src/main/java/org/kohsuke/github/GHCommitQueryBuilder.java b/src/main/java/org/kohsuke/github/GHCommitQueryBuilder.java index 6d0a7ae995..bac8c89ee6 100644 --- a/src/main/java/org/kohsuke/github/GHCommitQueryBuilder.java +++ b/src/main/java/org/kohsuke/github/GHCommitQueryBuilder.java @@ -92,8 +92,8 @@ public GHCommitQueryBuilder until(long timestamp) { */ public PagedIterable list() { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(req.asIterator(repo.getApiTailUrl("commits"), GHCommit[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(req.asIterator(repo.getApiTailUrl("commits"), GHCommit[].class, pageSize)) { protected void wrapUp(GHCommit[] page) { for (GHCommit c : page) c.wrapUp(repo); diff --git a/src/main/java/org/kohsuke/github/GHCommitSearchBuilder.java b/src/main/java/org/kohsuke/github/GHCommitSearchBuilder.java new file mode 100644 index 0000000000..3d29aa80fd --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHCommitSearchBuilder.java @@ -0,0 +1,137 @@ +package org.kohsuke.github; + +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; + +/** + * Search commits. + * + * @author Marc de Verdelhan + * @see GitHub#searchCommits() + */ +@Preview @Deprecated +public class GHCommitSearchBuilder extends GHSearchBuilder { + /*package*/ GHCommitSearchBuilder(GitHub root) { + super(root,CommitSearchResult.class); + req.withPreview(Previews.CLOAK); + } + + /** + * Search terms. + */ + public GHCommitSearchBuilder q(String term) { + super.q(term); + return this; + } + + public GHCommitSearchBuilder author(String v) { + return q("author:"+v); + } + + public GHCommitSearchBuilder committer(String v) { + return q("committer:"+v); + } + + public GHCommitSearchBuilder authorName(String v) { + return q("author-name:"+v); + } + + public GHCommitSearchBuilder committerName(String v) { + return q("committer-name:"+v); + } + + public GHCommitSearchBuilder authorEmail(String v) { + return q("author-email:"+v); + } + + public GHCommitSearchBuilder committerEmail(String v) { + return q("committer-email:"+v); + } + + public GHCommitSearchBuilder authorDate(String v) { + return q("author-date:"+v); + } + + public GHCommitSearchBuilder committerDate(String v) { + return q("committer-date:"+v); + } + + public GHCommitSearchBuilder merge(boolean merge) { + return q("merge:"+Boolean.valueOf(merge).toString().toLowerCase()); + } + + public GHCommitSearchBuilder hash(String v) { + return q("hash:"+v); + } + + public GHCommitSearchBuilder parent(String v) { + return q("parent:"+v); + } + + public GHCommitSearchBuilder tree(String v) { + return q("tree:"+v); + } + + public GHCommitSearchBuilder is(String v) { + return q("is:"+v); + } + + public GHCommitSearchBuilder user(String v) { + return q("user:"+v); + } + + public GHCommitSearchBuilder org(String v) { + return q("org:"+v); + } + + public GHCommitSearchBuilder repo(String v) { + return q("repo:"+v); + } + + public GHCommitSearchBuilder order(GHDirection v) { + req.with("order",v); + return this; + } + + public GHCommitSearchBuilder sort(Sort sort) { + req.with("sort",sort); + return this; + } + + public enum Sort { AUTHOR_DATE, COMMITTER_DATE } + + private static class CommitSearchResult extends SearchResult { + private GHCommit[] items; + + @Override + /*package*/ GHCommit[] getItems(GitHub root) { + for (GHCommit commit : items) { + String repoName = getRepoName(commit.url); + try { + GHRepository repo = root.getRepository(repoName); + commit.wrapUp(repo); + } catch (IOException ioe) {} + } + return items; + } + } + + /** + * @param commitUrl a commit URL + * @return the repo name ("username/reponame") + */ + private static String getRepoName(String commitUrl) { + if (StringUtils.isBlank(commitUrl)) { + return null; + } + int indexOfUsername = (GitHub.GITHUB_URL + "/repos/").length(); + String[] tokens = commitUrl.substring(indexOfUsername).split("/", 3); + return tokens[0] + '/' + tokens[1]; + } + + @Override + protected String getApiUrl() { + return "/search/commits"; + } +} diff --git a/src/main/java/org/kohsuke/github/GHCommitStatus.java b/src/main/java/org/kohsuke/github/GHCommitStatus.java index c9241442bf..63a62ae8d0 100644 --- a/src/main/java/org/kohsuke/github/GHCommitStatus.java +++ b/src/main/java/org/kohsuke/github/GHCommitStatus.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.net.URL; -import java.util.Date; /** * Represents a status of a commit. @@ -10,6 +9,7 @@ * @author Kohsuke Kawaguchi * @see GHRepository#getLastCommitStatus(String) * @see GHCommit#getLastStatus() + * @see GHRepository#createCommitStatus(String, GHCommitState, String, String) */ public class GHCommitStatus extends GHObject { String state; @@ -46,8 +46,8 @@ public String getDescription() { return description; } - public GHUser getCreator() { - return creator; + public GHUser getCreator() throws IOException { + return root.intern(creator); } public String getContext() { diff --git a/src/main/java/org/kohsuke/github/GHCompare.java b/src/main/java/org/kohsuke/github/GHCompare.java index 2c2bc5ba72..100753db01 100644 --- a/src/main/java/org/kohsuke/github/GHCompare.java +++ b/src/main/java/org/kohsuke/github/GHCompare.java @@ -1,9 +1,9 @@ package org.kohsuke.github; import com.infradna.tool.bridge_method_injector.WithBridgeMethods; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.net.URL; -import java.util.Date; /** * The model user for comparing 2 commits in the GitHub API. @@ -65,12 +65,24 @@ public Commit getMergeBaseCommit() { return merge_base_commit; } + /** + * Gets an array of commits. + * @return A copy of the array being stored in the class. + */ public Commit[] getCommits() { - return commits; + Commit[] newValue = new Commit[commits.length]; + System.arraycopy(commits, 0, newValue, 0, commits.length); + return newValue; } + /** + * Gets an array of commits. + * @return A copy of the array being stored in the class. + */ public GHCommit.File[] getFiles() { - return files; + GHCommit.File[] newValue = new GHCommit.File[files.length]; + System.arraycopy(files, 0, newValue, 0, files.length); + return newValue; } public GHCompare wrap(GHRepository owner) { @@ -87,6 +99,8 @@ public GHCompare wrap(GHRepository owner) { * Compare commits had a child commit element with additional details we want to capture. * This extenstion of GHCommit provides that. */ + @SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD"}, + justification = "JSON API") public static class Commit extends GHCommit { private InnerCommit commit; diff --git a/src/main/java/org/kohsuke/github/GHContent.java b/src/main/java/org/kohsuke/github/GHContent.java index 8ab48d2fdd..d7a7bd446f 100644 --- a/src/main/java/org/kohsuke/github/GHContent.java +++ b/src/main/java/org/kohsuke/github/GHContent.java @@ -1,13 +1,12 @@ package org.kohsuke.github; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import java.io.IOException; import java.io.InputStream; -import javax.xml.bind.DatatypeConverter; - /** * A Content of a repository. * @@ -75,8 +74,9 @@ public String getPath() { * @deprecated * Use {@link #read()} */ + @SuppressFBWarnings("DM_DEFAULT_ENCODING") public String getContent() throws IOException { - return new String(DatatypeConverter.parseBase64Binary(getEncodedContent())); + return new String(Base64.decodeBase64(getEncodedContent())); } /** @@ -113,7 +113,8 @@ public String getHtmlUrl() { * Retrieves the actual content stored here. */ public InputStream read() throws IOException { - return new Requester(root).asStream(getDownloadUrl()); + // if the download link is encoded with a token on the query string, the default behavior of POST will fail + return new Requester(root).method("GET").asStream(getDownloadUrl()); } /** @@ -150,8 +151,8 @@ public PagedIterable listDirectoryContent() throws IOException { throw new IllegalStateException(path+" is not a directory"); return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(url, GHContent[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(url, GHContent[].class, pageSize)) { @Override protected void wrapUp(GHContent[] page) { GHContent.wrap(page, repository); @@ -161,10 +162,12 @@ protected void wrapUp(GHContent[] page) { }; } + @SuppressFBWarnings("DM_DEFAULT_ENCODING") public GHContentUpdateResponse update(String newContent, String commitMessage) throws IOException { return update(newContent.getBytes(), commitMessage, null); } + @SuppressFBWarnings("DM_DEFAULT_ENCODING") public GHContentUpdateResponse update(String newContent, String commitMessage, String branch) throws IOException { return update(newContent.getBytes(), commitMessage, branch); } @@ -174,7 +177,7 @@ public GHContentUpdateResponse update(byte[] newContentBytes, String commitMessa } public GHContentUpdateResponse update(byte[] newContentBytes, String commitMessage, String branch) throws IOException { - String encodedContent = DatatypeConverter.printBase64Binary(newContentBytes); + String encodedContent = Base64.encodeBase64String(newContentBytes); Requester requester = new Requester(root) .with("path", path) diff --git a/src/main/java/org/kohsuke/github/GHContentBuilder.java b/src/main/java/org/kohsuke/github/GHContentBuilder.java new file mode 100644 index 0000000000..cdc019f6d1 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHContentBuilder.java @@ -0,0 +1,76 @@ +package org.kohsuke.github; + +import org.apache.commons.codec.binary.Base64; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +/** + * Used to create/update content. + * + *

+ * Call various methods to build up parameters, then call {@link #commit()} to make the change effective. + * + * @author Kohsuke Kawaguchi + * @see GHRepository#createContent() + */ +public final class GHContentBuilder { + private final GHRepository repo; + private final Requester req; + private String path; + + GHContentBuilder(GHRepository repo) { + this.repo = repo; + this.req = new Requester(repo.root).method("PUT"); + } + + public GHContentBuilder path(String path) { + this.path = path; + req.with("path",path); + return this; + } + + public GHContentBuilder branch(String branch) { + req.with("branch", branch); + return this; + } + + /** + * Used when updating (but not creating a new content) to specify + * Thetblob SHA of the file being replaced. + */ + public GHContentBuilder sha(String sha) { + req.with("sha", sha); + return this; + } + + public GHContentBuilder content(byte[] content) { + req.with("content", Base64.encodeBase64String(content)); + return this; + } + + public GHContentBuilder content(String content) { + try { + return content(content.getBytes("UTF-8")); + } catch (UnsupportedEncodingException x) { + throw new AssertionError(); + } + } + + public GHContentBuilder message(String commitMessage) { + req.with("message", commitMessage); + return this; + } + + /** + * Commits a new content. + */ + public GHContentUpdateResponse commit() throws IOException { + GHContentUpdateResponse response = req.to(repo.getApiTailUrl("contents/" + path), GHContentUpdateResponse.class); + + response.getContent().wrap(repo); + response.getCommit().wrapUp(repo); + + return response; + } +} diff --git a/src/main/java/org/kohsuke/github/GHContentWithLicense.java b/src/main/java/org/kohsuke/github/GHContentWithLicense.java new file mode 100644 index 0000000000..bbd115e649 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHContentWithLicense.java @@ -0,0 +1,21 @@ +package org.kohsuke.github; + +/** + * {@link GHContent} with license information. + * + * @author Kohsuke Kawaguchi + * @see documentation + * @see GHRepository#getLicense() + */ +@Preview @Deprecated +class GHContentWithLicense extends GHContent { + GHLicense license; + + @Override + GHContentWithLicense wrap(GHRepository owner) { + super.wrap(owner); + if (license!=null) + license.wrap(owner.root); + return this; + } +} diff --git a/src/main/java/org/kohsuke/github/GHCreateRepositoryBuilder.java b/src/main/java/org/kohsuke/github/GHCreateRepositoryBuilder.java new file mode 100644 index 0000000000..a11fc4493f --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHCreateRepositoryBuilder.java @@ -0,0 +1,138 @@ +package org.kohsuke.github; + +import java.io.IOException; +import java.net.URL; + +/** + * Creates a repository + * + * @author Kohsuke Kawaguchi + */ +public class GHCreateRepositoryBuilder { + private final GitHub root; + protected final Requester builder; + private final String apiUrlTail; + + /*package*/ GHCreateRepositoryBuilder(GitHub root, String apiUrlTail, String name) { + this.root = root; + this.apiUrlTail = apiUrlTail; + this.builder = new Requester(root); + this.builder.with("name",name); + } + + public GHCreateRepositoryBuilder description(String description) { + this.builder.with("description",description); + return this; + } + + public GHCreateRepositoryBuilder homepage(URL homepage) { + return homepage(homepage.toExternalForm()); + } + + public GHCreateRepositoryBuilder homepage(String homepage) { + this.builder.with("homepage",homepage); + return this; + } + + /** + * Creates a private repository + */ + public GHCreateRepositoryBuilder private_(boolean b) { + this.builder.with("private",b); + return this; + } + + /** + * Enables issue tracker + */ + public GHCreateRepositoryBuilder issues(boolean b) { + this.builder.with("has_issues",b); + return this; + } + + /** + * Enables wiki + */ + public GHCreateRepositoryBuilder wiki(boolean b) { + this.builder.with("has_wiki",b); + return this; + } + + /** + * Enables downloads + */ + public GHCreateRepositoryBuilder downloads(boolean b) { + this.builder.with("has_downloads",b); + return this; + } + + /** + * If true, create an initial commit with empty README. + */ + public GHCreateRepositoryBuilder autoInit(boolean b) { + this.builder.with("auto_init",b); + return this; + } + + /** + * Allow or disallow squash-merging pull requests. + */ + public GHCreateRepositoryBuilder allowSquashMerge(boolean b) { + this.builder.with("allow_squash_merge",b); + return this; + } + + /** + * Allow or disallow merging pull requests with a merge commit. + */ + public GHCreateRepositoryBuilder allowMergeCommit(boolean b) { + this.builder.with("allow_merge_commit",b); + return this; + } + + /** + * Allow or disallow rebase-merging pull requests. + */ + public GHCreateRepositoryBuilder allowRebaseMerge(boolean b) { + this.builder.with("allow_rebase_merge",b); + return this; + } + + /** + * Creates a default .gitignore + * + * See https://developer.github.com/v3/repos/#create + */ + public GHCreateRepositoryBuilder gitignoreTemplate(String language) { + this.builder.with("gitignore_template",language); + return this; + } + + /** + * Desired license template to apply + * + * See https://developer.github.com/v3/repos/#create + */ + public GHCreateRepositoryBuilder licenseTemplate(String license) { + this.builder.with("license_template",license); + return this; + } + + /** + * The team that gets granted access to this repository. Only valid for creating a repository in + * an organization. + */ + public GHCreateRepositoryBuilder team(GHTeam team) { + if (team!=null) + this.builder.with("team_id",team.getId()); + return this; + } + + /** + * Creates a repository with all the parameters. + */ + public GHRepository create() throws IOException { + return builder.method("POST").to(apiUrlTail, GHRepository.class).wrap(root); + } + +} diff --git a/src/main/java/org/kohsuke/github/GHDeployKey.java b/src/main/java/org/kohsuke/github/GHDeployKey.java index eb2c2c00fb..f5d0f9de6d 100644 --- a/src/main/java/org/kohsuke/github/GHDeployKey.java +++ b/src/main/java/org/kohsuke/github/GHDeployKey.java @@ -1,17 +1,17 @@ package org.kohsuke.github; -import java.io.IOException; +import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang.builder.ToStringBuilder; +import java.io.IOException; public class GHDeployKey { protected String url, key, title; protected boolean verified; - protected int id; + protected long id; private GHRepository owner; - public int getId() { + public long getId() { return id; } diff --git a/src/main/java/org/kohsuke/github/GHDeployment.java b/src/main/java/org/kohsuke/github/GHDeployment.java index 51a7843d0b..42118f3329 100644 --- a/src/main/java/org/kohsuke/github/GHDeployment.java +++ b/src/main/java/org/kohsuke/github/GHDeployment.java @@ -1,8 +1,15 @@ package org.kohsuke.github; - +import java.io.IOException; import java.net.URL; +/** + * Represents a deployment + * + * @see documentation + * @see GHRepository#listDeployments(String, String, String, String) + * @see GHRepository#getDeployment(long) + */ public class GHDeployment extends GHObject { private GHRepository owner; private GitHub root; @@ -41,8 +48,8 @@ public String getPayload() { public String getEnvironment() { return environment; } - public GHUser getCreator() { - return creator; + public GHUser getCreator() throws IOException { + return root.intern(creator); } public String getRef() { return ref; @@ -58,4 +65,23 @@ public String getSha(){ public URL getHtmlUrl() { return null; } + + public GHDeploymentStatusBuilder createStatus(GHDeploymentState state) { + return new GHDeploymentStatusBuilder(owner,id,state); + } + + public PagedIterable listStatuses() { + return new PagedIterable() { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(statuses_url, GHDeploymentStatus[].class, pageSize)) { + @Override + protected void wrapUp(GHDeploymentStatus[] page) { + for (GHDeploymentStatus c : page) + c.wrap(owner); + } + }; + } + }; + } + } diff --git a/src/main/java/org/kohsuke/github/GHDeploymentStatus.java b/src/main/java/org/kohsuke/github/GHDeploymentStatus.java index c8679c1343..a058b8a447 100644 --- a/src/main/java/org/kohsuke/github/GHDeploymentStatus.java +++ b/src/main/java/org/kohsuke/github/GHDeploymentStatus.java @@ -1,6 +1,7 @@ package org.kohsuke.github; import java.net.URL; +import java.util.Locale; public class GHDeploymentStatus extends GHObject { private GHRepository owner; @@ -28,8 +29,9 @@ public URL getDeploymentUrl() { public URL getRepositoryUrl() { return GitHub.parseURL(repository_url); } + public GHDeploymentState getState() { - return GHDeploymentState.valueOf(state.toUpperCase()); + return GHDeploymentState.valueOf(state.toUpperCase(Locale.ENGLISH)); } /** diff --git a/src/main/java/org/kohsuke/github/GHDeploymentStatusBuilder.java b/src/main/java/org/kohsuke/github/GHDeploymentStatusBuilder.java index 4310153800..821a3e744e 100644 --- a/src/main/java/org/kohsuke/github/GHDeploymentStatusBuilder.java +++ b/src/main/java/org/kohsuke/github/GHDeploymentStatusBuilder.java @@ -2,16 +2,30 @@ import java.io.IOException; +/** + * Creates a new deployment status. + * + * @see + * GHDeployment#createStatus(GHDeploymentState) + */ public class GHDeploymentStatusBuilder { private final Requester builder; private GHRepository repo; - private int deploymentId; + private long deploymentId; + /** + * @deprecated + * Use {@link GHDeployment#createStatus(GHDeploymentState)} + */ public GHDeploymentStatusBuilder(GHRepository repo, int deploymentId, GHDeploymentState state) { + this(repo,(long)deploymentId,state); + } + + /*package*/ GHDeploymentStatusBuilder(GHRepository repo, long deploymentId, GHDeploymentState state) { this.repo = repo; this.deploymentId = deploymentId; this.builder = new Requester(repo.root); - this.builder.with("state",state.toString().toLowerCase()); + this.builder.with("state",state); } public GHDeploymentStatusBuilder description(String description) { @@ -25,6 +39,6 @@ public GHDeploymentStatusBuilder targetUrl(String targetUrl) { } public GHDeploymentStatus create() throws IOException { - return builder.to(repo.getApiTailUrl("deployments")+"/"+deploymentId+"/statuses",GHDeploymentStatus.class).wrap(repo); + return builder.to(repo.getApiTailUrl("deployments/"+deploymentId+"/statuses"),GHDeploymentStatus.class).wrap(repo); } } diff --git a/src/main/java/org/kohsuke/github/GHDirection.java b/src/main/java/org/kohsuke/github/GHDirection.java new file mode 100644 index 0000000000..0db172dccf --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHDirection.java @@ -0,0 +1,10 @@ +package org.kohsuke.github; + +/** + * Sort direction + * + * @author Kohsuke Kawaguchi + */ +public enum GHDirection { + ASC, DESC +} diff --git a/src/main/java/org/kohsuke/github/GHEmail.java b/src/main/java/org/kohsuke/github/GHEmail.java index 5f230c5945..9eeac04336 100644 --- a/src/main/java/org/kohsuke/github/GHEmail.java +++ b/src/main/java/org/kohsuke/github/GHEmail.java @@ -23,12 +23,16 @@ */ package org.kohsuke.github; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + /** * Represents an email of GitHub. * * @author Kelly Campbell */ +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "NP_UNWRITTEN_FIELD", "NP_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD"}, justification = "JSON API") public class GHEmail { protected String email; diff --git a/src/main/java/org/kohsuke/github/GHEvent.java b/src/main/java/org/kohsuke/github/GHEvent.java index 13ef432f8f..970b6afbf7 100644 --- a/src/main/java/org/kohsuke/github/GHEvent.java +++ b/src/main/java/org/kohsuke/github/GHEvent.java @@ -1,12 +1,13 @@ package org.kohsuke.github; +import java.util.Locale; + /** * Hook event type. * - * See http://developer.github.com/v3/events/types/ - * * @author Kohsuke Kawaguchi * @see GHEventInfo + * @see Event type reference */ public enum GHEvent { COMMIT_COMMENT, @@ -20,15 +21,44 @@ public enum GHEvent { FORK_APPLY, GIST, GOLLUM, + INSTALLATION, + INSTALLATION_REPOSITORIES, ISSUE_COMMENT, ISSUES, + LABEL, + MARKETPLACE_PURCHASE, MEMBER, + MEMBERSHIP, + MILESTONE, + ORGANIZATION, + ORG_BLOCK, + PAGE_BUILD, + PROJECT_CARD, + PROJECT_COLUMN, + PROJECT, PUBLIC, PULL_REQUEST, + PULL_REQUEST_REVIEW, PULL_REQUEST_REVIEW_COMMENT, PUSH, RELEASE, + REPOSITORY, // only valid for org hooks STATUS, + TEAM, TEAM_ADD, - WATCH + WATCH, + PING, + /** + * Special event type that means "every possible event" + */ + ALL; + + + /** + * Returns GitHub's internal representation of this event. + */ + String symbol() { + if (this==ALL) return "*"; + return name().toLowerCase(Locale.ENGLISH); + } } diff --git a/src/main/java/org/kohsuke/github/GHEventInfo.java b/src/main/java/org/kohsuke/github/GHEventInfo.java index 09db42aa0f..ca7e3d4f04 100644 --- a/src/main/java/org/kohsuke/github/GHEventInfo.java +++ b/src/main/java/org/kohsuke/github/GHEventInfo.java @@ -1,21 +1,24 @@ package org.kohsuke.github; +import com.fasterxml.jackson.databind.node.ObjectNode; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import java.io.IOException; import java.util.Date; -import com.fasterxml.jackson.databind.node.ObjectNode; - /** * Represents an event. * * @author Kohsuke Kawaguchi */ +@SuppressFBWarnings(value = "UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", justification = "JSON API") public class GHEventInfo { private GitHub root; // we don't want to expose Jackson dependency to the user. This needs databinding private ObjectNode payload; + private long id; private String created_at; private String type; @@ -27,8 +30,12 @@ public class GHEventInfo { /** * Inside the event JSON model, GitHub uses a slightly different format. */ + @SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR" }, justification = "JSON API") public static class GHEventRepository { - private int id; + @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") + private long id; + @SuppressFBWarnings(value = "UUF_UNUSED_FIELD", justification = "We don't provide it in API now") private String url; // repository API URL private String name; // owner/repo } @@ -48,6 +55,10 @@ public GHEvent getType() { return this; } + public long getId() { + return id; + } + public Date getCreatedAt() { return GitHub.parseDate(created_at); } @@ -55,10 +66,14 @@ public Date getCreatedAt() { /** * Repository where the change was made. */ + @SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR" }, + justification = "The field comes from JSON deserialization") public GHRepository getRepository() throws IOException { return root.getRepository(repo.name); } + @SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR" }, + justification = "The field comes from JSON deserialization") public GHUser getActor() throws IOException { return root.getUser(actor.getLogin()); } @@ -70,6 +85,8 @@ public String getActorLogin() throws IOException { return actor.getLogin(); } + @SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR" }, + justification = "The field comes from JSON deserialization") public GHOrganization getOrganization() throws IOException { return (org==null || org.getLogin()==null) ? null : root.getOrganization(org.getLogin()); } diff --git a/src/main/java/org/kohsuke/github/GHEventPayload.java b/src/main/java/org/kohsuke/github/GHEventPayload.java index cd248c80ec..acdf5c74e9 100644 --- a/src/main/java/org/kohsuke/github/GHEventPayload.java +++ b/src/main/java/org/kohsuke/github/GHEventPayload.java @@ -1,5 +1,9 @@ package org.kohsuke.github; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import java.io.Reader; import java.util.List; @@ -13,11 +17,28 @@ public abstract class GHEventPayload { protected GitHub root; + private GHUser sender; + /*package*/ GHEventPayload() { } + /** + * Gets the sender or {@code null} if accessed via the events API. + * @return the sender or {@code null} if accessed via the events API. + */ + public GHUser getSender() { + return sender; + } + + public void setSender(GHUser sender) { + this.sender = sender; + } + /*package*/ void wrapUp(GitHub root) { this.root = root; + if (sender != null) { + sender.wrapUp(root); + } } /** @@ -25,6 +46,8 @@ public abstract class GHEventPayload { * * @see authoritative source */ + @SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "NP_UNWRITTEN_FIELD"}, justification = "JSON API") public static class PullRequest extends GHEventPayload { private String action; private int number; @@ -55,24 +78,161 @@ void wrapUp(GitHub root) { throw new IllegalStateException("Expected pull_request payload, but got something else. Maybe we've got another type of event?"); if (repository!=null) { repository.wrap(root); - pull_request.wrap(repository); + pull_request.wrapUp(repository); } else { pull_request.wrapUp(root); } } } + + /** + * A review was added to a pull request + * + * @see authoritative source + */ + public static class PullRequestReview extends GHEventPayload { + private String action; + private GHPullRequestReview review; + private GHPullRequest pull_request; + private GHRepository repository; + + public String getAction() { + return action; + } + + public GHPullRequestReview getReview() { + return review; + } + + public GHPullRequest getPullRequest() { + return pull_request; + } + + public GHRepository getRepository() { + return repository; + } + + @Override + void wrapUp(GitHub root) { + super.wrapUp(root); + if (review==null) + throw new IllegalStateException("Expected pull_request_review payload, but got something else. Maybe we've got another type of event?"); + + review.wrapUp(pull_request); + + if (repository!=null) { + repository.wrap(root); + pull_request.wrapUp(repository); + } else { + pull_request.wrapUp(root); + } + } + } + + /** + * A review comment was added to a pull request + * + * @see authoritative source + */ + public static class PullRequestReviewComment extends GHEventPayload { + private String action; + private GHPullRequestReviewComment comment; + private GHPullRequest pull_request; + private GHRepository repository; + + public String getAction() { + return action; + } + + public GHPullRequestReviewComment getComment() { + return comment; + } + + public GHPullRequest getPullRequest() { + return pull_request; + } + + public GHRepository getRepository() { + return repository; + } + + @Override + void wrapUp(GitHub root) { + super.wrapUp(root); + if (comment==null) + throw new IllegalStateException("Expected pull_request_review_comment payload, but got something else. Maybe we've got another type of event?"); + + comment.wrapUp(pull_request); + + if (repository!=null) { + repository.wrap(root); + pull_request.wrapUp(repository); + } else { + pull_request.wrapUp(root); + } + } + } + + /** + * A Issue has been assigned, unassigned, labeled, unlabeled, opened, edited, milestoned, demilestoned, closed, or reopened. + * + * @see authoritative source + */ + @SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD" }, + justification = "Constructed by JSON deserialization") + public static class Issue extends GHEventPayload { + private String action; + private GHIssue issue; + private GHRepository repository; + + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Comes from JSON deserialization") + public String getAction() { + return action; + } + + public GHIssue getIssue() { + return issue; + } + + public void setIssue(GHIssue issue) { + this.issue = issue; + } + + public GHRepository getRepository() { + return repository; + } + + public void setRepository(GHRepository repository) { + this.repository = repository; + } + + @Override + void wrapUp(GitHub root) { + super.wrapUp(root); + if (repository != null) { + repository.wrap(root); + issue.wrap(repository); + } else { + issue.wrap(root); + } + } + } + /** * A comment was added to an issue * * @see authoritative source */ + @SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD" }, + justification = "Constructed by JSON deserialization") public static class IssueComment extends GHEventPayload { private String action; private GHIssueComment comment; private GHIssue issue; private GHRepository repository; + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Comes from JSON deserialization") public String getAction() { return action; } @@ -104,23 +264,350 @@ 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); } } + /** + * A comment was added to a commit + * + * @see authoritative source + */ + @SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD" }, + justification = "Constructed by JSON deserialization") + public static class CommitComment extends GHEventPayload { + private String action; + private GHCommitComment comment; + private GHRepository repository; + + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Comes from JSON deserialization") + public String getAction() { + return action; + } + + public GHCommitComment getComment() { + return comment; + } + + public void setComment(GHCommitComment comment) { + this.comment = comment; + } + + public GHRepository getRepository() { + return repository; + } + + public void setRepository(GHRepository repository) { + this.repository = repository; + } + + @Override + void wrapUp(GitHub root) { + super.wrapUp(root); + if (repository != null) { + repository.wrap(root); + comment.wrap(repository); + } + } + } + + /** + * A repository, branch, or tag was created + * + * @see authoritative source + */ + @SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD" }, + justification = "Constructed by JSON deserialization") + public static class Create extends GHEventPayload { + private String ref; + @JsonProperty("ref_type") + private String refType; + @JsonProperty("master_branch") + private String masterBranch; + private String description; + private GHRepository repository; + + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Comes from JSON deserialization") + public String getRef() { + return ref; + } + + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Comes from JSON deserialization") + public String getRefType() { + return refType; + } + + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Comes from JSON deserialization") + public String getMasterBranch() { + return masterBranch; + } + + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Comes from JSON deserialization") + public String getDescription() { + return description; + } + + public GHRepository getRepository() { + return repository; + } + + public void setRepository(GHRepository repository) { + this.repository = repository; + } + + @Override + void wrapUp(GitHub root) { + super.wrapUp(root); + if (repository != null) { + repository.wrap(root); + } + } + } + + /** + * A branch, or tag was deleted + * + * @see authoritative source + */ + @SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD" }, + justification = "Constructed by JSON deserialization") + public static class Delete extends GHEventPayload { + private String ref; + @JsonProperty("ref_type") + private String refType; + private GHRepository repository; + + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Comes from JSON deserialization") + public String getRef() { + return ref; + } + + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Comes from JSON deserialization") + public String getRefType() { + return refType; + } + + public GHRepository getRepository() { + return repository; + } + + public void setRepository(GHRepository repository) { + this.repository = repository; + } + + @Override + void wrapUp(GitHub root) { + super.wrapUp(root); + if (repository != null) { + repository.wrap(root); + } + } + } + + /** + * A deployment + * + * @see authoritative source + */ + @SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD" }, + justification = "Constructed by JSON deserialization") + public static class Deployment extends GHEventPayload { + private GHDeployment deployment; + private GHRepository repository; + + public GHDeployment getDeployment() { + return deployment; + } + + public void setDeployment(GHDeployment deployment) { + this.deployment = deployment; + } + + public GHRepository getRepository() { + return repository; + } + + public void setRepository(GHRepository repository) { + this.repository = repository; + } + + @Override + void wrapUp(GitHub root) { + super.wrapUp(root); + if (repository != null) { + repository.wrap(root); + deployment.wrap(repository); + } + } + } + + /** + * A deployment + * + * @see authoritative source + */ + @SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD" }, + justification = "Constructed by JSON deserialization") + public static class DeploymentStatus extends GHEventPayload { + @JsonProperty("deployment_status") + private GHDeploymentStatus deploymentStatus; + private GHDeployment deployment; + private GHRepository repository; + + public GHDeploymentStatus getDeploymentStatus() { + return deploymentStatus; + } + + public void setDeploymentStatus(GHDeploymentStatus deploymentStatus) { + this.deploymentStatus = deploymentStatus; + } + + public GHDeployment getDeployment() { + return deployment; + } + + public void setDeployment(GHDeployment deployment) { + this.deployment = deployment; + } + + public GHRepository getRepository() { + return repository; + } + + public void setRepository(GHRepository repository) { + this.repository = repository; + } + + @Override + void wrapUp(GitHub root) { + super.wrapUp(root); + if (repository != null) { + repository.wrap(root); + deployment.wrap(repository); + deploymentStatus.wrap(repository); + } + } + } + + /** + * A user forked a repository + * + * @see authoritative source + */ + @SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD" }, + justification = "Constructed by JSON deserialization") + public static class Fork extends GHEventPayload { + private GHRepository forkee; + private GHRepository repository; + + + public GHRepository getForkee() { + return forkee; + } + + public void setForkee(GHRepository forkee) { + this.forkee = forkee; + } + + public GHRepository getRepository() { + return repository; + } + + public void setRepository(GHRepository repository) { + this.repository = repository; + } + + @Override + void wrapUp(GitHub root) { + super.wrapUp(root); + forkee.wrap(root); + if (repository != null) { + repository.wrap(root); + } + } + } + + /** + * A ping. + */ + public static class Ping extends GHEventPayload { + private GHRepository repository; + private GHOrganization organization; + + public void setRepository(GHRepository repository) { + this.repository = repository; + } + + public GHRepository getRepository() { + return repository; + } + + public GHOrganization getOrganization() { + return organization; + } + + public void setOrganization(GHOrganization organization) { + this.organization = organization; + } + + @Override + void wrapUp(GitHub root) { + super.wrapUp(root); + if (repository!=null) + repository.wrap(root); + if (organization != null) { + organization.wrapUp(root); + } + } + + } + + /** + * A repository was made public. + * + * @see authoritative source + */ + public static class Public extends GHEventPayload { + private GHRepository repository; + + public void setRepository(GHRepository repository) { + this.repository = repository; + } + + public GHRepository getRepository() { + return repository; + } + + @Override + void wrapUp(GitHub root) { + super.wrapUp(root); + if (repository!=null) + repository.wrap(root); + } + + } + /** * A commit was pushed. * * @see authoritative source */ + @SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD", "UUF_UNUSED_FIELD"}, + justification = "Constructed by JSON deserialization") public static class Push extends GHEventPayload { private String head, before; + private boolean created, deleted, forced; private String ref; private int size; private List commits; private GHRepository repository; + private Pusher pusher; /** * The SHA of the HEAD commit on the repository @@ -137,6 +624,11 @@ public String getBefore() { return before; } + @JsonSetter // alias + private void setAfter(String after) { + head = after; + } + /** * The full Git ref that was pushed. Example: “refs/heads/master” */ @@ -152,6 +644,18 @@ public int getSize() { return size; } + public boolean isCreated() { + return created; + } + + public boolean isDeleted() { + return deleted; + } + + public boolean isForced() { + return forced; + } + /** * The list of pushed commits. */ @@ -163,6 +667,14 @@ public GHRepository getRepository() { return repository; } + public Pusher getPusher() { + return pusher; + } + + public void setPusher(Pusher pusher) { + this.pusher = pusher; + } + @Override void wrapUp(GitHub root) { super.wrapUp(root); @@ -170,18 +682,44 @@ void wrapUp(GitHub root) { repository.wrap(root); } + public static class Pusher { + private String name, email; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + } + /** * Commit in a push */ public static class PushCommit { private GitUser author; + private GitUser committer; private String url, sha, message; private boolean distinct; + private List added, removed, modified; public GitUser getAuthor() { return author; } + public GitUser getCommitter() { + return committer; + } + /** * Points to the commit API resource. */ @@ -193,6 +731,11 @@ public String getSha() { return sha; } + @JsonSetter + private void setId(String id) { + sha = id; + } + public String getMessage() { return message; } @@ -203,6 +746,103 @@ public String getMessage() { public boolean isDistinct() { return distinct; } + + public List getAdded() { + return added; + } + + public List getRemoved() { + return removed; + } + + public List getModified() { + return modified; + } + } + } + + /** + * A release was added to the repo + * + * @see authoritative source + */ + @SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD" }, + justification = "Constructed by JSON deserialization") + public static class Release extends GHEventPayload { + private String action; + private GHRelease release; + private GHRepository repository; + + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Comes from JSON deserialization") + public String getAction() { + return action; + } + + public GHRelease getRelease() { + return release; + } + + public void setRelease(GHRelease release) { + this.release = release; + } + + public GHRepository getRepository() { + return repository; + } + + public void setRepository(GHRepository repository) { + this.repository = repository; + } + + @Override + void wrapUp(GitHub root) { + super.wrapUp(root); + if (repository != null) { + repository.wrap(root); + } } } + + /** + * A repository was created, deleted, made public, or made private. + * + * @see authoritative source + */ + @SuppressFBWarnings(value = {"UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", "NP_UNWRITTEN_FIELD", "UWF_UNWRITTEN_FIELD"}, + justification = "Constructed by JSON deserialization") + public static class Repository extends GHEventPayload { + private String action; + private GHRepository repository; + private GHOrganization organization; + + public String getAction() { + return action; + } + + public void setRepository(GHRepository repository) { + this.repository = repository; + } + + public GHRepository getRepository() { + return repository; + } + + public GHOrganization getOrganization() { + return organization; + } + + public void setOrganization(GHOrganization organization) { + this.organization = organization; + } + + @Override + void wrapUp(GitHub root) { + super.wrapUp(root); + repository.wrap(root); + if (organization != null) { + organization.wrapUp(root); + } + } + + } } diff --git a/src/main/java/org/kohsuke/github/GHFileNotFoundException.java b/src/main/java/org/kohsuke/github/GHFileNotFoundException.java new file mode 100644 index 0000000000..9f4b37c560 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHFileNotFoundException.java @@ -0,0 +1,34 @@ +package org.kohsuke.github; + +import javax.annotation.CheckForNull; +import java.io.FileNotFoundException; +import java.net.HttpURLConnection; +import java.util.List; +import java.util.Map; + +/** + * Request/responce contains useful metadata. + * Custom exception allows store info for next diagnostics. + * + * @author Kanstantsin Shautsou + */ +public class GHFileNotFoundException extends FileNotFoundException { + protected Map> responseHeaderFields; + + public GHFileNotFoundException() { + } + + public GHFileNotFoundException(String s) { + super(s); + } + + @CheckForNull + public Map> getResponseHeaderFields() { + return responseHeaderFields; + } + + GHFileNotFoundException withResponseHeaderFields(HttpURLConnection urlConnection) { + this.responseHeaderFields = urlConnection.getHeaderFields(); + return this; + } +} diff --git a/src/main/java/org/kohsuke/github/GHGist.java b/src/main/java/org/kohsuke/github/GHGist.java index f608af3da6..c4c91737d6 100644 --- a/src/main/java/org/kohsuke/github/GHGist.java +++ b/src/main/java/org/kohsuke/github/GHGist.java @@ -38,8 +38,8 @@ public class GHGist extends GHObject { /** * User that owns this Gist. */ - public GHUser getOwner() { - return owner; + public GHUser getOwner() throws IOException { + return root.intern(owner); } public String getForksUrl() { @@ -103,7 +103,7 @@ public Map getFiles() { * Used when caller obtains {@link GHGist} without knowing its owner. * A partially constructed owner object is interned. */ - /*package*/ GHGist wrapUp(GitHub root) throws IOException { + /*package*/ GHGist wrapUp(GitHub root) { this.owner = root.getUser(owner); this.root = root; wrapUp(); @@ -140,16 +140,12 @@ public GHGist fork() throws IOException { public PagedIterable listForks() { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("forks"), GHGist[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("forks"), GHGist[].class, pageSize)) { @Override protected void wrapUp(GHGist[] page) { - try { - for (GHGist c : page) - c.wrapUp(root); - } catch (IOException e) { - throw new Error(e); - } + for (GHGist c : page) + c.wrapUp(root); } }; } diff --git a/src/main/java/org/kohsuke/github/GHHook.java b/src/main/java/org/kohsuke/github/GHHook.java index ffe4837509..b7f450307a 100644 --- a/src/main/java/org/kohsuke/github/GHHook.java +++ b/src/main/java/org/kohsuke/github/GHHook.java @@ -1,5 +1,7 @@ package org.kohsuke.github; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import java.io.IOException; import java.net.URL; import java.util.Collections; @@ -11,30 +13,24 @@ /** * @author Kohsuke Kawaguchi */ -public class GHHook extends GHObject { - /** - * Repository that the hook belongs to. - */ - /*package*/ transient GHRepository repository; - +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "NP_UNWRITTEN_FIELD"}, justification = "JSON API") +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; } public EnumSet getEvents() { EnumSet s = EnumSet.noneOf(GHEvent.class); - for (String e : events) - s.add(Enum.valueOf(GHEvent.class,e.toUpperCase(Locale.ENGLISH))); + for (String e : events) { + if (e.equals("*")) s.add(GHEvent.ALL); + else s.add(Enum.valueOf(GHEvent.class, e.toUpperCase(Locale.ENGLISH))); + } return s; } @@ -46,11 +42,18 @@ public Map getConfig() { return Collections.unmodifiableMap(config); } + /** + * @see Ping hook + */ + public void ping() throws IOException { + new Requester(getRoot()).method("POST").to(getApiRoute() + "/pings"); + } + /** * 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 +63,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..6ecbb04b6e --- /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.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 { + + GHHook [] hookArray = root.retrieve().to(collection(),collectionClass()); // jdk/eclipse bug requires this to be on separate line + List list = new ArrayList(Arrays.asList(hookArray)); + 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.symbol()); + } + + 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/GHIOException.java b/src/main/java/org/kohsuke/github/GHIOException.java new file mode 100644 index 0000000000..b07144bcb7 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHIOException.java @@ -0,0 +1,34 @@ +package org.kohsuke.github; + +import javax.annotation.CheckForNull; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.List; +import java.util.Map; + +/** + * Request/responce contains useful metadata. + * Custom exception allows store info for next diagnostics. + * + * @author Kanstantsin Shautsou + */ +public class GHIOException extends IOException { + protected Map> responseHeaderFields; + + public GHIOException() { + } + + public GHIOException(String message) { + super(message); + } + + @CheckForNull + public Map> getResponseHeaderFields() { + return responseHeaderFields; + } + + GHIOException withResponseHeaderFields(HttpURLConnection urlConnection) { + this.responseHeaderFields = urlConnection.getHeaderFields(); + return this; + } +} diff --git a/src/main/java/org/kohsuke/github/GHInvitation.java b/src/main/java/org/kohsuke/github/GHInvitation.java new file mode 100644 index 0000000000..74619ad6e0 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHInvitation.java @@ -0,0 +1,46 @@ +package org.kohsuke.github; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.io.IOException; +import java.net.URL; + +/** + * @see GitHub#getMyInvitations() + * @see GHRepository#listInvitations() + */ +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "NP_UNWRITTEN_FIELD", "UUF_UNUSED_FIELD"}, justification = "JSON API") +public class GHInvitation extends GHObject { + /*package almost final*/ GitHub root; + + private int id; + private GHRepository repository; + private GHUser invitee, inviter; + private String permissions; + private String html_url; + + /*package*/ GHInvitation wrapUp(GitHub root) { + this.root = root; + return this; + } + + /** + * Accept a repository invitation. + */ + public void accept() throws IOException { + root.retrieve().method("PATCH").to("/user/repository_invitations/" + id); + } + + /** + * Decline a repository invitation. + */ + public void decline() throws IOException { + root.retrieve().method("DELETE").to("/user/repository_invitations/" + id); + } + + @Override + public URL getHtmlUrl() { + return GitHub.parseURL(html_url); + } +} diff --git a/src/main/java/org/kohsuke/github/GHIssue.java b/src/main/java/org/kohsuke/github/GHIssue.java index 8c071a93f8..55cf54b81f 100644 --- a/src/main/java/org/kohsuke/github/GHIssue.java +++ b/src/main/java/org/kohsuke/github/GHIssue.java @@ -24,13 +24,21 @@ package org.kohsuke.github; +import static org.kohsuke.github.Previews.SQUIRREL_GIRL; + +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Set; /** * Represents an issue on GitHub. @@ -41,16 +49,20 @@ * @see GitHub#searchIssues() * @see GHIssueSearchBuilder */ -public class GHIssue extends GHObject { +public class GHIssue extends GHObject implements Reactable{ + private static final String ASSIGNEES = "assignees"; + GitHub root; GHRepository owner; // API v3 - protected GHUser assignee; + protected GHUser assignee; // not sure what this field is now that 'assignees' exist + protected GHUser[] assignees; protected String state; protected int number; protected String closed_at; protected int comments; + @SkipFromToString protected String body; // for backward compatibility with < 1.63, this collection needs to hold instances of Label, not GHLabel protected List

+ * WARNING: This uses a PREVIEW API - subject to change. + * + * @author Duncan Dickinson + * @see GitHub#getLicense(String) + * @see GHRepository#getLicense() + * @see https://developer.github.com/v3/licenses/ + */ +@Preview @Deprecated +@SuppressWarnings({"UnusedDeclaration"}) +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "NP_UNWRITTEN_FIELD"}, justification = "JSON API") +public class GHLicense extends GHObject { + @SuppressFBWarnings("IS2_INCONSISTENT_SYNC") // root is set before the object is returned to the app + /*package almost final*/ GitHub root; + + // these fields are always present, even in the short form + protected String key, name; + + // the rest is only after populated + protected Boolean featured; + + protected String html_url, description, category, implementation, body; + + protected List required = new ArrayList(); + protected List permitted = new ArrayList(); + protected List forbidden = new ArrayList(); + + /** + * @return a mnemonic for the license + */ + public String getKey() { + return key; + } + + /** + * @return the license name + */ + public String getName() { + return name; + } + + /** + * @return API URL of this object. + */ + @WithBridgeMethods(value = String.class, adapterMethod = "urlToString") + public URL getUrl() { + return GitHub.parseURL(url); + } + + /** + * Featured licenses are bold in the new repository drop-down + * + * @return True if the license is featured, false otherwise + */ + public Boolean isFeatured() throws IOException { + populate(); + return featured; + } + + public URL getHtmlUrl() throws IOException { + populate(); + return GitHub.parseURL(html_url); + } + + public String getDescription() throws IOException { + populate(); + return description; + } + + public String getCategory() throws IOException { + populate(); + return category; + } + + public String getImplementation() throws IOException { + populate(); + return implementation; + } + + public List getRequired() throws IOException { + populate(); + return required; + } + + public List getPermitted() throws IOException { + populate(); + return permitted; + } + + public List getForbidden() throws IOException { + populate(); + return forbidden; + } + + public String getBody() throws IOException { + populate(); + return body; + } + + /** + * 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 synchronized void populate() throws IOException { + if (description!=null) return; // already populated + + root.retrieve().withPreview(DRAX).to(url, this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof GHLicense)) return false; + + GHLicense that = (GHLicense) o; + return this.url.equals(that.url); + } + + @Override + public int hashCode() { + return url.hashCode(); + } + + /*package*/ GHLicense wrap(GitHub root) { + this.root = root; + return this; + } +} diff --git a/src/main/java/org/kohsuke/github/GHMembership.java b/src/main/java/org/kohsuke/github/GHMembership.java new file mode 100644 index 0000000000..2847e1891c --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHMembership.java @@ -0,0 +1,84 @@ +package org.kohsuke.github; + +import java.io.IOException; +import java.net.URL; +import java.util.Locale; + +/** + * Represents a membership of a user in an organization. + * + * @author Kohsuke Kawaguchi + * @see GHMyself#listOrgMemberships() + */ +public class GHMembership /* extends GHObject --- but it doesn't have id, created_at, etc. */ { + GitHub root; + + String url; + String state; + String role; + GHUser user; + GHOrganization organization; + + public URL getUrl() { + return GitHub.parseURL(url); + } + + public State getState() { + return Enum.valueOf(State.class, state.toUpperCase(Locale.ENGLISH)); + } + + public Role getRole() { + return Enum.valueOf(Role.class, role.toUpperCase(Locale.ENGLISH)); + } + + public GHUser getUser() { + return user; + } + + public GHOrganization getOrganization() { + return organization; + } + + /** + * Accepts a pending invitation to an organization. + * + * @see GHMyself#getMembership(GHOrganization) + */ + public void activate() throws IOException { + root.retrieve().method("PATCH").with("state",State.ACTIVE).to(url,this); + } + + /*package*/ GHMembership wrap(GitHub root) { + this.root = root; + if (user!=null) user = root.getUser(user.wrapUp(root)); + if (organization!=null) organization.wrapUp(root); + return this; + } + + /*package*/ static void wrap(GHMembership[] page, GitHub root) { + for (GHMembership m : page) + m.wrap(root); + } + + /** + * Role of a user in an organization. + */ + public enum Role { + /** + * Organization owner. + */ + ADMIN, + /** + * Non-owner organization member. + */ + MEMBER; + } + + /** + * Whether a role is currently active or waiting for acceptance (pending) + */ + public enum State { + ACTIVE, + PENDING; + } +} diff --git a/src/main/java/org/kohsuke/github/GHMilestone.java b/src/main/java/org/kohsuke/github/GHMilestone.java index 146c92abb3..50ad549e82 100644 --- a/src/main/java/org/kohsuke/github/GHMilestone.java +++ b/src/main/java/org/kohsuke/github/GHMilestone.java @@ -17,6 +17,7 @@ public class GHMilestone extends GHObject { GHUser creator; private String state, due_on, title, description, html_url; private int closed_issues, open_issues, number; + protected String closed_at; public GitHub getRoot() { return root; @@ -26,15 +27,22 @@ public GHRepository getOwner() { return owner; } - public GHUser getCreator() { - return creator; + public GHUser getCreator() throws IOException { + return root.intern(creator); } public Date getDueOn() { if (due_on == null) return null; return GitHub.parseDate(due_on); } - + + /** + * When was this milestone closed? + */ + public Date getClosedAt() throws IOException { + return GitHub.parseDate(closed_at); + } + public String getTitle() { return title; } @@ -64,12 +72,19 @@ public GHMilestoneState getState() { } /** - * Closes this issue. + * Closes this milestone. */ public void close() throws IOException { edit("state", "closed"); } + /** + * Reopens this milestone. + */ + public void reopen() throws IOException { + edit("state", "open"); + } + private void edit(String key, Object value) throws IOException { new Requester(root)._with(key, value).method("PATCH").to(getApiRoute()); } diff --git a/src/main/java/org/kohsuke/github/GHMyself.java b/src/main/java/org/kohsuke/github/GHMyself.java index 7b588e88fa..5ab9b4a270 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,30 @@ 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)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().with("type",repoType).asIterator("/user/repos", GHRepository[].class, pageSize)) { @Override protected void wrapUp(GHRepository[] page) { for (GHRepository c : page) @@ -126,7 +166,7 @@ protected void wrapUp(GHRepository[] page) { } }; } - }; + }.withPageSize(pageSize); } /** @@ -137,6 +177,39 @@ public PagedIterable listAllRepositories() { return listRepositories(); } + /** + * List your organization memberships + */ + public PagedIterable listOrgMemberships() { + return listOrgMemberships(null); + } + + /** + * List your organization memberships + * + * @param state + * Filter by a specific state + */ + public PagedIterable listOrgMemberships(final GHMembership.State state) { + return new PagedIterable() { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().with("state",state).asIterator("/user/memberships/orgs", GHMembership[].class, pageSize)) { + @Override + protected void wrapUp(GHMembership[] page) { + GHMembership.wrap(page,root); + } + }; + } + }; + } + + /** + * Gets your membership in a specific organization. + */ + public GHMembership getMembership(GHOrganization o) throws IOException { + return root.retrieve().to("/user/memberships/orgs/"+o.getLogin(),GHMembership.class).wrap(root); + } + // public void addEmails(Collection emails) throws IOException { //// new Requester(root,ApiVersion.V3).withCredential().to("/user/emails"); // root.retrieveWithAuth3() diff --git a/src/main/java/org/kohsuke/github/GHNotificationStream.java b/src/main/java/org/kohsuke/github/GHNotificationStream.java index 5ea6fb69cb..b8f8d7c296 100644 --- a/src/main/java/org/kohsuke/github/GHNotificationStream.java +++ b/src/main/java/org/kohsuke/github/GHNotificationStream.java @@ -152,7 +152,7 @@ GHThread fetch() { while (true) { long now = System.currentTimeMillis(); if (nextCheckTime < now) break; - long waitTime = Math.max(Math.min(nextCheckTime - now, 1000), 60 * 1000); + long waitTime = Math.min(Math.max(nextCheckTime - now, 1000), 60 * 1000); Thread.sleep(waitTime); } @@ -180,7 +180,8 @@ GHThread fetch() { private long calcNextCheckTime() { String v = req.getResponseHeader("X-Poll-Interval"); if (v==null) v="60"; - return System.currentTimeMillis()+Integer.parseInt(v)*1000; + long seconds = Integer.parseInt(v); + return System.currentTimeMillis() + seconds*1000; } public void remove() { diff --git a/src/main/java/org/kohsuke/github/GHObject.java b/src/main/java/org/kohsuke/github/GHObject.java index 914f1280d3..2bad0891bc 100644 --- a/src/main/java/org/kohsuke/github/GHObject.java +++ b/src/main/java/org/kohsuke/github/GHObject.java @@ -1,23 +1,52 @@ package org.kohsuke.github; import com.infradna.tool.bridge_method_injector.WithBridgeMethods; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.apache.commons.lang3.builder.ReflectionToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import javax.annotation.CheckForNull; import java.io.IOException; +import java.lang.reflect.Field; import java.net.URL; import java.util.Date; +import java.util.List; +import java.util.Map; /** * Most (all?) domain objects in GitHub seems to have these 4 properties. */ +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "NP_UNWRITTEN_FIELD"}, justification = "JSON API") public abstract class GHObject { + /** + * Capture response HTTP headers on the state object. + */ + protected Map> responseHeaderFields; + protected String url; - protected int id; + protected long id; protected String created_at; protected String updated_at; /*package*/ GHObject() { } + /** + * Returns the HTTP response headers given along with the state of this object. + * + *

+ * Some of the HTTP headers have nothing to do with the object, for example "Cache-Control" + * and others are different depending on how this object was retrieved. + * + * This method was added as a kind of hack to allow the caller to retrieve OAuth scopes and such. + * Use with caution. The method might be removed in the future. + */ + @CheckForNull @Deprecated + public Map> getResponseHeaderFields() { + return responseHeaderFields; + } + /** * When was this resource created? */ @@ -26,6 +55,7 @@ public Date getCreatedAt() throws IOException { return GitHub.parseDate(created_at); } + @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification = "Bridge method of getCreatedAt") private Object createdAtStr(Date id, Class type) { return created_at; } @@ -42,7 +72,7 @@ public URL getUrl() { * URL of this object for humans, which renders some HTML. */ @WithBridgeMethods(value=String.class, adapterMethod="urlToString") - public abstract URL getHtmlUrl(); + public abstract URL getHtmlUrl() throws IOException; /** * When was this resource last updated? @@ -54,16 +84,57 @@ public Date getUpdatedAt() throws IOException { /** * Unique ID number of this resource. */ - @WithBridgeMethods(value=String.class, adapterMethod="intToString") - public int getId() { + @WithBridgeMethods(value={String.class,int.class}, adapterMethod="longToStringOrInt") + public long getId() { return id; } - private Object intToString(int id, Class type) { - return String.valueOf(id); + @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification = "Bridge method of getId") + private Object longToStringOrInt(long id, Class type) { + if (type==String.class) + return String.valueOf(id); + if (type==int.class) + return (int)id; + throw new AssertionError("Unexpected type: "+type); } + @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD", justification = "Bridge method of getHtmlUrl") private Object urlToString(URL url, Class type) { return url==null ? null : url.toString(); } + + /** + * String representation to assist debugging and inspection. The output format of this string + * is not a committed part of the API and is subject to change. + */ + @Override + public String toString() { + return new ReflectionToStringBuilder(this, TOSTRING_STYLE, null, null, false, false) { + @Override + protected boolean accept(Field field) { + return super.accept(field) && !field.isAnnotationPresent(SkipFromToString.class); + } + }.toString(); + } + + private static final ToStringStyle TOSTRING_STYLE = new ToStringStyle() { + { + this.setUseShortClassName(true); + } + + @Override + public void append(StringBuffer buffer, String fieldName, Object value, Boolean fullDetail) { + // skip unimportant properties. '_' is a heuristics as important properties tend to have short names + if (fieldName.contains("_")) + return; + // avoid recursing other GHObject + if (value instanceof GHObject) + return; + // likewise no point in showing root + if (value instanceof GitHub) + return; + + super.append(buffer,fieldName,value,fullDetail); + } + }; } 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..e5ce68e26b 100644 --- a/src/main/java/org/kohsuke/github/GHOrganization.java +++ b/src/main/java/org/kohsuke/github/GHOrganization.java @@ -1,9 +1,11 @@ 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.Map; import java.util.TreeMap; @@ -21,21 +23,35 @@ public class GHOrganization extends GHPerson { * * @return * Newly created repository. + * @deprecated + * Use {@link #createRepository(String)} that uses a builder pattern to let you control every aspect. */ public GHRepository createRepository(String name, String description, String homepage, String team, boolean isPublic) throws IOException { 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); } + /** + * @deprecated + * Use {@link #createRepository(String)} that uses a builder pattern to let you control every aspect. + */ public GHRepository createRepository(String name, String description, String homepage, GHTeam team, boolean isPublic) throws IOException { if (team==null) throw new IllegalArgumentException("Invalid team"); - // such API doesn't exist, so fall back to HTML scraping - 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); + return createRepository(name).description(description).homepage(homepage).private_(!isPublic).team(team).create(); + } + + /** + * Starts a builder that creates a new repository. + * + *

+ * You use the returned builder to set various properties, then call {@link GHCreateRepositoryBuilder#create()} + * to finally createa repository. + */ + public GHCreateRepositoryBuilder createRepository(String name) { + return new GHCreateRepositoryBuilder(root,"/orgs/"+login+"/repos",name); } /** @@ -54,8 +70,8 @@ public Map getTeams() throws IOException { */ public PagedIterable listTeams() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(String.format("/orgs/%s/teams", login), GHTeam[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(String.format("/orgs/%s/teams", login), GHTeam[].class, pageSize)) { @Override protected void wrapUp(GHTeam[] page) { for (GHTeam c : page) @@ -77,6 +93,17 @@ public GHTeam getTeamByName(String name) throws IOException { return null; } + /** + * Finds a team that has the given slug in its {@link GHTeam#getSlug()} + */ + public GHTeam getTeamBySlug(String slug) throws IOException { + for (GHTeam t : listTeams()) { + if(t.getSlug().equals(slug)) + return t; + } + return null; + } + /** * Checks if this organization has the specified user as a member. */ @@ -147,9 +174,9 @@ public PagedIterable listMembersWithFilter(String filter) throws IOExcep private PagedIterable listMembers(final String suffix, final String filter) throws IOException { return new PagedIterable() { - public PagedIterator iterator() { + public PagedIterator _iterator(int pageSize) { String filterParams = (filter == null) ? "" : ("?filter=" + filter); - return new PagedIterator(root.retrieve().asIterator(String.format("/orgs/%s/%s%s", login, suffix, filterParams), GHUser[].class)) { + return new PagedIterator(root.retrieve().asIterator(String.format("/orgs/%s/%s%s", login, suffix, filterParams), GHUser[].class, pageSize)) { @Override protected void wrapUp(GHUser[] users) { GHUser.wrap(users, root); @@ -172,7 +199,7 @@ public enum Permission { ADMIN, PUSH, PULL } * Creates a new team and assigns the repositories. */ public GHTeam createTeam(String name, Permission p, Collection repositories) throws IOException { - Requester post = new Requester(root).with("name", name).with("permission", p.name().toLowerCase()); + Requester post = new Requester(root).with("name", name).with("permission", p); List repo_names = new ArrayList(); for (GHRepository r : repositories) { repo_names.add(r.getName()); @@ -182,7 +209,7 @@ public GHTeam createTeam(String name, Permission p, Collection rep } public GHTeam createTeam(String name, Permission p, GHRepository... repositories) throws IOException { - return createTeam(name,p, Arrays.asList(repositories)); + return createTeam(name, p, Arrays.asList(repositories)); } /** @@ -193,7 +220,7 @@ public GHTeam createTeam(String name, Permission p, GHRepository... repositories */ public List getRepositoriesWithOpenPullRequests() throws IOException { List r = new ArrayList(); - for (GHRepository repository : listRepositories()) { + for (GHRepository repository : listRepositories(100)) { repository.wrap(root); List pullRequests = repository.getPullRequests(GHIssueState.OPEN); if (pullRequests.size() > 0) { @@ -219,8 +246,8 @@ public List getPullRequests() throws IOException { */ public PagedIterable listEvents() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(String.format("/orgs/%s/events", login), GHEventInfo[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(String.format("/orgs/%s/events", login), GHEventInfo[].class, pageSize)) { @Override protected void wrapUp(GHEventInfo[] page) { for (GHEventInfo c : page) @@ -241,8 +268,8 @@ protected void wrapUp(GHEventInfo[] page) { @Override public PagedIterable listRepositories(final int pageSize) { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator("/orgs/" + login + "/repos?per_page=" + pageSize, GHRepository[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator("/orgs/" + login + "/repos", GHRepository[].class, pageSize)) { @Override protected void wrapUp(GHRepository[] page) { for (GHRepository c : page) @@ -250,6 +277,41 @@ protected void wrapUp(GHRepository[] page) { } }; } - }; + }.withPageSize(pageSize); + } + + /** + * 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/GHPermission.java b/src/main/java/org/kohsuke/github/GHPermission.java new file mode 100644 index 0000000000..51a808dfc0 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHPermission.java @@ -0,0 +1,59 @@ +/* + * The MIT License + * + * Copyright 2016 CloudBees, Inc. + * + * 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 java.util.Locale; + +/** + * Permission for a user in a repository. + * @see API + */ +/*package*/ class GHPermission { + + private String permission; + private GHUser user; + + /** + * @return one of {@code admin}, {@code write}, {@code read}, or {@code none} + */ + public String getPermission() { + return permission; + } + + public GHPermissionType getPermissionType() { + return Enum.valueOf(GHPermissionType.class, permission.toUpperCase(Locale.ENGLISH)); + } + + public GHUser getUser() { + return user; + } + + void wrapUp(GitHub root) { + if (user != null) { + user.root = root; + } + } + +} diff --git a/src/main/java/org/kohsuke/github/GHPermissionType.java b/src/main/java/org/kohsuke/github/GHPermissionType.java new file mode 100644 index 0000000000..d3e2bd0909 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHPermissionType.java @@ -0,0 +1,11 @@ +package org.kohsuke.github; + +/** + * @author Kohsuke Kawaguchi + */ +public enum GHPermissionType { + ADMIN, + WRITE, + READ, + NONE +} diff --git a/src/main/java/org/kohsuke/github/GHPerson.java b/src/main/java/org/kohsuke/github/GHPerson.java index 1a9a0f1575..b1be225965 100644 --- a/src/main/java/org/kohsuke/github/GHPerson.java +++ b/src/main/java/org/kohsuke/github/GHPerson.java @@ -38,8 +38,12 @@ public abstract class GHPerson extends GHObject { * Depending on the original API call where this object is created, it may not contain everything. */ protected synchronized void populate() throws IOException { - if (created_at!=null) return; // already populated - + if (created_at!=null) { + return; // already populated + } + if (root == null || root.isOffline()) { + return; // cannot populate, will have to live with what we have + } root.retrieve().to(url, this); } @@ -52,7 +56,7 @@ protected synchronized void populate() throws IOException { */ public synchronized Map getRepositories() throws IOException { Map repositories = new TreeMap(); - for (GHRepository r : listRepositories()) { + for (GHRepository r : listRepositories(100)) { repositories.put(r.getName(),r); } return Collections.unmodifiableMap(repositories); @@ -76,8 +80,8 @@ public PagedIterable listRepositories() { */ public PagedIterable listRepositories(final int pageSize) { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator("/users/" + login + "/repos?per_page=" + pageSize, GHRepository[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator("/users/" + login + "/repos", GHRepository[].class, pageSize)) { @Override protected void wrapUp(GHRepository[] page) { for (GHRepository c : page) @@ -85,14 +89,14 @@ protected void wrapUp(GHRepository[] page) { } }; } - }; + }.withPageSize(pageSize); } /** - * Loads repository list in a pagenated fashion. + * Loads repository list in a paginated fashion. * *

- * For a person with a lot of repositories, GitHub returns the list of repositories in a pagenated fashion. + * For a person with a lot of repositories, GitHub returns the list of repositories in a paginated fashion. * Unlike {@link #getRepositories()}, this method allows the caller to start processing data as it arrives. * * Every {@link Iterator#next()} call results in I/O. Exceptions that occur during the processing is wrapped @@ -104,7 +108,7 @@ protected void wrapUp(GHRepository[] page) { public synchronized Iterable> iterateRepositories(final int pageSize) { return new Iterable>() { public Iterator> iterator() { - final Iterator pager = root.retrieve().asIterator("/users/" + login + "/repos?per_page="+pageSize,GHRepository[].class); + final Iterator pager = root.retrieve().asIterator("/users/" + login + "/repos",GHRepository[].class, pageSize); return new Iterator>() { public boolean hasNext() { @@ -204,7 +208,7 @@ public Date getCreatedAt() throws IOException { public Date getUpdatedAt() throws IOException { populate(); - return super.getCreatedAt(); + return super.getUpdatedAt(); } /** diff --git a/src/main/java/org/kohsuke/github/GHPersonSet.java b/src/main/java/org/kohsuke/github/GHPersonSet.java index 1c1d43dccb..eb35b1bf7a 100644 --- a/src/main/java/org/kohsuke/github/GHPersonSet.java +++ b/src/main/java/org/kohsuke/github/GHPersonSet.java @@ -10,6 +10,8 @@ * @author Kohsuke Kawaguchi */ public class GHPersonSet extends HashSet { + private static final long serialVersionUID = 1L; + public GHPersonSet() { } diff --git a/src/main/java/org/kohsuke/github/GHPullRequest.java b/src/main/java/org/kohsuke/github/GHPullRequest.java index 7c46a9c1c5..b4098829b9 100644 --- a/src/main/java/org/kohsuke/github/GHPullRequest.java +++ b/src/main/java/org/kohsuke/github/GHPullRequest.java @@ -23,21 +23,27 @@ */ package org.kohsuke.github; +import javax.annotation.CheckForNull; import java.io.IOException; import java.net.URL; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Date; -import java.util.Locale; +import java.util.List; /** * A pull request. - * + * * @author Kohsuke Kawaguchi * @see GHRepository#getPullRequest(int) */ @SuppressWarnings({"UnusedDeclaration"}) public class GHPullRequest extends GHIssue { + private static final String COMMENTS_ACTION = "/comments"; + private static final String REQUEST_REVIEWERS = "/requested_reviewers"; + private String patch_url, diff_url, issue_url; private GHCommitPointer base; private String merged_at; @@ -45,12 +51,16 @@ public class GHPullRequest extends GHIssue { // details that are only available when obtained from ID private GHUser merged_by; - private int review_comments, additions; - private boolean merged; + private int review_comments, additions, commits; + private boolean merged, maintainer_can_modify; private Boolean mergeable; private int deletions; private String mergeable_state; private int changed_files; + private String merge_commit_sha; + + // pull request reviewers + private GHUser[] requested_reviewers; /** * GitHub doesn't return some properties of {@link GHIssue} when requesting the GET on the 'pulls' API @@ -70,6 +80,7 @@ GHPullRequest wrapUp(GitHub root) { if (base != null) base.wrapUp(root); if (head != null) head.wrapUp(root); if (merged_by != null) merged_by.wrapUp(root); + if (requested_reviewers != null) GHUser.wrap(requested_reviewers, root); return this; } @@ -85,7 +96,7 @@ protected String getApiRoute() { public URL getPatchUrl() { return GitHub.parseURL(patch_url); } - + /** * The URL of the patch file. * like https://github.com/jenkinsci/jenkins/pull/100.patch @@ -108,7 +119,7 @@ public GHCommitPointer getBase() { public GHCommitPointer getHead() { return head; } - + @Deprecated public Date getIssueUpdatedAt() throws IOException { return super.getUpdatedAt(); @@ -143,9 +154,9 @@ public PullRequest getPullRequest() { } // -// details that are only available via get with ID -// -// + // details that are only available via get with ID + // + public GHUser getMergedBy() throws IOException { populate(); return merged_by; @@ -161,13 +172,32 @@ public int getAdditions() throws IOException { return additions; } + public int getCommits() throws IOException { + populate(); + return commits; + } + public boolean isMerged() throws IOException { populate(); return merged; } - public Boolean getMergeable() throws IOException { + public boolean canMaintainerModify() throws IOException { populate(); + return maintainer_can_modify; + } + + /** + * Is this PR mergeable? + * + * @return + * null if the state has not been determined yet, for example when a PR is newly created. + * If this method is called on an instance whose mergeable state is not yet known, + * API call is made to retrieve the latest state. + */ + public Boolean getMergeable() throws IOException { + if (mergeable==null) + refresh(); return mergeable; } @@ -186,34 +216,152 @@ public int getChangedFiles() throws IOException { return changed_files; } + /** + * See GitHub blog post + */ + public String getMergeCommitSha() throws IOException { + populate(); + return merge_commit_sha; + } + + public List getRequestedReviewers() throws IOException { + populate(); + return Collections.unmodifiableList(Arrays.asList(requested_reviewers)); + } + /** * 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 + if (mergeable_state!=null) return; // already populated + refresh(); + } + /** + * Repopulates this object. + */ + public void refresh() throws IOException { + if (root.isOffline()) { + return; // cannot populate, will have to live with what we have + } root.retrieve().to(url, this).wrapUp(owner); } + /** + * Retrieves all the files associated to this pull request. + */ + public PagedIterable listFiles() { + return new PagedIterable() { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(String.format("%s/files", getApiRoute()), + GHPullRequestFileDetail[].class, pageSize)) { + @Override + protected void wrapUp(GHPullRequestFileDetail[] page) { + } + }; + } + }; + } + + /** + * Retrieves all the reviews associated to this pull request. + */ + public PagedIterable listReviews() { + return new PagedIterable() { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve() + .asIterator(String.format("%s/reviews", getApiRoute()), + GHPullRequestReview[].class, pageSize)) { + @Override + protected void wrapUp(GHPullRequestReview[] page) { + for (GHPullRequestReview r: page) { + r.wrapUp(GHPullRequest.this); + } + } + }; + } + }; + } + + /** + * Obtains all the review comments associated with this pull request. + */ + public PagedIterable listReviewComments() throws IOException { + return new PagedIterable() { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiRoute() + COMMENTS_ACTION, + GHPullRequestReviewComment[].class, pageSize)) { + protected void wrapUp(GHPullRequestReviewComment[] page) { + for (GHPullRequestReviewComment c : page) + c.wrapUp(GHPullRequest.this); + } + }; + } + }; + } + /** * Retrieves all the commits associated to this pull request. */ public PagedIterable listCommits() { return new PagedIterable() { - public PagedIterator iterator() { + public PagedIterator _iterator(int pageSize) { return new PagedIterator(root.retrieve().asIterator( - String.format("%s/commits", getApiURL()), - GHPullRequestCommitDetail[].class)) { + String.format("%s/commits", getApiRoute()), + GHPullRequestCommitDetail[].class, pageSize)) { @Override protected void wrapUp(GHPullRequestCommitDetail[] page) { + for (GHPullRequestCommitDetail c : page) + c.wrapUp(GHPullRequest.this); } }; } }; } + /** + * @deprecated + * Use {@link #createReview()} + */ + public GHPullRequestReview createReview(String body, @CheckForNull GHPullRequestReviewState event, + GHPullRequestReviewComment... comments) throws IOException { + return createReview(body, event, Arrays.asList(comments)); + } + + /** + * @deprecated + * Use {@link #createReview()} + */ + public GHPullRequestReview createReview(String body, @CheckForNull GHPullRequestReviewState event, + List comments) throws IOException { + GHPullRequestReviewBuilder b = createReview().body(body); + for (GHPullRequestReviewComment c : comments) { + b.comment(c.getBody(), c.getPath(), c.getPosition()); + } + return b.create(); + } + + public GHPullRequestReviewBuilder createReview() { + return new GHPullRequestReviewBuilder(this); + } + + public GHPullRequestReviewComment createReviewComment(String body, String sha, String path, int position) throws IOException { + return new Requester(root).method("POST") + .with("body", body) + .with("commit_id", sha) + .with("path", path) + .with("position", position) + .to(getApiRoute() + COMMENTS_ACTION, GHPullRequestReviewComment.class).wrapUp(this); + } + + public void requestReviewers(List reviewers) throws IOException { + new Requester(root).method("POST") + .withLogins("reviewers", reviewers) + .to(getApiRoute() + REQUEST_REVIEWERS); + } + /** * Merge this pull request. * @@ -223,12 +371,46 @@ protected void wrapUp(GHPullRequestCommitDetail[] page) { * Commit message. If null, the default one will be used. */ public void merge(String msg) throws IOException { - new Requester(root).method("PUT").with("commit_message",msg).to(getApiRoute()+"/merge"); + merge(msg,null); } + /** + * Merge this pull request. + * + * The equivalent of the big green "Merge pull request" button. + * + * @param msg + * Commit message. If null, the default one will be used. + * @param sha + * SHA that pull request head must match to allow merge. + */ + public void merge(String msg, String sha) throws IOException { + merge(msg, sha, null); + } + + /** + * Merge this pull request, using the specified merge method. + * + * The equivalent of the big green "Merge pull request" button. + * + * @param msg + * Commit message. If null, the default one will be used. + * @param method + * SHA that pull request head must match to allow merge. + */ + public void merge(String msg, String sha, MergeMethod method) throws IOException { + new Requester(root).method("PUT") + .with("commit_message", msg) + .with("sha", sha) + .with("merge_method", method) + .to(getApiRoute() + "/merge"); + } + + public enum MergeMethod{ MERGE, SQUASH, REBASE } + private void fetchIssue() throws IOException { if (!fetchedIssueDetails) { - new Requester(root).to(getIssuesApiRoute(), this); + new Requester(root).method("GET").to(getIssuesApiRoute(), this); fetchedIssueDetails = true; } } diff --git a/src/main/java/org/kohsuke/github/GHPullRequestCommitDetail.java b/src/main/java/org/kohsuke/github/GHPullRequestCommitDetail.java index 0ec7fff486..e6c558b9e1 100644 --- a/src/main/java/org/kohsuke/github/GHPullRequestCommitDetail.java +++ b/src/main/java/org/kohsuke/github/GHPullRequestCommitDetail.java @@ -24,6 +24,7 @@ package org.kohsuke.github; import com.infradna.tool.bridge_method_injector.WithBridgeMethods; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.net.URL; @@ -31,99 +32,119 @@ * Commit detail inside a {@link GHPullRequest}. * * @author Luca Milanesio + * @see GHPullRequest#listCommits() */ +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "NP_UNWRITTEN_FIELD", "URF_UNREAD_FIELD"}, justification = "JSON API") public class GHPullRequestCommitDetail { + private GHPullRequest owner; + + /*package*/ void wrapUp(GHPullRequest owner) { + this.owner = owner; + } /** * @deprecated Use {@link GitUser} */ - public static class Authorship extends GitUser {} + public static class Authorship extends GitUser { + } public static class Tree { - String sha; - String url; + String sha; + String url; - public String getSha() { - return sha; - } + public String getSha() { + return sha; + } - public URL getUrl() { - return GitHub.parseURL(url); + public URL getUrl() { + return GitHub.parseURL(url); + } } - } - public static class Commit { - Authorship author; - Authorship committer; - String message; - Tree tree; - String url; - int comment_count; - - @WithBridgeMethods(value=Authorship.class,castRequired=true) - public GitUser getAuthor() { - return author; + public static class Commit { + Authorship author; + Authorship committer; + String message; + Tree tree; + String url; + int comment_count; + + @WithBridgeMethods(value = Authorship.class, castRequired = true) + public GitUser getAuthor() { + return author; + } + + @WithBridgeMethods(value = Authorship.class, castRequired = true) + public GitUser getCommitter() { + return committer; + } + + public String getMessage() { + return message; + } + + public URL getUrl() { + return GitHub.parseURL(url); + } + + public int getComment_count() { + return comment_count; + } + + public Tree getTree() { + return tree; + } } - @WithBridgeMethods(value=Authorship.class,castRequired=true) - public GitUser getCommitter() { - return committer; - } + public static class CommitPointer { + String sha; + String url; + String html_url; - public String getMessage() { - return message; - } + public URL getUrl() { + return GitHub.parseURL(url); + } - public URL getUrl() { - return GitHub.parseURL(url); - } + public URL getHtml_url() { + return GitHub.parseURL(html_url); + } - public int getComment_count() { - return comment_count; + public String getSha() { + return sha; + } } - } - public static class CommitPointer { String sha; + Commit commit; String url; String html_url; + String comments_url; + CommitPointer[] parents; + + public String getSha() { + return sha; + } + + public Commit getCommit() { + return commit; + } + + public URL getApiUrl() { + return GitHub.parseURL(url); + } public URL getUrl() { - return GitHub.parseURL(url); + return GitHub.parseURL(html_url); } - public URL getHtml_url() { - return GitHub.parseURL(html_url); + public URL getCommentsUrl() { + return GitHub.parseURL(comments_url); } - public String getSha() { - return sha; + public CommitPointer[] getParents() { + CommitPointer[] newValue = new CommitPointer[parents.length]; + System.arraycopy(parents, 0, newValue, 0, parents.length); + return newValue; } - } - - String sha; - Commit commit; - String url; - String html_url; - String comments_url; - CommitPointer[] parents; - - public String getSha() { - return sha; - } - public Commit getCommit() { - return commit; - } - public URL getApiUrl() { - return GitHub.parseURL(url); - } - public URL getUrl() { - return GitHub.parseURL(html_url); - } - public URL getCommentsUrl() { - return GitHub.parseURL(comments_url); - } - public CommitPointer[] getParents() { - return parents; - } } diff --git a/src/main/java/org/kohsuke/github/GHPullRequestFileDetail.java b/src/main/java/org/kohsuke/github/GHPullRequestFileDetail.java new file mode 100644 index 0000000000..35bb86c444 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHPullRequestFileDetail.java @@ -0,0 +1,92 @@ +/* + * The MIT License + * + * Copyright (c) 2015, Julien Henry + * + * 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 java.net.URL; + +/** + * File detail inside a {@link GHPullRequest}. + * + * @author Julien Henry + * @see GHPullRequest#listFiles() + */ +public class GHPullRequestFileDetail { + + String sha; + String filename; + String status; + int additions; + int deletions; + int changes; + String blob_url; + String raw_url; + String contents_url; + String patch; + String previous_filename; + + public String getSha() { + return sha; + } + + public String getFilename() { + return filename; + } + + public String getStatus() { + return status; + } + + public int getAdditions() { + return additions; + } + + public int getDeletions() { + return deletions; + } + + public int getChanges() { + return changes; + } + + public URL getBlobUrl() { + return GitHub.parseURL(blob_url); + } + + public URL getRawUrl() { + return GitHub.parseURL(raw_url); + } + + public URL getContentsUrl() { + return GitHub.parseURL(contents_url); + } + + public String getPatch() { + return patch; + } + + public String getPreviousFilename() + { + return previous_filename; + } +} diff --git a/src/main/java/org/kohsuke/github/GHPullRequestQueryBuilder.java b/src/main/java/org/kohsuke/github/GHPullRequestQueryBuilder.java new file mode 100644 index 0000000000..141cf4d681 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHPullRequestQueryBuilder.java @@ -0,0 +1,58 @@ +package org.kohsuke.github; + +/** + * Lists up pull requests with some filtering and sorting. + * + * @author Kohsuke Kawaguchi + * @see GHRepository#queryPullRequests() + */ +public class GHPullRequestQueryBuilder extends GHQueryBuilder { + private final GHRepository repo; + + /*package*/ GHPullRequestQueryBuilder(GHRepository repo) { + super(repo.root); + this.repo = repo; + } + + public GHPullRequestQueryBuilder state(GHIssueState state) { + req.with("state",state); + return this; + } + + public GHPullRequestQueryBuilder head(String head) { + req.with("head",head); + return this; + } + + public GHPullRequestQueryBuilder base(String base) { + req.with("base",base); + return this; + } + + public GHPullRequestQueryBuilder sort(Sort sort) { + req.with("sort",sort); + return this; + } + + public enum Sort { CREATED, UPDATED, POPULARITY, LONG_RUNNING } + + public GHPullRequestQueryBuilder direction(GHDirection d) { + req.with("direction",d); + return this; + } + + @Override + public PagedIterable list() { + return new PagedIterable() { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(req.asIterator(repo.getApiTailUrl("pulls"), GHPullRequest[].class, pageSize)) { + @Override + protected void wrapUp(GHPullRequest[] page) { + for (GHPullRequest pr : page) + pr.wrapUp(repo); + } + }; + } + }; + } +} diff --git a/src/main/java/org/kohsuke/github/GHPullRequestReview.java b/src/main/java/org/kohsuke/github/GHPullRequestReview.java new file mode 100644 index 0000000000..45b6c044d9 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHPullRequestReview.java @@ -0,0 +1,148 @@ +/* + * The MIT License + * + * Copyright (c) 2017, CloudBees, Inc. + * + * 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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import javax.annotation.CheckForNull; +import java.io.IOException; +import java.net.URL; + +/** + * Review to a pull request. + * + * @see GHPullRequest#listReviews() + * @see GHPullRequestReviewBuilder + */ +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_FIELD"}, justification = "JSON API") +public class GHPullRequestReview extends GHObject { + GHPullRequest owner; + + private String body; + private GHUser user; + private String commit_id; + private GHPullRequestReviewState state; + + /*package*/ GHPullRequestReview wrapUp(GHPullRequest owner) { + this.owner = owner; + return this; + } + + /** + * Gets the pull request to which this review is associated. + */ + public GHPullRequest getParent() { + return owner; + } + + /** + * The comment itself. + */ + public String getBody() { + return body; + } + + /** + * Gets the user who posted this review. + */ + public GHUser getUser() throws IOException { + return owner.root.getUser(user.getLogin()); + } + + public String getCommitId() { + return commit_id; + } + + @CheckForNull + public GHPullRequestReviewState getState() { + return state; + } + + @Override + public URL getHtmlUrl() { + return null; + } + + protected String getApiRoute() { + return owner.getApiRoute()+"/reviews/"+id; + } + + /** + * @deprecated + * Former preview method that changed when it got public. Left here for backward compatibility. + * Use {@link #submit(String, GHPullRequestReviewEvent)} + */ + public void submit(String body, GHPullRequestReviewState state) throws IOException { + submit(body,state.toEvent()); + } + + /** + * Updates the comment. + */ + public void submit(String body, GHPullRequestReviewEvent event) throws IOException { + new Requester(owner.root).method("POST") + .with("body", body) + .with("event", event.action()) + .to(getApiRoute()+"/events",this); + this.body = body; + this.state = event.toState(); + } + + /** + * Deletes this review. + */ + public void delete() throws IOException { + new Requester(owner.root).method("DELETE") + .to(getApiRoute()); + } + + /** + * Dismisses this review. + */ + public void dismiss(String message) throws IOException { + new Requester(owner.root).method("PUT") + .with("message", message) + .to(getApiRoute()+"/dismissals"); + state = GHPullRequestReviewState.DISMISSED; + } + + /** + * Obtains all the review comments associated with this pull request review. + */ + public PagedIterable listReviewComments() throws IOException { + return new PagedIterable() { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator( + owner.root.retrieve() + .asIterator(getApiRoute() + "/comments", + GHPullRequestReviewComment[].class, pageSize)) { + protected void wrapUp(GHPullRequestReviewComment[] page) { + for (GHPullRequestReviewComment c : page) + c.wrapUp(owner); + } + }; + } + }; + } +} diff --git a/src/main/java/org/kohsuke/github/GHPullRequestReviewBuilder.java b/src/main/java/org/kohsuke/github/GHPullRequestReviewBuilder.java new file mode 100644 index 0000000000..318daf721f --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHPullRequestReviewBuilder.java @@ -0,0 +1,91 @@ +package org.kohsuke.github; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Builds up a creation of new {@link GHPullRequestReview}. + * + * @author Kohsuke Kawaguchi + * @see GHPullRequest#createReview() + */ +public class GHPullRequestReviewBuilder { + private final GHPullRequest pr; + private final Requester builder; + private final List comments = new ArrayList(); + + /*package*/ GHPullRequestReviewBuilder(GHPullRequest pr) { + this.pr = pr; + this.builder = new Requester(pr.root); + } + + // public GHPullRequestReview createReview(@Nullable String commitId, String body, GHPullRequestReviewEvent event, + // List comments) throws IOException + + /** + * The SHA of the commit that needs a review. Not using the latest commit SHA may render your review comment outdated if a subsequent commit modifies the line you specify as the position. Defaults to the most recent commit in the pull request when you do not specify a value. + */ + public GHPullRequestReviewBuilder commitId(String commitId) { + builder.with("commit_id",commitId); + return this; + } + + /** + * Required when using REQUEST_CHANGES or COMMENT for the event parameter. The body text of the pull request review. + */ + public GHPullRequestReviewBuilder body(String body) { + builder.with("body",body); + return this; + } + + /** + * The review action you want to perform. The review actions include: APPROVE, REQUEST_CHANGES, or COMMENT. + * By leaving this blank, you set the review action state to PENDING, + * which means you will need to {@linkplain GHPullRequestReview#submit(String, GHPullRequestReviewEvent) submit the pull request review} when you are ready. + */ + public GHPullRequestReviewBuilder event(GHPullRequestReviewEvent event) { + builder.with("event",event.action()); + return this; + } + + /** + * @param body The relative path to the file that necessitates a review comment. + * @param path The position in the diff where you want to add a review comment. Note this value is not the same as the line number in the file. For help finding the position value, read the note below. + * @param position Text of the review comment. + */ + public GHPullRequestReviewBuilder comment(String body, String path, int position) { + comments.add(new DraftReviewComment(body,path,position)); + return this; + } + + public GHPullRequestReview create() throws IOException { + return builder.method("POST")._with("comments",comments) + .to(pr.getApiRoute() + "/reviews", GHPullRequestReview.class) + .wrapUp(pr); + } + + private static class DraftReviewComment { + private String body; + private String path; + private int position; + + DraftReviewComment(String body, String path, int position) { + this.body = body; + this.path = path; + this.position = position; + } + + public String getBody() { + return body; + } + + public String getPath() { + return path; + } + + public int getPosition() { + return position; + } + } +} diff --git a/src/main/java/org/kohsuke/github/GHPullRequestReviewComment.java b/src/main/java/org/kohsuke/github/GHPullRequestReviewComment.java new file mode 100644 index 0000000000..5cda4abb0a --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHPullRequestReviewComment.java @@ -0,0 +1,163 @@ +/* + * 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 java.io.IOException; +import java.net.URL; +import javax.annotation.CheckForNull; + +import static org.kohsuke.github.Previews.*; + +/** + * Review comment to the pull request + * + * @author Julien Henry + * @see GHPullRequest#listReviewComments() + * @see GHPullRequest#createReviewComment(String, String, String, int) + */ +public class GHPullRequestReviewComment extends GHObject implements Reactable { + GHPullRequest owner; + + private String body; + private GHUser user; + private String path; + private int position = -1; + private int original_position = -1; + private long in_reply_to_id = -1L; + + + /** + * @deprecated + * You should be using {@link GHPullRequestReviewBuilder#comment(String, String, int)} + */ + public static GHPullRequestReviewComment draft(String body, String path, int position) { + GHPullRequestReviewComment result = new GHPullRequestReviewComment(); + result.body = body; + result.path = path; + result.position = position; + return result; + } + + /*package*/ GHPullRequestReviewComment wrapUp(GHPullRequest owner) { + this.owner = owner; + return this; + } + + /** + * Gets the pull request to which this review comment is associated. + */ + public GHPullRequest getParent() { + return owner; + } + + /** + * The comment itself. + */ + public String getBody() { + return body; + } + + /** + * Gets the user who posted this comment. + */ + public GHUser getUser() throws IOException { + return owner.root.getUser(user.getLogin()); + } + + public String getPath() { + return path; + } + + @CheckForNull + public int getPosition() { + return position; + } + + public int getOriginalPosition() { + return original_position; + } + + @CheckForNull + public long getInReplyToId() { + return in_reply_to_id; + } + + @Override + public URL getHtmlUrl() { + return null; + } + + protected String getApiRoute() { + return "/repos/"+owner.getRepository().getFullName()+"/pulls/comments/"+id; + } + + /** + * Updates the comment. + */ + public void update(String body) throws IOException { + new Requester(owner.root).method("PATCH").with("body", body).to(getApiRoute(),this); + this.body = body; + } + + /** + * Deletes this review comment. + */ + public void delete() throws IOException { + new Requester(owner.root).method("DELETE").to(getApiRoute()); + } + + /** + * Create a new comment that replies to this comment. + */ + public GHPullRequestReviewComment reply(String body) throws IOException { + return new Requester(owner.root).method("POST") + .with("body", body) + .with("in_reply_to", getId()) + .to(getApiRoute() + "/comments", GHPullRequestReviewComment.class) + .wrapUp(owner); + } + + @Preview @Deprecated + public GHReaction createReaction(ReactionContent content) throws IOException { + return new Requester(owner.root) + .withPreview(SQUIRREL_GIRL) + .with("content", content.getContent()) + .to(getApiRoute()+"/reactions", GHReaction.class).wrap(owner.root); + } + + @Preview @Deprecated + public PagedIterable listReactions() { + return new PagedIterable() { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(owner.root.retrieve().withPreview(SQUIRREL_GIRL).asIterator(getApiRoute() + "/reactions", GHReaction[].class, pageSize)) { + @Override + protected void wrapUp(GHReaction[] page) { + for (GHReaction c : page) + c.wrap(owner.root); + } + }; + } + }; + } +} diff --git a/src/main/java/org/kohsuke/github/GHPullRequestReviewEvent.java b/src/main/java/org/kohsuke/github/GHPullRequestReviewEvent.java new file mode 100644 index 0000000000..e6537e0f09 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHPullRequestReviewEvent.java @@ -0,0 +1,51 @@ +/* + * The MIT License + * + * Copyright (c) 2011, Eric Maupin + * + * 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; + +/** + * Action to perform on {@link GHPullRequestReview}. + */ +public enum GHPullRequestReviewEvent { + PENDING, + APPROVE, + REQUEST_CHANGES, + COMMENT; + + /*package*/ String action() { + return this==PENDING ? null : name(); + } + + /** + * When a {@link GHPullRequestReview} is submitted with this event, it should transition to this state. + */ + /*package*/ GHPullRequestReviewState toState() { + switch (this) { + case PENDING: return GHPullRequestReviewState.PENDING; + case APPROVE: return GHPullRequestReviewState.APPROVED; + case REQUEST_CHANGES: return GHPullRequestReviewState.CHANGES_REQUESTED; + case COMMENT: return GHPullRequestReviewState.COMMENTED; + } + throw new IllegalStateException(); + } +} diff --git a/src/main/java/org/kohsuke/github/GHPullRequestReviewState.java b/src/main/java/org/kohsuke/github/GHPullRequestReviewState.java new file mode 100644 index 0000000000..a64a105994 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHPullRequestReviewState.java @@ -0,0 +1,39 @@ +package org.kohsuke.github; + +/** + * Current state of {@link GHPullRequestReview} + */ +public enum GHPullRequestReviewState { + PENDING, + APPROVED, + CHANGES_REQUESTED, + /** + * @deprecated + * This was the thing when this API was in preview, but it changed when it became public. + * Use {@link #CHANGES_REQUESTED}. Left here for compatibility. + */ + REQUEST_CHANGES, + COMMENTED, + DISMISSED; + + /** + * @deprecated + * This was an internal method accidentally exposed. + * Left here for compatibility. + */ + public String action() { + GHPullRequestReviewEvent e = toEvent(); + return e==null ? null : e.action(); + } + + /*package*/ GHPullRequestReviewEvent toEvent() { + switch (this) { + case PENDING: return GHPullRequestReviewEvent.PENDING; + case APPROVED: return GHPullRequestReviewEvent.APPROVE; + case CHANGES_REQUESTED: return GHPullRequestReviewEvent.REQUEST_CHANGES; + case REQUEST_CHANGES: return GHPullRequestReviewEvent.REQUEST_CHANGES; + case COMMENTED: return GHPullRequestReviewEvent.COMMENT; + } + return null; + } +} diff --git a/src/main/java/org/kohsuke/github/GHQueryBuilder.java b/src/main/java/org/kohsuke/github/GHQueryBuilder.java new file mode 100644 index 0000000000..bb85fbbe95 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHQueryBuilder.java @@ -0,0 +1,21 @@ +package org.kohsuke.github; + +/** + * Used to specify filters, sort order, etc for listing items in a collection. + * + * @author Kohsuke Kawaguchi + */ +public abstract class GHQueryBuilder { + protected final GitHub root; + protected final Requester req; + + /*package*/ GHQueryBuilder(GitHub root) { + this.root = root; + this.req = root.retrieve(); + } + + /** + * Start listing items by using the settings built up on this object. + */ + public abstract PagedIterable list(); +} diff --git a/src/main/java/org/kohsuke/github/GHRateLimit.java b/src/main/java/org/kohsuke/github/GHRateLimit.java index 301bd7993d..9500b0a736 100644 --- a/src/main/java/org/kohsuke/github/GHRateLimit.java +++ b/src/main/java/org/kohsuke/github/GHRateLimit.java @@ -1,5 +1,7 @@ package org.kohsuke.github; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import java.util.Date; /** @@ -24,6 +26,8 @@ public class GHRateLimit { /** * Non-epoch date */ + @SuppressFBWarnings(value = "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", + justification = "The value comes from JSON deserialization") public Date getResetDate() { return new Date(reset.getTime() * 1000); } diff --git a/src/main/java/org/kohsuke/github/GHReaction.java b/src/main/java/org/kohsuke/github/GHReaction.java new file mode 100644 index 0000000000..6a00eb305c --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHReaction.java @@ -0,0 +1,55 @@ +package org.kohsuke.github; + +import java.io.IOException; +import java.net.URL; + +import static org.kohsuke.github.Previews.*; + +/** + * Reaction to issue, comment, PR, and so on. + * + * @author Kohsuke Kawaguchi + * @see Reactable + */ +@Preview @Deprecated +public class GHReaction extends GHObject { + private GitHub root; + + private GHUser user; + private ReactionContent content; + + /*package*/ GHReaction wrap(GitHub root) { + this.root = root; + user.wrapUp(root); + return this; + } + + /** + * The kind of reaction left. + */ + public ReactionContent getContent() { + return content; + } + + /** + * User who left the reaction. + */ + public GHUser getUser() { + return user; + } + + /** + * Reaction has no HTML URL. Don't call this method. + */ + @Deprecated + public URL getHtmlUrl() { + return null; + } + + /** + * Removes this reaction. + */ + public void delete() throws IOException { + new Requester(root).method("DELETE").withPreview(SQUIRREL_GIRL).to("/reactions/"+id); + } +} diff --git a/src/main/java/org/kohsuke/github/GHRef.java b/src/main/java/org/kohsuke/github/GHRef.java index f052cdf903..c8462d3fe6 100644 --- a/src/main/java/org/kohsuke/github/GHRef.java +++ b/src/main/java/org/kohsuke/github/GHRef.java @@ -1,5 +1,7 @@ package org.kohsuke.github; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import java.io.IOException; import java.net.URL; @@ -77,7 +79,8 @@ public void delete() throws IOException { return in; } - + @SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "NP_UNWRITTEN_FIELD"}, justification = "JSON API") public static class GHObject { private String type, sha, url; diff --git a/src/main/java/org/kohsuke/github/GHRelease.java b/src/main/java/org/kohsuke/github/GHRelease.java index 86736e909d..0df6b415b0 100644 --- a/src/main/java/org/kohsuke/github/GHRelease.java +++ b/src/main/java/org/kohsuke/github/GHRelease.java @@ -3,12 +3,13 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.util.Arrays; import java.util.Date; import java.util.List; -import static java.lang.String.format; +import static java.lang.String.*; /** * Release in a github repository. @@ -45,6 +46,14 @@ public boolean isDraft() { return draft; } + /** + * @deprecated + * Use {@link #update()} + */ + public GHRelease setDraft(boolean draft) throws IOException { + return update().draft(draft).update(); + } + public URL getHtmlUrl() { return GitHub.parseURL(html_url); } @@ -70,7 +79,7 @@ public boolean isPrerelease() { } public Date getPublished_at() { - return published_at; + return new Date(published_at.getTime()); } public GitHub getRoot() { @@ -115,16 +124,23 @@ static GHRelease[] wrap(GHRelease[] releases, GHRepository owner) { * Java 7 or greater. Options for fixing this for earlier JVMs can be found here * http://stackoverflow.com/questions/12361090/server-name-indication-sni-on-java but involve more complicated * handling of the HTTP requests to github's API. - * - * @throws IOException - */ + */ public GHAsset uploadAsset(File file, String contentType) throws IOException { + FileInputStream s = new FileInputStream(file); + try { + return uploadAsset(file.getName(), s, contentType); + } finally { + s.close(); + } + } + + public GHAsset uploadAsset(String filename, InputStream stream, String contentType) throws IOException { Requester builder = new Requester(owner.root); String url = format("https://uploads.github.com%s/releases/%d/assets?name=%s", - owner.getApiTailUrl(""), getId(), file.getName()); + owner.getApiTailUrl(""), getId(), filename); return builder.contentType(contentType) - .with(new FileInputStream(file)) + .with(stream) .to(url, GHAsset.class).wrap(this); } @@ -144,6 +160,13 @@ public void delete() throws IOException { new Requester(root).method("DELETE").to(owner.getApiTailUrl("releases/"+id)); } + /** + * Updates this release via a builder. + */ + public GHReleaseUpdater update() { + return new GHReleaseUpdater(this); + } + private String getApiTailUrl(String end) { return owner.getApiTailUrl(format("releases/%s/%s",id,end)); } diff --git a/src/main/java/org/kohsuke/github/GHReleaseBuilder.java b/src/main/java/org/kohsuke/github/GHReleaseBuilder.java index 04e368f2b2..e427bdf8a5 100644 --- a/src/main/java/org/kohsuke/github/GHReleaseBuilder.java +++ b/src/main/java/org/kohsuke/github/GHReleaseBuilder.java @@ -21,9 +21,7 @@ public GHReleaseBuilder(GHRepository ghRepository, String tag) { * @param body The release notes body. */ public GHReleaseBuilder body(String body) { - if (body != null) { - builder.with("body", body); - } + builder.with("body", body); return this; } @@ -33,12 +31,9 @@ public GHReleaseBuilder body(String body) { * * @param commitish Defaults to the repository’s default branch (usually "master"). Unused if the Git tag * already exists. - * @return */ public GHReleaseBuilder commitish(String commitish) { - if (commitish != null) { - builder.with("target_commitish", commitish); - } + builder.with("target_commitish", commitish); return this; } @@ -57,9 +52,7 @@ public GHReleaseBuilder draft(boolean draft) { * @param name the name of the release */ public GHReleaseBuilder name(String name) { - if (name != null) { - builder.with("name", name); - } + builder.with("name", name); return this; } diff --git a/src/main/java/org/kohsuke/github/GHReleaseUpdater.java b/src/main/java/org/kohsuke/github/GHReleaseUpdater.java new file mode 100644 index 0000000000..a34a5b0bc2 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHReleaseUpdater.java @@ -0,0 +1,81 @@ +package org.kohsuke.github; + +import java.io.IOException; + +/** + * Modifies {@link GHRelease}. + * + * @author Kohsuke Kawaguchi + * @see GHRelease#update() + */ +public class GHReleaseUpdater { + private final GHRelease base; + private final Requester builder; + + GHReleaseUpdater(GHRelease base) { + this.base = base; + this.builder = new Requester(base.root); + } + + public GHReleaseUpdater tag(String tag) { + builder.with("tag_name",tag); + return this; + } + + /** + * @param body The release notes body. + */ + public GHReleaseUpdater body(String body) { + builder.with("body", body); + return this; + } + + /** + * Specifies the commitish value that determines where the Git tag is created from. Can be any branch or + * commit SHA. + * + * @param commitish Defaults to the repository’s default branch (usually "master"). Unused if the Git tag + * already exists. + */ + public GHReleaseUpdater commitish(String commitish) { + builder.with("target_commitish", commitish); + return this; + } + + /** + * Optional. + * + * @param draft {@code true} to create a draft (unpublished) release, {@code false} to create a published one. + * Default is {@code false}. + */ + public GHReleaseUpdater draft(boolean draft) { + builder.with("draft", draft); + return this; + } + + /** + * @param name the name of the release + */ + public GHReleaseUpdater name(String name) { + builder.with("name", name); + return this; + } + + /** + * Optional + * + * @param prerelease {@code true} to identify the release as a prerelease. {@code false} to identify the release + * as a full release. Default is {@code false}. + */ + public GHReleaseUpdater prerelease(boolean prerelease) { + builder.with("prerelease", prerelease); + return this; + } + + public GHRelease update() throws IOException { + return builder + .method("PATCH") + .to(base.owner.getApiTailUrl("releases/"+base.id), GHRelease.class).wrap(base.owner); + } + +} 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..a937836b27 100644 --- a/src/main/java/org/kohsuke/github/GHRepository.java +++ b/src/main/java/org/kohsuke/github/GHRepository.java @@ -25,70 +25,89 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.infradna.tool.bridge_method_injector.WithBridgeMethods; -import org.apache.commons.lang.StringUtils; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.apache.commons.lang3.StringUtils; -import javax.xml.bind.DatatypeConverter; 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.URL; -import java.util.*; - -import static java.util.Arrays.asList; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.WeakHashMap; + +import static java.util.Arrays.*; +import static org.kohsuke.github.Previews.*; /** * A repository on GitHub. - * + * * @author Kohsuke Kawaguchi */ @SuppressWarnings({"UnusedDeclaration"}) +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "NP_UNWRITTEN_FIELD"}, justification = "JSON API") public class GHRepository extends GHObject { /*package almost final*/ GitHub root; private String description, homepage, name, full_name; private String html_url; // this is the UI - private String git_url, ssh_url, clone_url, svn_url; + /* + * The license information makes use of the preview API. + * + * See: https://developer.github.com/v3/licenses/ + */ + private GHLicense license; + + private String git_url, ssh_url, clone_url, svn_url, mirror_url; private GHUser owner; // not fully populated. beware. - private boolean has_issues, has_wiki, fork, has_downloads; + private boolean has_issues, has_wiki, fork, has_downloads, has_pages, archived; @JsonProperty("private") private boolean _private; - private int watchers,forks,open_issues,size,network_count,subscribers_count; + private int forks_count, stargazers_count, watchers_count, size, open_issues_count, subscribers_count; private String pushed_at; - private Map milestones = new HashMap(); - + private Map milestones = new WeakHashMap(); + private String default_branch,language; - private Map commits = new HashMap(); + private Map commits = new WeakHashMap(); + @SkipFromToString private GHRepoPermission permissions; private GHRepository source, parent; - + public GHDeploymentBuilder createDeployment(String ref) { return new GHDeploymentBuilder(this,ref); } - public PagedIterable getDeploymentStatuses(final int id) { - return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("deployments")+"/"+id+"/statuses", GHDeploymentStatus[].class)) { - @Override - protected void wrapUp(GHDeploymentStatus[] page) { - for (GHDeploymentStatus c : page) - c.wrap(GHRepository.this); - } - }; - } - }; + /** + * @deprecated + * Use {@code getDeployment(id).listStatuses()} + */ + public PagedIterable getDeploymentStatuses(final int id) throws IOException { + return getDeployment(id).listStatuses(); } public PagedIterable listDeployments(String sha,String ref,String task,String environment){ List params = Arrays.asList(getParam("sha", sha), getParam("ref", ref), getParam("task", task), getParam("environment", environment)); final String deploymentsUrl = getApiTailUrl("deployments") + "?"+ join(params,"&"); return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(deploymentsUrl, GHDeployment[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(deploymentsUrl, GHDeployment[].class, pageSize)) { @Override protected void wrapUp(GHDeployment[] page) { for (GHDeployment c : page) @@ -97,7 +116,13 @@ protected void wrapUp(GHDeployment[] page) { }; } }; + } + /** + * Obtains a single {@link GHDeployment} by its ID. + */ + public GHDeployment getDeployment(long id) throws IOException { + return root.retrieve().to("deployments/" + id, GHDeployment.class).wrap(this); } private String join(List params, String joinStr) { @@ -114,8 +139,12 @@ private String getParam(String name, String value) { return StringUtils.trimToNull(value)== null? null: name+"="+value; } - public GHDeploymentStatusBuilder createDeployStatus(int deploymentId, GHDeploymentState ghDeploymentState) { - return new GHDeploymentStatusBuilder(this,deploymentId,ghDeploymentState); + /** + * @deprecated + * Use {@code getDeployment(deploymentId).createStatus(ghDeploymentState)} + */ + public GHDeploymentStatusBuilder createDeployStatus(int deploymentId, GHDeploymentState ghDeploymentState) throws IOException { + return getDeployment(deploymentId).createStatus(ghDeploymentState); } private static class GHRepoPermission { @@ -143,6 +172,14 @@ public String getGitTransportUrl() { * Gets the HTTPS URL to this repository, such as "https://github.com/kohsuke/jenkins.git" * This URL is read-only. */ + public String getHttpTransportUrl() { + return clone_url; + } + + /** + * @deprecated + * Typo of {@link #getHttpTransportUrl()} + */ public String gitHttpTransportUrl() { return clone_url; } @@ -154,6 +191,14 @@ public String getSvnUrl() { return svn_url; } + /** + * Gets the Mirror URL to access this repository: https://github.com/apache/tomee + * mirrored from git://git.apache.org/tomee.git + */ + public String getMirrorUrl() { + return mirror_url; + } + /** * Gets the SSH URL to access this repository, such as git@github.com:rails/rails.git */ @@ -164,7 +209,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 */ @@ -199,7 +244,7 @@ public String getLanguage() { } public GHUser getOwner() throws IOException { - return root.getUser(owner.login); // because 'owner' isn't fully populated + return root.isOffline() ? owner : root.getUser(getOwnerName()); // because 'owner' isn't fully populated } public GHIssue getIssue(int id) throws IOException { @@ -216,10 +261,10 @@ public List getIssues(GHIssueState state) throws IOException { public List getIssues(GHIssueState state, GHMilestone milestone) throws IOException { return Arrays.asList(GHIssue.wrap(root.retrieve() - .to(getApiTailUrl(String.format("issues?state=%s&milestone=%s", - state.toString().toLowerCase(), milestone == null ? "none" : "" + milestone.getNumber())), - GHIssue[].class - ), this)); + .with("state", state) + .with("milestone", milestone == null ? "none" : "" + milestone.getNumber()) + .to(getApiTailUrl("issues"), + GHIssue[].class), this)); } /** @@ -227,8 +272,8 @@ public List getIssues(GHIssueState state, GHMilestone milestone) throws */ public PagedIterable listIssues(final GHIssueState state) { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("issues?state="+state.toString().toLowerCase(Locale.ENGLISH)), GHIssue[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().with("state",state).asIterator(getApiTailUrl("issues"), GHIssue[].class, pageSize)) { @Override protected void wrapUp(GHIssue[] page) { for (GHIssue c : page) @@ -265,10 +310,34 @@ public List getReleases() throws IOException { return listReleases().asList(); } + public GHRelease getRelease(long id) throws IOException { + try { + return root.retrieve().to(getApiTailUrl("releases/" + id), GHRelease.class).wrap(this); + } catch (FileNotFoundException e) { + return null; // no release for this id + } + } + + public GHRelease getReleaseByTagName(String tag) throws IOException { + try { + return root.retrieve().to(getApiTailUrl("releases/tags/" + tag), GHRelease.class).wrap(this); + } catch (FileNotFoundException e) { + return null; // no release for this tag + } + } + + public GHRelease getLatestRelease() throws IOException { + try { + return root.retrieve().to(getApiTailUrl("releases/latest"), GHRelease.class).wrap(this); + } catch (FileNotFoundException e) { + return null; // no latest release + } + } + public PagedIterable listReleases() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("releases"), GHRelease[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("releases"), GHRelease[].class, pageSize)) { @Override protected void wrapUp(GHRelease[] page) { for (GHRelease c : page) @@ -281,8 +350,8 @@ protected void wrapUp(GHRelease[] page) { public PagedIterable listTags() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("tags"), GHTag[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("tags"), GHTag[].class, pageSize)) { @Override protected void wrapUp(GHTag[] page) { for (GHTag c : page) @@ -306,7 +375,11 @@ public Map listLanguages() throws IOException { } public String getOwnerName() { - return owner.login; + // consistency of the GitHub API is super... some serialized forms of GHRepository populate + // a full GHUser while others populate only the owner and email. This later form is super helpful + // in putting the login in owner.name not owner.login... thankfully we can easily identify this + // second set because owner.login will be null + return owner.login != null ? owner.login : owner.name; } public boolean hasIssues() { @@ -321,8 +394,20 @@ public boolean isFork() { return fork; } + public boolean isArchived() { + return archived; + } + + /** + * 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; + return forks_count; + } + + public int getStargazersCount() { + return stargazers_count; } public boolean isPrivate() { @@ -333,16 +418,25 @@ public boolean hasDownloads() { return has_downloads; } + public boolean hasPages() { + return has_pages; + } + public int getWatchers() { - return watchers; + return watchers_count; } public int getOpenIssueCount() { - return open_issues; + return open_issues_count; } + /** + * @deprecated + * This no longer exists in the official API documentation. + * Use {@link #getForks()} + */ public int getNetworkCount() { - return network_count; + return forks_count; } public int getSubscribersCount() { @@ -364,6 +458,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; } @@ -372,6 +474,7 @@ public int getSize() { return size; } + /** * Gets the collaborators on this repository. * This set always appear to include the owner. @@ -385,25 +488,24 @@ public GHPersonSet getCollaborators() throws IOException { * Lists up the collaborators on this repository. * * @return Users - * @throws IOException */ public PagedIterable listCollaborators() throws IOException { - return new PagedIterable() { - public PagedIterator iterator() { - - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("collaborators"), GHUser[].class)) { - - @Override - protected void wrapUp(GHUser[] users) { - for (GHUser user : users) { - user.wrapUp(root); - } - } - }; + return listUsers("collaborators"); + } - } - }; + /** + * Lists all the available assignees + * to which issues may be assigned. + */ + public PagedIterable listAssignees() throws IOException { + return listUsers("assignees"); + } + /** + * Checks if the given user is an assignee for this repository. + */ + public boolean hasAssignee(GHUser u) throws IOException { + return root.retrieve().asHttpStatusCode(getApiTailUrl("assignees/" + u.getLogin()))/100==2; } /** @@ -417,11 +519,32 @@ public Set getCollaboratorNames() throws IOException { return r; } + /** + * Obtain permission for a given user in this repository. + * @param user a {@link GHUser#getLogin} + * @throws FileNotFoundException under some conditions (e.g., private repo you can see but are not an admin of); treat as unknown + * @throws HttpException with a 403 under other conditions (e.g., public repo you have no special rights to); treat as unknown + */ + public GHPermissionType getPermission(String user) throws IOException { + GHPermission perm = root.retrieve().to(getApiTailUrl("collaborators/" + user + "/permission"), GHPermission.class); + perm.wrapUp(root); + return perm.getPermissionType(); + } + + /** + * Obtain permission for a given user in this repository. + * @throws FileNotFoundException under some conditions (e.g., private repo you can see but are not an admin of); treat as unknown + * @throws HttpException with a 403 under other conditions (e.g., public repo you have no special rights to); treat as unknown + */ + public GHPermissionType getPermission(GHUser u) throws IOException { + return getPermission(u.getLogin()); + } + /** * If this repository belongs to an organization, return a set of teams. */ public Set getTeams() throws IOException { - return Collections.unmodifiableSet(new HashSet(Arrays.asList(GHTeam.wrapUp(root.retrieve().to(getApiTailUrl("teams"), GHTeam[].class), root.getOrganization(owner.login))))); + return Collections.unmodifiableSet(new HashSet(Arrays.asList(GHTeam.wrapUp(root.retrieve().to(getApiTailUrl("teams"), GHTeam[].class), root.getOrganization(getOwnerName()))))); } public void addCollaborators(GHUser... users) throws IOException { @@ -441,7 +564,6 @@ public void removeCollaborators(Collection users) throws IOException { } private void modifyCollaborators(Collection users, String method) throws IOException { - verifyMine(); for (GHUser user : users) { new Requester(root).method(method).to(getApiTailUrl("collaborators/" + user.getLogin())); } @@ -450,7 +572,7 @@ private void modifyCollaborators(Collection users, String method) throws public void setEmailServiceHook(String address) throws IOException { Map config = new HashMap(); config.put("address", address); - new Requester(root).method("POST").with("name", "email").with("config", config).with("active", "true") + new Requester(root).method("POST").with("name", "email").with("config", config).with("active", true) .to(getApiTailUrl("hooks")); } @@ -494,6 +616,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. */ @@ -501,10 +627,43 @@ public void delete() throws IOException { try { new Requester(root).method("DELETE").to(getApiTailUrl("")); } catch (FileNotFoundException x) { - throw (FileNotFoundException) new FileNotFoundException("Failed to delete " + owner.login + "/" + name + "; might not exist, or you might need the delete_repo scope in your token: http://stackoverflow.com/a/19327004/12916").initCause(x); + throw (FileNotFoundException) new FileNotFoundException("Failed to delete " + getOwnerName() + "/" + name + "; might not exist, or you might need the delete_repo scope in your token: http://stackoverflow.com/a/19327004/12916").initCause(x); } } + /** + * Sort orders for listing forks + */ + public 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(int pageSize) { + return new PagedIterator(root.retrieve().with("sort",sort).asIterator(getApiTailUrl("forks"), GHRepository[].class, pageSize)) { + @Override + protected void wrapUp(GHRepository[] page) { + for (GHRepository c : page) { + c.wrap(root); + } + } + }; + } + }; + } + /** * Forks this repository as your repository. * @@ -512,7 +671,19 @@ public void delete() throws IOException { * Newly forked repository that belong to you. */ public GHRepository fork() throws IOException { - return new Requester(root).method("POST").to(getApiTailUrl("forks"), GHRepository.class).wrap(root); + new Requester(root).method("POST").to(getApiTailUrl("forks"), null); + + // this API is asynchronous. we need to wait for a bit + for (int i=0; i<10; i++) { + GHRepository r = root.getMyself().getRepository(name); + if (r!=null) return r; + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + throw (IOException)new InterruptedIOException().initCause(e); + } + } + throw new IOException(this+" was forked but can't find the new repository"); } /** @@ -550,24 +721,24 @@ public GHPullRequest getPullRequest(int i) throws IOException { * @see #listPullRequests(GHIssueState) */ public List getPullRequests(GHIssueState state) throws IOException { - return listPullRequests(state).asList(); + return queryPullRequests().state(state).list().asList(); } /** * Retrieves all the pull requests of a particular state. + * + * @deprecated + * Use {@link #queryPullRequests()} */ - public PagedIterable listPullRequests(final GHIssueState state) { - return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("pulls?state="+state.name().toLowerCase(Locale.ENGLISH)), GHPullRequest[].class)) { - @Override - protected void wrapUp(GHPullRequest[] page) { - for (GHPullRequest pr : page) - pr.wrapUp(GHRepository.this); - } - }; - } - }; + public PagedIterable listPullRequests(GHIssueState state) { + return queryPullRequests().state(state).list(); + } + + /** + * Retrieves pull requests. + */ + public GHPullRequestQueryBuilder queryPullRequests() { + return new GHPullRequestQueryBuilder(this); } /** @@ -597,15 +768,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); } /** @@ -626,7 +793,23 @@ public GHCompare getCompare(GHCommit id1, GHCommit id2) throws IOException { } public GHCompare getCompare(GHBranch id1, GHBranch id2) throws IOException { - return getCompare(id1.getName(),id2.getName()); + + GHRepository owner1 = id1.getOwner(); + GHRepository owner2 = id2.getOwner(); + + // If the owner of the branches is different, we have a cross-fork compare. + if (owner1!=null && owner2!=null) { + String ownerName1 = owner1.getOwnerName(); + String ownerName2 = owner2.getOwnerName(); + if (!StringUtils.equals(ownerName1, ownerName2)) { + String qualifiedName1 = String.format("%s:%s", ownerName1, id1.getName()); + String qualifiedName2 = String.format("%s:%s", ownerName2, id2.getName()); + return getCompare(qualifiedName1, qualifiedName2); + } + } + + return getCompare(id1.getName(), id2.getName()); + } /** @@ -635,7 +818,29 @@ public GHCompare getCompare(GHBranch id1, GHBranch id2) throws IOException { * @throws IOException on failure communicating with GitHub */ public GHRef[] getRefs() throws IOException { - return GHRef.wrap(root.retrieve().to(String.format("/repos/%s/%s/git/refs", owner.login, name), GHRef[].class),root); + return GHRef.wrap(root.retrieve().to(String.format("/repos/%s/%s/git/refs", getOwnerName(), name), GHRef[].class), root); + } + + + /** + * Retrieves all refs for the github repository. + * + * @return paged iterable of all refs + * @throws IOException on failure communicating with GitHub, potentially due to an invalid ref type being requested + */ + public PagedIterable listRefs() throws IOException { + final String url = String.format("/repos/%s/%s/git/refs", getOwnerName(), name); + return new PagedIterable() { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(url, GHRef[].class, pageSize)) { + protected void wrapUp(GHRef[] page) { + for(GHRef p: page) { + p.wrap(root); + } + } + }; + } + }; } /** @@ -645,11 +850,32 @@ public GHRef[] getRefs() throws IOException { * @throws IOException on failure communicating with GitHub, potentially due to an invalid ref type being requested */ public GHRef[] getRefs(String refType) throws IOException { - return GHRef.wrap(root.retrieve().to(String.format("/repos/%s/%s/git/refs/%s", owner.login, name, refType), GHRef[].class),root); + return GHRef.wrap(root.retrieve().to(String.format("/repos/%s/%s/git/refs/%s", getOwnerName(), name, refType), GHRef[].class),root); + } + + /** + * Retrieves all refs of the given type for the current GitHub repository. + * + * @param refType the type of reg to search for e.g. tags or commits + * @return paged iterable of all refs of the specified type + * @throws IOException on failure communicating with GitHub, potentially due to an invalid ref type being requested + */ + public PagedIterable listRefs(String refType) throws IOException { + final String url = String.format("/repos/%s/%s/git/refs/%s", getOwnerName(), name, refType); + return new PagedIterable() { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(url, GHRef[].class, pageSize)) { + protected void wrapUp(GHRef[] page) { + // no-op + } + }; + } + }; } + /** * Retrive a ref of the given type for the current GitHub repository. - * + * * @param refName * eg: heads/branch * @return refs matching the request type @@ -658,11 +884,27 @@ public GHRef[] getRefs(String refType) throws IOException { * invalid ref type being requested */ public GHRef getRef(String refName) throws IOException { - return root.retrieve().to(String.format("/repos/%s/%s/git/refs/%s", owner.login, name, refName), GHRef.class).wrap(root); + // hashes in branch names must be replaced with the url encoded equivalent or this call will fail + // FIXME: how about other URL unsafe characters, like space, @, : etc? do we need to be using URLEncoder.encode()? + // OTOH, '/' need no escaping + refName = refName.replaceAll("#", "%23"); + return root.retrieve().to(String.format("/repos/%s/%s/git/refs/%s", getOwnerName(), name, refName), GHRef.class).wrap(root); } + + /** + * Returns the annotated tag object. Only valid if the {@link GHRef#getObject()} has a + * {@link GHRef.GHObject#getType()} of {@code tag}. + * + * @param sha the sha of the tag object + * @return the annotated tag object + */ + public GHTagObject getTagObject(String sha) throws IOException { + return root.retrieve().to(getApiTailUrl("git/tags/" + sha), GHTagObject.class).wrap(this); + } + /** * 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 @@ -670,14 +912,18 @@ public GHRef getRef(String refName) throws IOException { * invalid tree type being requested */ 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); + String url = String.format("/repos/%s/%s/git/trees/%s", getOwnerName(), name, sha); + return root.retrieve().to(url, GHTree.class).wrap(this); } - + + public GHTreeBuilder createTree() { + return new GHTreeBuilder(this); + } + /** * 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 @@ -685,8 +931,37 @@ public GHTree getTree(String sha) throws IOException { * invalid tree type being requested */ public GHTree getTreeRecursive(String sha, int recursive) throws IOException { - String url = String.format("/repos/%s/%s/git/trees/%s?recursive=%d", owner.login, name, sha, recursive); - return root.retrieve().to(url, GHTree.class).wrap(root); + String url = String.format("/repos/%s/%s/git/trees/%s?recursive=%d", getOwnerName(), name, sha, recursive); + return root.retrieve().to(url, GHTree.class).wrap(this); + } + + /** + * Obtains the metadata & the content of a blob. + * + *

+ * This method retrieves the whole content in memory, so beware when you are dealing with large BLOB. + * + * @see Get a blob + * @see #readBlob(String) + */ + public GHBlob getBlob(String blobSha) throws IOException { + String target = getApiTailUrl("git/blobs/" + blobSha); + return root.retrieve().to(target, GHBlob.class); + } + + public GHBlobBuilder createBlob() { + return new GHBlobBuilder(this); + } + + /** + * Reads the content of a blob as a stream for better efficiency. + * + * @see Get a blob + * @see #getBlob(String) + */ + public InputStream readBlob(String blobSha) throws IOException { + String target = getApiTailUrl("git/blobs/" + blobSha); + return root.retrieve().withHeader("Accept","application/vnd.github.VERSION.raw").asStream(target); } /** @@ -695,19 +970,23 @@ public GHTree getTreeRecursive(String sha, int recursive) throws IOException { public GHCommit getCommit(String sha1) throws IOException { GHCommit c = commits.get(sha1); if (c==null) { - c = root.retrieve().to(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", getOwnerName(), name, sha1), GHCommit.class).wrapUp(this); commits.put(sha1,c); } return c; } + public GHCommitBuilder createCommit() { + return new GHCommitBuilder(this); + } + /** * Lists all the commits. */ public PagedIterable listCommits() { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(String.format("/repos/%s/%s/commits", owner.login, name), GHCommit[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(String.format("/repos/%s/%s/commits", getOwnerName(), name), GHCommit[].class, pageSize)) { protected void wrapUp(GHCommit[] page) { for (GHCommit c : page) c.wrapUp(GHRepository.this); @@ -729,8 +1008,8 @@ public GHCommitQueryBuilder queryCommits() { */ public PagedIterable listCommitComments() { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(String.format("/repos/%s/%s/comments", owner.login, name), GHCommitComment[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(String.format("/repos/%s/%s/comments", getOwnerName(), name), GHCommitComment[].class, pageSize)) { @Override protected void wrapUp(GHCommitComment[] page) { for (GHCommitComment c : page) @@ -741,13 +1020,53 @@ protected void wrapUp(GHCommitComment[] page) { }; } + /** + * Gets the basic license details for the repository. + *

+ * This is a preview item and subject to change. + * + * @throws IOException as usual but also if you don't use the preview connector + * @return null if there's no license. + */ + @Preview @Deprecated + public GHLicense getLicense() throws IOException{ + GHContentWithLicense lic = getLicenseContent_(); + return lic!=null ? lic.license : null; + } + + /** + * Retrieves the contents of the repository's license file - makes an additional API call + *

+ * This is a preview item and subject to change. + * + * @return details regarding the license contents, or null if there's no license. + * @throws IOException as usual but also if you don't use the preview connector + */ + @Preview @Deprecated + public GHContent getLicenseContent() throws IOException { + return getLicenseContent_(); + } + + @Preview @Deprecated + private GHContentWithLicense getLicenseContent_() throws IOException { + try { + return root.retrieve() + .withPreview(DRAX) + .to(getApiTailUrl("license"), GHContentWithLicense.class).wrap(this); + } catch (FileNotFoundException e) { + return null; + } + } + + /** + /** * Lists all the commit statues attached to the given commit, newer ones first. */ public PagedIterable listCommitStatuses(final String sha1) throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(String.format("/repos/%s/%s/statuses/%s", owner.login, name, sha1), GHCommitStatus[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(String.format("/repos/%s/%s/statuses/%s", getOwnerName(), name, sha1), GHCommitStatus[].class, pageSize)) { @Override protected void wrapUp(GHCommitStatus[] page) { for (GHCommitStatus c : page) @@ -774,22 +1093,22 @@ 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) - .with("state", state.name().toLowerCase(Locale.ENGLISH)) + .with("state", state) .with("target_url", targetUrl) .with("description", description) .with("context", context) - .to(String.format("/repos/%s/%s/statuses/%s",owner.login,this.name,sha1),GHCommitStatus.class).wrapUp(root); + .to(String.format("/repos/%s/%s/statuses/%s",getOwnerName(),this.name,sha1),GHCommitStatus.class).wrapUp(root); } - + /** * @see #createCommitStatus(String, GHCommitState,String,String,String) */ public GHCommitStatus createCommitStatus(String sha1, GHCommitState state, String targetUrl, String description) throws IOException { - return createCommitStatus(sha1, state, targetUrl, description,null); + return createCommitStatus(sha1, state, targetUrl, description, null); } /** @@ -797,8 +1116,8 @@ public GHCommitStatus createCommitStatus(String sha1, GHCommitState state, Strin */ public PagedIterable listEvents() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(String.format("/repos/%s/%s/events", owner.login, name), GHEventInfo[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(String.format("/repos/%s/%s/events", getOwnerName(), name), GHEventInfo[].class, pageSize)) { @Override protected void wrapUp(GHEventInfo[] page) { for (GHEventInfo c : page) @@ -816,8 +1135,8 @@ protected void wrapUp(GHEventInfo[] page) { */ public PagedIterable listLabels() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("labels"), GHLabel[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("labels"), GHLabel[].class, pageSize)) { @Override protected void wrapUp(GHLabel[] page) { for (GHLabel c : page) @@ -835,19 +1154,70 @@ public GHLabel getLabel(String name) throws IOException { public GHLabel createLabel(String name, String color) throws IOException { return root.retrieve().method("POST") .with("name",name) - .with("color",color) + .with("color", color) .to(getApiTailUrl("labels"), GHLabel.class).wrapUp(this); } + /** + * Lists all the invitations. + */ + public PagedIterable listInvitations() { + return new PagedIterable() { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(String.format("/repos/%s/%s/invitations", getOwnerName(), name), GHInvitation[].class, pageSize)) { + protected void wrapUp(GHInvitation[] page) { + for (GHInvitation c : page) + c.wrapUp(root); + } + }; + } + }; + } + /** * Lists all the subscribers (aka watchers.) * * https://developer.github.com/v3/activity/watching/ */ public PagedIterable listSubscribers() { + return listUsers("subscribers"); + } + + /** + * Lists all the users who have starred this repo based on the old version of the API. For + * additional information, like date when the repository was starred, see {@link #listStargazers2()} + */ + public PagedIterable listStargazers() { + return listUsers("stargazers"); + } + + /** + * Lists all the users who have starred this repo based on new version of the API, having extended + * information like the time when the repository was starred. For compatibility with the old API + * see {@link #listStargazers()} + */ + public PagedIterable listStargazers2() { + return new PagedIterable() { + @Override + public PagedIterator _iterator(int pageSize) { + Requester requester = root.retrieve(); + requester.setHeader("Accept", "application/vnd.github.v3.star+json"); + return new PagedIterator(requester.asIterator(getApiTailUrl("stargazers"), GHStargazer[].class, pageSize)) { + @Override + protected void wrapUp(GHStargazer[] page) { + for (GHStargazer c : page) { + c.wrapUp(GHRepository.this); + } + } + }; + } + }; + } + + private PagedIterable listUsers(final String suffix) { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("subscribers"), GHUser[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl(suffix), GHUser[].class, pageSize)) { protected void wrapUp(GHUser[] page) { for (GHUser c : page) c.wrapUp(root); @@ -858,10 +1228,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 +1240,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); } @@ -901,18 +1259,15 @@ public GHHook createWebHook(URL url) throws IOException { // return root.retrieveWithAuth("/pulls/"+owner+'/'+name,JsonPullRequests.class).wrap(root); // } - private void verifyMine() throws IOException { - if (!root.login.equals(owner.login)) - throw new IOException("Operation not applicable to a repository owned by someone else: "+owner.login); - } - /** * 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)} */ + @SuppressFBWarnings(value = "DMI_COLLECTION_OF_URLS", + justification = "It causes a performance degradation, but we have already exposed it to the API") public Set getPostCommitHooks() { return postCommitHooks; } @@ -920,6 +1275,9 @@ public Set getPostCommitHooks() { /** * Live set view of the post-commit hook. */ + @SuppressFBWarnings(value = "DMI_COLLECTION_OF_URLS", + justification = "It causes a performance degradation, but we have already exposed it to the API") + @SkipFromToString private final Set postCommitHooks = new AbstractSet() { private List getPostCommitHooks() { try { @@ -974,6 +1332,9 @@ public boolean remove(Object url) { /*package*/ GHRepository wrap(GitHub root) { this.root = root; + if (root.isOffline()) { + owner.wrapUp(root); + } return this; } @@ -989,6 +1350,10 @@ public Map getBranches() throws IOException { return r; } + public GHBranch getBranch(String name) throws IOException { + return root.retrieve().to(getApiTailUrl("branches/"+name),GHBranch.class).wrap(this); + } + /** * @deprecated * Use {@link #listMilestones(GHIssueState)} @@ -1006,8 +1371,8 @@ public Map getMilestones() throws IOException { */ public PagedIterable listMilestones(final GHIssueState state) { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("milestones?state="+state.toString().toLowerCase(Locale.ENGLISH)), GHMilestone[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().with("state",state).asIterator(getApiTailUrl("milestones"), GHMilestone[].class, pageSize)) { @Override protected void wrapUp(GHMilestone[] page) { for (GHMilestone c : page) @@ -1046,6 +1411,9 @@ public List getDirectoryContent(String path) throws IOException { public List getDirectoryContent(String path, String ref) throws IOException { Requester requester = root.retrieve(); + while (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } String target = getApiTailUrl("contents/" + path); GHContent[] files = requester.with("ref",ref).to(target, GHContent[].class); @@ -1063,54 +1431,62 @@ public GHContent getReadme() throws IOException { return requester.to(getApiTailUrl("readme"), GHContent.class).wrap(this); } + /** + * Creates a new content, or update an existing content. + */ + public GHContentBuilder createContent() { + return new GHContentBuilder(this); + } + + /** + * Use {@link #createContent()}. + */ + @Deprecated public GHContentUpdateResponse createContent(String content, String commitMessage, String path) throws IOException { - return createContent(content.getBytes(), commitMessage, path, null); + return createContent().content(content).message(commitMessage).path(path).commit(); } + /** + * Use {@link #createContent()}. + */ + @Deprecated public GHContentUpdateResponse createContent(String content, String commitMessage, String path, String branch) throws IOException { - return createContent(content.getBytes(), commitMessage, path, branch); + return createContent().content(content).message(commitMessage).path(path).branch(branch).commit(); } + /** + * Use {@link #createContent()}. + */ + @Deprecated public GHContentUpdateResponse createContent(byte[] contentBytes, String commitMessage, String path) throws IOException { - return createContent(contentBytes, commitMessage, path, null); + return createContent().content(contentBytes).message(commitMessage).path(path).commit(); } + /** + * Use {@link #createContent()}. + */ + @Deprecated public GHContentUpdateResponse createContent(byte[] contentBytes, String commitMessage, String path, String branch) throws IOException { - Requester requester = new Requester(root) - .with("path", path) - .with("message", commitMessage) - .with("content", DatatypeConverter.printBase64Binary(contentBytes)) - .method("PUT"); - - if (branch != null) { - requester.with("branch", branch); - } - - GHContentUpdateResponse response = requester.to(getApiTailUrl("contents/" + path), GHContentUpdateResponse.class); - - response.getContent().wrap(this); - response.getCommit().wrapUp(this); - - return response; + return createContent().content(contentBytes).message(commitMessage).path(path).branch(branch).commit(); } public GHMilestone createMilestone(String title, String description) throws IOException { 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 +1495,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 +1512,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 +1520,7 @@ public GHRepository getParent() throws IOException { parent = root.getRepository(parent.getFullName()); return parent; } - + /** * Subscribes to this repository to get notifications. */ @@ -1162,7 +1538,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; } @@ -1170,8 +1546,8 @@ public GHSubscription getSubscription() throws IOException { public PagedIterable listContributors() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("contributors"), Contributor[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("contributors"), Contributor[].class, pageSize)) { @Override protected void wrapUp(Contributor[] page) { for (Contributor c : page) @@ -1188,6 +1564,18 @@ public static class Contributor extends GHUser { public int getContributions() { return contributions; } + + @Override + public int hashCode() { + // We ignore contributions in the calculation + return super.hashCode(); + } + + @Override + public boolean equals(Object obj) { + // We ignore contributions in the calculation + return super.equals(obj); + } } /** @@ -1215,22 +1603,30 @@ public GHNotificationStream listNotifications() { return new GHNotificationStream(root,getApiTailUrl("/notifications")); } + /** + * https://developer.github.com/v3/repos/traffic/#views + */ + public GHRepositoryViewTraffic getViewTraffic() throws IOException{ + return root.retrieve().to(getApiTailUrl("/traffic/views"), GHRepositoryViewTraffic.class); + } - @Override - public String toString() { - return "Repository:"+owner.login+":"+name; + /** + * https://developer.github.com/v3/repos/traffic/#clones + */ + public GHRepositoryCloneTraffic getCloneTraffic() throws IOException{ + return root.retrieve().to(getApiTailUrl("/traffic/clones"), GHRepositoryCloneTraffic.class); } @Override public int hashCode() { - return toString().hashCode(); + return ("Repository:"+getOwnerName()+":"+name).hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof GHRepository) { GHRepository that = (GHRepository) obj; - return this.owner.login.equals(that.owner.login) + return this.getOwnerName().equals(that.getOwnerName()) && this.name.equals(that.name); } return false; @@ -1238,6 +1634,6 @@ public boolean equals(Object obj) { String getApiTailUrl(String tail) { if (tail.length()>0 && !tail.startsWith("/")) tail='/'+tail; - return "/repos/" + owner.login + "/" + name +tail; + return "/repos/" + getOwnerName() + "/" + name +tail; } } diff --git a/src/main/java/org/kohsuke/github/GHRepositoryCloneTraffic.java b/src/main/java/org/kohsuke/github/GHRepositoryCloneTraffic.java new file mode 100644 index 0000000000..c75198e4a8 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHRepositoryCloneTraffic.java @@ -0,0 +1,37 @@ +package org.kohsuke.github; + +import java.util.List; + +/** + * Repository clone statistics. + * + * @see GHRepository#getCloneTraffic() + */ +public class GHRepositoryCloneTraffic extends GHRepositoryTraffic { + private List clones; + + /*package*/ GHRepositoryCloneTraffic() { + } + + /*package*/ GHRepositoryCloneTraffic(Integer count, Integer uniques, List clones) { + super(count, uniques); + this.clones = clones; + } + + public List getClones() { + return clones; + } + + public List getDailyInfo() { + return getClones(); + } + + public static class DailyInfo extends GHRepositoryTraffic.DailyInfo { + /*package*/ DailyInfo() { + } + + /*package*/ DailyInfo(String timestamp, int count, int uniques) { + super(timestamp, count, uniques); + } + } +} diff --git a/src/main/java/org/kohsuke/github/GHRepositorySearchBuilder.java b/src/main/java/org/kohsuke/github/GHRepositorySearchBuilder.java index cbcbb9e19d..0ecd77e600 100644 --- a/src/main/java/org/kohsuke/github/GHRepositorySearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHRepositorySearchBuilder.java @@ -1,7 +1,5 @@ package org.kohsuke.github; -import java.util.Locale; - /** * Search repositories. * @@ -57,8 +55,17 @@ public GHRepositorySearchBuilder stars(String v) { return q("stars:"+v); } + public GHRepositorySearchBuilder topic(String v) { + return q("topic:"+v); + } + + public GHRepositorySearchBuilder order(GHDirection v) { + req.with("order",v); + return this; + } + public GHRepositorySearchBuilder sort(Sort sort) { - req.with("sort",sort.toString().toLowerCase(Locale.ENGLISH)); + req.with("sort",sort); return this; } diff --git a/src/main/java/org/kohsuke/github/GHRepositoryTraffic.java b/src/main/java/org/kohsuke/github/GHRepositoryTraffic.java new file mode 100644 index 0000000000..42d07e848d --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHRepositoryTraffic.java @@ -0,0 +1,54 @@ +package org.kohsuke.github; + +import java.util.Date; +import java.util.List; + +public abstract class GHRepositoryTraffic implements TrafficInfo { + private int count; + private int uniques; + + /*package*/ GHRepositoryTraffic() { + } + + /*package*/ GHRepositoryTraffic(int count, int uniques) { + this.count = count; + this.uniques = uniques; + } + + public int getCount() { + return count; + } + + public int getUniques() { + return uniques; + } + + public abstract List getDailyInfo(); + + public static abstract class DailyInfo implements TrafficInfo { + private String timestamp; + private int count; + private int uniques; + + public Date getTimestamp() { + return GitHub.parseDate(timestamp); + } + + public int getCount() { + return count; + } + + public int getUniques() { + return uniques; + } + + /*package*/ DailyInfo() { + } + + /*package*/ DailyInfo(String timestamp, Integer count, Integer uniques) { + this.timestamp = timestamp; + this.count = count; + this.uniques = uniques; + } + } +} diff --git a/src/main/java/org/kohsuke/github/GHRepositoryViewTraffic.java b/src/main/java/org/kohsuke/github/GHRepositoryViewTraffic.java new file mode 100644 index 0000000000..f2f1e5b440 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHRepositoryViewTraffic.java @@ -0,0 +1,37 @@ +package org.kohsuke.github; + +import java.util.List; + +/** + * Repository view statistics. + * + * @see GHRepository#getViewTraffic() + */ +public class GHRepositoryViewTraffic extends GHRepositoryTraffic { + private List views; + + /*package*/ GHRepositoryViewTraffic() { + } + + /*package*/ GHRepositoryViewTraffic(int count, int uniques, List views) { + super(count, uniques); + this.views = views; + } + + public List getViews() { + return views; + } + + public List getDailyInfo() { + return getViews(); + } + + public static class DailyInfo extends GHRepositoryTraffic.DailyInfo { + /*package*/ DailyInfo() { + } + + /*package*/ DailyInfo(String timestamp, int count, int uniques) { + super(timestamp, count, uniques); + } + } +} diff --git a/src/main/java/org/kohsuke/github/GHSearchBuilder.java b/src/main/java/org/kohsuke/github/GHSearchBuilder.java index 766c6a319f..44521687f4 100644 --- a/src/main/java/org/kohsuke/github/GHSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHSearchBuilder.java @@ -1,6 +1,6 @@ package org.kohsuke.github; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.List; @@ -10,9 +10,7 @@ * * @author Kohsuke Kawaguchi */ -public abstract class GHSearchBuilder { - protected final GitHub root; - protected final Requester req; +public abstract class GHSearchBuilder extends GHQueryBuilder { protected final List terms = new ArrayList(); /** @@ -21,15 +19,14 @@ public abstract class GHSearchBuilder { private final Class> receiverType; /*package*/ GHSearchBuilder(GitHub root, Class> receiverType) { - this.root = root; - this.req = root.retrieve(); + super(root); this.receiverType = receiverType; } /** * Search terms. */ - public GHSearchBuilder q(String term) { + public GHQueryBuilder q(String term) { terms.add(term); return this; } @@ -37,11 +34,12 @@ public GHSearchBuilder q(String term) { /** * Performs the search. */ + @Override public PagedSearchIterable list() { return new PagedSearchIterable(root) { - public PagedIterator iterator() { + public PagedIterator _iterator(int pageSize) { req.set("q", StringUtils.join(terms, " ")); - return new PagedIterator(adapt(req.asIterator(getApiUrl(), receiverType))) { + return new PagedIterator(adapt(req.asIterator(getApiUrl(), receiverType, pageSize))) { protected void wrapUp(T[] page) { // SearchResult.getItems() should do it } diff --git a/src/main/java/org/kohsuke/github/GHStargazer.java b/src/main/java/org/kohsuke/github/GHStargazer.java new file mode 100644 index 0000000000..2384d01939 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHStargazer.java @@ -0,0 +1,51 @@ +package org.kohsuke.github; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.util.Date; + +/** + * A stargazer at a repository on GitHub. + * + * @author noctarius + */ +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD"}, justification = "JSON API") +public class GHStargazer { + + private GHRepository repository; + private String starred_at; + private GHUser user; + + /** + * Gets the repository that is stargazed + * + * @return the starred repository + */ + public GHRepository getRepository() { + return repository; + } + + /** + * Gets the date when the repository was starred, however old stars before + * August 2012, will all show the date the API was changed to support starred_at. + * + * @return the date the stargazer was added + */ + public Date getStarredAt() { + return GitHub.parseDate(starred_at); + } + + /** + * Gets the user that starred the repository + * + * @return the stargazer user + */ + public GHUser getUser() { + return user; + } + + void wrapUp(GHRepository repository) { + this.repository = repository; + user.wrapUp(repository.root); + } +} diff --git a/src/main/java/org/kohsuke/github/GHTag.java b/src/main/java/org/kohsuke/github/GHTag.java index 3a72407754..802f51f8f3 100644 --- a/src/main/java/org/kohsuke/github/GHTag.java +++ b/src/main/java/org/kohsuke/github/GHTag.java @@ -1,10 +1,14 @@ package org.kohsuke.github; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + /** * Represents a tag in {@link GHRepository} * * @see GHRepository#listTags() */ +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "NP_UNWRITTEN_FIELD"}, justification = "JSON API") public class GHTag { private GHRepository owner; private GitHub root; diff --git a/src/main/java/org/kohsuke/github/GHTagObject.java b/src/main/java/org/kohsuke/github/GHTagObject.java new file mode 100644 index 0000000000..0e3acdeaa7 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHTagObject.java @@ -0,0 +1,60 @@ +package org.kohsuke.github; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * Represents an annotated tag in a {@link GHRepository} + * + * @see GHRepository#getTagObject(String) + */ +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "NP_UNWRITTEN_FIELD"}, justification = "JSON API") +public class GHTagObject { + private GHRepository owner; + private GitHub root; + + private String tag; + private String sha; + private String url; + private String message; + private GitUser tagger; + private GHRef.GHObject object; + + /*package*/ GHTagObject wrap(GHRepository owner) { + this.owner = owner; + this.root = owner.root; + return this; + } + + public GHRepository getOwner() { + return owner; + } + + public GitHub getRoot() { + return root; + } + + public String getTag() { + return tag; + } + + public String getSha() { + return sha; + } + + public String getUrl() { + return url; + } + + public String getMessage() { + return message; + } + + public GitUser getTagger() { + return tagger; + } + + public GHRef.GHObject getObject() { + return object; + } +} diff --git a/src/main/java/org/kohsuke/github/GHTeam.java b/src/main/java/org/kohsuke/github/GHTeam.java index 7213f327c9..bccec4b5ed 100644 --- a/src/main/java/org/kohsuke/github/GHTeam.java +++ b/src/main/java/org/kohsuke/github/GHTeam.java @@ -12,12 +12,24 @@ * @author Kohsuke Kawaguchi */ public class GHTeam { - private String name,permission; + private String name,permission,slug; private int id; private GHOrganization organization; // populated by GET /user/teams where Teams+Orgs are returned together protected /*final*/ GHOrganization org; + /** Member's role in a team */ + public enum Role { + /** + * A normal member of the team + */ + MEMBER, + /** + * Able to add/remove other team members, promote other team members to team maintainer, and edit the team's name and description. + */ + MAINTAINER + } + /*package*/ GHTeam wrapUp(GHOrganization owner) { this.org = owner; return this; @@ -43,6 +55,10 @@ public String getPermission() { return permission; } + public String getSlug() { + return slug; + } + public int getId() { return id; } @@ -52,8 +68,8 @@ public int getId() { */ public PagedIterable listMembers() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(org.root.retrieve().asIterator(api("/members"), GHUser[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(org.root.retrieve().asIterator(api("/members"), GHUser[].class, pageSize)) { @Override protected void wrapUp(GHUser[] page) { GHUser.wrap(page, org.root); @@ -89,8 +105,8 @@ public Map getRepositories() throws IOException { public PagedIterable listRepositories() { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(org.root.retrieve().asIterator(api("/repos"), GHRepository[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(org.root.retrieve().asIterator(api("/repos"), GHRepository[].class, pageSize)) { @Override protected void wrapUp(GHRepository[] page) { for (GHRepository r : page) @@ -112,6 +128,22 @@ public void add(GHUser u) throws IOException { org.root.retrieve().method("PUT").to(api("/memberships/" + u.getLogin()), null); } + /** + * Adds a member to the team + * + * The user will be invited to the organization if required. + * + * @param user github user + * @param role role for the new member + * + * @throws IOException + */ + public void add(GHUser user, Role role) throws IOException { + org.root.retrieve().method("PUT") + .with("role", role.name()) + .to(api("/memberships/" + user.getLogin()), null); + } + /** * Removes a member to the team. */ @@ -120,15 +152,28 @@ public void remove(GHUser u) throws IOException { } public void add(GHRepository r) throws IOException { - org.root.retrieve().method("PUT").to(api("/repos/" + r.getOwnerName() + '/' + r.getName()), null); + add(r,null); + } + + public void add(GHRepository r, GHOrganization.Permission permission) throws IOException { + org.root.retrieve().method("PUT") + .with("permission", permission) + .to(api("/repos/" + r.getOwnerName() + '/' + r.getName()), null); } public void remove(GHRepository r) throws IOException { org.root.retrieve().method("DELETE").to(api("/repos/" + r.getOwnerName() + '/' + r.getName()), null); } + + /** + * Deletes this team. + */ + public void delete() throws IOException { + org.root.retrieve().method("DELETE").to(api("")); + } private String api(String tail) { - return "/teams/"+id+tail; + return "/teams/" + id + tail; } public GHOrganization getOrganization() { diff --git a/src/main/java/org/kohsuke/github/GHThread.java b/src/main/java/org/kohsuke/github/GHThread.java index 6a2608f253..faf4c87b7b 100644 --- a/src/main/java/org/kohsuke/github/GHThread.java +++ b/src/main/java/org/kohsuke/github/GHThread.java @@ -1,5 +1,7 @@ package org.kohsuke.github; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import java.io.FileNotFoundException; import java.io.IOException; import java.net.URL; @@ -12,6 +14,8 @@ * @see GHNotificationStream * @author Kohsuke Kawaguchi */ +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "NP_UNWRITTEN_FIELD"}, justification = "JSON API") public class GHThread extends GHObject { private GitHub root; private GHRepository repository; @@ -67,6 +71,10 @@ public String getTitle() { public String getType() { return subject.type; } + + public String getLastCommentUrl() { + return subject.latest_comment_url; + } /** * If this thread is about an issue, return that issue. diff --git a/src/main/java/org/kohsuke/github/GHTree.java b/src/main/java/org/kohsuke/github/GHTree.java index aaf98e77e2..d52e02a793 100644 --- a/src/main/java/org/kohsuke/github/GHTree.java +++ b/src/main/java/org/kohsuke/github/GHTree.java @@ -10,10 +10,12 @@ * https://developer.github.com/v3/git/trees/ * * @author Daniel Teixeira - https://github.com/ddtxra + * @see GHCommit#getTree() * @see GHRepository#getTree(String) + * @see GHTreeEntry#asTree() */ public class GHTree { - /* package almost final */GitHub root; + /* package almost final */GHRepository repo; private boolean truncated; private String sha, url; @@ -28,12 +30,24 @@ public String getSha() { /** * Return an array of entries of the trees - * @return */ public List getTree() { return Collections.unmodifiableList(Arrays.asList(tree)); } + /** + * Finds a tree entry by its name. + * + * IOW, find a directory entry by a file name. + */ + public GHTreeEntry getEntry(String path) { + for (GHTreeEntry e : tree) { + if (e.getPath().equals(path)) + return e; + } + return null; + } + /** * Returns true if the number of items in the tree array exceeded the GitHub maximum limit. * @return true true if the number of items in the tree array exceeded the GitHub maximum limit otherwise false. @@ -50,8 +64,11 @@ public URL getUrl() { return GitHub.parseURL(url); } - /* package */GHTree wrap(GitHub root) { - this.root = root; + /* package */GHTree wrap(GHRepository repo) { + this.repo = repo; + for (GHTreeEntry e : tree) { + e.tree = this; + } return this; } diff --git a/src/main/java/org/kohsuke/github/GHTreeBuilder.java b/src/main/java/org/kohsuke/github/GHTreeBuilder.java new file mode 100644 index 0000000000..122c5775b5 --- /dev/null +++ b/src/main/java/org/kohsuke/github/GHTreeBuilder.java @@ -0,0 +1,90 @@ +package org.kohsuke.github; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Builder pattern for creating a new tree. + * Based on https://developer.github.com/v3/git/trees/#create-a-tree + */ +public class GHTreeBuilder { + private final GHRepository repo; + private final Requester req; + + private final List treeEntries = new ArrayList(); + + @SuppressFBWarnings("URF_UNREAD_FIELD") + private static final class TreeEntry { + private final String path; + private final String mode; + private final String type; + private String sha; + private String content; + + private TreeEntry(String path, String mode, String type) { + this.path = path; + this.mode = mode; + this.type = type; + } + } + + GHTreeBuilder(GHRepository repo) { + this.repo = repo; + req = new Requester(repo.root); + } + + /** + * @param baseTree the SHA of tree you want to update with new data + */ + public GHTreeBuilder baseTree(String baseTree) { + req.with("base_tree", baseTree); + return this; + } + + /** + * Adds a new entry to the tree. + * Exactly one of the parameters {@code sha} and {@code content} must be non-null. + */ + public GHTreeBuilder entry(String path, String mode, String type, String sha, String content) { + TreeEntry entry = new TreeEntry(path, mode, type); + entry.sha = sha; + entry.content = content; + treeEntries.add(entry); + return this; + } + + /** + * Specialized version of {@link #entry(String, String, String, String, String)} for adding an existing blob referred by its SHA. + */ + public GHTreeBuilder shaEntry(String path, String sha, boolean executable) { + TreeEntry entry = new TreeEntry(path, executable ? "100755" : "100644", "blob"); + entry.sha = sha; + treeEntries.add(entry); + return this; + } + + /** + * Specialized version of {@link #entry(String, String, String, String, String)} for adding a text file with the specified {@code content}. + */ + public GHTreeBuilder textEntry(String path, String content, boolean executable) { + TreeEntry entry = new TreeEntry(path, executable ? "100755" : "100644", "blob"); + entry.content = content; + treeEntries.add(entry); + return this; + } + + private String getApiTail() { + return String.format("/repos/%s/%s/git/trees", repo.getOwnerName(), repo.getName()); + } + + /** + * Creates a tree based on the parameters specified thus far. + */ + public GHTree create() throws IOException { + req._with("tree", treeEntries); + return req.method("POST").to(getApiTail(), GHTree.class).wrap(repo); + } +} diff --git a/src/main/java/org/kohsuke/github/GHTreeEntry.java b/src/main/java/org/kohsuke/github/GHTreeEntry.java index e3d831c073..ba6d45e15e 100644 --- a/src/main/java/org/kohsuke/github/GHTreeEntry.java +++ b/src/main/java/org/kohsuke/github/GHTreeEntry.java @@ -1,5 +1,7 @@ package org.kohsuke.github; +import java.io.IOException; +import java.io.InputStream; import java.net.URL; /** @@ -10,6 +12,8 @@ * @see GHTree */ public class GHTreeEntry { + /* package almost final */GHTree tree; + private String path, mode, type, sha, url; private long size; @@ -44,7 +48,7 @@ public long getSize() { /** * Gets the type such as: - * "blob" + * "blob", "tree", etc. * * @return The type */ @@ -68,4 +72,37 @@ public String getSha() { public URL getUrl() { return GitHub.parseURL(url); } + + /** + * If this tree entry represents a file, then return its information. + * Otherwise null. + */ + public GHBlob asBlob() throws IOException { + if (type.equals("blob")) + return tree.repo.getBlob(sha); + else + return null; + } + + /** + * If this tree entry represents a file, then return its content. + * Otherwise null. + */ + public InputStream readAsBlob() throws IOException { + if (type.equals("blob")) + return tree.repo.readBlob(sha); + else + return null; + } + + /** + * If this tree entry represents a directory, then return it. + * Otherwise null. + */ + public GHTree asTree() throws IOException { + if (type.equals("tree")) + return tree.repo.getTree(sha); + else + return null; + } } diff --git a/src/main/java/org/kohsuke/github/GHUser.java b/src/main/java/org/kohsuke/github/GHUser.java index 2242f4a6e4..77beb11b88 100644 --- a/src/main/java/org/kohsuke/github/GHUser.java +++ b/src/main/java/org/kohsuke/github/GHUser.java @@ -26,7 +26,6 @@ import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import java.io.IOException; -import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -56,8 +55,14 @@ public void unfollow() throws IOException { */ @WithBridgeMethods(Set.class) public GHPersonSet getFollows() throws IOException { - GHUser[] followers = root.retrieve().to("/users/" + login + "/following", GHUser[].class); - return new GHPersonSet(Arrays.asList(wrap(followers,root))); + return new GHPersonSet(listFollows().asList()); + } + + /** + * Lists the users that this user is following + */ + public PagedIterable listFollows() { + return listUser("following"); } /** @@ -65,8 +70,26 @@ public GHPersonSet getFollows() throws IOException { */ @WithBridgeMethods(Set.class) public GHPersonSet getFollowers() throws IOException { - GHUser[] followers = root.retrieve().to("/users/" + login + "/followers", GHUser[].class); - return new GHPersonSet(Arrays.asList(wrap(followers,root))); + return new GHPersonSet(listFollowers().asList()); + } + + /** + * Lists the users who are following this user. + */ + public PagedIterable listFollowers() { + return listUser("followers"); + } + + private PagedIterable listUser(final String suffix) { + return new PagedIterable() { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl(suffix), GHUser[].class, pageSize)) { + protected void wrapUp(GHUser[] page) { + GHUser.wrap(page,root); + } + }; + } + }; } /** @@ -75,9 +98,20 @@ public GHPersonSet getFollowers() throws IOException { * https://developer.github.com/v3/activity/watching/ */ public PagedIterable listSubscriptions() { + return listRepositories("subscriptions"); + } + + /** + * Lists all the repositories that this user has starred. + */ + public PagedIterable listStarredRepositories() { + return listRepositories("starred"); + } + + private PagedIterable listRepositories(final String suffix) { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(getApiTailUrl("subscriptions"), GHRepository[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(getApiTailUrl(suffix), GHRepository[].class, pageSize)) { protected void wrapUp(GHRepository[] page) { for (GHRepository c : page) c.wrap(root); @@ -133,8 +167,8 @@ public GHPersonSet getOrganizations() throws IOException { */ public PagedIterable listEvents() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(String.format("/users/%s/events", login), GHEventInfo[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(String.format("/users/%s/events", login), GHEventInfo[].class, pageSize)) { @Override protected void wrapUp(GHEventInfo[] page) { for (GHEventInfo c : page) @@ -150,8 +184,8 @@ protected void wrapUp(GHEventInfo[] page) { */ public PagedIterable listGists() throws IOException { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(root.retrieve().asIterator(String.format("/users/%s/gists", login), GHGist[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(root.retrieve().asIterator(String.format("/users/%s/gists", login), GHGist[].class, pageSize)) { @Override protected void wrapUp(GHGist[] page) { for (GHGist c : page) @@ -162,11 +196,6 @@ protected void wrapUp(GHGist[] page) { }; } - @Override - public String toString() { - return "User:"+login; - } - @Override public int hashCode() { return login.hashCode(); @@ -185,4 +214,9 @@ String getApiTailUrl(String tail) { if (tail.length()>0 && !tail.startsWith("/")) tail='/'+tail; return "/users/" + login + tail; } + + /*package*/ GHUser wrapUp(GitHub root) { + super.wrapUp(root); + return this; + } } diff --git a/src/main/java/org/kohsuke/github/GHUserSearchBuilder.java b/src/main/java/org/kohsuke/github/GHUserSearchBuilder.java index ee0c5f62a0..3edcd15065 100644 --- a/src/main/java/org/kohsuke/github/GHUserSearchBuilder.java +++ b/src/main/java/org/kohsuke/github/GHUserSearchBuilder.java @@ -1,7 +1,5 @@ package org.kohsuke.github; -import java.util.Locale; - /** * Search users. * @@ -49,8 +47,13 @@ public GHUserSearchBuilder followers(String v) { return q("followers:"+v); } + public GHUserSearchBuilder order(GHDirection v) { + req.with("order",v); + return this; + } + public GHUserSearchBuilder sort(Sort sort) { - req.with("sort",sort.toString().toLowerCase(Locale.ENGLISH)); + req.with("sort",sort); return this; } diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java index 744a6d36e9..d5d0a2be33 100644 --- a/src/main/java/org/kohsuke/github/GitHub.java +++ b/src/main/java/org/kohsuke/github/GitHub.java @@ -23,14 +23,23 @@ */ package org.kohsuke.github; -import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; -import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.introspect.VisibilityChecker.Std; +import com.infradna.tool.bridge_method_injector.WithBridgeMethods; +import org.apache.commons.codec.Charsets; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.IOUtils; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; +import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.text.ParseException; @@ -40,18 +49,18 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; -import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TimeZone; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.logging.Logger; -import org.apache.commons.codec.binary.Base64; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.introspect.VisibilityChecker.Std; -import com.infradna.tool.bridge_method_injector.WithBridgeMethods; +import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.*; +import static java.net.HttpURLConnection.*; +import static java.util.logging.Level.*; +import static org.kohsuke.github.Previews.*; /** * Root of the GitHub API. @@ -72,15 +81,21 @@ public class GitHub { */ /*package*/ final String encodedAuthorization; - private final Map users = new Hashtable(); - private final Map orgs = new Hashtable(); - + private final ConcurrentMap users; + private final ConcurrentMap orgs; + // Cache of myself object. + private GHMyself myself; private final String apiUrl; /*package*/ final RateLimitHandler rateLimitHandler; + /*package*/ final AbuseLimitHandler abuseLimitHandler; private HttpConnector connector = HttpConnector.DEFAULT; + private final Object headerRateLimitLock = new Object(); + private GHRateLimit headerRateLimit = null; + private volatile GHRateLimit rateLimit = null; + /** * Creates a client API root object. * @@ -117,7 +132,7 @@ public class GitHub { * @param connector * HttpConnector to use. Pass null to use default connector. */ - /* package */ GitHub(String apiUrl, String login, String oauthAccessToken, String password, HttpConnector connector, RateLimitHandler rateLimitHandler) throws IOException { + /* package */ GitHub(String apiUrl, String login, String oauthAccessToken, String password, HttpConnector connector, RateLimitHandler rateLimitHandler, AbuseLimitHandler abuseLimitHandler) throws IOException { if (apiUrl.endsWith("/")) apiUrl = apiUrl.substring(0, apiUrl.length()-1); // normalize this.apiUrl = apiUrl; if (null != connector) this.connector = connector; @@ -127,16 +142,21 @@ public class GitHub { } else { if (password!=null) { String authorization = (login + ':' + password); - encodedAuthorization = "Basic "+new String(Base64.encodeBase64(authorization.getBytes())); + String charsetName = Charsets.UTF_8.name(); + encodedAuthorization = "Basic "+new String(Base64.encodeBase64(authorization.getBytes(charsetName)), charsetName); } else {// anonymous access encodedAuthorization = null; } } + users = new ConcurrentHashMap(); + orgs = new ConcurrentHashMap(); + this.rateLimitHandler = rateLimitHandler; + this.abuseLimitHandler = abuseLimitHandler; + if (login==null && encodedAuthorization!=null) login = getMyself().getLogin(); this.login = login; - this.rateLimitHandler = rateLimitHandler; } /** @@ -146,6 +166,16 @@ public static GitHub connect() throws IOException { return GitHubBuilder.fromCredentials().build(); } + /** + * Version that connects to GitHub Enterprise. + * + * @deprecated + * Use {@link #connectToEnterpriseWithOAuth(String, String, String)} + */ + public static GitHub connectToEnterprise(String apiUrl, String oauthAccessToken) throws IOException { + return connectToEnterpriseWithOAuth(apiUrl,null,oauthAccessToken); + } + /** * Version that connects to GitHub Enterprise. * @@ -154,10 +184,16 @@ public static GitHub connect() throws IOException { * "http://ghe.acme.com/api/v3". Note that GitHub Enterprise has /api/v3 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 oauthAccessToken) throws IOException { - return new GitHubBuilder().withEndpoint(apiUrl).withOAuthToken(oauthAccessToken).build(); + public static GitHub connectToEnterpriseWithOAuth(String apiUrl, String login, String oauthAccessToken) throws IOException { + return new GitHubBuilder().withEndpoint(apiUrl).withOAuthToken(oauthAccessToken, login).build(); } + /** + * Version that connects to GitHub Enterprise. + * + * @deprecated + * Use with caution. Login with password is not a preferred method. + */ public static GitHub connectToEnterprise(String apiUrl, String login, String password) throws IOException { return new GitHubBuilder().withEndpoint(apiUrl).withPassword(login, password).build(); } @@ -195,6 +231,33 @@ public static GitHub connectAnonymously() throws IOException { return new GitHubBuilder().build(); } + /** + * Connects to GitHub Enterprise anonymously. + * + * All operations that requires authentication will fail. + */ + public static GitHub connectToEnterpriseAnonymously(String apiUrl) throws IOException { + return new GitHubBuilder().withEndpoint(apiUrl).build(); + } + + /** + * An offline-only {@link GitHub} useful for parsing event notification from an unknown source. + * + * All operations that require a connection will fail. + * + * @return An offline-only {@link GitHub}. + */ + public static GitHub offline() { + try { + return new GitHubBuilder() + .withEndpoint("https://api.github.invalid") + .withConnector(HttpConnector.OFFLINE) + .build(); + } catch (IOException e) { + throw new IllegalStateException("The offline implementation constructor should not connect", e); + } + } + /** * Is this an anonymous connection * @return {@code true} if operations that require authentication will fail. @@ -203,10 +266,22 @@ public boolean isAnonymous() { return login==null && encodedAuthorization==null; } + /** + * Is this an always offline "connection". + * @return {@code true} if this is an always offline "connection". + */ + public boolean isOffline() { + return connector == HttpConnector.OFFLINE; + } + public HttpConnector getConnector() { return connector; } + public String getApiUrl() { + return apiUrl; + } + /** * Sets the custom connector used to make requests to GitHub. */ @@ -240,30 +315,78 @@ public void setConnector(HttpConnector connector) { */ public GHRateLimit getRateLimit() throws IOException { try { - return retrieve().to("/rate_limit", JsonRateLimit.class).rate; + return rateLimit = retrieve().to("/rate_limit", JsonRateLimit.class).rate; } catch (FileNotFoundException e) { // GitHub Enterprise doesn't have the rate limit, so in that case // return some big number that's not too big. // see issue #78 GHRateLimit r = new GHRateLimit(); r.limit = r.remaining = 1000000; - return r; + long hour = 60L * 60L; // this is madness, storing the date as seconds in a Date object + r.reset = new Date(System.currentTimeMillis() / 1000L + hour); + return rateLimit = r; + } + } + + /*package*/ void updateRateLimit(@Nonnull GHRateLimit observed) { + synchronized (headerRateLimitLock) { + if (headerRateLimit == null + || headerRateLimit.getResetDate().getTime() < observed.getResetDate().getTime() + || headerRateLimit.remaining > observed.remaining) { + headerRateLimit = observed; + LOGGER.log(FINE, "Rate limit now: {0}", headerRateLimit); + } + } + } + + /** + * Returns the most recently observed rate limit data or {@code null} if either there is no rate limit + * (for example GitHub Enterprise) or if no requests have been made. + * + * @return the most recently observed rate limit data or {@code null}. + */ + @CheckForNull + public GHRateLimit lastRateLimit() { + synchronized (headerRateLimitLock) { + return headerRateLimit; } } + /** + * Gets the current rate limit while trying not to actually make any remote requests unless absolutely necessary. + * + * @return the current rate limit data. + * @throws IOException if we couldn't get the current rate limit data. + */ + @Nonnull + public GHRateLimit rateLimit() throws IOException { + synchronized (headerRateLimitLock) { + if (headerRateLimit != null) { + return headerRateLimit; + } + } + GHRateLimit rateLimit = this.rateLimit; + if (rateLimit == null || rateLimit.getResetDate().getTime() < System.currentTimeMillis()) { + rateLimit = getRateLimit(); + } + return rateLimit; + } + /** * Gets the {@link GHUser} that represents yourself. */ @WithBridgeMethods(GHUser.class) public GHMyself getMyself() throws IOException { requireCredential(); + synchronized (this) { + if (this.myself != null) return myself; + + GHMyself u = retrieve().to("/user", GHMyself.class); - GHMyself u = retrieve().to("/user", GHMyself.class); - - u.root = this; - users.put(u.getLogin(), u); - - return u; + u.root = this; + this.myself = u; + return u; + } } /** @@ -279,7 +402,7 @@ public GHUser getUser(String login) throws IOException { return u; } - + /** * clears all cached data in order for external changes (modifications and del */ @@ -291,7 +414,7 @@ public void refreshCache() { /** * Interns the given {@link GHUser}. */ - protected GHUser getUser(GHUser orig) throws IOException { + protected GHUser getUser(GHUser orig) { GHUser u = users.get(orig.getLogin()); if (u==null) { orig.root = this; @@ -301,6 +424,9 @@ protected GHUser getUser(GHUser orig) throws IOException { return u; } + /** + * Gets {@link GHOrganization} specified by name. + */ public GHOrganization getOrganization(String name) throws IOException { GHOrganization o = orgs.get(name); if (o==null) { @@ -310,6 +436,35 @@ public GHOrganization getOrganization(String name) throws IOException { return o; } + /** + * Gets a list of all organizations. + */ + public PagedIterable listOrganizations() { + return listOrganizations(null); + } + + /** + * Gets a list of all organizations starting after the organization identifier specified by 'since'. + * + * @see List All Orgs - Parameters + */ + public PagedIterable listOrganizations(final String since) { + return new PagedIterable() { + @Override + public PagedIterator _iterator(int pageSize) { + System.out.println("page size: " + pageSize); + return new PagedIterator(retrieve().with("since",since) + .asIterator("/organizations", GHOrganization[].class, pageSize)) { + @Override + protected void wrapUp(GHOrganization[] page) { + for (GHOrganization c : page) + c.wrapUp(GitHub.this); + } + }; + } + }; + } + /** * Gets the repository object from 'user/reponame' string that GitHub calls as "repository name" * @@ -319,6 +474,71 @@ public GHRepository getRepository(String name) throws IOException { String[] tokens = name.split("/"); return retrieve().to("/repos/" + tokens[0] + '/' + tokens[1], GHRepository.class).wrap(this); } + /** + * Returns a list of popular open source licenses + * + * WARNING: This uses a PREVIEW API. + * + * @see GitHub API - Licenses + * + * @return a list of popular open source licenses + */ + @Preview @Deprecated + public PagedIterable listLicenses() throws IOException { + return new PagedIterable() { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(retrieve().withPreview(DRAX).asIterator("/licenses", GHLicense[].class, pageSize)) { + @Override + protected void wrapUp(GHLicense[] page) { + for (GHLicense c : page) + c.wrap(GitHub.this); + } + }; + } + }; + } + + /** + * Returns a list of all users. + */ + public PagedIterable listUsers() throws IOException { + return new PagedIterable() { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(retrieve().asIterator("/users", GHUser[].class, pageSize)) { + @Override + protected void wrapUp(GHUser[] page) { + for (GHUser u : page) + u.wrapUp(GitHub.this); + } + }; + } + }; + } + + /** + * Returns the full details for a license + * + * WARNING: This uses a PREVIEW API. + * + * @param key The license key provided from the API + * @return The license details + * @see GHLicense#getKey() + */ + @Preview @Deprecated + public GHLicense getLicense(String key) throws IOException { + return retrieve().withPreview(DRAX).to("/licenses/" + key, GHLicense.class); + } + + /** + * Gets complete list of open invitations for current user. + */ + public List getMyInvitations() throws IOException { + GHInvitation[] invitations = retrieve().to("/user/repository_invitations", GHInvitation[].class); + for (GHInvitation i : invitations) { + i.wrapUp(this); + } + return Arrays.asList(invitations); + } /** * This method returns a shallowly populated organizations. @@ -395,17 +615,28 @@ public T parseEventPayload(Reader r, Class type) t /** * Creates a new repository. * - * To create a repository in an organization, see - * {@link GHOrganization#createRepository(String, String, String, GHTeam, boolean)} - * * @return * Newly created repository. + * @deprecated + * Use {@link #createRepository(String)} that uses a builder pattern to let you control every aspect. */ public GHRepository createRepository(String name, String description, String homepage, boolean isPublic) throws IOException { - Requester requester = new Requester(this) - .with("name", name).with("description", description).with("homepage", homepage) - .with("public", isPublic ? 1 : 0); - return requester.method("POST").to("/user/repos", GHRepository.class).wrap(this); + return createRepository(name).description(description).homepage(homepage).private_(!isPublic).create(); + } + + /** + * Starts a builder that creates a new repository. + * + *

+ * You use the returned builder to set various properties, then call {@link GHCreateRepositoryBuilder#create()} + * to finally createa repository. + * + *

+ * To create a repository in an organization, see + * {@link GHOrganization#createRepository(String, String, String, GHTeam, boolean)} + */ + public GHCreateRepositoryBuilder createRepository(String name) { + return new GHCreateRepositoryBuilder(this,"/user/repos",name); } /** @@ -424,6 +655,42 @@ public GHAuthorization createToken(Collection scope, String note, String return requester.method("POST").to("/authorizations", GHAuthorization.class).wrap(this); } + /** + * @see docs + */ + public GHAuthorization createOrGetAuth(String clientId, String clientSecret, List scopes, String note, + String note_url) + throws IOException { + Requester requester = new Requester(this) + .with("client_secret", clientSecret) + .with("scopes", scopes) + .with("note", note) + .with("note_url", note_url); + + return requester.method("PUT").to("/authorizations/clients/" + clientId, GHAuthorization.class); + } + + /** + * @see Delete an authorization + */ + public void deleteAuth(long id) throws IOException { + retrieve().method("DELETE").to("/authorizations/" + id); + } + + /** + * @see Check an authorization + */ + public GHAuthorization checkAuth(@Nonnull String clientId, @Nonnull String accessToken) throws IOException { + return retrieve().to("/applications/" + clientId + "/tokens/" + accessToken, GHAuthorization.class); + } + + /** + * @see Reset an authorization + */ + public GHAuthorization resetAuth(@Nonnull String clientId, @Nonnull String accessToken) throws IOException { + return retrieve().method("POST").to("/applications/" + clientId + "/tokens/" + accessToken, GHAuthorization.class); + } + /** * Ensures that the credential is valid. */ @@ -431,11 +698,111 @@ public boolean isCredentialValid() throws IOException { try { retrieve().to("/user", GHUser.class); return true; + } catch (IOException e) { + if (LOGGER.isLoggable(FINE)) + LOGGER.log(FINE, "Exception validating credentials on " + this.apiUrl + " with login '" + this.login + "' " + e, e); + return false; + } + } + + /*package*/ GHUser intern(GHUser user) throws IOException { + if (user==null) return user; + + // if we already have this user in our map, use it + GHUser u = users.get(user.getLogin()); + if (u!=null) return u; + + // if not, remember this new user + users.putIfAbsent(user.getLogin(),user); + return user; + } + + private static class GHApiInfo { + private String rate_limit_url; + + void check(String apiUrl) throws IOException { + if (rate_limit_url==null) + throw new IOException(apiUrl+" doesn't look like GitHub API URL"); + + // make sure that the URL is legitimate + new URL(rate_limit_url); + } + } + + /** + * Tests the connection. + * + *

+ * Verify that the API URL and credentials are valid to access this GitHub. + * + *

+ * This method returns normally if the endpoint is reachable and verified to be GitHub API URL. + * Otherwise this method throws {@link IOException} to indicate the problem. + */ + public void checkApiUrlValidity() throws IOException { + try { + retrieve().to("/", GHApiInfo.class).check(apiUrl); + } catch (IOException e) { + if (isPrivateModeEnabled()) { + throw (IOException)new IOException("GitHub Enterprise server (" + apiUrl + ") with private mode enabled").initCause(e); + } + throw e; + } + } + + /** + * Ensures if a GitHub Enterprise server is configured in private mode. + * + * @return {@code true} if private mode is enabled. If it tries to use this method with GitHub, returns {@code + * false}. + */ + private boolean isPrivateModeEnabled() { + try { + HttpURLConnection uc = getConnector().connect(getApiURL("/")); + /* + $ curl -i https://github.mycompany.com/api/v3/ + HTTP/1.1 401 Unauthorized + Server: GitHub.com + Date: Sat, 05 Mar 2016 19:45:01 GMT + Content-Type: application/json; charset=utf-8 + Content-Length: 130 + Status: 401 Unauthorized + X-GitHub-Media-Type: github.v3 + X-XSS-Protection: 1; mode=block + X-Frame-Options: deny + Content-Security-Policy: default-src 'none' + Access-Control-Allow-Credentials: true + Access-Control-Expose-Headers: ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval + Access-Control-Allow-Origin: * + X-GitHub-Request-Id: dbc70361-b11d-4131-9a7f-674b8edd0411 + Strict-Transport-Security: max-age=31536000; includeSubdomains; preload + X-Content-Type-Options: nosniff + */ + try { + return uc.getResponseCode() == HTTP_UNAUTHORIZED + && uc.getHeaderField("X-GitHub-Media-Type") != null; + } finally { + // ensure that the connection opened by getResponseCode gets closed + try { + IOUtils.closeQuietly(uc.getInputStream()); + } catch (IOException ignore) { + // ignore + } + IOUtils.closeQuietly(uc.getErrorStream()); + } } catch (IOException e) { return false; } } + /** + * Search commits. + */ + @Preview @Deprecated + public GHCommitSearchBuilder searchCommits() { + return new GHCommitSearchBuilder(this); + } + /** * Search issues. */ @@ -483,13 +850,13 @@ public PagedIterable listAllPublicRepositories() { * This provides a dump of every public repository, in the order that they were created. * * @param since - * The integer ID of the last Repository that you’ve seen. See {@link GHRepository#getId()} + * The numeric ID of the last Repository that you’ve seen. See {@link GHRepository#getId()} * @see documentation */ public PagedIterable listAllPublicRepositories(final String since) { return new PagedIterable() { - public PagedIterator iterator() { - return new PagedIterator(retrieve().with("since",since).asIterator("/repositories", GHRepository[].class)) { + public PagedIterator _iterator(int pageSize) { + return new PagedIterator(retrieve().with("since",since).asIterator("/repositories", GHRepository[].class, pageSize)) { @Override protected void wrapUp(GHRepository[] page) { for (GHRepository c : page) @@ -552,7 +919,10 @@ public Reader renderMarkdown(String text) throws IOException { static { MAPPER.setVisibilityChecker(new Std(NONE, NONE, NONE, NONE, ANY)); MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + MAPPER.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true); } /* package */ static final String GITHUB_URL = "https://api.github.com"; + + private static final Logger LOGGER = Logger.getLogger(GitHub.class.getName()); } diff --git a/src/main/java/org/kohsuke/github/GitHubBuilder.java b/src/main/java/org/kohsuke/github/GitHubBuilder.java index c0ecaffda5..e54359892c 100644 --- a/src/main/java/org/kohsuke/github/GitHubBuilder.java +++ b/src/main/java/org/kohsuke/github/GitHubBuilder.java @@ -1,6 +1,7 @@ package org.kohsuke.github; import org.apache.commons.io.IOUtils; +import org.kohsuke.github.extras.ImpatientHttpConnector; import java.io.File; import java.io.FileInputStream; @@ -14,7 +15,7 @@ import java.util.Properties; /** - * + * Configures connection details and produces {@link GitHub}. * * @since 1.59 */ @@ -29,6 +30,7 @@ public class GitHubBuilder { private HttpConnector connector; private RateLimitHandler rateLimitHandler = RateLimitHandler.WAIT; + private AbuseLimitHandler abuseLimitHandler = AbuseLimitHandler.WAIT; public GitHubBuilder() { } @@ -51,7 +53,7 @@ public static GitHubBuilder fromCredentials() throws IOException { try { builder = fromPropertyFile(); - if (builder.user != null) + if (builder.oauthToken != null || builder.user != null) return builder; } catch (FileNotFoundException e) { // fall through @@ -60,7 +62,7 @@ public static GitHubBuilder fromCredentials() throws IOException { builder = fromEnvironment(); - if (builder.user != null) + if (builder.oauthToken != null || builder.user != null) return builder; else throw (IOException)new IOException("Failed to resolve credentials from ~/.github or the environment.").initCause(cause); @@ -152,6 +154,12 @@ public static GitHubBuilder fromProperties(Properties props) { return self; } + /** + * @param endpoint + * 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 /api/v3 in the URL. + * For historical reasons, this parameter still accepts the bare domain name, but that's considered deprecated. + */ public GitHubBuilder withEndpoint(String endpoint) { this.endpoint = endpoint; return this; @@ -177,6 +185,10 @@ public GitHubBuilder withRateLimitHandler(RateLimitHandler handler) { this.rateLimitHandler = handler; return this; } + public GitHubBuilder withAbuseLimitHandler(AbuseLimitHandler handler) { + this.abuseLimitHandler = handler; + return this; + } /** * Configures {@linkplain #withConnector(HttpConnector) connector} @@ -184,14 +196,14 @@ public GitHubBuilder withRateLimitHandler(RateLimitHandler handler) { * the system default one. */ public GitHubBuilder withProxy(final Proxy p) { - return withConnector(new HttpConnector() { + return withConnector(new ImpatientHttpConnector(new HttpConnector() { public HttpURLConnection connect(URL url) throws IOException { return (HttpURLConnection) url.openConnection(p); } - }); + })); } public GitHub build() throws IOException { - return new GitHub(endpoint, user, oauthToken, password, connector, rateLimitHandler); + return new GitHub(endpoint, user, oauthToken, password, connector, rateLimitHandler, abuseLimitHandler); } } diff --git a/src/main/java/org/kohsuke/github/GitUser.java b/src/main/java/org/kohsuke/github/GitUser.java index fc97ef8882..9cd50bb22a 100644 --- a/src/main/java/org/kohsuke/github/GitUser.java +++ b/src/main/java/org/kohsuke/github/GitUser.java @@ -1,5 +1,7 @@ package org.kohsuke.github; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import java.util.Date; /** @@ -11,6 +13,8 @@ * * @author Kohsuke Kawaguchi */ +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "NP_UNWRITTEN_FIELD"}, justification = "JSON API") public class GitUser { private String name, email, date; diff --git a/src/main/java/org/kohsuke/github/HttpConnector.java b/src/main/java/org/kohsuke/github/HttpConnector.java index 6cff72dc53..2d87c148c9 100644 --- a/src/main/java/org/kohsuke/github/HttpConnector.java +++ b/src/main/java/org/kohsuke/github/HttpConnector.java @@ -1,5 +1,7 @@ package org.kohsuke.github; +import org.kohsuke.github.extras.ImpatientHttpConnector; + import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; @@ -21,9 +23,18 @@ public interface HttpConnector { /** * Default implementation that uses {@link URL#openConnection()}. */ - HttpConnector DEFAULT = new HttpConnector() { + HttpConnector DEFAULT = new ImpatientHttpConnector(new HttpConnector() { public HttpURLConnection connect(URL url) throws IOException { return (HttpURLConnection) url.openConnection(); } + }); + + /** + * Stub implementation that is always off-line. + */ + HttpConnector OFFLINE = new HttpConnector() { + public HttpURLConnection connect(URL url) throws IOException { + throw new IOException("Offline"); + } }; } diff --git a/src/main/java/org/kohsuke/github/HttpException.java b/src/main/java/org/kohsuke/github/HttpException.java new file mode 100644 index 0000000000..79def83c2a --- /dev/null +++ b/src/main/java/org/kohsuke/github/HttpException.java @@ -0,0 +1,117 @@ +package org.kohsuke.github; + +import javax.annotation.CheckForNull; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * {@link IOException} for http exceptions because {@link HttpURLConnection} throws un-discerned + * {@link IOException} and it can help to know the http response code to decide how to handle an + * http exceptions. + * + * @author Cyrille Le Clerc + */ +public class HttpException extends IOException { + static final long serialVersionUID = 1L; + + private final int responseCode; + private final String responseMessage; + private final String url; + + /** + * @param message The detail message (which is saved for later retrieval + * by the {@link #getMessage()} method) + * @param responseCode Http response code. {@code -1} if no code can be discerned. + * @param responseMessage Http response message + * @param url The url that was invoked + * @see HttpURLConnection#getResponseCode() + * @see HttpURLConnection#getResponseMessage() + */ + public HttpException(String message, int responseCode, String responseMessage, String url) { + super(message); + this.responseCode = responseCode; + this.responseMessage = responseMessage; + this.url = url; + } + + /** + * @param message The detail message (which is saved for later retrieval + * by the {@link #getMessage()} method) + * @param responseCode Http response code. {@code -1} if no code can be discerned. + * @param responseMessage Http response message + * @param url The url that was invoked + * @param cause The cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is permitted, + * and indicates that the cause is nonexistent or unknown.) + * @see HttpURLConnection#getResponseCode() + * @see HttpURLConnection#getResponseMessage() + */ + public HttpException(String message, int responseCode, String responseMessage, String url, Throwable cause) { + super(message); + initCause(cause); + this.responseCode = responseCode; + this.responseMessage = responseMessage; + this.url = url; + } + + /** + * @param responseCode Http response code. {@code -1} if no code can be discerned. + * @param responseMessage Http response message + * @param url The url that was invoked + * @param cause The cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is permitted, + * and indicates that the cause is nonexistent or unknown.) + * @see HttpURLConnection#getResponseCode() + * @see HttpURLConnection#getResponseMessage() + */ + public HttpException(int responseCode, String responseMessage, String url, Throwable cause) { + super("Server returned HTTP response code: " + responseCode + ", message: '" + responseMessage + "'" + + " for URL: " + url); + initCause(cause); + this.responseCode = responseCode; + this.responseMessage = responseMessage; + this.url = url; + } + + /** + * @param responseCode Http response code. {@code -1} if no code can be discerned. + * @param responseMessage Http response message + * @param url The url that was invoked + * @param cause The cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is permitted, + * and indicates that the cause is nonexistent or unknown.) + * @see HttpURLConnection#getResponseCode() + * @see HttpURLConnection#getResponseMessage() + */ + public HttpException(int responseCode, String responseMessage, @CheckForNull URL url, Throwable cause) { + this(responseCode, responseMessage, url == null ? null : url.toString(), cause); + } + + /** + * Http response code of the request that cause the exception + * + * @return {@code -1} if no code can be discerned. + */ + public int getResponseCode() { + return responseCode; + } + + /** + * Http response message of the request that cause the exception + * + * @return {@code null} if no response message can be discerned. + */ + public String getResponseMessage() { + return responseMessage; + } + + /** + * The http URL that caused the exception + * + * @return url + */ + public String getUrl() { + return url; + } +} diff --git a/src/main/java/org/kohsuke/github/PagedIterable.java b/src/main/java/org/kohsuke/github/PagedIterable.java index 8085e1ed53..1c6eccea36 100644 --- a/src/main/java/org/kohsuke/github/PagedIterable.java +++ b/src/main/java/org/kohsuke/github/PagedIterable.java @@ -11,7 +11,27 @@ * @author Kohsuke Kawaguchi */ public abstract class PagedIterable implements Iterable { - public abstract PagedIterator iterator(); + /** + * Page size. 0 is default. + */ + private int size = 0; + + /** + * Sets the pagination size. + * + *

+ * When set to non-zero, each API call will retrieve this many entries. + */ + public PagedIterable withPageSize(int size) { + this.size = size; + return this; + } + + public final PagedIterator iterator() { + return _iterator(size); + } + + public abstract PagedIterator _iterator(int pageSize); /** * Eagerly walk {@link Iterable} and return the result in a list. diff --git a/src/main/java/org/kohsuke/github/PagedIterator.java b/src/main/java/org/kohsuke/github/PagedIterator.java index 9149290911..aaa8182076 100644 --- a/src/main/java/org/kohsuke/github/PagedIterator.java +++ b/src/main/java/org/kohsuke/github/PagedIterator.java @@ -6,7 +6,7 @@ import java.util.NoSuchElementException; /** - * Iterator over a pagenated data source. + * Iterator over a paginated data source. * * Aside from the normal iterator operation, this method exposes {@link #nextPage()} * that allows the caller to retrieve items per page. diff --git a/src/main/java/org/kohsuke/github/PagedSearchIterable.java b/src/main/java/org/kohsuke/github/PagedSearchIterable.java index 65b6c7490d..f23bd6b58c 100644 --- a/src/main/java/org/kohsuke/github/PagedSearchIterable.java +++ b/src/main/java/org/kohsuke/github/PagedSearchIterable.java @@ -1,5 +1,7 @@ package org.kohsuke.github; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import java.util.Iterator; /** @@ -7,6 +9,8 @@ * * @author Kohsuke Kawaguchi */ +@SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "UWF_UNWRITTEN_FIELD", + "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR"}, justification = "Constructed by JSON API") public abstract class PagedSearchIterable extends PagedIterable { private final GitHub root; @@ -19,6 +23,11 @@ public abstract class PagedSearchIterable extends PagedIterable { this.root = root; } + @Override + public PagedSearchIterable withPageSize(int size) { + return (PagedSearchIterable)super.withPageSize(size); + } + /** * Returns the total number of hit, including the results that's not yet fetched. */ diff --git a/src/main/java/org/kohsuke/github/Preview.java b/src/main/java/org/kohsuke/github/Preview.java new file mode 100644 index 0000000000..2eeed60456 --- /dev/null +++ b/src/main/java/org/kohsuke/github/Preview.java @@ -0,0 +1,18 @@ +package org.kohsuke.github; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Indicates that the method/class/etc marked maps to GitHub API in the preview period. + * + * These APIs are subject to change and not a part of the backward compatibility commitment. + * Always used in conjunction with 'deprecated' to raise awareness to clients. + * + * @author Kohsuke Kawaguchi + */ +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Preview { +} diff --git a/src/main/java/org/kohsuke/github/Previews.java b/src/main/java/org/kohsuke/github/Previews.java new file mode 100644 index 0000000000..41ac7e155b --- /dev/null +++ b/src/main/java/org/kohsuke/github/Previews.java @@ -0,0 +1,12 @@ +package org.kohsuke.github; + +/** + * @author Kohsuke Kawaguchi + */ +/*package*/ class Previews { + static final String LUKE_CAGE = "application/vnd.github.luke-cage-preview+json"; + static final String DRAX = "application/vnd.github.drax-preview+json"; + static final String SQUIRREL_GIRL = "application/vnd.github.squirrel-girl-preview"; + static final String CLOAK = "application/vnd.github.cloak-preview"; + static final String ZZZAX = "application/vnd.github.zzzax-preview+json"; +} diff --git a/src/main/java/org/kohsuke/github/RateLimitHandler.java b/src/main/java/org/kohsuke/github/RateLimitHandler.java index b00624ce2b..e5351da541 100644 --- a/src/main/java/org/kohsuke/github/RateLimitHandler.java +++ b/src/main/java/org/kohsuke/github/RateLimitHandler.java @@ -9,6 +9,7 @@ * * @author Kohsuke Kawaguchi * @see GitHubBuilder#withRateLimitHandler(RateLimitHandler) + * @see AbuseLimitHandler */ public abstract class RateLimitHandler { /** @@ -36,7 +37,7 @@ public abstract class RateLimitHandler { public void onError(IOException e, HttpURLConnection uc) throws IOException { try { Thread.sleep(parseWaitTime(uc)); - } catch (InterruptedException _) { + } catch (InterruptedException x) { throw (InterruptedIOException)new InterruptedIOException().initCause(e); } } diff --git a/src/main/java/org/kohsuke/github/Reactable.java b/src/main/java/org/kohsuke/github/Reactable.java new file mode 100644 index 0000000000..d8821362cf --- /dev/null +++ b/src/main/java/org/kohsuke/github/Reactable.java @@ -0,0 +1,23 @@ +package org.kohsuke.github; + +import java.io.IOException; + +/** + * Those {@link GHObject}s that can have {@linkplain GHReaction reactions}. + * + * @author Kohsuke Kawaguchi + */ +@Preview @Deprecated +public interface Reactable { + /** + * List all the reactions left to this object. + */ + @Preview @Deprecated + PagedIterable listReactions(); + + /** + * Leaves a reaction to this object. + */ + @Preview @Deprecated + GHReaction createReaction(ReactionContent content) throws IOException; +} diff --git a/src/main/java/org/kohsuke/github/ReactionContent.java b/src/main/java/org/kohsuke/github/ReactionContent.java new file mode 100644 index 0000000000..57a204b56d --- /dev/null +++ b/src/main/java/org/kohsuke/github/ReactionContent.java @@ -0,0 +1,40 @@ +package org.kohsuke.github; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * Content of reactions. + * + * @author Kohsuke Kawaguchi + * @see API documentation + * @see GHReaction + */ +public enum ReactionContent { + PLUS_ONE("+1"), + MINUS_ONE("-1"), + LAUGH("laugh"), + CONFUSED("confused"), + HEART("heart"), + HOORAY("hooray"); + + private final String content; + + ReactionContent(String content) { + this.content = content; + } + + @JsonValue + public String getContent() { + return content; + } + + @JsonCreator + public static ReactionContent forContent(String content) { + for (ReactionContent c : ReactionContent.values()) { + if (c.getContent().equals(content)) + return c; + } + return null; + } +} diff --git a/src/main/java/org/kohsuke/github/Requester.java b/src/main/java/org/kohsuke/github/Requester.java index 960940e7cf..4670e311e5 100644 --- a/src/main/java/org/kohsuke/github/Requester.java +++ b/src/main/java/org/kohsuke/github/Requester.java @@ -24,8 +24,12 @@ package org.kohsuke.github; import com.fasterxml.jackson.databind.JsonMappingException; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import javax.annotation.CheckForNull; +import javax.annotation.WillClose; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -37,23 +41,30 @@ import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.ProtocolException; +import java.net.SocketTimeoutException; import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; -import java.util.Set; +import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; -import static org.kohsuke.github.GitHub.*; +import static java.util.Arrays.asList; +import static java.util.logging.Level.FINE; +import static java.util.logging.Level.FINEST; +import static java.util.logging.Level.INFO; +import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.kohsuke.github.GitHub.MAPPER; /** * A builder pattern for making HTTP call and parsing its output. @@ -69,13 +80,14 @@ class Requester { * Request method. */ private String method = "POST"; - private String contentType = "application/x-www-form-urlencoded"; + private String contentType = null; private InputStream body; /** * Current connection. */ private HttpURLConnection uc; + private boolean forceBody; private static class Entry { String key; @@ -100,6 +112,15 @@ public void setHeader(String name, String value) { headers.put(name,value); } + public Requester withHeader(String name, String value) { + setHeader(name,value); + return this; + } + + /*package*/ Requester withPreview(String name) { + return withHeader("Accept",name); + } + /** * Makes a request with authentication credential. */ @@ -114,9 +135,13 @@ public Requester with(String key, int value) { return _with(key, value); } + public Requester with(String key, long value) { + return _with(key, value); + } + public Requester with(String key, Integer value) { if (value!=null) - _with(key, value.intValue()); + _with(key, value); return this; } @@ -127,6 +152,14 @@ public Requester with(String key, Boolean value) { return _with(key, value); } + public Requester with(String key, Enum e) { + if (e==null) return _with(key, null); + + // by convention Java constant names are upper cases, but github uses + // lower-case constants. GitHub also uses '-', which in Java we always + // replace by '_' + return with(key, e.toString().toLowerCase(Locale.ENGLISH).replace('_', '-')); + } public Requester with(String key, String value) { return _with(key, value); @@ -136,15 +169,28 @@ public Requester with(String key, Collection value) { return _with(key, value); } + public Requester withLogins(String key, Collection users) { + List names = new ArrayList(users.size()); + for (GHUser a : users) { + names.add(a.getLogin()); + } + return with(key,names); + } + public Requester with(String key, Map value) { return _with(key, value); } - public Requester with(InputStream body) { + public Requester with(@WillClose/*later*/ InputStream body) { this.body = body; return this; } + public Requester withNullable(String key, Object value) { + args.add(new Entry(key, value)); + return this; + } + public Requester _with(String key, Object value) { if (value!=null) { args.add(new Entry(key,value)); @@ -175,6 +221,16 @@ public Requester contentType(String contentType) { return this; } + /** + * Small number of GitHub APIs use HTTP methods somewhat inconsistently, and use a body where it's not expected. + * Normally whether parameters go as query parameters or a body depends on the HTTP verb in use, + * but this method forces the parameters to be sent as a body. + */ + /*package*/ Requester inBody() { + forceBody = true; + return this; + } + public void to(String tailApiUrl) throws IOException { to(tailApiUrl,null); } @@ -203,19 +259,24 @@ public T to(String tailApiUrl, T existingInstance) throws IOException { */ @Deprecated public T to(String tailApiUrl, Class type, String method) throws IOException { - return method(method).to(tailApiUrl,type); + return method(method).to(tailApiUrl, type); } + @SuppressFBWarnings("SBSC_USE_STRINGBUFFER_CONCATENATION") 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()) { - StringBuilder qs=new StringBuilder(); - for (Entry arg : args) { - qs.append(qs.length()==0 ? '?' : '&'); - qs.append(arg.key).append('=').append(URLEncoder.encode(arg.value.toString(),"UTF-8")); + if (!isMethodWithBody() && !args.isEmpty()) { + boolean questionMarkFound = tailApiUrl.indexOf('?') != -1; + tailApiUrl += questionMarkFound ? '&' : '?'; + for (Iterator it = args.listIterator(); it.hasNext();) { + Entry arg = it.next(); + tailApiUrl += arg.key + '=' + URLEncoder.encode(arg.value.toString(),"UTF-8"); + if (it.hasNext()) { + tailApiUrl += '&'; } - tailApiUrl += qs.toString(); } + } + + while (true) {// loop while API rate limit is hit setupConnection(root.getApiURL(tailApiUrl)); buildRequest(); @@ -230,7 +291,7 @@ private T _to(String tailApiUrl, Class type, T instance) throws IOExcepti if (nextLinkMatcher.find()) { final String link = nextLinkMatcher.group(1); T nextResult = _to(link, type, instance); - + setResponseHeaders(nextResult); final int resultLength = Array.getLength(result); final int nextResultLength = Array.getLength(nextResult); T concatResult = (T) Array.newInstance(type.getComponentType(), resultLength + nextResultLength); @@ -240,9 +301,11 @@ private T _to(String tailApiUrl, Class type, T instance) throws IOExcepti } } } - return result; + return setResponseHeaders(result); } catch (IOException e) { handleApiError(e); + } finally { + noteRateLimit(tailApiUrl); } } } @@ -252,6 +315,7 @@ private T _to(String tailApiUrl, Class type, T instance) throws IOExcepti */ public int asHttpStatusCode(String tailApiUrl) throws IOException { while (true) {// loop while API rate limit is hit + method("GET"); setupConnection(root.getApiURL(tailApiUrl)); buildRequest(); @@ -260,6 +324,8 @@ public int asHttpStatusCode(String tailApiUrl) throws IOException { return uc.getResponseCode(); } catch (IOException e) { handleApiError(e); + } finally { + noteRateLimit(tailApiUrl); } } } @@ -274,6 +340,59 @@ public InputStream asStream(String tailApiUrl) throws IOException { return wrapStream(uc.getInputStream()); } catch (IOException e) { handleApiError(e); + } finally { + noteRateLimit(tailApiUrl); + } + } + } + + private void noteRateLimit(String tailApiUrl) { + if ("/rate_limit".equals(tailApiUrl)) { + // the rate_limit API is "free" + return; + } + if (tailApiUrl.startsWith("/search")) { + // the search API uses a different rate limit + return; + } + String limit = uc.getHeaderField("X-RateLimit-Limit"); + if (StringUtils.isBlank(limit)) { + // if we are missing a header, return fast + return; + } + String remaining = uc.getHeaderField("X-RateLimit-Remaining"); + if (StringUtils.isBlank(remaining)) { + // if we are missing a header, return fast + return; + } + String reset = uc.getHeaderField("X-RateLimit-Reset"); + if (StringUtils.isBlank(reset)) { + // if we are missing a header, return fast + return; + } + GHRateLimit observed = new GHRateLimit(); + try { + observed.limit = Integer.parseInt(limit); + } catch (NumberFormatException e) { + if (LOGGER.isLoggable(FINEST)) { + LOGGER.log(FINEST, "Malformed X-RateLimit-Limit header value " + limit, e); + } + return; + } + try { + observed.remaining = Integer.parseInt(remaining); + } catch (NumberFormatException e) { + if (LOGGER.isLoggable(FINEST)) { + LOGGER.log(FINEST, "Malformed X-RateLimit-Remaining header value " + remaining, e); + } + return; + } + try { + observed.reset = new Date(Long.parseLong(reset)); // this is madness, storing the date as seconds + root.updateRateLimit(observed); + } catch (NumberFormatException e) { + if (LOGGER.isLoggable(FINEST)) { + LOGGER.log(FINEST, "Malformed X-RateLimit-Reset header value " + reset, e); } } } @@ -287,20 +406,21 @@ 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); if (body == null) { + uc.setRequestProperty("Content-type", defaultString(contentType,"application/json")); Map json = new HashMap(); for (Entry e : args) { json.put(e.key, e.value); } MAPPER.writeValue(uc.getOutputStream(), json); } else { + uc.setRequestProperty("Content-type", defaultString(contentType,"application/x-www-form-urlencoded")); try { byte[] bytes = new byte[32768]; - int read = 0; + int read; while ((read = body.read(bytes)) != -1) { uc.getOutputStream().write(bytes, 0, read); } @@ -311,106 +431,125 @@ private void buildRequest() throws IOException { } } + private boolean isMethodWithBody() { + return forceBody || !METHODS_WITHOUT_BODY.contains(method); + } + /** - * Loads pagenated resources. + * Loads paginated resources. * * Every iterator call reports a new batch. */ - /*package*/ Iterator asIterator(String _tailApiUrl, final Class type) { + /*package*/ Iterator asIterator(String tailApiUrl, Class type, int pageSize) { method("GET"); + if (pageSize!=0) + args.add(new Entry("per_page",pageSize)); + + StringBuilder s = new StringBuilder(tailApiUrl); if (!args.isEmpty()) { - boolean first=true; + boolean first = true; try { for (Entry a : args) { - _tailApiUrl += first ? '?' : '&'; + s.append(first ? '?' : '&'); first = false; - _tailApiUrl += URLEncoder.encode(a.key,"UTF-8")+'='+URLEncoder.encode(a.value.toString(),"UTF-8"); + s.append(URLEncoder.encode(a.key, "UTF-8")); + s.append('='); + s.append(URLEncoder.encode(a.value.toString(), "UTF-8")); } } catch (UnsupportedEncodingException e) { throw new AssertionError(e); // UTF-8 is mandatory } } - final String tailApiUrl = _tailApiUrl; + try { + return new PagingIterator(type, tailApiUrl, root.getApiURL(s.toString())); + } catch (IOException e) { + throw new GHException("Unable to build github Api URL",e); + } + } - return new Iterator() { - /** - * The next batch to be returned from {@link #next()}. - */ - T next; - /** - * URL of the next resource to be retrieved, or null if no more data is available. - */ - URL url; + class PagingIterator implements Iterator { - { - try { - url = root.getApiURL(tailApiUrl); - } catch (IOException e) { - throw new Error(e); - } - } + private final Class type; + private final String tailApiUrl; - public boolean hasNext() { - fetch(); - return next!=null; - } + /** + * The next batch to be returned from {@link #next()}. + */ + private T next; - public T next() { - fetch(); - T r = next; - if (r==null) throw new NoSuchElementException(); - next = null; - return r; - } + /** + * URL of the next resource to be retrieved, or null if no more data is available. + */ + private URL url; - public void remove() { - throw new UnsupportedOperationException(); - } + PagingIterator(Class type, String tailApiUrl, URL url) { + this.type = type; + this.tailApiUrl = tailApiUrl; + this.url = url; + } - private void fetch() { - if (next!=null) return; // already fetched - if (url==null) return; // no more data to fetch + public boolean hasNext() { + fetch(); + return next!=null; + } - try { - while (true) {// loop while API rate limit is hit - setupConnection(url); - try { - next = parse(type,null); - assert next!=null; - findNextURL(); - return; - } catch (IOException e) { - handleApiError(e); - } - } - } catch (IOException e) { - throw new Error(e); - } - } + public T next() { + fetch(); + T r = next; + if (r==null) throw new NoSuchElementException(); + next = null; + return r; + } + + public void remove() { + throw new UnsupportedOperationException(); + } - /** - * Locate the next page from the pagination "Link" tag. - */ - private void findNextURL() throws MalformedURLException { - url = null; // start defensively - String link = uc.getHeaderField("Link"); - if (link==null) return; - - for (String token : link.split(", ")) { - if (token.endsWith("rel=\"next\"")) { - // found the next page. This should look something like - // ; rel="next" - int idx = token.indexOf('>'); - url = new URL(token.substring(1,idx)); + 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 + setupConnection(url); + try { + next = parse(type,null); + assert next!=null; + findNextURL(); return; + } catch (IOException e) { + handleApiError(e); + } finally { + noteRateLimit(tailApiUrl); } } + } catch (IOException e) { + throw new GHException("Failed to retrieve "+url); + } + } - // no more "next" link. we are done. + /** + * Locate the next page from the pagination "Link" tag. + */ + private void findNextURL() throws MalformedURLException { + url = null; // start defensively + String link = uc.getHeaderField("Link"); + if (link==null) return; + + for (String token : link.split(", ")) { + if (token.endsWith("rel=\"next\"")) { + // found the next page. This should look something like + // ; rel="next" + int idx = token.indexOf('>'); + url = new URL(token.substring(1,idx)); + return; + } } - }; + + // no more "next" link. we are done. + } } @@ -428,6 +567,11 @@ private void setupConnection(URL url) throws IOException { uc.setRequestProperty(e.getKey(), v); } + setRequestMethod(uc); + uc.setRequestProperty("Accept-Encoding", "gzip"); + } + + private void setRequestMethod(HttpURLConnection uc) throws IOException { try { uc.setRequestMethod(method); } catch (ProtocolException e) { @@ -439,31 +583,87 @@ private void setupConnection(URL url) throws IOException { } catch (Exception x) { throw (IOException)new IOException("Failed to set the custom verb").initCause(x); } + // sun.net.www.protocol.https.DelegatingHttpsURLConnection delegates to another HttpURLConnection + try { + Field $delegate = uc.getClass().getDeclaredField("delegate"); + $delegate.setAccessible(true); + Object delegate = $delegate.get(uc); + if (delegate instanceof HttpURLConnection) { + HttpURLConnection nested = (HttpURLConnection) delegate; + setRequestMethod(nested); + } + } catch (NoSuchFieldException x) { + // no problem + } catch (IllegalAccessException x) { + throw (IOException)new IOException("Failed to set the custom verb").initCause(x); + } } - uc.setRequestProperty("Accept-Encoding", "gzip"); + if (!uc.getRequestMethod().equals(method)) + throw new IllegalStateException("Failed to set the request method to "+method); } + @CheckForNull private T parse(Class type, T instance) throws IOException { - if (uc.getResponseCode()==304) - return null; // special case handling for 304 unmodified, as the content will be "" + return parse(type, instance, 2); + } + + private T parse(Class type, T instance, int timeouts) throws IOException { InputStreamReader r = null; + int responseCode = -1; + String responseMessage = null; try { + responseCode = uc.getResponseCode(); + responseMessage = uc.getResponseMessage(); + if (responseCode == 304) { + return null; // special case handling for 304 unmodified, as the content will be "" + } + if (responseCode == 204 && type!=null && type.isArray()) { + // no content + return type.cast(Array.newInstance(type.getComponentType(),0)); + } + r = new InputStreamReader(wrapStream(uc.getInputStream()), "UTF-8"); String data = IOUtils.toString(r); if (type!=null) try { - return MAPPER.readValue(data,type); + return setResponseHeaders(MAPPER.readValue(data, type)); } catch (JsonMappingException e) { - throw (IOException)new IOException("Failed to deserialize "+data).initCause(e); + throw (IOException)new IOException("Failed to deserialize " +data).initCause(e); } - if (instance!=null) - return MAPPER.readerForUpdating(instance).readValue(data); + if (instance!=null) { + return setResponseHeaders(MAPPER.readerForUpdating(instance).readValue(data)); + } return null; + } catch (FileNotFoundException e) { + // java.net.URLConnection handles 404 exception has FileNotFoundException, don't wrap exception in HttpException + // to preserve backward compatibility + throw e; + } catch (IOException e) { + if (e instanceof SocketTimeoutException && timeouts > 0) { + LOGGER.log(INFO, "timed out accessing " + uc.getURL() + "; will try " + timeouts + " more time(s)", e); + return parse(type, instance, timeouts - 1); + } + throw new HttpException(responseCode, responseMessage, uc.getURL(), e); } finally { IOUtils.closeQuietly(r); } } + private T setResponseHeaders(T readValue) { + if (readValue instanceof GHObject[]) { + for (GHObject ghObject : (GHObject[]) readValue) { + setResponseHeaders(ghObject); + } + } else if (readValue instanceof GHObject) { + setResponseHeaders((GHObject) readValue); + } + return readValue; + } + + private void setResponseHeaders(GHObject readValue) { + readValue.responseHeaderFields = uc.getHeaderFields(); + } + /** * Handles the "Content-Encoding" header. */ @@ -479,32 +679,53 @@ private InputStream wrapStream(InputStream in) throws IOException { * Handle API error by either throwing it or by returning normally to retry. */ /*package*/ void handleApiError(IOException e) throws IOException { - if (uc.getResponseCode() == 401) // Unauthorized == bad creds + int responseCode; + try { + responseCode = uc.getResponseCode(); + } catch (IOException e2) { + // likely to be a network exception (e.g. SSLHandshakeException), + // uc.getResponseCode() and any other getter on the response will cause an exception + if (LOGGER.isLoggable(FINE)) + LOGGER.log(FINE, "Silently ignore exception retrieving response code for '" + uc.getURL() + "'" + + " handling exception " + e, e); + throw e; + } + InputStream es = wrapStream(uc.getErrorStream()); + if (es != null) { + try { + String error = IOUtils.toString(es, "UTF-8"); + if (e instanceof FileNotFoundException) { + // pass through 404 Not Found to allow the caller to handle it intelligently + e = (IOException) new GHFileNotFoundException(error).withResponseHeaderFields(uc).initCause(e); + } else if (e instanceof HttpException) { + HttpException http = (HttpException) e; + e = new HttpException(error, http.getResponseCode(), http.getResponseMessage(), + http.getUrl(), e); + } else { + e = (IOException) new GHIOException(error).withResponseHeaderFields(uc).initCause(e); + } + } finally { + IOUtils.closeQuietly(es); + } + } + if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) // 401 / Unauthorized == bad creds throw e; if ("0".equals(uc.getHeaderField("X-RateLimit-Remaining"))) { root.rateLimitHandler.onError(e,uc); + return; } - InputStream es = wrapStream(uc.getErrorStream()); - try { - if (es!=null) { - if (e instanceof FileNotFoundException) { - // pass through 404 Not Found to allow the caller to handle it intelligently - throw (IOException) new FileNotFoundException(IOUtils.toString(es, "UTF-8")).initCause(e); - } else - throw (IOException) new IOException(IOUtils.toString(es, "UTF-8")).initCause(e); - } else - throw e; - } finally { - IOUtils.closeQuietly(es); + // Retry-After is not documented but apparently that field exists + if (responseCode == HttpURLConnection.HTTP_FORBIDDEN && + uc.getHeaderField("Retry-After") != null) { + this.root.abuseLimitHandler.onError(e,uc); + return; } - } - private Set toSet(String s) { - Set r = new HashSet(); - for (String t : s.split(",")) - r.add(t.trim()); - return r; + throw e; } + + private static final List METHODS_WITHOUT_BODY = asList("GET", "DELETE"); + private static final Logger LOGGER = Logger.getLogger(Requester.class.getName()); } diff --git a/src/main/java/org/kohsuke/github/SearchResult.java b/src/main/java/org/kohsuke/github/SearchResult.java index a8a7583a4c..2fb2dca06e 100644 --- a/src/main/java/org/kohsuke/github/SearchResult.java +++ b/src/main/java/org/kohsuke/github/SearchResult.java @@ -1,12 +1,17 @@ package org.kohsuke.github; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + /** * Represents the result of a search * * @author Kohsuke Kawaguchi */ abstract class SearchResult { + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") int total_count; + + @SuppressFBWarnings(value = "UWF_UNWRITTEN_FIELD", justification = "Field comes from JSON deserialization") boolean incomplete_results; /** diff --git a/src/main/java/org/kohsuke/github/SkipFromToString.java b/src/main/java/org/kohsuke/github/SkipFromToString.java new file mode 100644 index 0000000000..67027a5e33 --- /dev/null +++ b/src/main/java/org/kohsuke/github/SkipFromToString.java @@ -0,0 +1,16 @@ +package org.kohsuke.github; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Ignores this field for {@link GHObject#toString()} + * + * @author Kohsuke Kawaguchi + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@interface SkipFromToString { +} diff --git a/src/main/java/org/kohsuke/github/TrafficInfo.java b/src/main/java/org/kohsuke/github/TrafficInfo.java new file mode 100644 index 0000000000..9b232e9f1a --- /dev/null +++ b/src/main/java/org/kohsuke/github/TrafficInfo.java @@ -0,0 +1,16 @@ +package org.kohsuke.github; + +/** + * @author Kohsuke Kawaguchi + */ +public interface TrafficInfo { + /** + * Total count of hits. + */ + int getCount(); + + /** + * Unique visitors. + */ + int getUniques(); +} diff --git a/src/main/java/org/kohsuke/github/extras/ImpatientHttpConnector.java b/src/main/java/org/kohsuke/github/extras/ImpatientHttpConnector.java new file mode 100644 index 0000000000..740b9a037d --- /dev/null +++ b/src/main/java/org/kohsuke/github/extras/ImpatientHttpConnector.java @@ -0,0 +1,58 @@ +package org.kohsuke.github.extras; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.kohsuke.github.HttpConnector; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.concurrent.TimeUnit; + +/** + * {@link HttpConnector} wrapper that sets timeout + * + * @author Kohsuke Kawaguchi + */ +public class ImpatientHttpConnector implements HttpConnector { + private final HttpConnector base; + private final int readTimeout, connectTimeout; + + /** + * @param connectTimeout + * HTTP connection timeout in milliseconds + * @param readTimeout + * HTTP read timeout in milliseconds + */ + public ImpatientHttpConnector(HttpConnector base, int connectTimeout, int readTimeout) { + this.base = base; + this.connectTimeout = connectTimeout; + this.readTimeout = readTimeout; + } + + public ImpatientHttpConnector(HttpConnector base, int timeout) { + this(base,timeout,timeout); + } + + public ImpatientHttpConnector(HttpConnector base) { + this(base,CONNECT_TIMEOUT,READ_TIMEOUT); + } + + public HttpURLConnection connect(URL url) throws IOException { + HttpURLConnection con = base.connect(url); + con.setConnectTimeout(connectTimeout); + con.setReadTimeout(readTimeout); + return con; + } + + /** + * Default connection timeout in milliseconds + */ + @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + public static int CONNECT_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(10); + + /** + * Default read timeout in milliseconds + */ + @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + public static int READ_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(10); +} diff --git a/src/main/java/org/kohsuke/github/extras/OkHttp3Connector.java b/src/main/java/org/kohsuke/github/extras/OkHttp3Connector.java new file mode 100644 index 0000000000..d2fd8c6978 --- /dev/null +++ b/src/main/java/org/kohsuke/github/extras/OkHttp3Connector.java @@ -0,0 +1,32 @@ +package org.kohsuke.github.extras; + +import okhttp3.OkHttpClient; +import okhttp3.OkUrlFactory; +import org.kohsuke.github.HttpConnector; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * {@link HttpConnector} for {@link OkHttpClient}. + * + * Unlike {@link #DEFAULT}, OkHttp does response caching. + * Making a conditional request against GitHubAPI and receiving a 304 + * response does not count against the rate limit. + * See http://developer.github.com/v3/#conditional-requests + * + * @author Roberto Tyley + * @author Kohsuke Kawaguchi + */ +public class OkHttp3Connector implements HttpConnector { + private final OkUrlFactory urlFactory; + + public OkHttp3Connector(OkUrlFactory urlFactory) { + this.urlFactory = urlFactory; + } + + public HttpURLConnection connect(URL url) throws IOException { + return urlFactory.open(url); + } +} diff --git a/src/main/java/org/kohsuke/github/extras/OkHttpConnector.java b/src/main/java/org/kohsuke/github/extras/OkHttpConnector.java index faa06b0d3b..e7802c6bae 100644 --- a/src/main/java/org/kohsuke/github/extras/OkHttpConnector.java +++ b/src/main/java/org/kohsuke/github/extras/OkHttpConnector.java @@ -1,13 +1,25 @@ package org.kohsuke.github.extras; +import com.squareup.okhttp.ConnectionSpec; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.OkUrlFactory; + import org.kohsuke.github.HttpConnector; import java.io.IOException; + import java.net.HttpURLConnection; import java.net.URL; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; + +import java.util.Arrays; +import java.util.List; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; + /** * {@link HttpConnector} for {@link OkHttpClient}. * @@ -23,10 +35,33 @@ public class OkHttpConnector implements HttpConnector { private final OkUrlFactory urlFactory; public OkHttpConnector(OkUrlFactory urlFactory) { + urlFactory.client().setSslSocketFactory(TlsSocketFactory()); + urlFactory.client().setConnectionSpecs(TlsConnectionSpecs()); this.urlFactory = urlFactory; } public HttpURLConnection connect(URL url) throws IOException { return urlFactory.open(url); } + + /** Returns TLSv1.2 only SSL Socket Factory. */ + private SSLSocketFactory TlsSocketFactory() { + SSLContext sc; + try { + sc = SSLContext.getInstance("TLSv1.2"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e.getMessage(), e); + } + try { + sc.init(null, null, null); + return sc.getSocketFactory(); + } catch (KeyManagementException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + /** Returns connection spec with TLS v1.2 in it */ + private List TlsConnectionSpecs() { + return Arrays.asList(ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT); + } } diff --git a/src/test/java/Foo.java b/src/test/java/Foo.java index 5df13995c5..540f9fd3ca 100644 --- a/src/test/java/Foo.java +++ b/src/test/java/Foo.java @@ -1,17 +1,22 @@ -import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHRepository.Contributor; +import org.kohsuke.github.GHUser; import org.kohsuke.github.GitHub; -import java.util.Collection; - /** * @author Kohsuke Kawaguchi */ public class Foo { public static void main(String[] args) throws Exception { - Collection lst = GitHub.connect().getUser("kohsuke").getRepositories().values(); - for (GHRepository r : lst) { - System.out.println(r.getName()); + GitHub gh = GitHub.connect(); + for (Contributor c : gh.getRepository("kohsuke/yo").listContributors()) { + System.out.println(c); + } + } + + private static void testRateLimit() throws Exception { + GitHub g = GitHub.connectAnonymously(); + for (GHUser u : g.getOrganization("jenkinsci").listMembers()) { + u.getFollowersCount(); } - System.out.println(lst.size()); } } diff --git a/src/test/java/org/kohsuke/github/AbstractGitHubApiTestBase.java b/src/test/java/org/kohsuke/github/AbstractGitHubApiTestBase.java index f1753481e7..a7afb38fd7 100644 --- a/src/test/java/org/kohsuke/github/AbstractGitHubApiTestBase.java +++ b/src/test/java/org/kohsuke/github/AbstractGitHubApiTestBase.java @@ -1,10 +1,15 @@ package org.kohsuke.github; +import java.io.FileInputStream; +import java.util.Properties; +import org.apache.commons.io.IOUtils; import org.junit.Assert; +import org.junit.Assume; import org.junit.Before; import org.kohsuke.randname.RandomNameGenerator; import java.io.File; +import java.io.IOException; /** * @author Kohsuke Kawaguchi @@ -17,13 +22,34 @@ public abstract class AbstractGitHubApiTestBase extends Assert { public void setUp() throws Exception { File f = new File(System.getProperty("user.home"), ".github.kohsuke2"); if (f.exists()) { + Properties props = new Properties(); + FileInputStream in = null; + try { + in = new FileInputStream(f); + props.load(in); + } finally { + IOUtils.closeQuietly(in); + } // use the non-standard credential preferentially, so that developers of this library do not have // to clutter their event stream. - gitHub = GitHubBuilder.fromPropertyFile(f.getPath()).withRateLimitHandler(RateLimitHandler.FAIL).build(); + gitHub = GitHubBuilder.fromProperties(props).withRateLimitHandler(RateLimitHandler.FAIL).build(); } else { gitHub = GitHubBuilder.fromCredentials().withRateLimitHandler(RateLimitHandler.FAIL).build(); } } + protected GHUser getUser() { + try { + return gitHub.getMyself(); + } catch (IOException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + protected void kohsuke() { + String login = getUser().getLogin(); + Assume.assumeTrue(login.equals("kohsuke") || login.equals("kohsuke2")); + } + protected static final RandomNameGenerator rnd = new RandomNameGenerator(); } diff --git a/src/test/java/org/kohsuke/github/AppTest.java b/src/test/java/org/kohsuke/github/AppTest.java index 5cdb17e987..807e8ca7c6 100755 --- a/src/test/java/org/kohsuke/github/AppTest.java +++ b/src/test/java/org/kohsuke/github/AppTest.java @@ -5,17 +5,19 @@ import com.google.common.collect.Lists; import org.apache.commons.io.IOUtils; -import org.junit.Assume; import org.junit.Test; import org.kohsuke.github.GHCommit.File; import org.kohsuke.github.GHOrganization.Permission; import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.util.*; import java.util.Map.Entry; import java.util.regex.Pattern; +import static org.hamcrest.CoreMatchers.*; + /** * Unit test for simple App. */ @@ -38,6 +40,22 @@ public void testRepoCRUD() throws Exception { getUser().getRepository(targetName).delete(); } + @Test + public void testRepositoryWithAutoInitializationCRUD() throws Exception { + String name = "github-api-test-autoinit"; + deleteRepository(name); + GHRepository r = gitHub.createRepository(name) + .description("a test repository for auto init") + .homepage("http://github-api.kohsuke.org/") + .autoInit(true).create(); + r.enableIssueTracker(false); + r.enableDownloads(false); + r.enableWiki(false); + Thread.sleep(3000); + assertNotNull(r.getReadme()); + getUser().getRepository(name).delete(); + } + private void deleteRepository(final String name) throws IOException { GHRepository repository = getUser().getRepository(name); if(repository != null) { @@ -116,10 +134,10 @@ public void testGetDeploymentStatuses() throws IOException { .description("question") .payload("{\"user\":\"atmos\",\"room_id\":123456}") .create(); - GHDeploymentStatus ghDeploymentStatus = repository.createDeployStatus(deployment.getId(), GHDeploymentState.SUCCESS) + GHDeploymentStatus ghDeploymentStatus = deployment.createStatus(GHDeploymentState.SUCCESS) .description("success") .targetUrl("http://www.github.com").create(); - Iterable deploymentStatuses = repository.getDeploymentStatuses(deployment.getId()); + Iterable deploymentStatuses = deployment.listStatuses(); assertNotNull(deploymentStatuses); assertEquals(1,Iterables.size(deploymentStatuses)); assertEquals(ghDeploymentStatus.getId(), Iterables.get(deploymentStatuses, 0).getId()); @@ -152,14 +170,6 @@ private GHRepository getTestRepository() throws IOException { return repository; } - private GHUser getUser() { - try { - return gitHub.getMyself(); - } catch (IOException e) { - throw new RuntimeException(e.getMessage(), e); - } - } - @Test public void testListIssues() throws IOException { GHUser u = getUser(); @@ -206,10 +216,12 @@ public void testMyOrganizations() throws IOException { } @Test - public void testMyTeamsContainsAllMyOrganizations() throws IOException { + public void testMyOrganizationsContainMyTeams() throws IOException { Map> teams = gitHub.getMyTeams(); Map myOrganizations = gitHub.getMyOrganizations(); - assertEquals(teams.keySet(), myOrganizations.keySet()); + //GitHub no longer has default 'owners' team, so there may be organization memberships without a team + //https://help.github.com/articles/about-improved-organization-permissions/ + assertTrue(myOrganizations.keySet().containsAll(teams.keySet())); } @Test @@ -286,12 +298,13 @@ public void testOrgFork() throws Exception { @Test public void testGetTeamsForRepo() throws Exception { kohsuke(); - assertEquals(1, gitHub.getOrganization("github-api-test-org").getRepository("testGetTeamsForRepo").getTeams().size()); + // 'Core Developers' and 'Owners' + assertEquals(2, gitHub.getOrganization("github-api-test-org").getRepository("testGetTeamsForRepo").getTeams().size()); } @Test public void testMembership() throws Exception { - Set members = gitHub.getOrganization("jenkinsci").getRepository("violations-plugin").getCollaboratorNames(); + Set members = gitHub.getOrganization("github-api-test-org").getRepository("jenkins").getCollaboratorNames(); System.out.println(members.contains("kohsuke")); } @@ -319,17 +332,31 @@ public void testOrgTeamByName() throws Exception { assertNotNull(e); } + @Test + public void testOrgTeamBySlug() throws Exception { + kohsuke(); + GHTeam e = gitHub.getOrganization("github-api-test-org").getTeamBySlug("core-developers"); + assertNotNull(e); + } + @Test public void testCommit() throws Exception { GHCommit commit = gitHub.getUser("jenkinsci").getRepository("jenkins").getCommit("08c1c9970af4d609ae754fbe803e06186e3206f7"); System.out.println(commit); assertEquals(1, commit.getParents().size()); assertEquals(1,commit.getFiles().size()); + assertEquals("https://github.com/jenkinsci/jenkins/commit/08c1c9970af4d609ae754fbe803e06186e3206f7", + commit.getHtmlUrl().toString()); File f = commit.getFiles().get(0); assertEquals(48,f.getLinesChanged()); assertEquals("modified",f.getStatus()); assertEquals("changelog.html", f.getFileName()); + + // walk the tree + GHTree t = commit.getTree(); + assertThat(IOUtils.toString(t.getEntry("todo.txt").readAsBlob()), containsString("executor rendering")); + assertNotNull(t.getEntry("war").asTree()); } @Test @@ -351,7 +378,7 @@ public void testQueryCommits() throws Exception { sha1.add(c.getSHA1()); } assertEquals("1cccddb22e305397151b2b7b87b4b47d74ca337b",sha1.get(0)); - assertEquals(29,sha1.size()); + assertEquals(29, sha1.size()); } @Test @@ -574,6 +601,8 @@ public void testCreateRelease() throws Exception { .prerelease(false) .create(); + Thread.sleep(3000); + try { for (GHTag tag : r.listTags()) { @@ -618,7 +647,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 +665,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()); @@ -653,6 +682,15 @@ public void testMemberPagenation() throws IOException { assertFalse(all.isEmpty()); } + @Test + public void testCommitSearch() throws IOException { + PagedSearchIterable r = gitHub.searchCommits().author("kohsuke").list(); + assertTrue(r.getTotalCount() > 0); + + GHCommit firstCommit = r.iterator().next(); + assertTrue(firstCommit.getFiles().size() > 0); + } + @Test public void testIssueSearch() throws IOException { PagedSearchIterable r = gitHub.searchIssues().mentions("kohsuke").isOpen().list(); @@ -715,6 +753,10 @@ public void testRepoLabel() throws IOException { assertEquals(t.getColor(), "123456"); assertEquals(t.getColor(), t2.getColor()); assertEquals(t.getUrl(), t2.getUrl()); + + t.setColor("000000"); + GHLabel t3 = r.getLabel("test"); + assertEquals(t3.getColor(), "000000"); t.delete(); } } @@ -746,7 +788,7 @@ public void testListAllRepositories() throws Exception { GHRepository r = itr.next(); System.out.println(r.getFullName()); assertNotNull(r.getUrl()); - assertNotEquals(0,r.getId()); + assertNotEquals(0L,r.getId()); } } @@ -773,7 +815,7 @@ public void markDown() throws Exception { assertTrue(actual.contains("href=\"https://github.com/kohsuke\"")); assertTrue(actual.contains("href=\"https://github.com/kohsuke/github-api/pull/1\"")); assertTrue(actual.contains("class=\"user-mention\"")); - assertTrue(actual.contains("class=\"issue-link\"")); + assertTrue(actual.contains("class=\"issue-link ")); assertTrue(actual.contains("to fix issue")); } @@ -827,8 +869,65 @@ public void notifications() throws Exception { gitHub.listNotifications().markAsRead(); } - private void kohsuke() { - String login = getUser().getLogin(); - Assume.assumeTrue(login.equals("kohsuke") || login.equals("kohsuke2")); + /** + * Just basic code coverage to make sure toString() doesn't blow up + */ + @Test + public void checkToString() throws Exception { + GHUser u = gitHub.getUser("rails"); + System.out.println(u); + GHRepository r = u.getRepository("rails"); + System.out.println(r); + System.out.println(r.getIssue(1)); + } + + @Test + public void reactions() throws Exception { + GHIssue i = gitHub.getRepository("kohsuke/github-api").getIssue(311); + + // retrieval + GHReaction r = i.listReactions().iterator().next(); + assertThat(r.getUser().getLogin(), is("kohsuke")); + assertThat(r.getContent(),is(ReactionContent.HEART)); + + // CRUD + GHReaction a = i.createReaction(ReactionContent.HOORAY); + assertThat(a.getUser().getLogin(),is(gitHub.getMyself().getLogin())); + a.delete(); + } + + @Test + public void listOrgMemberships() throws Exception { + GHMyself me = gitHub.getMyself(); + for (GHMembership m : me.listOrgMemberships()) { + assertThat(m.getUser(), is((GHUser)me)); + assertNotNull(m.getState()); + assertNotNull(m.getRole()); + + System.out.printf("%s %s %s\n", + m.getOrganization().getLogin(), + m.getState(), + m.getRole()); + } + } + + @Test + public void blob() throws Exception { + GHRepository r = gitHub.getRepository("kohsuke/github-api"); + String sha1 = "a12243f2fc5b8c2ba47dd677d0b0c7583539584d"; + + assertBlobContent(r.readBlob(sha1)); + + GHBlob blob = r.getBlob(sha1); + assertBlobContent(blob.read()); + assertThat(blob.getSha(),is("a12243f2fc5b8c2ba47dd677d0b0c7583539584d")); + assertThat(blob.getSize(),is(1104L)); + } + + private void assertBlobContent(InputStream is) throws Exception { + String content = new String(IOUtils.toByteArray(is),"UTF-8"); + assertThat(content,containsString("Copyright (c) 2011- Kohsuke Kawaguchi and other contributors")); + assertThat(content,containsString("FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR")); + assertThat(content.length(),is(1104)); } } diff --git a/src/test/java/org/kohsuke/github/CommitTest.java b/src/test/java/org/kohsuke/github/CommitTest.java index fff9084ebd..8e4edb1fed 100644 --- a/src/test/java/org/kohsuke/github/CommitTest.java +++ b/src/test/java/org/kohsuke/github/CommitTest.java @@ -1,5 +1,6 @@ package org.kohsuke.github; +import com.google.common.collect.Iterables; import org.junit.Test; import java.io.IOException; @@ -13,4 +14,14 @@ public void lastStatus() throws IOException { GHTag t = gitHub.getRepository("stapler/stapler").listTags().iterator().next(); t.getCommit().getLastStatus(); } + + @Test // issue 230 + public void listFiles() throws Exception { + GHRepository repo = gitHub.getRepository("stapler/stapler"); + PagedIterable commits = repo.queryCommits().path("pom.xml").list(); + for (GHCommit commit : Iterables.limit(commits, 10)) { + GHCommit expected = repo.getCommit( commit.getSHA1() ); + assertEquals(expected.getFiles().size(), commit.getFiles().size()); + } + } } diff --git a/src/test/java/org/kohsuke/github/GHBranchProtectionTest.java b/src/test/java/org/kohsuke/github/GHBranchProtectionTest.java new file mode 100644 index 0000000000..19e42c51ba --- /dev/null +++ b/src/test/java/org/kohsuke/github/GHBranchProtectionTest.java @@ -0,0 +1,103 @@ +package org.kohsuke.github; + +import org.junit.Before; +import org.junit.Test; +import org.kohsuke.github.GHBranchProtection.EnforceAdmins; +import org.kohsuke.github.GHBranchProtection.RequiredReviews; +import org.kohsuke.github.GHBranchProtection.RequiredStatusChecks; + +import java.io.FileNotFoundException; + +public class GHBranchProtectionTest extends AbstractGitHubApiTestBase { + private static final String BRANCH = "bp-test"; + private static final String BRANCH_REF = "heads/" + BRANCH; + + private GHBranch branch; + + private GHRepository repo; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + repo = gitHub.getRepository("github-api-test-org/GHContentIntegrationTest").fork(); + + try { + repo.getRef(BRANCH_REF); + } catch (FileNotFoundException e) { + repo.createRef("refs/" + BRANCH_REF, repo.getBranch("master").getSHA1()); + } + + branch = repo.getBranch(BRANCH); + + if (branch.isProtected()) { + GHBranchProtection protection = branch.getProtection(); + if (protection.getRequiredSignatures()) { + protection.disableSignedCommits(); + } + + assertFalse(protection.getRequiredSignatures()); + branch.disableProtection(); + } + + branch = repo.getBranch(BRANCH); + assertFalse(branch.isProtected()); + } + + @Test + public void testEnableBranchProtections() throws Exception { + // team/user restrictions require an organization repo to test against + GHBranchProtection protection = branch.enableProtection() + .addRequiredChecks("test-status-check") + .requireBranchIsUpToDate() + .requireCodeOwnReviews() + .dismissStaleReviews() + .requiredReviewers(2) + .includeAdmins() + .enable(); + + RequiredStatusChecks statusChecks = protection.getRequiredStatusChecks(); + assertNotNull(statusChecks); + assertTrue(statusChecks.isRequiresBranchUpToDate()); + assertTrue(statusChecks.getContexts().contains("test-status-check")); + + RequiredReviews requiredReviews = protection.getRequiredReviews(); + assertNotNull(requiredReviews); + assertTrue(requiredReviews.isDismissStaleReviews()); + assertTrue(requiredReviews.isRequireCodeOwnerReviews()); + assertEquals(2, requiredReviews.getRequiredReviewers()); + + EnforceAdmins enforceAdmins = protection.getEnforceAdmins(); + assertNotNull(enforceAdmins); + assertTrue(enforceAdmins.isEnabled()); + } + + @Test + public void testEnableProtectionOnly() throws Exception { + branch.enableProtection().enable(); + assertTrue(repo.getBranch(BRANCH).isProtected()); + } + + @Test + public void testEnableRequireReviewsOnly() throws Exception { + GHBranchProtection protection = branch.enableProtection() + .requireReviews() + .enable(); + + assertNotNull(protection.getRequiredReviews()); + } + + @Test + public void testSignedCommits() throws Exception { + GHBranchProtection protection = branch.enableProtection().enable(); + + assertFalse(protection.getRequiredSignatures()); + + protection.enabledSignedCommits(); + assertTrue(protection.getRequiredSignatures()); + + protection.disableSignedCommits(); + assertFalse(protection.getRequiredSignatures()); + } +} diff --git a/src/test/java/org/kohsuke/github/GHContentIntegrationTest.java b/src/test/java/org/kohsuke/github/GHContentIntegrationTest.java index 39f8545aaa..4b8bf72d63 100644 --- a/src/test/java/org/kohsuke/github/GHContentIntegrationTest.java +++ b/src/test/java/org/kohsuke/github/GHContentIntegrationTest.java @@ -43,9 +43,18 @@ public void testGetDirectoryContent() throws Exception { assertTrue(entries.size() == 3); } + @Test + public void testGetDirectoryContentTrailingSlash() throws Exception { + //Used to truncate the ?ref=master, see gh-224 https://github.com/kohsuke/github-api/pull/224 + List entries = repo.getDirectoryContent("ghcontent-ro/a-dir-with-3-entries/", "master"); + + assertTrue(entries.get(0).getUrl().endsWith("?ref=master")); + } + @Test public void testCRUDContent() throws Exception { - GHContentUpdateResponse created = repo.createContent("this is an awesome file I created\n", "Creating a file for integration tests.", createdFilename); + GHContentUpdateResponse created = + repo.createContent("this is an awesome file I created\n", "Creating a file for integration tests.", createdFilename); GHContent createdContent = created.getContent(); assertNotNull(created.getCommit()); @@ -58,7 +67,8 @@ public void testCRUDContent() throws Exception { assertNotNull(updatedContentResponse.getCommit()); assertNotNull(updatedContentResponse.getContent()); - assertEquals("this is some new content\n", updatedContent.getContent()); + // due to what appears to be a cache propagation delay, this test is too flaky + // assertEquals("this is some new content\n", updatedContent.getContent()); GHContentUpdateResponse deleteResponse = updatedContent.delete("Enough of this foolishness!"); diff --git a/src/test/java/org/kohsuke/github/GHEventPayloadTest.java b/src/test/java/org/kohsuke/github/GHEventPayloadTest.java new file mode 100644 index 0000000000..b0c2a05408 --- /dev/null +++ b/src/test/java/org/kohsuke/github/GHEventPayloadTest.java @@ -0,0 +1,309 @@ +package org.kohsuke.github; + +import org.junit.Rule; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; + +public class GHEventPayloadTest { + + @Rule + public final PayloadRule payload = new PayloadRule(".json"); + + @Test + public void commit_comment() throws Exception { + GHEventPayload.CommitComment event = + GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.CommitComment.class); + assertThat(event.getAction(), is("created")); + assertThat(event.getComment().getSHA1(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); + assertThat(event.getComment().getUser().getLogin(), is("baxterthehacker")); + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); + } + + @Test + public void create() throws Exception { + GHEventPayload.Create event = + GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.Create.class); + assertThat(event.getRef(), is("0.0.1")); + assertThat(event.getRefType(), is("tag")); + assertThat(event.getMasterBranch(), is("master")); + assertThat(event.getDescription(), is("")); + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); + } + + @Test + public void delete() throws Exception { + GHEventPayload.Delete event = + GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.Delete.class); + assertThat(event.getRef(), is("simple-tag")); + assertThat(event.getRefType(), is("tag")); + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); + } + + @Test + public void deployment() throws Exception { + GHEventPayload.Deployment event = + GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.Deployment.class); + assertThat(event.getDeployment().getSha(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); + assertThat(event.getDeployment().getEnvironment(), is("production")); + assertThat(event.getDeployment().getCreator().getLogin(), is("baxterthehacker")); + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); + } + + @Test + public void deployment_status() throws Exception { + GHEventPayload.DeploymentStatus event = + GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.DeploymentStatus.class); + assertThat(event.getDeploymentStatus().getState(), is(GHDeploymentState.SUCCESS)); + assertThat(event.getDeploymentStatus().getTargetUrl(), nullValue()); + assertThat(event.getDeployment().getSha(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); + assertThat(event.getDeployment().getEnvironment(), is("production")); + assertThat(event.getDeployment().getCreator().getLogin(), is("baxterthehacker")); + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); + } + + @Test + public void fork() throws Exception { + GHEventPayload.Fork event = + GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.Fork.class); + assertThat(event.getForkee().getName(), is("public-repo")); + assertThat(event.getForkee().getOwner().getLogin(), is("baxterandthehackers")); + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); + assertThat(event.getSender().getLogin(), is("baxterandthehackers")); + } + +// TODO uncomment when we have GHPage implemented +// @Test +// public void gollum() throws Exception { +// GHEventPayload.Gollum event = +// GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.Gollum.class); +// assertThat(event.getPages().size(), is(1)); +// GHPage page = event.getPages().get(0); +// assertThat(page.getName(), is("Home")); +// assertThat(page.getTitle(), is("Home")); +// assertThat(page.getSummary(), nullValue()); +// assertThat(page.getAction(), is("created")); +// assertThat(page.getSha(), is("91ea1bd42aa2ba166b86e8aefe049e9837214e67")); +// assertThat(page.getHtmlUrl(), is("https://github.com/baxterthehacker/public-repo/wiki/Home")); +// assertThat(event.getRepository().getName(), is("public-repo")); +// assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); +// assertThat(event.getSender().getLogin(), is("baxterthehacker")); +// } + + @Test + public void issue_comment() throws Exception { + GHEventPayload.IssueComment event = + GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.IssueComment.class); + assertThat(event.getAction(), is("created")); + assertThat(event.getIssue().getNumber(), is(2)); + assertThat(event.getIssue().getTitle(), is("Spelling error in the README file")); + assertThat(event.getIssue().getState(), is(GHIssueState.OPEN)); + assertThat(event.getIssue().getLabels().size(), is(1)); + assertThat(event.getIssue().getLabels().iterator().next().getName(), is("bug")); + assertThat(event.getComment().getUser().getLogin(), is("baxterthehacker")); + assertThat(event.getComment().getBody(), is("You are totally right! I'll get this fixed right away.")); + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); + } + + @Test + public void issues() throws Exception { + GHEventPayload.Issue event = GitHub.offline().parseEventPayload(payload.asReader(),GHEventPayload.Issue.class); + assertThat(event.getAction(),is("opened")); + assertThat(event.getIssue().getNumber(), is(2)); + assertThat(event.getIssue().getTitle(), is("Spelling error in the README file")); + assertThat(event.getIssue().getState(), is(GHIssueState.OPEN)); + assertThat(event.getIssue().getLabels().size(), is(1)); + assertThat(event.getIssue().getLabels().iterator().next().getName(), is("bug")); + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); + } + +// TODO implement support classes and write test +// @Test +// public void label() throws Exception {} + +// TODO implement support classes and write test +// @Test +// public void member() throws Exception {} + +// TODO implement support classes and write test +// @Test +// public void membership() throws Exception {} + +// TODO implement support classes and write test +// @Test +// public void milestone() throws Exception {} + +// TODO implement support classes and write test +// @Test +// public void page_build() throws Exception {} + + @Test + @Payload("public") + public void public_() throws Exception { + GHEventPayload.Public event = + GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.Public.class); + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); + } + + @Test + public void pull_request() throws Exception { + GHEventPayload.PullRequest event = + GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.PullRequest.class); + assertThat(event.getAction(), is("opened")); + assertThat(event.getNumber(), is(1)); + assertThat(event.getPullRequest().getNumber(), is(1)); + assertThat(event.getPullRequest().getTitle(), is("Update the README with new information")); + assertThat(event.getPullRequest().getBody(), is("This is a pretty simple change that we need to pull into " + + "master.")); + assertThat(event.getPullRequest().getUser().getLogin(), is("baxterthehacker")); + assertThat(event.getPullRequest().getHead().getUser().getLogin(), is("baxterthehacker")); + assertThat(event.getPullRequest().getHead().getRef(), is("changes")); + assertThat(event.getPullRequest().getHead().getLabel(), is("baxterthehacker:changes")); + assertThat(event.getPullRequest().getHead().getSha(), is("0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c")); + assertThat(event.getPullRequest().getBase().getUser().getLogin(), is("baxterthehacker")); + assertThat(event.getPullRequest().getBase().getRef(), is("master")); + assertThat(event.getPullRequest().getBase().getLabel(), is("baxterthehacker:master")); + assertThat(event.getPullRequest().getBase().getSha(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); + assertThat(event.getPullRequest().isMerged(), is(false)); + assertThat(event.getPullRequest().getMergeable(), nullValue()); + assertThat(event.getPullRequest().getMergeableState(), is("unknown")); + assertThat(event.getPullRequest().getMergedBy(), nullValue()); + assertThat(event.getPullRequest().getCommentsCount(), is(0)); + assertThat(event.getPullRequest().getReviewComments(), is(0)); + assertThat(event.getPullRequest().getAdditions(), is(1)); + assertThat(event.getPullRequest().getDeletions(), is(1)); + assertThat(event.getPullRequest().getChangedFiles(), is(1)); + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); + } + + @Test + public void pull_request_review() throws Exception { + GHEventPayload.PullRequestReview event = + GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.PullRequestReview.class); + assertThat(event.getAction(), is("submitted")); + + assertThat(event.getReview().getId(), is(2626884L)); + assertThat(event.getReview().getBody(), is("Looks great!\n")); + assertThat(event.getReview().getState(), is(GHPullRequestReviewState.APPROVED)); + + assertThat(event.getPullRequest().getNumber(), is(8)); + assertThat(event.getPullRequest().getTitle(), is("Add a README description")); + assertThat(event.getPullRequest().getBody(), is("Just a few more details")); + assertThat(event.getPullRequest().getUser().getLogin(), is("skalnik")); + assertThat(event.getPullRequest().getHead().getUser().getLogin(), is("skalnik")); + assertThat(event.getPullRequest().getHead().getRef(), is("patch-2")); + assertThat(event.getPullRequest().getHead().getLabel(), is("skalnik:patch-2")); + assertThat(event.getPullRequest().getHead().getSha(), is("b7a1f9c27caa4e03c14a88feb56e2d4f7500aa63")); + assertThat(event.getPullRequest().getBase().getUser().getLogin(), is("baxterthehacker")); + assertThat(event.getPullRequest().getBase().getRef(), is("master")); + assertThat(event.getPullRequest().getBase().getLabel(), is("baxterthehacker:master")); + assertThat(event.getPullRequest().getBase().getSha(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); + + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); + + assertThat(event.getSender().getLogin(), is("baxterthehacker")); + } + + @Test + public void pull_request_review_comment() throws Exception { + GHEventPayload.PullRequestReviewComment event = + GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.PullRequestReviewComment.class); + assertThat(event.getAction(), is("created")); + + assertThat(event.getComment().getBody(), is("Maybe you should use more emojji on this line.")); + + assertThat(event.getPullRequest().getNumber(), is(1)); + assertThat(event.getPullRequest().getTitle(), is("Update the README with new information")); + assertThat(event.getPullRequest().getBody(), is("This is a pretty simple change that we need to pull into master.")); + assertThat(event.getPullRequest().getUser().getLogin(), is("baxterthehacker")); + assertThat(event.getPullRequest().getHead().getUser().getLogin(), is("baxterthehacker")); + assertThat(event.getPullRequest().getHead().getRef(), is("changes")); + assertThat(event.getPullRequest().getHead().getLabel(), is("baxterthehacker:changes")); + assertThat(event.getPullRequest().getHead().getSha(), is("0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c")); + assertThat(event.getPullRequest().getBase().getUser().getLogin(), is("baxterthehacker")); + assertThat(event.getPullRequest().getBase().getRef(), is("master")); + assertThat(event.getPullRequest().getBase().getLabel(), is("baxterthehacker:master")); + assertThat(event.getPullRequest().getBase().getSha(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); + + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterthehacker")); + + assertThat(event.getSender().getLogin(), is("baxterthehacker")); + } + + @Test + public void push() throws Exception { + GHEventPayload.Push event = + GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.Push.class); + assertThat(event.getRef(), is("refs/heads/changes")); + assertThat(event.getBefore(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); + assertThat(event.getHead(), is("0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c")); + assertThat(event.isCreated(), is(false)); + assertThat(event.isDeleted(), is(false)); + assertThat(event.isForced(), is(false)); + assertThat(event.getCommits().size(), is(1)); + assertThat(event.getCommits().get(0).getSha(), is("0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c")); + assertThat(event.getCommits().get(0).getAuthor().getEmail(), is("baxterthehacker@users.noreply.github.com")); + assertThat(event.getCommits().get(0).getCommitter().getEmail(), is("baxterthehacker@users.noreply.github.com")); + assertThat(event.getCommits().get(0).getAdded().size(), is(0)); + assertThat(event.getCommits().get(0).getRemoved().size(), is(0)); + assertThat(event.getCommits().get(0).getModified().size(), is(1)); + assertThat(event.getCommits().get(0).getModified().get(0), is("README.md")); + assertThat(event.getRepository().getName(), is("public-repo")); + assertThat(event.getRepository().getOwnerName(), is("baxterthehacker")); + assertThat(event.getRepository().getUrl().toExternalForm(), is("https://github.com/baxterthehacker/public-repo")); + assertThat(event.getPusher().getName(), is("baxterthehacker")); + assertThat(event.getPusher().getEmail(), is("baxterthehacker@users.noreply.github.com")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); + } + +// TODO implement support classes and write test +// @Test +// public void release() throws Exception {} + + @Test + public void repository() throws Exception { + GHEventPayload.Repository event = + GitHub.offline().parseEventPayload(payload.asReader(), GHEventPayload.Repository.class); + assertThat(event.getAction(), is("created")); + assertThat(event.getRepository().getName(), is("new-repository")); + assertThat(event.getRepository().getOwner().getLogin(), is("baxterandthehackers")); + assertThat(event.getOrganization().getLogin(), is("baxterandthehackers")); + assertThat(event.getSender().getLogin(), is("baxterthehacker")); + } + +// TODO implement support classes and write test +// @Test +// public void status() throws Exception {} + +// TODO implement support classes and write test +// @Test +// public void team_add() throws Exception {} + +// TODO implement support classes and write test +// @Test +// public void watch() throws Exception {} + +} diff --git a/src/test/java/org/kohsuke/github/GHHookTest.java b/src/test/java/org/kohsuke/github/GHHookTest.java new file mode 100644 index 0000000000..b27484b5e5 --- /dev/null +++ b/src/test/java/org/kohsuke/github/GHHookTest.java @@ -0,0 +1,78 @@ +package org.kohsuke.github; + +import org.apache.commons.lang.StringUtils; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.hasValue; +import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.junit.Assert.assertThat; + + +/** + * @author Kanstantsin Shautsou + */ +public class GHHookTest { + + @Ignore + @Test + public void exposeResponceHeaders() throws Exception { + String user1Login = "KostyaSha-auto"; + String user1Pass = "secret"; + + String clientId = "90140219451"; + String clientSecret = "1451245425"; + + String orgRepo = "KostyaSha-org/test"; + + // some login based user that has access to application + final GitHub gitHub = GitHub.connectUsingPassword(user1Login, user1Pass); + gitHub.getMyself(); + + // we request read + final List scopes = Arrays.asList("repo", "read:org", "user:email", "read:repo_hook"); + + // application creates token with scopes + final GHAuthorization auth = gitHub.createOrGetAuth(clientId, clientSecret, scopes, "", ""); + String token = auth.getToken(); + if (StringUtils.isEmpty(token)) { + gitHub.deleteAuth(auth.getId()); + token = gitHub.createOrGetAuth(clientId, clientSecret, scopes, "", "").getToken(); + } + + /// now create connection using token + final GitHub gitHub2 = GitHub.connectUsingOAuth(token); + // some repo in organisation + final GHRepository repository = gitHub2.getRepository(orgRepo); + + // doesn't fail because we have read access + final List hooks = repository.getHooks(); + + try { + // fails because application isn't approved in organisation and you can find it only after doing real call + final GHHook hook = repository.createHook( + "my-hook", + singletonMap("url", "http://localhost"), + singletonList(GHEvent.PUSH), + true + ); + } catch (IOException ex) { + assertThat(ex, instanceOf(GHFileNotFoundException.class)); + final GHFileNotFoundException ghFileNotFoundException = (GHFileNotFoundException) ex; + final Map> responseHeaderFields = ghFileNotFoundException.getResponseHeaderFields(); + assertThat(responseHeaderFields, hasKey("X-Accepted-OAuth-Scopes")); + assertThat(responseHeaderFields.get("X-Accepted-OAuth-Scopes"), + hasItem("admin:repo_hook, public_repo, repo, write:repo_hook") + ); + } + } +} diff --git a/src/test/java/org/kohsuke/github/GHLicenseTest.java b/src/test/java/org/kohsuke/github/GHLicenseTest.java new file mode 100644 index 0000000000..b2cc73f928 --- /dev/null +++ b/src/test/java/org/kohsuke/github/GHLicenseTest.java @@ -0,0 +1,193 @@ +/* + * The MIT License + * + * Copyright (c) 2016, Duncan Dickinson + * + * 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 org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.net.URL; + +/** + * @author Duncan Dickinson + */ +public class GHLicenseTest extends Assert { + private GitHub gitHub; + + @Before + public void setUp() throws Exception { + gitHub = new GitHubBuilder() + .fromCredentials() + .build(); + } + + /** + * Basic test to ensure that the list of licenses from {@link GitHub#listLicenses()} is returned + * + * @throws IOException + */ + @Test + public void listLicenses() throws IOException { + Iterable licenses = gitHub.listLicenses(); + assertTrue(licenses.iterator().hasNext()); + } + + /** + * Tests that {@link GitHub#listLicenses()} returns the MIT license + * in the expected manner. + * + * @throws IOException + */ + @Test + public void listLicensesCheckIndividualLicense() throws IOException { + PagedIterable licenses = gitHub.listLicenses(); + for (GHLicense lic : licenses) { + if (lic.getKey().equals("mit")) { + assertTrue(lic.getUrl().equals(new URL("https://api.github.com/licenses/mit"))); + return; + } + } + fail("The MIT license was not found"); + } + + /** + * Checks that the request for an individual license using {@link GitHub#getLicense(String)} + * returns expected values (not all properties are checked) + * + * @throws IOException + */ + @Test + public void getLicense() throws IOException { + String key = "mit"; + GHLicense license = gitHub.getLicense(key); + assertNotNull(license); + assertTrue("The name is correct", license.getName().equals("MIT License")); + assertTrue("The HTML URL is correct", license.getHtmlUrl().equals(new URL("http://choosealicense.com/licenses/mit/"))); + } + + /** + * Accesses the 'kohsuke/github-api' repo using {@link GitHub#getRepository(String)} + * and checks that the license is correct + * + * @throws IOException + */ + @Test + public void checkRepositoryLicense() throws IOException { + GHRepository repo = gitHub.getRepository("kohsuke/github-api"); + GHLicense license = repo.getLicense(); + assertNotNull("The license is populated", license); + assertTrue("The key is correct", license.getKey().equals("mit")); + assertTrue("The name is correct", license.getName().equals("MIT License")); + assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/mit"))); + } + + /** + * Accesses the 'atom/atom' repo using {@link GitHub#getRepository(String)} + * and checks that the license is correct + * + * @throws IOException + */ + @Test + public void checkRepositoryLicenseAtom() throws IOException { + GHRepository repo = gitHub.getRepository("atom/atom"); + GHLicense license = repo.getLicense(); + assertNotNull("The license is populated", license); + assertTrue("The key is correct", license.getKey().equals("mit")); + assertTrue("The name is correct", license.getName().equals("MIT License")); + assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/mit"))); + } + + /** + * Accesses the 'pomes/pomes' repo using {@link GitHub#getRepository(String)} + * and checks that the license is correct + * + * @throws IOException + */ + @Test + public void checkRepositoryLicensePomes() throws IOException { + GHRepository repo = gitHub.getRepository("pomes/pomes"); + GHLicense license = repo.getLicense(); + assertNotNull("The license is populated", license); + assertTrue("The key is correct", license.getKey().equals("apache-2.0")); + assertTrue("The name is correct", license.getName().equals("Apache License 2.0")); + assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/apache-2.0"))); + } + + /** + * Accesses the 'dedickinson/test-repo' repo using {@link GitHub#getRepository(String)} + * and checks that *no* license is returned as the repo doesn't have one + * + * @throws IOException + */ + @Test + public void checkRepositoryWithoutLicense() throws IOException { + GHRepository repo = gitHub.getRepository("dedickinson/test-repo"); + GHLicense license = repo.getLicense(); + assertNull("There is no license", license); + } + + /** + * Accesses the 'kohsuke/github-api' repo using {@link GitHub#getRepository(String)} + * and then calls {@link GHRepository#getLicense()} and checks that certain + * properties are correct + * + * @throws IOException + */ + @Test + public void checkRepositoryFullLicense() throws IOException { + GHRepository repo = gitHub.getRepository("kohsuke/github-api"); + GHLicense license = repo.getLicense(); + assertNotNull("The license is populated", license); + assertTrue("The key is correct", license.getKey().equals("mit")); + assertTrue("The name is correct", license.getName().equals("MIT License")); + assertTrue("The URL is correct", license.getUrl().equals(new URL("https://api.github.com/licenses/mit"))); + assertTrue("The HTML URL is correct", license.getHtmlUrl().equals(new URL("http://choosealicense.com/licenses/mit/"))); + } + + /** + * Accesses the 'pomes/pomes' repo using {@link GitHub#getRepository(String)} + * and then calls {@link GHRepository#getLicenseContent()} and checks that certain + * properties are correct + * + * @throws IOException + */ + @Test + public void checkRepositoryLicenseContent() throws IOException { + GHRepository repo = gitHub.getRepository("pomes/pomes"); + GHContent content = repo.getLicenseContent(); + assertNotNull("The license content is populated", content); + assertTrue("The type is 'file'", content.getType().equals("file")); + assertTrue("The license file is 'LICENSE'", content.getName().equals("LICENSE")); + + if (content.getEncoding().equals("base64")) { + String licenseText = new String(IOUtils.toByteArray(content.read())); + assertTrue("The license appears to be an Apache License", licenseText.contains("Apache License")); + } else { + fail("Expected the license to be Base64 encoded but instead it was " + content.getEncoding()); + } + } +} diff --git a/src/test/java/org/kohsuke/github/GHOrganizationTest.java b/src/test/java/org/kohsuke/github/GHOrganizationTest.java new file mode 100644 index 0000000000..a1c5d3b921 --- /dev/null +++ b/src/test/java/org/kohsuke/github/GHOrganizationTest.java @@ -0,0 +1,43 @@ +package org.kohsuke.github; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; + +public class GHOrganizationTest extends AbstractGitHubApiTestBase { + + public static final String GITHUB_API_TEST = "github-api-test"; + private GHOrganization org; + + @Override + public void setUp() throws Exception { + super.setUp(); + org = gitHub.getOrganization("github-api-test-org"); + } + + @Test + public void testCreateRepository() throws IOException { + GHRepository repository = org.createRepository(GITHUB_API_TEST, + "a test repository used to test kohsuke's github-api", "http://github-api.kohsuke.org/", "Core Developers", true); + Assert.assertNotNull(repository); + } + + @Test + public void testCreateRepositoryWithAutoInitialization() throws IOException { + GHRepository repository = org.createRepository(GITHUB_API_TEST) + .description("a test repository used to test kohsuke's github-api") + .homepage("http://github-api.kohsuke.org/") + .team(org.getTeamByName("Core Developers")) + .autoInit(true).create(); + Assert.assertNotNull(repository); + Assert.assertNotNull(repository.getReadme()); + } + + @After + public void cleanUp() throws Exception { + GHRepository repository = org.getRepository(GITHUB_API_TEST); + repository.delete(); + } +} diff --git a/src/test/java/org/kohsuke/github/GitHubTest.java b/src/test/java/org/kohsuke/github/GitHubTest.java index 578bbc13f3..f4b77b8271 100644 --- a/src/test/java/org/kohsuke/github/GitHubTest.java +++ b/src/test/java/org/kohsuke/github/GitHubTest.java @@ -1,34 +1,59 @@ 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.HashSet; import java.util.Map; -import java.util.Properties; +import java.util.Set; -import junit.framework.TestCase; +import com.google.common.collect.Iterables; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +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 testOffline() throws Exception { + GitHub hub = GitHub.offline(); + assertEquals("https://api.github.invalid/test", hub.getApiURL("/test").toString()); + assertTrue(hub.isAnonymous()); + try { + hub.getRateLimit(); + fail("Offline instance should always fail"); + } catch (IOException e) { + assertEquals("Offline", e.getMessage()); + } + } + @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 +111,7 @@ private void setupEnvironment(Map newenv) { e1.printStackTrace(); } } - + @Test public void testGitHubBuilderFromCustomEnvironment() throws IOException { Map props = new HashMap(); @@ -105,4 +130,44 @@ 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()); + } + + @Test + public void testGitHubIsApiUrlValid() throws IOException { + GitHub github = GitHub.connectAnonymously(); + //GitHub github = GitHub.connectToEnterpriseAnonymously("https://github.mycompany.com/api/v3/"); + try { + github.checkApiUrlValidity(); + } catch (IOException ioe) { + assertTrue(ioe.getMessage().contains("private mode enabled")); + } + } + + @Test + public void listUsers() throws IOException { + GitHub hub = GitHub.connect(); + for (GHUser u : Iterables.limit(hub.listUsers(),10)) { + assert u.getName()!=null; + System.out.println(u.getName()); + } + } + + @Test + public void getOrgs() throws IOException { + GitHub hub = GitHub.connect(); + int iterations = 10; + Set orgIds = new HashSet(); + for (GHOrganization org : Iterables.limit(hub.listOrganizations().withPageSize(2), iterations)) { + orgIds.add(org.getId()); + System.out.println(org.getName()); + } + assertThat(orgIds.size(), equalTo(iterations)); + } } diff --git a/src/test/java/org/kohsuke/github/Payload.java b/src/test/java/org/kohsuke/github/Payload.java new file mode 100644 index 0000000000..7365468d24 --- /dev/null +++ b/src/test/java/org/kohsuke/github/Payload.java @@ -0,0 +1,12 @@ +package org.kohsuke.github; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Payload { + String value(); +} diff --git a/src/test/java/org/kohsuke/github/PayloadRule.java b/src/test/java/org/kohsuke/github/PayloadRule.java new file mode 100644 index 0000000000..7f9364fdb2 --- /dev/null +++ b/src/test/java/org/kohsuke/github/PayloadRule.java @@ -0,0 +1,87 @@ +package org.kohsuke.github; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.Charset; +import org.apache.commons.io.IOUtils; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * @author Stephen Connolly + */ +public class PayloadRule implements TestRule { + + private final String type; + + private Class testClass; + + private String resourceName; + + public PayloadRule(String type) { + this.type = type; + } + + public Statement apply(final Statement base, final Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + Payload payload = description.getAnnotation(Payload.class); + resourceName = payload == null ? description.getMethodName() : payload.value(); + testClass = description.getTestClass(); + try { + base.evaluate(); + } finally { + resourceName = null; + } + } + }; + } + + public InputStream asInputStream() throws FileNotFoundException { + String name = resourceName.startsWith("/") + ? resourceName + type + : testClass.getSimpleName() + "/" + resourceName + type; + InputStream stream = testClass.getResourceAsStream(name); + if (stream == null) { + throw new FileNotFoundException(String.format("Resource %s from class %s", name, testClass)); + } + return stream; + } + + public byte[] asBytes() throws IOException { + InputStream input = asInputStream(); + try { + return IOUtils.toByteArray(input); + } finally { + IOUtils.closeQuietly(input); + } + } + + public String asString(Charset encoding) throws IOException { + return new String(asBytes(), encoding.name()); + } + + public String asString(String encoding) throws IOException { + return new String(asBytes(), encoding); + } + + public String asString() throws IOException { + return new String(asBytes(), Charset.defaultCharset().name()); + } + + public Reader asReader() throws FileNotFoundException { + return new InputStreamReader(asInputStream(), Charset.defaultCharset()); + } + + public Reader asReader(String encoding) throws IOException { + return new InputStreamReader(asInputStream(), encoding); + } + public Reader asReader(Charset encoding) throws FileNotFoundException { + return new InputStreamReader(asInputStream(), encoding); + } +} diff --git a/src/test/java/org/kohsuke/github/PullRequestTest.java b/src/test/java/org/kohsuke/github/PullRequestTest.java index c513860eae..ceb741bd34 100644 --- a/src/test/java/org/kohsuke/github/PullRequestTest.java +++ b/src/test/java/org/kohsuke/github/PullRequestTest.java @@ -5,6 +5,9 @@ import java.io.IOException; import java.util.Collection; +import java.util.List; + +import static org.hamcrest.CoreMatchers.*; /** * @author Kohsuke Kawaguchi @@ -18,7 +21,122 @@ 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 testPullRequestReviews() throws Exception { + String name = rnd.next(); + GHPullRequest p = getRepository().createPullRequest(name, "stable", "master", "## test"); + GHPullRequestReview draftReview = p.createReview() + .body("Some draft review") + .comment("Some niggle", "changelog.html", 1) + .create(); + assertThat(draftReview.getState(), is(GHPullRequestReviewState.PENDING)); + assertThat(draftReview.getBody(), is("Some draft review")); + assertThat(draftReview.getCommitId(), notNullValue()); + List reviews = p.listReviews().asList(); + assertThat(reviews.size(), is(1)); + GHPullRequestReview review = reviews.get(0); + assertThat(review.getState(), is(GHPullRequestReviewState.PENDING)); + assertThat(review.getBody(), is("Some draft review")); + assertThat(review.getCommitId(), notNullValue()); + draftReview.submit("Some review comment", GHPullRequestReviewEvent.COMMENT); + List comments = review.listReviewComments().asList(); + assertEquals(1, comments.size()); + GHPullRequestReviewComment comment = comments.get(0); + assertEquals("Some niggle", comment.getBody()); + draftReview = p.createReview() + .body("Some new review") + .comment("Some niggle", "changelog.html", 1) + .create(); + draftReview.delete(); + } + + @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 + public void testMergeCommitSHA() throws Exception { + String name = rnd.next(); + GHPullRequest p = getRepository().createPullRequest(name, "mergeable-branch", "master", "## test"); + for (int i=0; i<100; i++) { + GHPullRequest updated = getRepository().getPullRequest(p.getNumber()); + if (updated.getMergeCommitSha()!=null) { + // make sure commit exists + GHCommit commit = getRepository().getCommit(updated.getMergeCommitSha()); + assertNotNull(commit); + return; + } + + // mergeability computation takes time. give it more chance + Thread.sleep(100); + } + // hmm? + fail(); + } + + @Test + public void testSquashMerge() throws Exception { + String name = rnd.next(); + GHRef masterRef = getRepository().getRef("heads/master"); + GHRef branchRef = getRepository().createRef("refs/heads/" + name, masterRef.getObject().getSha()); + + getRepository().createContent(name, name, name, name); + Thread.sleep(1000); + GHPullRequest p = getRepository().createPullRequest(name, name, "master", "## test squash"); + Thread.sleep(1000); + p.merge("squash merge", null, GHPullRequest.MergeMethod.SQUASH); + branchRef.delete(); + } + @Test + public void testUpdateContentSquashMerge() throws Exception { + String name = rnd.next(); + GHRef masterRef = getRepository().getRef("heads/master"); + GHRef branchRef = getRepository().createRef("refs/heads/" + name, masterRef.getObject().getSha()); + + GHContentUpdateResponse response = getRepository().createContent(name, name, name, name); + Thread.sleep(1000); + + getRepository().createContent() + .content(name + name) + .path(name) + .branch(name) + .message(name) + .sha(response.getContent().getSha()) + .commit(); + GHPullRequest p = getRepository().createPullRequest(name, name, "master", "## test squash"); + Thread.sleep(1000); + p.merge("squash merge", null, GHPullRequest.MergeMethod.SQUASH); + branchRef.delete(); + } + + @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 +147,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 +168,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); } } diff --git a/src/test/java/org/kohsuke/github/RepositoryMockTest.java b/src/test/java/org/kohsuke/github/RepositoryMockTest.java index 8c12ed640a..2ff984ef2c 100644 --- a/src/test/java/org/kohsuke/github/RepositoryMockTest.java +++ b/src/test/java/org/kohsuke/github/RepositoryMockTest.java @@ -48,7 +48,7 @@ public void listCollaborators() throws Exception { when(requester.asIterator("/repos/*/*/collaborators", - GHUser[].class)).thenReturn(iterator, iterator); + GHUser[].class, 0)).thenReturn(iterator, iterator); PagedIterable pagedIterable = Mockito.mock(PagedIterable.class); diff --git a/src/test/java/org/kohsuke/github/RepositoryTest.java b/src/test/java/org/kohsuke/github/RepositoryTest.java index 61cdc09dca..66fe689507 100644 --- a/src/test/java/org/kohsuke/github/RepositoryTest.java +++ b/src/test/java/org/kohsuke/github/RepositoryTest.java @@ -3,6 +3,7 @@ import org.junit.Test; import org.kohsuke.github.GHRepository.Contributor; +import java.io.FileNotFoundException; import java.io.IOException; /** @@ -40,6 +41,89 @@ public void listContributors() throws IOException { assertTrue(kohsuke); } + @Test + public void getPermission() throws Exception { + kohsuke(); + GHRepository r = gitHub.getRepository("github-api-test-org/test-permission"); + assertEquals(GHPermissionType.ADMIN, r.getPermission("kohsuke")); + assertEquals(GHPermissionType.READ, r.getPermission("dude")); + r = gitHub.getOrganization("apache").getRepository("groovy"); + try { + r.getPermission("jglick"); + fail(); + } catch (HttpException x) { + x.printStackTrace(); // good + assertEquals(403, x.getResponseCode()); + } + + if (false) { + // can't easily test this; there's no private repository visible to the test user + r = gitHub.getOrganization("cloudbees").getRepository("private-repo-not-writable-by-me"); + try { + r.getPermission("jglick"); + fail(); + } catch (FileNotFoundException x) { + x.printStackTrace(); // good + } + } + } + + + + @Test + public void LatestRepositoryExist() { + try { + // add the repository that have latest release + GHRelease release = gitHub.getRepository("kamontat/CheckIDNumber").getLatestRelease(); + assertEquals("v3.0", release.getTagName()); + } catch (IOException e) { + e.printStackTrace(); + fail(); + } + } + + @Test + public void LatestRepositoryNotExist() { + try { + // add the repository that `NOT` have latest release + GHRelease release = gitHub.getRepository("kamontat/Java8Example").getLatestRelease(); + assertNull(release); + } catch (IOException e) { + e.printStackTrace(); + fail(); + } + } + + @Test public void listReleases() throws IOException { + PagedIterable releases = gitHub.getOrganization("github").getRepository("hub").listReleases(); + assertTrue(releases.iterator().hasNext()); + } + + @Test + public void getReleaseExists() throws IOException { + GHRelease release = gitHub.getOrganization("github").getRepository("hub").getRelease(6839710); + assertEquals("v2.3.0-pre10", release.getTagName()); + } + + @Test + public void getReleaseDoesNotExist() throws IOException { + GHRelease release = gitHub.getOrganization("github").getRepository("hub").getRelease(Long.MAX_VALUE); + assertNull(release); + } + + @Test + public void getReleaseByTagNameExists() throws IOException { + GHRelease release = gitHub.getOrganization("github").getRepository("hub").getReleaseByTagName("v2.3.0-pre10"); + assertNotNull(release); + assertEquals("v2.3.0-pre10", release.getTagName()); + } + + @Test + public void getReleaseByTagNameDoesNotExist() throws IOException { + GHRelease release = getRepository().getReleaseByTagName("foo-bar-baz"); + assertNull(release); + } + private GHRepository getRepository() throws IOException { return gitHub.getOrganization("github-api-test-org").getRepository("jenkins"); } @@ -50,4 +134,12 @@ public void listLanguages() throws IOException { String mainLanguage = r.getLanguage(); assertTrue(r.listLanguages().containsKey(mainLanguage)); } + + @Test // Issue #261 + public void listEmptyContributors() throws IOException { + GitHub gh = GitHub.connect(); + for (Contributor c : gh.getRepository("github-api-test-org/empty").listContributors()) { + System.out.println(c); + } + } } diff --git a/src/test/java/org/kohsuke/github/RepositoryTrafficTest.java b/src/test/java/org/kohsuke/github/RepositoryTrafficTest.java new file mode 100644 index 0000000000..b1f13e6c97 --- /dev/null +++ b/src/test/java/org/kohsuke/github/RepositoryTrafficTest.java @@ -0,0 +1,167 @@ +package org.kohsuke.github; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Test; +import org.kohsuke.github.GHRepositoryTraffic.DailyInfo; +import org.mockito.Mockito; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.TimeZone; + +public class RepositoryTrafficTest { + final private String login = "kohsuke", repositoryName = "github-api"; + + @SuppressWarnings("unchecked") + private void checkResponse(T expected, T actual){ + Assert.assertEquals(expected.getCount(), actual.getCount()); + Assert.assertEquals(expected.getUniques(), actual.getUniques()); + + List expectedList = expected.getDailyInfo(); + List actualList = actual.getDailyInfo(); + Iterator expectedIt; + Iterator actualIt; + + Assert.assertEquals(expectedList.size(), actualList.size()); + expectedIt = expectedList.iterator(); + actualIt = actualList.iterator(); + + while(expectedIt.hasNext() && actualIt.hasNext()) { + DailyInfo expectedDailyInfo = expectedIt.next(); + DailyInfo actualDailyInfo = actualIt.next(); + Assert.assertEquals(expectedDailyInfo.getCount(), actualDailyInfo.getCount()); + Assert.assertEquals(expectedDailyInfo.getUniques(), actualDailyInfo.getUniques()); + Assert.assertEquals(expectedDailyInfo.getTimestamp(), actualDailyInfo.getTimestamp()); + } + } + + private void testTraffic(T expectedResult) throws IOException{ + SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); + ObjectMapper mapper = new ObjectMapper().setDateFormat(dateFormat); + String mockedResponse = mapper.writeValueAsString(expectedResult); + + + GitHub gitHub = GitHub.connect(login, null); + GitHub gitHubSpy = Mockito.spy(gitHub); + GHRepository repo = gitHubSpy.getUser(login).getRepository(repositoryName); + + + // accessing traffic info requires push access to the repo + // since we don't have that, let the mocking begin... + + HttpConnector connectorSpy = Mockito.spy(gitHubSpy.getConnector()); + Mockito.doReturn(connectorSpy).when(gitHubSpy).getConnector(); + + + // also known as the "uc" in the Requester class + HttpURLConnection mockHttpURLConnection = Mockito.mock(HttpURLConnection.class); + + + // needed for Requester.setRequestMethod + Mockito.doReturn("GET").when(mockHttpURLConnection).getRequestMethod(); + + + // this covers calls on "uc" in Requester.setupConnection and Requester.buildRequest + URL trafficURL = new URL( + "https://api.github.com/repos/"+login+"/"+repositoryName+"/traffic/" + + ((expectedResult instanceof GHRepositoryViewTraffic) ? "views" : "clones") + ); + Mockito.doReturn(mockHttpURLConnection).when(connectorSpy).connect(Mockito.eq(trafficURL)); + + + // make Requester.parse work + Mockito.doReturn(200).when(mockHttpURLConnection).getResponseCode(); + Mockito.doReturn("OK").when(mockHttpURLConnection).getResponseMessage(); + InputStream stubInputStream = IOUtils.toInputStream(mockedResponse, "UTF-8"); + Mockito.doReturn(stubInputStream).when(mockHttpURLConnection).getInputStream(); + + if(expectedResult instanceof GHRepositoryViewTraffic){ + GHRepositoryViewTraffic views = repo.getViewTraffic(); + checkResponse(expectedResult, views); + } + else if(expectedResult instanceof GHRepositoryCloneTraffic) { + GHRepositoryCloneTraffic clones = repo.getCloneTraffic(); + checkResponse(expectedResult, clones); + } + } + + @Test + public void testGetViews() throws IOException{ + GHRepositoryViewTraffic expectedResult = new GHRepositoryViewTraffic( + 21523359, + 65534, + Arrays.asList( + new GHRepositoryViewTraffic.DailyInfo("2016-10-10T00:00:00Z", 3, 2), + new GHRepositoryViewTraffic.DailyInfo("2016-10-11T00:00:00Z", 9, 4), + new GHRepositoryViewTraffic.DailyInfo("2016-10-12T00:00:00Z", 27, 8), + new GHRepositoryViewTraffic.DailyInfo("2016-10-13T00:00:00Z", 81, 16), + new GHRepositoryViewTraffic.DailyInfo("2016-10-14T00:00:00Z", 243, 32), + new GHRepositoryViewTraffic.DailyInfo("2016-10-15T00:00:00Z", 729, 64), + new GHRepositoryViewTraffic.DailyInfo("2016-10-16T00:00:00Z", 2187, 128), + new GHRepositoryViewTraffic.DailyInfo("2016-10-17T00:00:00Z", 6561, 256), + new GHRepositoryViewTraffic.DailyInfo("2016-10-18T00:00:00Z", 19683, 512), + new GHRepositoryViewTraffic.DailyInfo("2016-10-19T00:00:00Z", 59049, 1024), + new GHRepositoryViewTraffic.DailyInfo("2016-10-20T00:00:00Z", 177147, 2048), + new GHRepositoryViewTraffic.DailyInfo("2016-10-21T00:00:00Z", 531441, 4096), + new GHRepositoryViewTraffic.DailyInfo("2016-10-22T00:00:00Z", 1594323, 8192), + new GHRepositoryViewTraffic.DailyInfo("2016-10-23T00:00:00Z", 4782969, 16384), + new GHRepositoryViewTraffic.DailyInfo("2016-10-24T00:00:00Z", 14348907, 32768) + ) + ); + testTraffic(expectedResult); + } + + @Test + public void testGetClones() throws IOException{ + GHRepositoryCloneTraffic expectedResult = new GHRepositoryCloneTraffic( + 1500, + 455, + Arrays.asList( + new GHRepositoryCloneTraffic.DailyInfo("2016-10-10T00:00:00Z", 10,3), + new GHRepositoryCloneTraffic.DailyInfo("2016-10-11T00:00:00Z", 20,6), + new GHRepositoryCloneTraffic.DailyInfo("2016-10-12T00:00:00Z", 30,5), + new GHRepositoryCloneTraffic.DailyInfo("2016-10-13T00:00:00Z", 40,7), + new GHRepositoryCloneTraffic.DailyInfo("2016-10-14T00:00:00Z", 50,11), + new GHRepositoryCloneTraffic.DailyInfo("2016-10-15T00:00:00Z", 60,12), + new GHRepositoryCloneTraffic.DailyInfo("2016-10-16T00:00:00Z", 70,19), + new GHRepositoryCloneTraffic.DailyInfo("2016-10-17T00:00:00Z", 170,111), + new GHRepositoryCloneTraffic.DailyInfo("2016-10-18T00:00:00Z", 180,70), + new GHRepositoryCloneTraffic.DailyInfo("2016-10-19T00:00:00Z", 190,10), + new GHRepositoryCloneTraffic.DailyInfo("2016-10-20T00:00:00Z", 200,18), + new GHRepositoryCloneTraffic.DailyInfo("2016-10-21T00:00:00Z", 210,8), + new GHRepositoryCloneTraffic.DailyInfo("2016-10-22T00:00:00Z", 220,168), + new GHRepositoryCloneTraffic.DailyInfo("2016-10-23T00:00:00Z", 5,2), + new GHRepositoryCloneTraffic.DailyInfo("2016-10-24T00:00:00Z", 45,5) + ) + ); + testTraffic(expectedResult); + } + + @Test + public void testGetTrafficStatsAccessFailureDueToInsufficientPermissions() throws IOException { + String errorMsg = "Exception should be thrown, since we don't have permission to access repo traffic info."; + GitHub gitHub = GitHub.connect(login, null); + GHRepository repo = gitHub.getUser(login).getRepository(repositoryName); + try { + repo.getViewTraffic(); + Assert.fail(errorMsg); + } + catch (HttpException ex){ + } + try { + repo.getCloneTraffic(); + Assert.fail(errorMsg); + } + catch (HttpException ex){ + } + } +} diff --git a/src/test/java/org/kohsuke/github/UserTest.java b/src/test/java/org/kohsuke/github/UserTest.java new file mode 100644 index 0000000000..9d1cfcbbac --- /dev/null +++ b/src/test/java/org/kohsuke/github/UserTest.java @@ -0,0 +1,30 @@ +package org.kohsuke.github; + +import org.junit.Test; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +/** + * @author Kohsuke Kawaguchi + */ +public class UserTest extends AbstractGitHubApiTestBase { + @Test + public void listFollowsAndFollowers() throws IOException { + GHUser u = gitHub.getUser("rtyler"); + assertNotEquals( + count30(u.listFollowers()), + count30(u.listFollows())); + } + + private Set count30(PagedIterable l) { + Set users = new HashSet(); + PagedIterator itr = l.iterator(); + for (int i=0; i<30 && itr.hasNext(); i++) { + users.add(itr.next()); + } + assertEquals(30, users.size()); + return users; + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/commit_comment.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/commit_comment.json new file mode 100644 index 0000000000..bcf70894cd --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/commit_comment.json @@ -0,0 +1,140 @@ +{ + "action": "created", + "comment": { + "url": "https://api.github.com/repos/baxterthehacker/public-repo/comments/11056394", + "html_url": "https://github.com/baxterthehacker/public-repo/commit/9049f1265b7d61be4a8904a9a27120d2064dab3b#commitcomment-11056394", + "id": 11056394, + "user": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "position": null, + "line": null, + "path": null, + "commit_id": "9049f1265b7d61be4a8904a9a27120d2064dab3b", + "created_at": "2015-05-05T23:40:29Z", + "updated_at": "2015-05-05T23:40:29Z", + "body": "This is a really good change! :+1:" + }, + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:12Z", + "pushed_at": "2015-05-05T23:40:27Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 2, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/create.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/create.json new file mode 100644 index 0000000000..51d690c378 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/create.json @@ -0,0 +1,113 @@ +{ + "ref": "0.0.1", + "ref_type": "tag", + "master_branch": "master", + "description": "", + "pusher_type": "user", + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:30Z", + "pushed_at": "2015-05-05T23:40:38Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 2, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/delete.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/delete.json new file mode 100644 index 0000000000..0759bcd04a --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/delete.json @@ -0,0 +1,111 @@ +{ + "ref": "simple-tag", + "ref_type": "tag", + "pusher_type": "user", + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:30Z", + "pushed_at": "2015-05-05T23:40:40Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 2, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/deployment.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/deployment.json new file mode 100644 index 0000000000..9f2ca9668f --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/deployment.json @@ -0,0 +1,142 @@ +{ + "deployment": { + "url": "https://api.github.com/repos/baxterthehacker/public-repo/deployments/710692", + "id": 710692, + "sha": "9049f1265b7d61be4a8904a9a27120d2064dab3b", + "ref": "master", + "task": "deploy", + "payload": { + }, + "environment": "production", + "description": null, + "creator": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "created_at": "2015-05-05T23:40:38Z", + "updated_at": "2015-05-05T23:40:38Z", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/deployments/710692/statuses", + "repository_url": "https://api.github.com/repos/baxterthehacker/public-repo" + }, + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:30Z", + "pushed_at": "2015-05-05T23:40:38Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 2, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/deployment_status.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/deployment_status.json new file mode 100644 index 0000000000..e1dcd0706f --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/deployment_status.json @@ -0,0 +1,172 @@ +{ + "deployment_status": { + "url": "https://api.github.com/repos/baxterthehacker/public-repo/deployments/710692/statuses/1115122", + "id": 1115122, + "state": "success", + "creator": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "description": null, + "target_url": null, + "created_at": "2015-05-05T23:40:39Z", + "updated_at": "2015-05-05T23:40:39Z", + "deployment_url": "https://api.github.com/repos/baxterthehacker/public-repo/deployments/710692", + "repository_url": "https://api.github.com/repos/baxterthehacker/public-repo" + }, + "deployment": { + "url": "https://api.github.com/repos/baxterthehacker/public-repo/deployments/710692", + "id": 710692, + "sha": "9049f1265b7d61be4a8904a9a27120d2064dab3b", + "ref": "master", + "task": "deploy", + "payload": { + }, + "environment": "production", + "description": null, + "creator": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "created_at": "2015-05-05T23:40:38Z", + "updated_at": "2015-05-05T23:40:38Z", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/deployments/710692/statuses", + "repository_url": "https://api.github.com/repos/baxterthehacker/public-repo" + }, + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:30Z", + "pushed_at": "2015-05-05T23:40:38Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 2, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/fork.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/fork.json new file mode 100644 index 0000000000..e01d3c50fc --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/fork.json @@ -0,0 +1,196 @@ +{ + "forkee": { + "id": 35129393, + "name": "public-repo", + "full_name": "baxterandthehackers/public-repo", + "owner": { + "login": "baxterandthehackers", + "id": 7649605, + "avatar_url": "https://avatars.githubusercontent.com/u/7649605?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterandthehackers", + "html_url": "https://github.com/baxterandthehackers", + "followers_url": "https://api.github.com/users/baxterandthehackers/followers", + "following_url": "https://api.github.com/users/baxterandthehackers/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterandthehackers/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterandthehackers/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterandthehackers/subscriptions", + "organizations_url": "https://api.github.com/users/baxterandthehackers/orgs", + "repos_url": "https://api.github.com/users/baxterandthehackers/repos", + "events_url": "https://api.github.com/users/baxterandthehackers/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterandthehackers/received_events", + "type": "Organization", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterandthehackers/public-repo", + "description": "", + "fork": true, + "url": "https://api.github.com/repos/baxterandthehackers/public-repo", + "forks_url": "https://api.github.com/repos/baxterandthehackers/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterandthehackers/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterandthehackers/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterandthehackers/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterandthehackers/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterandthehackers/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterandthehackers/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterandthehackers/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterandthehackers/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterandthehackers/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterandthehackers/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterandthehackers/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterandthehackers/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterandthehackers/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterandthehackers/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterandthehackers/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterandthehackers/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterandthehackers/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterandthehackers/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterandthehackers/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterandthehackers/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterandthehackers/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterandthehackers/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterandthehackers/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterandthehackers/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterandthehackers/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterandthehackers/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterandthehackers/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterandthehackers/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterandthehackers/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:30Z", + "updated_at": "2015-05-05T23:40:30Z", + "pushed_at": "2015-05-05T23:40:27Z", + "git_url": "git://github.com/baxterandthehackers/public-repo.git", + "ssh_url": "git@github.com:baxterandthehackers/public-repo.git", + "clone_url": "https://github.com/baxterandthehackers/public-repo.git", + "svn_url": "https://github.com/baxterandthehackers/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": false, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master", + "public": true + }, + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:30Z", + "pushed_at": "2015-05-05T23:40:27Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 1, + "mirror_url": null, + "open_issues_count": 2, + "forks": 1, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "baxterandthehackers", + "id": 7649605, + "avatar_url": "https://avatars.githubusercontent.com/u/7649605?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterandthehackers", + "html_url": "https://github.com/baxterandthehackers", + "followers_url": "https://api.github.com/users/baxterandthehackers/followers", + "following_url": "https://api.github.com/users/baxterandthehackers/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterandthehackers/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterandthehackers/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterandthehackers/subscriptions", + "organizations_url": "https://api.github.com/users/baxterandthehackers/orgs", + "repos_url": "https://api.github.com/users/baxterandthehackers/repos", + "events_url": "https://api.github.com/users/baxterandthehackers/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterandthehackers/received_events", + "type": "Organization", + "site_admin": false + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/gollum.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/gollum.json new file mode 100644 index 0000000000..79527b91ca --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/gollum.json @@ -0,0 +1,118 @@ +{ + "pages": [ + { + "page_name": "Home", + "title": "Home", + "summary": null, + "action": "created", + "sha": "91ea1bd42aa2ba166b86e8aefe049e9837214e67", + "html_url": "https://github.com/baxterthehacker/public-repo/wiki/Home" + } + ], + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:12Z", + "pushed_at": "2015-05-05T23:40:17Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "jasonrudolph", + "id": 2988, + "avatar_url": "https://avatars.githubusercontent.com/u/2988?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/jasonrudolph", + "html_url": "https://github.com/jasonrudolph", + "followers_url": "https://api.github.com/users/jasonrudolph/followers", + "following_url": "https://api.github.com/users/jasonrudolph/following{/other_user}", + "gists_url": "https://api.github.com/users/jasonrudolph/gists{/gist_id}", + "starred_url": "https://api.github.com/users/jasonrudolph/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/jasonrudolph/subscriptions", + "organizations_url": "https://api.github.com/users/jasonrudolph/orgs", + "repos_url": "https://api.github.com/users/jasonrudolph/repos", + "events_url": "https://api.github.com/users/jasonrudolph/events{/privacy}", + "received_events_url": "https://api.github.com/users/jasonrudolph/received_events", + "type": "User", + "site_admin": true + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/issue_comment.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/issue_comment.json new file mode 100644 index 0000000000..6f6e7d8975 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/issue_comment.json @@ -0,0 +1,182 @@ +{ + "action": "created", + "issue": { + "url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/2", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/2/labels{/name}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/2/comments", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/2/events", + "html_url": "https://github.com/baxterthehacker/public-repo/issues/2", + "id": 73464126, + "number": 2, + "title": "Spelling error in the README file", + "user": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "url": "https://api.github.com/repos/baxterthehacker/public-repo/labels/bug", + "name": "bug", + "color": "fc2929" + } + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 1, + "created_at": "2015-05-05T23:40:28Z", + "updated_at": "2015-05-05T23:40:28Z", + "closed_at": null, + "body": "It looks like you accidently spelled 'commit' with two 't's." + }, + "comment": { + "url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments/99262140", + "html_url": "https://github.com/baxterthehacker/public-repo/issues/2#issuecomment-99262140", + "issue_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/2", + "id": 99262140, + "user": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "created_at": "2015-05-05T23:40:28Z", + "updated_at": "2015-05-05T23:40:28Z", + "body": "You are totally right! I'll get this fixed right away." + }, + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:12Z", + "pushed_at": "2015-05-05T23:40:27Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 2, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/issues.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/issues.json new file mode 100644 index 0000000000..5e0c52b0c1 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/issues.json @@ -0,0 +1,156 @@ +{ + "action": "opened", + "issue": { + "url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/2", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/2/labels{/name}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/2/comments", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/2/events", + "html_url": "https://github.com/baxterthehacker/public-repo/issues/2", + "id": 73464126, + "number": 2, + "title": "Spelling error in the README file", + "user": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 208045946, + "url": "https://api.github.com/repos/baxterthehacker/public-repo/labels/bug", + "name": "bug", + "color": "fc2929", + "default": true + } + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 0, + "created_at": "2015-05-05T23:40:28Z", + "updated_at": "2015-05-05T23:40:28Z", + "closed_at": null, + "body": "It looks like you accidently spelled 'commit' with two 't's." + }, + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:12Z", + "pushed_at": "2015-05-05T23:40:27Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 2, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/label.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/label.json new file mode 100644 index 0000000000..b7a0be1b2e --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/label.json @@ -0,0 +1,128 @@ +{ + "action": "created", + "label": { + "url": "https://api.github.com/repos/baxterandthehackers/public-repo/labels/blocked", + "name": "blocked", + "color": "ff0000" + }, + "repository": { + "id": 67075329, + "name": "public-repo", + "full_name": "baxterandthehackers/public-repo", + "owner": { + "login": "baxterandthehackers", + "id": 4312013, + "avatar_url": "https://avatars.githubusercontent.com/u/4312013?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterandthehackers", + "html_url": "https://github.com/baxterandthehackers", + "followers_url": "https://api.github.com/users/baxterandthehackers/followers", + "following_url": "https://api.github.com/users/baxterandthehackers/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterandthehackers/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterandthehackers/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterandthehackers/subscriptions", + "organizations_url": "https://api.github.com/users/baxterandthehackers/orgs", + "repos_url": "https://api.github.com/users/baxterandthehackers/repos", + "events_url": "https://api.github.com/users/baxterandthehackers/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterandthehackers/received_events", + "type": "Organization", + "site_admin": false + }, + "private": true, + "html_url": "https://github.com/baxterandthehackers/public-repo", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/baxterandthehackers/public-repo", + "forks_url": "https://api.github.com/repos/baxterandthehackers/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterandthehackers/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterandthehackers/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterandthehackers/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterandthehackers/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterandthehackers/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterandthehackers/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterandthehackers/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterandthehackers/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterandthehackers/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterandthehackers/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterandthehackers/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterandthehackers/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterandthehackers/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterandthehackers/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterandthehackers/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterandthehackers/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterandthehackers/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterandthehackers/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterandthehackers/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterandthehackers/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterandthehackers/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterandthehackers/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterandthehackers/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterandthehackers/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterandthehackers/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterandthehackers/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterandthehackers/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterandthehackers/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterandthehackers/public-repo/releases{/id}", + "deployments_url": "https://api.github.com/repos/baxterandthehackers/public-repo/deployments", + "created_at": "2016-08-31T21:38:51Z", + "updated_at": "2016-08-31T21:38:51Z", + "pushed_at": "2016-08-31T21:38:51Z", + "git_url": "git://github.com/baxterandthehackers/public-repo.git", + "ssh_url": "git@github.com:baxterandthehackers/public-repo.git", + "clone_url": "https://github.com/baxterandthehackers/public-repo.git", + "svn_url": "https://github.com/baxterandthehackers/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 2, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "organization": { + "login": "baxterandthehackers", + "id": 4312013, + "url": "https://api.github.com/orgs/baxterandthehackers", + "repos_url": "https://api.github.com/orgs/baxterandthehackers/repos", + "events_url": "https://api.github.com/orgs/baxterandthehackers/events", + "hooks_url": "https://api.github.com/orgs/baxterandthehackers/hooks", + "issues_url": "https://api.github.com/orgs/baxterandthehackers/issues", + "members_url": "https://api.github.com/orgs/baxterandthehackers/members{/member}", + "public_members_url": "https://api.github.com/orgs/baxterandthehackers/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/4312013?v=3", + "description": "" + }, + "sender": { + "login": "baxterthehacker", + "id": 7649605, + "avatar_url": "https://avatars.githubusercontent.com/u/7649605?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": true + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/member.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/member.json new file mode 100644 index 0000000000..20305670e3 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/member.json @@ -0,0 +1,128 @@ +{ + "action": "added", + "member": { + "login": "octocat", + "id": 583231, + "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:30Z", + "pushed_at": "2015-05-05T23:40:40Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 2, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/membership.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/membership.json new file mode 100644 index 0000000000..612d967f6d --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/membership.json @@ -0,0 +1,61 @@ +{ + "action": "added", + "scope": "team", + "member": { + "login": "kdaigle", + "id": 2501, + "avatar_url": "https://avatars.githubusercontent.com/u/2501?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/kdaigle", + "html_url": "https://github.com/kdaigle", + "followers_url": "https://api.github.com/users/kdaigle/followers", + "following_url": "https://api.github.com/users/kdaigle/following{/other_user}", + "gists_url": "https://api.github.com/users/kdaigle/gists{/gist_id}", + "starred_url": "https://api.github.com/users/kdaigle/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/kdaigle/subscriptions", + "organizations_url": "https://api.github.com/users/kdaigle/orgs", + "repos_url": "https://api.github.com/users/kdaigle/repos", + "events_url": "https://api.github.com/users/kdaigle/events{/privacy}", + "received_events_url": "https://api.github.com/users/kdaigle/received_events", + "type": "User", + "site_admin": true + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=2", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "team": { + "name": "Contractors", + "id": 123456, + "slug": "contractors", + "permission": "admin", + "url": "https://api.github.com/teams/123456", + "members_url": "https://api.github.com/teams/123456/members{/member}", + "repositories_url": "https://api.github.com/teams/123456/repos" + }, + "organization": { + "login": "baxterandthehackers", + "id": 7649605, + "url": "https://api.github.com/orgs/baxterandthehackers", + "repos_url": "https://api.github.com/orgs/baxterandthehackers/repos", + "events_url": "https://api.github.com/orgs/baxterandthehackers/events", + "members_url": "https://api.github.com/orgs/baxterandthehackers/members{/member}", + "public_members_url": "https://api.github.com/orgs/baxterandthehackers/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/7649605?v=2" + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/milestone.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/milestone.json new file mode 100644 index 0000000000..1cd673eef5 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/milestone.json @@ -0,0 +1,158 @@ +{ + "action": "created", + "milestone": { + "url": "https://api.github.com/repos/baxterandthehackers/public-repo/milestones/3", + "html_url": "https://github.com/baxterandthehackers/public-repo/milestones/Test%20milestone%20creation%20webhook%20from%20command%20line2", + "labels_url": "https://api.github.com/repos/baxterandthehackers/public-repo/milestones/3/labels", + "id": 2055681, + "number": 3, + "title": "I am a milestone", + "description": null, + "creator": { + "login": "baxterthehacker", + "id": 7649605, + "avatar_url": "https://avatars.githubusercontent.com/u/7649605?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": true + }, + "open_issues": 0, + "closed_issues": 0, + "state": "open", + "created_at": "2016-10-07T19:26:08Z", + "updated_at": "2016-10-07T19:26:08Z", + "due_on": null, + "closed_at": null + }, + "repository": { + "id": 70275481, + "name": "public-repo", + "full_name": "baxterandthehackers/public-repo", + "owner": { + "login": "baxterandthehackers", + "id": 4312013, + "avatar_url": "https://avatars.githubusercontent.com/u/4312013?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterandthehackers", + "html_url": "https://github.com/baxterandthehackers", + "followers_url": "https://api.github.com/users/baxterandthehackers/followers", + "following_url": "https://api.github.com/users/baxterandthehackers/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterandthehackers/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterandthehackers/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterandthehackers/subscriptions", + "organizations_url": "https://api.github.com/users/baxterandthehackers/orgs", + "repos_url": "https://api.github.com/users/baxterandthehackers/repos", + "events_url": "https://api.github.com/users/baxterandthehackers/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterandthehackers/received_events", + "type": "Organization", + "site_admin": false + }, + "private": true, + "html_url": "https://github.com/baxterandthehackers/public-repo", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/baxterandthehackers/public-repo", + "forks_url": "https://api.github.com/repos/baxterandthehackers/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterandthehackers/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterandthehackers/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterandthehackers/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterandthehackers/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterandthehackers/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterandthehackers/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterandthehackers/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterandthehackers/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterandthehackers/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterandthehackers/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterandthehackers/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterandthehackers/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterandthehackers/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterandthehackers/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterandthehackers/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterandthehackers/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterandthehackers/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterandthehackers/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterandthehackers/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterandthehackers/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterandthehackers/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterandthehackers/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterandthehackers/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterandthehackers/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterandthehackers/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterandthehackers/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterandthehackers/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterandthehackers/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterandthehackers/public-repo/releases{/id}", + "deployments_url": "https://api.github.com/repos/baxterandthehackers/public-repo/deployments", + "created_at": "2016-10-07T19:10:12Z", + "updated_at": "2016-10-07T19:10:12Z", + "pushed_at": "2016-10-07T19:10:13Z", + "git_url": "git://github.com/baxterandthehackers/public-repo.git", + "ssh_url": "git@github.com:baxterandthehackers/public-repo.git", + "clone_url": "https://github.com/baxterandthehackers/public-repo.git", + "svn_url": "https://github.com/baxterandthehackers/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + }, + "organization": { + "login": "baxterandthehackers", + "id": 4312013, + "url": "https://api.github.com/orgs/baxterandthehackers", + "repos_url": "https://api.github.com/orgs/baxterandthehackers/repos", + "events_url": "https://api.github.com/orgs/baxterandthehackers/events", + "hooks_url": "https://api.github.com/orgs/baxterandthehackers/hooks", + "issues_url": "https://api.github.com/orgs/baxterandthehackers/issues", + "members_url": "https://api.github.com/orgs/baxterandthehackers/members{/member}", + "public_members_url": "https://api.github.com/orgs/baxterandthehackers/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/4312013?v=3", + "description": "" + }, + "sender": { + "login": "baxterthehacker", + "id": 7649605, + "avatar_url": "https://avatars.githubusercontent.com/u/7649605?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": true + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/page_build.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/page_build.json new file mode 100644 index 0000000000..80962c016e --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/page_build.json @@ -0,0 +1,139 @@ +{ + "id": 15995382, + "build": { + "url": "https://api.github.com/repos/baxterthehacker/public-repo/pages/builds/15995382", + "status": "built", + "error": { + "message": null + }, + "pusher": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "commit": "053b99542c83021d6b202d1a1f5ecd5ef7084e55", + "duration": 3790, + "created_at": "2015-05-05T23:40:13Z", + "updated_at": "2015-05-05T23:40:17Z" + }, + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:12Z", + "pushed_at": "2015-05-05T23:40:17Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/public.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/public.json new file mode 100644 index 0000000000..f596a54282 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/public.json @@ -0,0 +1,108 @@ +{ + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:41Z", + "pushed_at": "2015-05-05T23:40:40Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 2, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/pull_request.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/pull_request.json new file mode 100644 index 0000000000..316cc4e8f7 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/pull_request.json @@ -0,0 +1,412 @@ +{ + "action": "opened", + "number": 1, + "pull_request": { + "url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1", + "id": 34778301, + "html_url": "https://github.com/baxterthehacker/public-repo/pull/1", + "diff_url": "https://github.com/baxterthehacker/public-repo/pull/1.diff", + "patch_url": "https://github.com/baxterthehacker/public-repo/pull/1.patch", + "issue_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/1", + "number": 1, + "state": "open", + "locked": false, + "title": "Update the README with new information", + "user": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "body": "This is a pretty simple change that we need to pull into master.", + "created_at": "2015-05-05T23:40:27Z", + "updated_at": "2015-05-05T23:40:27Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": null, + "assignee": null, + "milestone": null, + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1/commits", + "review_comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1/comments", + "review_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/1/comments", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "head": { + "label": "baxterthehacker:changes", + "ref": "changes", + "sha": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "user": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:12Z", + "pushed_at": "2015-05-05T23:40:26Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 1, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "master" + } + }, + "base": { + "label": "baxterthehacker:master", + "ref": "master", + "sha": "9049f1265b7d61be4a8904a9a27120d2064dab3b", + "user": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:12Z", + "pushed_at": "2015-05-05T23:40:26Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 1, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "master" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1" + }, + "html": { + "href": "https://github.com/baxterthehacker/public-repo/pull/1" + }, + "issue": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/issues/1" + }, + "comments": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/issues/1/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c" + } + }, + "merged": false, + "mergeable": null, + "mergeable_state": "unknown", + "merged_by": null, + "comments": 0, + "review_comments": 0, + "commits": 1, + "additions": 1, + "deletions": 1, + "changed_files": 1 + }, + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:12Z", + "pushed_at": "2015-05-05T23:40:26Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 1, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/pull_request_review.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/pull_request_review.json new file mode 100644 index 0000000000..dd6698c3ac --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/pull_request_review.json @@ -0,0 +1,442 @@ +{ + "action": "submitted", + "review": { + "id": 2626884, + "user": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars3.githubusercontent.com/u/6752317?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "body": "Looks great!\n", + "state": "APPROVED", + "html_url": "https://github.com/baxterthehacker/public-repo/pull/8#pullrequestreview-2626884", + "pull_request_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/8", + "author_association": "OWNER", + "_links": { + "html": { + "href": "https://github.com/baxterthehacker/public-repo/pull/8#pullrequestreview-2626884" + }, + "pull_request": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/8" + } + }, + "submitted_at": "2016-10-03T23:39:09Z", + "commit_id": "b7a1f9c27caa4e03c14a88feb56e2d4f7500aa63" + }, + "pull_request": { + "url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/8", + "id": 87811438, + "html_url": "https://github.com/baxterthehacker/public-repo/pull/8", + "diff_url": "https://github.com/baxterthehacker/public-repo/pull/8.diff", + "patch_url": "https://github.com/baxterthehacker/public-repo/pull/8.patch", + "issue_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/8", + "number": 8, + "state": "open", + "locked": false, + "title": "Add a README description", + "user": { + "login": "skalnik", + "id": 2546, + "avatar_url": "https://avatars.githubusercontent.com/u/2546?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/skalnik", + "html_url": "https://github.com/skalnik", + "followers_url": "https://api.github.com/users/skalnik/followers", + "following_url": "https://api.github.com/users/skalnik/following{/other_user}", + "gists_url": "https://api.github.com/users/skalnik/gists{/gist_id}", + "starred_url": "https://api.github.com/users/skalnik/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/skalnik/subscriptions", + "organizations_url": "https://api.github.com/users/skalnik/orgs", + "repos_url": "https://api.github.com/users/skalnik/repos", + "events_url": "https://api.github.com/users/skalnik/events{/privacy}", + "received_events_url": "https://api.github.com/users/skalnik/received_events", + "type": "User", + "site_admin": true + }, + "body": "Just a few more details", + "created_at": "2016-10-03T23:37:43Z", + "updated_at": "2016-10-03T23:39:09Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": "faea154a7decef6819754aab0f8c0e232e6c8b4f", + "assignee": null, + "assignees": [], + "milestone": null, + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/8/commits", + "review_comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/8/comments", + "review_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/8/comments", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/b7a1f9c27caa4e03c14a88feb56e2d4f7500aa63", + "head": { + "label": "skalnik:patch-2", + "ref": "patch-2", + "sha": "b7a1f9c27caa4e03c14a88feb56e2d4f7500aa63", + "user": { + "login": "skalnik", + "id": 2546, + "avatar_url": "https://avatars.githubusercontent.com/u/2546?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/skalnik", + "html_url": "https://github.com/skalnik", + "followers_url": "https://api.github.com/users/skalnik/followers", + "following_url": "https://api.github.com/users/skalnik/following{/other_user}", + "gists_url": "https://api.github.com/users/skalnik/gists{/gist_id}", + "starred_url": "https://api.github.com/users/skalnik/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/skalnik/subscriptions", + "organizations_url": "https://api.github.com/users/skalnik/orgs", + "repos_url": "https://api.github.com/users/skalnik/repos", + "events_url": "https://api.github.com/users/skalnik/events{/privacy}", + "received_events_url": "https://api.github.com/users/skalnik/received_events", + "type": "User", + "site_admin": true + }, + "repo": { + "id": 69919152, + "name": "public-repo", + "full_name": "skalnik/public-repo", + "owner": { + "login": "skalnik", + "id": 2546, + "avatar_url": "https://avatars.githubusercontent.com/u/2546?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/skalnik", + "html_url": "https://github.com/skalnik", + "followers_url": "https://api.github.com/users/skalnik/followers", + "following_url": "https://api.github.com/users/skalnik/following{/other_user}", + "gists_url": "https://api.github.com/users/skalnik/gists{/gist_id}", + "starred_url": "https://api.github.com/users/skalnik/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/skalnik/subscriptions", + "organizations_url": "https://api.github.com/users/skalnik/orgs", + "repos_url": "https://api.github.com/users/skalnik/repos", + "events_url": "https://api.github.com/users/skalnik/events{/privacy}", + "received_events_url": "https://api.github.com/users/skalnik/received_events", + "type": "User", + "site_admin": true + }, + "private": false, + "html_url": "https://github.com/skalnik/public-repo", + "description": null, + "fork": true, + "url": "https://api.github.com/repos/skalnik/public-repo", + "forks_url": "https://api.github.com/repos/skalnik/public-repo/forks", + "keys_url": "https://api.github.com/repos/skalnik/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/skalnik/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/skalnik/public-repo/teams", + "hooks_url": "https://api.github.com/repos/skalnik/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/skalnik/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/skalnik/public-repo/events", + "assignees_url": "https://api.github.com/repos/skalnik/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/skalnik/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/skalnik/public-repo/tags", + "blobs_url": "https://api.github.com/repos/skalnik/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/skalnik/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/skalnik/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/skalnik/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/skalnik/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/skalnik/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/skalnik/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/skalnik/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/skalnik/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/skalnik/public-repo/subscription", + "commits_url": "https://api.github.com/repos/skalnik/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/skalnik/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/skalnik/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/skalnik/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/skalnik/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/skalnik/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/skalnik/public-repo/merges", + "archive_url": "https://api.github.com/repos/skalnik/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/skalnik/public-repo/downloads", + "issues_url": "https://api.github.com/repos/skalnik/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/skalnik/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/skalnik/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/skalnik/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/skalnik/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/skalnik/public-repo/releases{/id}", + "deployments_url": "https://api.github.com/repos/skalnik/public-repo/deployments", + "created_at": "2016-10-03T23:23:31Z", + "updated_at": "2016-08-15T17:19:01Z", + "pushed_at": "2016-10-03T23:36:52Z", + "git_url": "git://github.com/skalnik/public-repo.git", + "ssh_url": "git@github.com:skalnik/public-repo.git", + "clone_url": "https://github.com/skalnik/public-repo.git", + "svn_url": "https://github.com/skalnik/public-repo", + "homepage": null, + "size": 233, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": false, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + } + }, + "base": { + "label": "baxterthehacker:master", + "ref": "master", + "sha": "9049f1265b7d61be4a8904a9a27120d2064dab3b", + "user": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "deployments_url": "https://api.github.com/repos/baxterthehacker/public-repo/deployments", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2016-08-15T17:19:01Z", + "pushed_at": "2016-10-03T23:37:43Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 233, + "stargazers_count": 2, + "watchers_count": 2, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 2, + "mirror_url": null, + "open_issues_count": 5, + "forks": 2, + "open_issues": 5, + "watchers": 2, + "default_branch": "master" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/8" + }, + "html": { + "href": "https://github.com/baxterthehacker/public-repo/pull/8" + }, + "issue": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/issues/8" + }, + "comments": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/issues/8/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/8/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/8/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/b7a1f9c27caa4e03c14a88feb56e2d4f7500aa63" + } + } + }, + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "deployments_url": "https://api.github.com/repos/baxterthehacker/public-repo/deployments", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2016-08-15T17:19:01Z", + "pushed_at": "2016-10-03T23:37:43Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 233, + "stargazers_count": 2, + "watchers_count": 2, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 2, + "mirror_url": null, + "open_issues_count": 5, + "forks": 2, + "open_issues": 5, + "watchers": 2, + "default_branch": "master" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/pull_request_review_comment.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/pull_request_review_comment.json new file mode 100644 index 0000000000..a89b6b547d --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/pull_request_review_comment.json @@ -0,0 +1,446 @@ +{ + "action": "created", + "comment": { + "url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/comments/29724692", + "id": 29724692, + "diff_hunk": "@@ -1 +1 @@\n-# public-repo", + "path": "README.md", + "position": 1, + "original_position": 1, + "commit_id": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "original_commit_id": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "user": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "body": "Maybe you should use more emojji on this line.", + "created_at": "2015-05-05T23:40:27Z", + "updated_at": "2015-05-05T23:40:27Z", + "html_url": "https://github.com/baxterthehacker/public-repo/pull/1#discussion_r29724692", + "pull_request_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1", + "_links": { + "self": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/comments/29724692" + }, + "html": { + "href": "https://github.com/baxterthehacker/public-repo/pull/1#discussion_r29724692" + }, + "pull_request": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1" + } + } + }, + "pull_request": { + "url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1", + "id": 34778301, + "html_url": "https://github.com/baxterthehacker/public-repo/pull/1", + "diff_url": "https://github.com/baxterthehacker/public-repo/pull/1.diff", + "patch_url": "https://github.com/baxterthehacker/public-repo/pull/1.patch", + "issue_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/1", + "number": 1, + "state": "open", + "locked": false, + "title": "Update the README with new information", + "user": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "body": "This is a pretty simple change that we need to pull into master.", + "created_at": "2015-05-05T23:40:27Z", + "updated_at": "2015-05-05T23:40:27Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": "18721552ba489fb84e12958c1b5694b5475f7991", + "assignee": null, + "milestone": null, + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1/commits", + "review_comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1/comments", + "review_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/1/comments", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "head": { + "label": "baxterthehacker:changes", + "ref": "changes", + "sha": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "user": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:12Z", + "pushed_at": "2015-05-05T23:40:27Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 1, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "master" + } + }, + "base": { + "label": "baxterthehacker:master", + "ref": "master", + "sha": "9049f1265b7d61be4a8904a9a27120d2064dab3b", + "user": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:12Z", + "pushed_at": "2015-05-05T23:40:27Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 1, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "master" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1" + }, + "html": { + "href": "https://github.com/baxterthehacker/public-repo/pull/1" + }, + "issue": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/issues/1" + }, + "comments": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/issues/1/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c" + } + } + }, + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:12Z", + "pushed_at": "2015-05-05T23:40:27Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 1, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/push.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/push.json new file mode 100644 index 0000000000..11477e0bb7 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/push.json @@ -0,0 +1,159 @@ +{ + "ref": "refs/heads/changes", + "before": "9049f1265b7d61be4a8904a9a27120d2064dab3b", + "after": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "created": false, + "deleted": false, + "forced": false, + "base_ref": null, + "compare": "https://github.com/baxterthehacker/public-repo/compare/9049f1265b7d...0d1a26e67d8f", + "commits": [ + { + "id": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "tree_id": "f9d2a07e9488b91af2641b26b9407fe22a451433", + "distinct": true, + "message": "Update README.md", + "timestamp": "2015-05-05T19:40:15-04:00", + "url": "https://github.com/baxterthehacker/public-repo/commit/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "author": { + "name": "baxterthehacker", + "email": "baxterthehacker@users.noreply.github.com", + "username": "baxterthehacker" + }, + "committer": { + "name": "baxterthehacker", + "email": "baxterthehacker@users.noreply.github.com", + "username": "baxterthehacker" + }, + "added": [ + ], + "removed": [ + ], + "modified": [ + "README.md" + ] + } + ], + "head_commit": { + "id": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "tree_id": "f9d2a07e9488b91af2641b26b9407fe22a451433", + "distinct": true, + "message": "Update README.md", + "timestamp": "2015-05-05T19:40:15-04:00", + "url": "https://github.com/baxterthehacker/public-repo/commit/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "author": { + "name": "baxterthehacker", + "email": "baxterthehacker@users.noreply.github.com", + "username": "baxterthehacker" + }, + "committer": { + "name": "baxterthehacker", + "email": "baxterthehacker@users.noreply.github.com", + "username": "baxterthehacker" + }, + "added": [ + ], + "removed": [ + ], + "modified": [ + "README.md" + ] + }, + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "name": "baxterthehacker", + "email": "baxterthehacker@users.noreply.github.com" + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://github.com/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": 1430869212, + "updated_at": "2015-05-05T23:40:12Z", + "pushed_at": 1430869217, + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master", + "stargazers": 0, + "master_branch": "master" + }, + "pusher": { + "name": "baxterthehacker", + "email": "baxterthehacker@users.noreply.github.com" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/release.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/release.json new file mode 100644 index 0000000000..35de2f33eb --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/release.json @@ -0,0 +1,147 @@ +{ + "action": "published", + "release": { + "url": "https://api.github.com/repos/baxterthehacker/public-repo/releases/1261438", + "assets_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases/1261438/assets", + "upload_url": "https://uploads.github.com/repos/baxterthehacker/public-repo/releases/1261438/assets{?name}", + "html_url": "https://github.com/baxterthehacker/public-repo/releases/tag/0.0.1", + "id": 1261438, + "tag_name": "0.0.1", + "target_commitish": "master", + "name": null, + "draft": false, + "author": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "prerelease": false, + "created_at": "2015-05-05T23:40:12Z", + "published_at": "2015-05-05T23:40:38Z", + "assets": [ + ], + "tarball_url": "https://api.github.com/repos/baxterthehacker/public-repo/tarball/0.0.1", + "zipball_url": "https://api.github.com/repos/baxterthehacker/public-repo/zipball/0.0.1", + "body": null + }, + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:30Z", + "pushed_at": "2015-05-05T23:40:38Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 2, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/repository.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/repository.json new file mode 100644 index 0000000000..d22386c3f2 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/repository.json @@ -0,0 +1,119 @@ +{ + "action": "created", + "repository": { + "id": 27496774, + "name": "new-repository", + "full_name": "baxterandthehackers/new-repository", + "owner": { + "login": "baxterandthehackers", + "id": 7649605, + "avatar_url": "https://avatars.githubusercontent.com/u/7649605?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterandthehackers", + "html_url": "https://github.com/baxterandthehackers", + "followers_url": "https://api.github.com/users/baxterandthehackers/followers", + "following_url": "https://api.github.com/users/baxterandthehackers/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterandthehackers/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterandthehackers/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterandthehackers/subscriptions", + "organizations_url": "https://api.github.com/users/baxterandthehackers/orgs", + "repos_url": "https://api.github.com/users/baxterandthehackers/repos", + "events_url": "https://api.github.com/users/baxterandthehackers/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterandthehackers/received_events", + "type": "Organization", + "site_admin": false + }, + "private": true, + "html_url": "https://github.com/baxterandthehackers/new-repository", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterandthehackers/new-repository", + "forks_url": "https://api.github.com/repos/baxterandthehackers/new-repository/forks", + "keys_url": "https://api.github.com/repos/baxterandthehackers/new-repository/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterandthehackers/new-repository/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterandthehackers/new-repository/teams", + "hooks_url": "https://api.github.com/repos/baxterandthehackers/new-repository/hooks", + "issue_events_url": "https://api.github.com/repos/baxterandthehackers/new-repository/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterandthehackers/new-repository/events", + "assignees_url": "https://api.github.com/repos/baxterandthehackers/new-repository/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterandthehackers/new-repository/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterandthehackers/new-repository/tags", + "blobs_url": "https://api.github.com/repos/baxterandthehackers/new-repository/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterandthehackers/new-repository/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterandthehackers/new-repository/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterandthehackers/new-repository/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterandthehackers/new-repository/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterandthehackers/new-repository/languages", + "stargazers_url": "https://api.github.com/repos/baxterandthehackers/new-repository/stargazers", + "contributors_url": "https://api.github.com/repos/baxterandthehackers/new-repository/contributors", + "subscribers_url": "https://api.github.com/repos/baxterandthehackers/new-repository/subscribers", + "subscription_url": "https://api.github.com/repos/baxterandthehackers/new-repository/subscription", + "commits_url": "https://api.github.com/repos/baxterandthehackers/new-repository/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterandthehackers/new-repository/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterandthehackers/new-repository/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterandthehackers/new-repository/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/baxterandthehackers/new-repository/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterandthehackers/new-repository/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterandthehackers/new-repository/merges", + "archive_url": "https://api.github.com/repos/baxterandthehackers/new-repository/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterandthehackers/new-repository/downloads", + "issues_url": "https://api.github.com/repos/baxterandthehackers/new-repository/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterandthehackers/new-repository/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterandthehackers/new-repository/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterandthehackers/new-repository/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterandthehackers/new-repository/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterandthehackers/new-repository/releases{/id}", + "created_at": "2014-12-03T16:39:25Z", + "updated_at": "2014-12-03T16:39:25Z", + "pushed_at": "2014-12-03T16:39:25Z", + "git_url": "git://github.com/baxterandthehackers/new-repository.git", + "ssh_url": "git@github.com:baxterandthehackers/new-repository.git", + "clone_url": "https://github.com/baxterandthehackers/new-repository.git", + "svn_url": "https://github.com/baxterandthehackers/new-repository", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + }, + "organization": { + "login": "baxterandthehackers", + "id": 7649605, + "url": "https://api.github.com/orgs/baxterandthehackers", + "repos_url": "https://api.github.com/orgs/baxterandthehackers/repos", + "events_url": "https://api.github.com/orgs/baxterandthehackers/events", + "members_url": "https://api.github.com/orgs/baxterandthehackers/members{/member}", + "public_members_url": "https://api.github.com/orgs/baxterandthehackers/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/7649605?v=2" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=2", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/status.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/status.json new file mode 100644 index 0000000000..82f5a09967 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/status.json @@ -0,0 +1,205 @@ +{ + "id": 214015194, + "sha": "9049f1265b7d61be4a8904a9a27120d2064dab3b", + "name": "baxterthehacker/public-repo", + "target_url": null, + "context": "default", + "description": null, + "state": "success", + "commit": { + "sha": "9049f1265b7d61be4a8904a9a27120d2064dab3b", + "commit": { + "author": { + "name": "baxterthehacker", + "email": "baxterthehacker@users.noreply.github.com", + "date": "2015-05-05T23:40:12Z" + }, + "committer": { + "name": "baxterthehacker", + "email": "baxterthehacker@users.noreply.github.com", + "date": "2015-05-05T23:40:12Z" + }, + "message": "Initial commit", + "tree": { + "sha": "02b49ad0ba4f1acd9f06531b21e16a4ac5d341d0", + "url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees/02b49ad0ba4f1acd9f06531b21e16a4ac5d341d0" + }, + "url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits/9049f1265b7d61be4a8904a9a27120d2064dab3b", + "comment_count": 1 + }, + "url": "https://api.github.com/repos/baxterthehacker/public-repo/commits/9049f1265b7d61be4a8904a9a27120d2064dab3b", + "html_url": "https://github.com/baxterthehacker/public-repo/commit/9049f1265b7d61be4a8904a9a27120d2064dab3b", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits/9049f1265b7d61be4a8904a9a27120d2064dab3b/comments", + "author": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "committer": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "parents": [ + ] + }, + "branches": [ + { + "name": "master", + "commit": { + "sha": "9049f1265b7d61be4a8904a9a27120d2064dab3b", + "url": "https://api.github.com/repos/baxterthehacker/public-repo/commits/9049f1265b7d61be4a8904a9a27120d2064dab3b" + } + }, + { + "name": "changes", + "commit": { + "sha": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "url": "https://api.github.com/repos/baxterthehacker/public-repo/commits/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c" + } + }, + { + "name": "gh-pages", + "commit": { + "sha": "b11bb7545ac14abafc6191a0481b0d961e7793c6", + "url": "https://api.github.com/repos/baxterthehacker/public-repo/commits/b11bb7545ac14abafc6191a0481b0d961e7793c6" + } + } + ], + "created_at": "2015-05-05T23:40:39Z", + "updated_at": "2015-05-05T23:40:39Z", + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:30Z", + "pushed_at": "2015-05-05T23:40:39Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 2, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/team_add.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/team_add.json new file mode 100644 index 0000000000..bd4256bc07 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/team_add.json @@ -0,0 +1,129 @@ +{ + "team": { + "name": "github", + "id": 836012, + "slug": "github", + "description": "", + "permission": "pull", + "url": "https://api.github.com/teams/836012", + "members_url": "https://api.github.com/teams/836012/members{/member}", + "repositories_url": "https://api.github.com/teams/836012/repos" + }, + "repository": { + "id": 35129393, + "name": "public-repo", + "full_name": "baxterandthehackers/public-repo", + "owner": { + "login": "baxterandthehackers", + "id": 7649605, + "avatar_url": "https://avatars.githubusercontent.com/u/7649605?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterandthehackers", + "html_url": "https://github.com/baxterandthehackers", + "followers_url": "https://api.github.com/users/baxterandthehackers/followers", + "following_url": "https://api.github.com/users/baxterandthehackers/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterandthehackers/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterandthehackers/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterandthehackers/subscriptions", + "organizations_url": "https://api.github.com/users/baxterandthehackers/orgs", + "repos_url": "https://api.github.com/users/baxterandthehackers/repos", + "events_url": "https://api.github.com/users/baxterandthehackers/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterandthehackers/received_events", + "type": "Organization", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterandthehackers/public-repo", + "description": "", + "fork": true, + "url": "https://api.github.com/repos/baxterandthehackers/public-repo", + "forks_url": "https://api.github.com/repos/baxterandthehackers/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterandthehackers/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterandthehackers/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterandthehackers/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterandthehackers/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterandthehackers/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterandthehackers/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterandthehackers/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterandthehackers/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterandthehackers/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterandthehackers/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterandthehackers/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterandthehackers/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterandthehackers/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterandthehackers/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterandthehackers/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterandthehackers/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterandthehackers/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterandthehackers/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterandthehackers/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterandthehackers/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterandthehackers/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterandthehackers/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterandthehackers/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterandthehackers/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterandthehackers/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterandthehackers/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterandthehackers/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterandthehackers/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterandthehackers/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:30Z", + "updated_at": "2015-05-05T23:40:30Z", + "pushed_at": "2015-05-05T23:40:27Z", + "git_url": "git://github.com/baxterandthehackers/public-repo.git", + "ssh_url": "git@github.com:baxterandthehackers/public-repo.git", + "clone_url": "https://github.com/baxterandthehackers/public-repo.git", + "svn_url": "https://github.com/baxterandthehackers/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": false, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + }, + "organization": { + "login": "baxterandthehackers", + "id": 7649605, + "url": "https://api.github.com/orgs/baxterandthehackers", + "repos_url": "https://api.github.com/orgs/baxterandthehackers/repos", + "events_url": "https://api.github.com/orgs/baxterandthehackers/events", + "members_url": "https://api.github.com/orgs/baxterandthehackers/members{/member}", + "public_members_url": "https://api.github.com/orgs/baxterandthehackers/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/7649605?v=3", + "description": null + }, + "sender": { + "login": "baxterandthehackers", + "id": 7649605, + "avatar_url": "https://avatars.githubusercontent.com/u/7649605?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterandthehackers", + "html_url": "https://github.com/baxterandthehackers", + "followers_url": "https://api.github.com/users/baxterandthehackers/followers", + "following_url": "https://api.github.com/users/baxterandthehackers/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterandthehackers/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterandthehackers/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterandthehackers/subscriptions", + "organizations_url": "https://api.github.com/users/baxterandthehackers/orgs", + "repos_url": "https://api.github.com/users/baxterandthehackers/repos", + "events_url": "https://api.github.com/users/baxterandthehackers/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterandthehackers/received_events", + "type": "Organization", + "site_admin": false + } +} diff --git a/src/test/resources/org/kohsuke/github/GHEventPayloadTest/watch.json b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/watch.json new file mode 100644 index 0000000000..88bc71d0ab --- /dev/null +++ b/src/test/resources/org/kohsuke/github/GHEventPayloadTest/watch.json @@ -0,0 +1,109 @@ +{ + "action": "started", + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:30Z", + "pushed_at": "2015-05-05T23:40:27Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 2, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +}