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 extends GHHook[]> collectionClass();
+
+ abstract Class extends GHHook> 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 extends GHHook[]> collectionClass() {
+ return GHRepoHook[].class;
+ }
+
+ @Override
+ Class extends GHHook> 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 extends GHHook[]> collectionClass() {
+ return GHOrgHook[].class;
+ }
+
+ @Override
+ Class extends GHHook> 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