Merge remote-tracking branch 'github-api/master' into ghmeta

This commit is contained in:
Liam Newman
2019-11-14 09:05:20 -08:00
171 changed files with 4578 additions and 4704 deletions

View File

@@ -26,6 +26,7 @@ package org.kohsuke.github;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.introspect.VisibilityChecker.Std;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import org.apache.commons.codec.Charsets;
@@ -59,28 +60,28 @@ import static org.kohsuke.github.Previews.MACHINE_MAN;
*
* <h2>Thread safety</h2>
* <p>
* This library aims to be safe for use by multiple threads concurrently, although
* the library itself makes no attempt to control/serialize potentially conflicting
* operations to GitHub, such as updating &amp; deleting a repository at the same time.
* This library aims to be safe for use by multiple threads concurrently, although the library itself makes no attempt
* to control/serialize potentially conflicting operations to GitHub, such as updating &amp; deleting a repository at
* the same time.
*
* @author Kohsuke Kawaguchi
*/
public class GitHub {
/*package*/ final String login;
final String login;
/**
* Value of the authorization header to be sent with the request.
*/
/*package*/ final String encodedAuthorization;
final String encodedAuthorization;
private final ConcurrentMap<String,GHUser> users;
private final ConcurrentMap<String,GHOrganization> orgs;
private final ConcurrentMap<String, GHUser> users;
private final ConcurrentMap<String, GHOrganization> orgs;
// Cache of myself object.
private GHMyself myself;
private final String apiUrl;
/*package*/ final RateLimitHandler rateLimitHandler;
/*package*/ final AbuseLimitHandler abuseLimitHandler;
final RateLimitHandler rateLimitHandler;
final AbuseLimitHandler abuseLimitHandler;
private HttpConnector connector = HttpConnector.DEFAULT;
@@ -92,58 +93,62 @@ public class GitHub {
* Creates a client API root object.
*
* <p>
* Several different combinations of the login/oauthAccessToken/password parameters are allowed
* to represent different ways of authentication.
* Several different combinations of the login/oauthAccessToken/password parameters are allowed to represent
* different ways of authentication.
*
* <dl>
* <dt>Log in anonymously
* <dd>Leave all three parameters null and you will be making HTTP requests without any authentication.
* <dt>Log in anonymously
* <dd>Leave all three parameters null and you will be making HTTP requests without any authentication.
*
* <dt>Log in with password
* <dd>Specify the login and password, then leave oauthAccessToken null.
* This will use the HTTP BASIC auth with the GitHub API.
* <dt>Log in with password
* <dd>Specify the login and password, then leave oauthAccessToken null. This will use the HTTP BASIC auth with the
* GitHub API.
*
* <dt>Log in with OAuth token
* <dd>Specify oauthAccessToken, and optionally specify the login. Leave password null.
* This will send OAuth token to the GitHub API. If the login parameter is null,
* The constructor makes an API call to figure out the user name that owns the token.
* <dt>Log in with OAuth token
* <dd>Specify oauthAccessToken, and optionally specify the login. Leave password null. This will send OAuth token
* to the GitHub API. If the login parameter is null, The constructor makes an API call to figure out the user name
* that owns the token.
*
* <dt>Log in with JWT token
* <dd>Specify jwtToken. Leave password null.
* This will send JWT token to the GitHub API via the Authorization HTTP header.
* Please note that only operations in which permissions have been previously configured and accepted during
* the GitHub App will be executed successfully.
* <dt>Log in with JWT token
* <dd>Specify jwtToken. Leave password null. This will send JWT token to the GitHub API via the Authorization HTTP
* header. Please note that only operations in which permissions have been previously configured and accepted during
* the GitHub App will be executed successfully.
* </dl>
*
* @param apiUrl
* The URL of GitHub (or GitHub enterprise) API endpoint, such as "https://api.github.com" or
* "http://ghe.acme.com/api/v3". Note that GitHub Enterprise has <code>/api/v3</code> in the URL.
* For historical reasons, this parameter still accepts the bare domain name, but that's considered deprecated.
* Password is also considered deprecated as it is no longer required for api usage.
* 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 <code>/api/v3</code> in the URL. For
* historical reasons, this parameter still accepts the bare domain name, but that's considered
* deprecated. Password is also considered deprecated as it is no longer required for api usage.
* @param login
* The user ID on GitHub that you are logging in as. Can be omitted if the OAuth token is
* provided or if logging in anonymously. Specifying this would save one API call.
* The user ID on GitHub that you are logging in as. Can be omitted if the OAuth token is provided or if
* logging in anonymously. Specifying this would save one API call.
* @param oauthAccessToken
* Secret OAuth token.
* Secret OAuth token.
* @param password
* User's password. Always used in conjunction with the {@code login} parameter
* User's password. Always used in conjunction with the {@code login} parameter
* @param connector
* HttpConnector to use. Pass null to use default connector.
* HttpConnector to use. Pass null to use default connector.
*/
/* package */ GitHub(String apiUrl, String login, String oauthAccessToken, String jwtToken, String password, HttpConnector connector, RateLimitHandler rateLimitHandler, AbuseLimitHandler abuseLimitHandler) throws IOException {
if (apiUrl.endsWith("/")) apiUrl = apiUrl.substring(0, apiUrl.length()-1); // normalize
GitHub(String apiUrl, String login, String oauthAccessToken, String jwtToken, 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;
if (null != connector)
this.connector = connector;
if (oauthAccessToken!=null) {
encodedAuthorization = "token "+oauthAccessToken;
if (oauthAccessToken != null) {
encodedAuthorization = "token " + oauthAccessToken;
} else {
if(jwtToken!=null){
encodedAuthorization = "Bearer "+jwtToken;
}else if (password!=null) {
if (jwtToken != null) {
encodedAuthorization = "Bearer " + jwtToken;
} else if (password != null) {
String authorization = (login + ':' + password);
String charsetName = Charsets.UTF_8.name();
encodedAuthorization = "Basic "+new String(Base64.encodeBase64(authorization.getBytes(charsetName)), charsetName);
encodedAuthorization = "Basic "
+ new String(Base64.encodeBase64(authorization.getBytes(charsetName)), charsetName);
} else {// anonymous access
encodedAuthorization = null;
}
@@ -154,7 +159,7 @@ public class GitHub {
this.rateLimitHandler = rateLimitHandler;
this.abuseLimitHandler = abuseLimitHandler;
if (login==null && encodedAuthorization!=null && jwtToken == null)
if (login == null && encodedAuthorization != null && jwtToken == null)
login = getMyself().getLogin();
this.login = login;
}
@@ -169,31 +174,31 @@ public class GitHub {
/**
* Version that connects to GitHub Enterprise.
*
* @deprecated
* Use {@link #connectToEnterpriseWithOAuth(String, String, String)}
* @deprecated Use {@link #connectToEnterpriseWithOAuth(String, String, String)}
*/
@Deprecated
public static GitHub connectToEnterprise(String apiUrl, String oauthAccessToken) throws IOException {
return connectToEnterpriseWithOAuth(apiUrl,null,oauthAccessToken);
return connectToEnterpriseWithOAuth(apiUrl, null, oauthAccessToken);
}
/**
* Version that connects to GitHub Enterprise.
*
* @param apiUrl
* The URL of GitHub (or GitHub Enterprise) API endpoint, such as "https://api.github.com" or
* "http://ghe.acme.com/api/v3". Note that GitHub Enterprise has <code>/api/v3</code> in the URL.
* For historical reasons, this parameter still accepts the bare domain name, but that's considered deprecated.
* 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 <code>/api/v3</code> in the URL. For
* historical reasons, this parameter still accepts the bare domain name, but that's considered
* deprecated.
*/
public static GitHub connectToEnterpriseWithOAuth(String apiUrl, String login, String oauthAccessToken) throws IOException {
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.
* @deprecated Use with caution. Login with password is not a preferred method.
*/
@Deprecated
public static GitHub connectToEnterprise(String apiUrl, String login, String password) throws IOException {
@@ -205,9 +210,8 @@ public class GitHub {
}
/**
* @deprecated
* Either OAuth token or password is sufficient, so there's no point in passing both.
* Use {@link #connectUsingPassword(String, String)} or {@link #connectUsingOAuth(String)}.
* @deprecated Either OAuth token or password is sufficient, so there's no point in passing both. Use
* {@link #connectUsingPassword(String, String)} or {@link #connectUsingOAuth(String)}.
*/
@Deprecated
public static GitHub connect(String login, String oauthAccessToken, String password) throws IOException {
@@ -225,6 +229,7 @@ public class GitHub {
public static GitHub connectUsingOAuth(String githubServer, String oauthAccessToken) throws IOException {
return new GitHubBuilder().withEndpoint(githubServer).withOAuthToken(oauthAccessToken).build();
}
/**
* Connects to GitHub anonymously.
*
@@ -252,9 +257,7 @@ public class GitHub {
*/
public static GitHub offline() {
try {
return new GitHubBuilder()
.withEndpoint("https://api.github.invalid")
.withConnector(HttpConnector.OFFLINE)
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);
@@ -263,14 +266,16 @@ public class GitHub {
/**
* Is this an anonymous connection
*
* @return {@code true} if operations that require authentication will fail.
*/
public boolean isAnonymous() {
return login==null && encodedAuthorization==null;
return login == null && encodedAuthorization == null;
}
/**
* Is this an always offline "connection".
*
* @return {@code true} if this is an always offline "connection".
*/
public boolean isOffline() {
@@ -292,12 +297,13 @@ public class GitHub {
this.connector = connector;
}
/*package*/ void requireCredential() {
void requireCredential() {
if (isAnonymous())
throw new IllegalStateException("This operation requires a credential but none is given to the GitHub constructor");
throw new IllegalStateException(
"This operation requires a credential but none is given to the GitHub constructor");
}
/*package*/ URL getApiURL(String tailApiUrl) throws IOException {
URL getApiURL(String tailApiUrl) throws IOException {
if (tailApiUrl.startsWith("/")) {
if ("github.com".equals(apiUrl)) {// backward compatibility
return new URL(GITHUB_URL + tailApiUrl);
@@ -309,7 +315,7 @@ public class GitHub {
}
}
/*package*/ Requester retrieve() {
Requester retrieve() {
return new Requester(this).method("GET");
}
@@ -330,10 +336,11 @@ public class GitHub {
}
/**
* Update the Rate Limit with the latest info from response header.
* Due to multi-threading requests might complete out of order, we want to pick the one with the most recent info from the server.
* Update the Rate Limit with the latest info from response header. Due to multi-threading requests might complete
* out of order, we want to pick the one with the most recent info from the server.
*
* @param observed {@link GHRateLimit.Record} constructed from the response header information
* @param observed
* {@link GHRateLimit.Record} constructed from the response header information
*/
void updateCoreRateLimit(@Nonnull GHRateLimit.Record observed) {
synchronized (headerRateLimitLock) {
@@ -345,36 +352,40 @@ public class GitHub {
}
/**
* Update the Rate Limit with the latest info from response header.
* Due to multi-threading requests might complete out of order, we want to pick the one with the most recent info from the server.
* Header date is only accurate to the second, so we look at the information in the record itself.
* Update the Rate Limit with the latest info from response header. Due to multi-threading requests might complete
* out of order, we want to pick the one with the most recent info from the server. Header date is only accurate to
* the second, so we look at the information in the record itself.
*
* {@link GHRateLimit.UnknownLimitRecord}s are always replaced by regular {@link GHRateLimit.Record}s.
* Regular {@link GHRateLimit.Record}s are never replaced by {@link GHRateLimit.UnknownLimitRecord}s.
* Candidates with resetEpochSeconds later than current record are more recent.
* Candidates with the same reset and a lower remaining count are more recent.
* Candidates with an earlier reset are older.
* {@link GHRateLimit.UnknownLimitRecord}s are always replaced by regular {@link GHRateLimit.Record}s. Regular
* {@link GHRateLimit.Record}s are never replaced by {@link GHRateLimit.UnknownLimitRecord}s. Candidates with
* resetEpochSeconds later than current record are more recent. Candidates with the same reset and a lower remaining
* count are more recent. Candidates with an earlier reset are older.
*
* @param candidate {@link GHRateLimit.Record} constructed from the response header information
* @param current the current {@link GHRateLimit.Record} record
* @param candidate
* {@link GHRateLimit.Record} constructed from the response header information
* @param current
* the current {@link GHRateLimit.Record} record
*/
static boolean shouldReplace(@Nonnull GHRateLimit.Record candidate, @Nonnull GHRateLimit.Record current) {
if (candidate instanceof GHRateLimit.UnknownLimitRecord && !(current instanceof GHRateLimit.UnknownLimitRecord)) {
if (candidate instanceof GHRateLimit.UnknownLimitRecord
&& !(current instanceof GHRateLimit.UnknownLimitRecord)) {
// Unknown candidate never replaces a regular record
return false;
} else if (current instanceof GHRateLimit.UnknownLimitRecord && !(candidate instanceof GHRateLimit.UnknownLimitRecord)) {
} else if (current instanceof GHRateLimit.UnknownLimitRecord
&& !(candidate instanceof GHRateLimit.UnknownLimitRecord)) {
// Any real record should replace an unknown Record.
return true;
} else {
// records of the same type compare to each other as normal.
return current.getResetEpochSeconds() < candidate.getResetEpochSeconds()
|| (current.getResetEpochSeconds() == candidate.getResetEpochSeconds() && current.getRemaining() > candidate.getRemaining());
|| (current.getResetEpochSeconds() == candidate.getResetEpochSeconds()
&& current.getRemaining() > candidate.getRemaining());
}
}
/**
* 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.
* 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}.
*/
@@ -389,7 +400,8 @@ public class GitHub {
* 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.
* @throws IOException
* if we couldn't get the current rate limit data.
*/
@Nonnull
public GHRateLimit rateLimit() throws IOException {
@@ -412,7 +424,8 @@ public class GitHub {
public GHMyself getMyself() throws IOException {
requireCredential();
synchronized (this) {
if (this.myself != null) return myself;
if (this.myself != null)
return myself;
GHMyself u = retrieve().to("/user", GHMyself.class);
@@ -435,7 +448,6 @@ public class GitHub {
return u;
}
/**
* clears all cached data in order for external changes (modifications and del) to be reflected
*/
@@ -449,9 +461,9 @@ public class GitHub {
*/
protected GHUser getUser(GHUser orig) {
GHUser u = users.get(orig.getLogin());
if (u==null) {
if (u == null) {
orig.root = this;
users.put(orig.getLogin(),orig);
users.put(orig.getLogin(), orig);
return orig;
}
return u;
@@ -462,9 +474,9 @@ public class GitHub {
*/
public GHOrganization getOrganization(String name) throws IOException {
GHOrganization o = orgs.get(name);
if (o==null) {
if (o == null) {
o = retrieve().to("/orgs/" + name, GHOrganization.class).wrapUp(this);
orgs.put(name,o);
orgs.put(name, o);
}
return o;
}
@@ -482,12 +494,8 @@ public class GitHub {
* @see <a href="https://developer.github.com/v3/orgs/#parameters">List All Orgs - Parameters</a>
*/
public PagedIterable<GHOrganization> listOrganizations(final String since) {
return retrieve()
.with("since",since)
.asPagedIterable(
"/organizations",
GHOrganization[].class,
item -> item.wrapUp(GitHub.this) );
return retrieve().with("since", since).asPagedIterable("/organizations", GHOrganization[].class,
item -> item.wrapUp(GitHub.this));
}
/**
@@ -515,28 +523,21 @@ public class GitHub {
* @return a list of popular open source licenses
*/
public PagedIterable<GHLicense> listLicenses() throws IOException {
return retrieve()
.asPagedIterable(
"/licenses",
GHLicense[].class,
item -> item.wrap(GitHub.this) );
return retrieve().asPagedIterable("/licenses", GHLicense[].class, item -> item.wrap(GitHub.this));
}
/**
* Returns a list of all users.
*/
public PagedIterable<GHUser> listUsers() throws IOException {
return retrieve()
.asPagedIterable(
"/users",
GHUser[].class,
item -> item.wrapUp(GitHub.this) );
return retrieve().asPagedIterable("/users", GHUser[].class, item -> item.wrapUp(GitHub.this));
}
/**
* Returns the full details for a license
*
* @param key The license key provided from the API
* @param key
* The license key provided from the API
* @return The license details
* @see GHLicense#getKey()
*/
@@ -558,24 +559,24 @@ public class GitHub {
/**
* This method returns shallowly populated organizations.
*
* To retrieve full organization details, you need to call {@link #getOrganization(String)}
* TODO: make this automatic.
* To retrieve full organization details, you need to call {@link #getOrganization(String)} TODO: make this
* automatic.
*/
public Map<String, GHOrganization> getMyOrganizations() throws IOException {
GHOrganization[] orgs = retrieve().to("/user/orgs", GHOrganization[].class);
Map<String, GHOrganization> r = new HashMap<String, GHOrganization>();
for (GHOrganization o : orgs) {
// don't put 'o' into orgs because they are shallow
r.put(o.getLogin(),o.wrapUp(this));
r.put(o.getLogin(), o.wrapUp(this));
}
return r;
}
/**
* Alias for {@link #getUserPublicOrganizations(String)}.
*/
public Map<String, GHOrganization> getUserPublicOrganizations(GHUser user) throws IOException {
return getUserPublicOrganizations( user.getLogin() );
return getUserPublicOrganizations(user.getLogin());
}
/**
@@ -583,7 +584,8 @@ public class GitHub {
*
* To retrieve full organization details, you need to call {@link #getOrganization(String)}
*
* @param login the user to retrieve public Organization membership information for
* @param login
* the user to retrieve public Organization membership information for
*
* @return the public Organization memberships for the user
*/
@@ -592,7 +594,7 @@ public class GitHub {
Map<String, GHOrganization> r = new HashMap<String, GHOrganization>();
for (GHOrganization o : orgs) {
// don't put 'o' into orgs because they are shallow
r.put(o.getLogin(),o.wrapUp(this));
r.put(o.getLogin(), o.wrapUp(this));
}
return r;
}
@@ -600,9 +602,8 @@ public class GitHub {
/**
* Gets complete map of organizations/teams that current user belongs to.
*
* Leverages the new GitHub API /user/teams made available recently to
* get in a single call the complete set of organizations, teams and permissions
* in a single call.
* Leverages the new GitHub API /user/teams made available recently to get in a single call the complete set of
* organizations, teams and permissions in a single call.
*/
public Map<String, Set<GHTeam>> getMyTeams() throws IOException {
Map<String, Set<GHTeam>> allMyTeams = new HashMap<String, Set<GHTeam>>();
@@ -625,7 +626,7 @@ public class GitHub {
public GHTeam getTeam(int id) throws IOException {
return retrieve().to("/teams/" + id, GHTeam.class).wrapUp(this);
}
/**
* Public events visible to you. Equivalent of what's displayed on https://github.com/
*/
@@ -640,7 +641,7 @@ public class GitHub {
* Gets a single gist by ID.
*/
public GHGist getGist(String id) throws IOException {
return retrieve().to("/gists/"+id,GHGist.class).wrapUp(this);
return retrieve().to("/gists/" + id, GHGist.class).wrapUp(this);
}
public GHGistBuilder createGist() {
@@ -650,9 +651,8 @@ public class GitHub {
/**
* Parses the GitHub event object.
*
* This is primarily intended for receiving a POST HTTP call from a hook.
* Unfortunately, hook script payloads aren't self-descriptive, so you need
* to know the type of the payload you are expecting.
* This is primarily intended for receiving a POST HTTP call from a hook. Unfortunately, hook script payloads aren't
* self-descriptive, so you need to know the type of the payload you are expecting.
*/
public <T extends GHEventPayload> T parseEventPayload(Reader r, Class<T> type) throws IOException {
T t = MAPPER.readValue(r, type);
@@ -663,13 +663,12 @@ public class GitHub {
/**
* Creates a new repository.
*
* @return
* Newly created repository.
* @deprecated
* Use {@link #createRepository(String)} that uses a builder pattern to let you control every aspect.
* @return Newly created repository.
* @deprecated Use {@link #createRepository(String)} that uses a builder pattern to let you control every aspect.
*/
@Deprecated
public GHRepository createRepository(String name, String description, String homepage, boolean isPublic) throws IOException {
public GHRepository createRepository(String name, String description, String homepage, boolean isPublic)
throws IOException {
return createRepository(name).description(description).homepage(homepage).private_(!isPublic).create();
}
@@ -677,15 +676,15 @@ public class GitHub {
* Starts a builder that creates a new repository.
*
* <p>
* You use the returned builder to set various properties, then call {@link GHCreateRepositoryBuilder#create()}
* to finally create a repository.
* You use the returned builder to set various properties, then call {@link GHCreateRepositoryBuilder#create()} to
* finally create a repository.
*
* <p>
* 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);
return new GHCreateRepositoryBuilder(this, "/user/repos", name);
}
/**
@@ -695,85 +694,82 @@ public class GitHub {
*
* @see <a href="http://developer.github.com/v3/oauth/#create-a-new-authorization">Documentation</a>
*/
public GHAuthorization createToken(Collection<String> scope, String note, String noteUrl) throws IOException{
Requester requester = new Requester(this)
.with("scopes", scope)
.with("note", note)
.with("note_url", noteUrl);
public GHAuthorization createToken(Collection<String> scope, String note, String noteUrl) throws IOException {
Requester requester = new Requester(this).with("scopes", scope).with("note", note).with("note_url", noteUrl);
return requester.method("POST").to("/authorizations", GHAuthorization.class).wrap(this);
}
/**
* Creates a new authorization using an OTP.
*
*
* Start by running createToken, if exception is thrown, prompt for OTP from user
*
*
* Once OTP is received, call this token request
*
* The token created can be then used for {@link GitHub#connectUsingOAuth(String)} in the future.
*
* @see <a href="http://developer.github.com/v3/oauth/#create-a-new-authorization">Documentation</a>
*/
public GHAuthorization createToken(Collection<String> scope, String note, String noteUrl, Supplier<String> OTP) throws IOException{
try {
return createToken(scope, note, noteUrl);
}catch (GHOTPRequiredException ex){
String OTPstring=OTP.get();
Requester requester = new Requester(this)
.with("scopes", scope)
.with("note", note)
.with("note_url", noteUrl);
public GHAuthorization createToken(Collection<String> scope, String note, String noteUrl, Supplier<String> OTP)
throws IOException {
try {
return createToken(scope, note, noteUrl);
} catch (GHOTPRequiredException ex) {
String OTPstring = OTP.get();
Requester requester = new Requester(this).with("scopes", scope).with("note", note).with("note_url",
noteUrl);
// Add the OTP from the user
requester.setHeader("x-github-otp", OTPstring);
return requester.method("POST").to("/authorizations", GHAuthorization.class).wrap(this);
}
}
}
/**
* @see <a href="https://developer.github.com/v3/oauth_authorizations/#get-or-create-an-authorization-for-a-specific-app">docs</a>
* @see <a href=
* "https://developer.github.com/v3/oauth_authorizations/#get-or-create-an-authorization-for-a-specific-app">docs</a>
*/
public GHAuthorization createOrGetAuth(String clientId, String clientSecret, List<String> 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);
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 <a href="https://developer.github.com/v3/oauth_authorizations/#delete-an-authorization">Delete an authorization</a>
* @see <a href="https://developer.github.com/v3/oauth_authorizations/#delete-an-authorization">Delete an
* authorization</a>
*/
public void deleteAuth(long id) throws IOException {
retrieve().method("DELETE").to("/authorizations/" + id);
}
/**
* @see <a href="https://developer.github.com/v3/oauth_authorizations/#check-an-authorization">Check an authorization</a>
* @see <a href="https://developer.github.com/v3/oauth_authorizations/#check-an-authorization">Check an
* authorization</a>
*/
public GHAuthorization checkAuth(@Nonnull String clientId, @Nonnull String accessToken) throws IOException {
return retrieve().to("/applications/" + clientId + "/tokens/" + accessToken, GHAuthorization.class);
}
/**
* @see <a href="https://developer.github.com/v3/oauth_authorizations/#reset-an-authorization">Reset an authorization</a>
* @see <a href="https://developer.github.com/v3/oauth_authorizations/#reset-an-authorization">Reset an
* authorization</a>
*/
public GHAuthorization resetAuth(@Nonnull String clientId, @Nonnull String accessToken) throws IOException {
return retrieve().method("POST").to("/applications/" + clientId + "/tokens/" + accessToken, GHAuthorization.class);
return retrieve().method("POST").to("/applications/" + clientId + "/tokens/" + accessToken,
GHAuthorization.class);
}
/**
* Returns a list of all authorizations.
* @see <a href="https://developer.github.com/v3/oauth_authorizations/#list-your-authorizations">List your authorizations</a>
*
* @see <a href="https://developer.github.com/v3/oauth_authorizations/#list-your-authorizations">List your
* authorizations</a>
*/
public PagedIterable<GHAuthorization> listMyAuthorizations() throws IOException {
return retrieve()
.asPagedIterable(
"/authorizations",
GHAuthorization[].class,
item -> item.wrap(GitHub.this) );
return retrieve().asPagedIterable("/authorizations", GHAuthorization[].class, item -> item.wrap(GitHub.this));
}
/**
@@ -781,9 +777,11 @@ public class GitHub {
*
* You must use a JWT to access this endpoint.
*
* @see <a href="https://developer.github.com/v3/apps/#get-the-authenticated-github-app">Get the authenticated GitHub App</a>
* @see <a href="https://developer.github.com/v3/apps/#get-the-authenticated-github-app">Get the authenticated
* GitHub App</a>
*/
@Preview @Deprecated
@Preview
@Deprecated
public GHApp getApp() throws IOException {
return retrieve().withPreview(MACHINE_MAN).to("/app", GHApp.class).wrapUp(this);
}
@@ -797,7 +795,9 @@ public class GitHub {
return true;
} catch (IOException e) {
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, "Exception validating credentials on " + this.apiUrl + " with login '" + this.login + "' " + e, e);
LOGGER.log(FINE,
"Exception validating credentials on " + this.apiUrl + " with login '" + this.login + "' " + e,
e);
return false;
}
}
@@ -815,36 +815,38 @@ public class GitHub {
return retrieve().to("/meta", GHMeta.class);
}
/*package*/ GHUser intern(GHUser user) throws IOException {
if (user==null) return user;
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 (u != null)
return u;
// if not, remember this new user
users.putIfAbsent(user.getLogin(),user);
users.putIfAbsent(user.getLogin(), user);
return user;
}
public GHProject getProject(long id) throws IOException {
return retrieve().withPreview(INERTIA).to("/projects/"+id, GHProject.class).wrap(this);
return retrieve().withPreview(INERTIA).to("/projects/" + id, GHProject.class).wrap(this);
}
public GHProjectColumn getProjectColumn(long id) throws IOException {
return retrieve().withPreview(INERTIA).to("/projects/columns/"+id, GHProjectColumn.class).wrap(this);
return retrieve().withPreview(INERTIA).to("/projects/columns/" + id, GHProjectColumn.class).wrap(this);
}
public GHProjectCard getProjectCard(long id) throws IOException {
return retrieve().withPreview(INERTIA).to("/projects/columns/cards/"+id, GHProjectCard.class).wrap(this);
return retrieve().withPreview(INERTIA).to("/projects/columns/cards/" + id, GHProjectCard.class).wrap(this);
}
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");
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);
@@ -858,22 +860,45 @@ public class GitHub {
* Verify that the API URL and credentials are valid to access this GitHub.
*
* <p>
* 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.
* 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 (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.
* Checks if a GitHub Enterprise server is configured in private mode.
*
* In private mode response looks like:
*
* <pre>
* $ 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
* </pre>
*
* @return {@code true} if private mode is enabled. If it tries to use this method with GitHub, returns {@code
* false}.
@@ -881,28 +906,8 @@ public class GitHub {
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;
return uc.getResponseCode() == HTTP_UNAUTHORIZED && uc.getHeaderField("X-GitHub-Media-Type") != null;
} finally {
// ensure that the connection opened by getResponseCode gets closed
try {
@@ -920,7 +925,8 @@ public class GitHub {
/**
* Search commits.
*/
@Preview @Deprecated
@Preview
@Deprecated
public GHCommitSearchBuilder searchCommits() {
return new GHCommitSearchBuilder(this);
}
@@ -957,11 +963,12 @@ public class GitHub {
* List all the notifications.
*/
public GHNotificationStream listNotifications() {
return new GHNotificationStream(this,"/notifications");
return new GHNotificationStream(this, "/notifications");
}
/**
* This provides a dump of every public repository, in the order that they were created.
*
* @see <a href="https://developer.github.com/v3/repos/#list-all-public-repositories">documentation</a>
*/
public PagedIterable<GHRepository> listAllPublicRepositories() {
@@ -972,46 +979,39 @@ public class GitHub {
* This provides a dump of every public repository, in the order that they were created.
*
* @param since
* The numeric ID of the last Repository that youve seen. See {@link GHRepository#getId()}
* The numeric ID of the last Repository that youve seen. See {@link GHRepository#getId()}
* @see <a href="https://developer.github.com/v3/repos/#list-all-public-repositories">documentation</a>
*/
public PagedIterable<GHRepository> listAllPublicRepositories(final String since) {
return retrieve().with("since",since)
.asPagedIterable(
"/repositories",
GHRepository[].class,
item -> item.wrap(GitHub.this) );
return retrieve().with("since", since).asPagedIterable("/repositories", GHRepository[].class,
item -> item.wrap(GitHub.this));
}
/**
* Render a Markdown document in raw mode.
*
* <p>
* It takes a Markdown document as plaintext and renders it as plain Markdown
* without a repository context (just like a README.md file is rendered this
* is the simplest way to preview a readme online).
* It takes a Markdown document as plaintext and renders it as plain Markdown without a repository context (just
* like a README.md file is rendered this is the simplest way to preview a readme online).
*
* @see GHRepository#renderMarkdown(String, MarkdownMode)
*/
public Reader renderMarkdown(String text) throws IOException {
return new InputStreamReader(
new Requester(this)
.with(new ByteArrayInputStream(text.getBytes("UTF-8")))
.contentType("text/plain;charset=UTF-8")
.asStream("/markdown/raw"),
"UTF-8");
return new InputStreamReader(new Requester(this).with(new ByteArrayInputStream(text.getBytes("UTF-8")))
.contentType("text/plain;charset=UTF-8").asStream("/markdown/raw"), "UTF-8");
}
/*package*/ static URL parseURL(String s) {
static URL parseURL(String s) {
try {
return s==null ? null : new URL(s);
return s == null ? null : new URL(s);
} catch (MalformedURLException e) {
throw new IllegalStateException("Invalid URL: "+s);
throw new IllegalStateException("Invalid URL: " + s);
}
}
/*package*/ static Date parseDate(String timestamp) {
if (timestamp==null) return null;
static Date parseDate(String timestamp) {
if (timestamp == null)
return null;
for (String f : TIME_FORMATS) {
try {
SimpleDateFormat df = new SimpleDateFormat(f);
@@ -1021,20 +1021,18 @@ public class GitHub {
// try next
}
}
throw new IllegalStateException("Unable to parse the timestamp: "+timestamp);
throw new IllegalStateException("Unable to parse the timestamp: " + timestamp);
}
/*package*/ static String printDate(Date dt) {
static String printDate(Date dt) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
df.setTimeZone(TimeZone.getTimeZone("GMT"));
return df.format(dt);
}
/*package*/ static final ObjectMapper MAPPER = new ObjectMapper();
static final ObjectMapper MAPPER = new ObjectMapper();
private static final String[] TIME_FORMATS = {
"yyyy/MM/dd HH:mm:ss ZZZZ",
"yyyy-MM-dd'T'HH:mm:ss'Z'",
private static final String[] TIME_FORMATS = { "yyyy/MM/dd HH:mm:ss ZZZZ", "yyyy-MM-dd'T'HH:mm:ss'Z'",
"yyyy-MM-dd'T'HH:mm:ss.S'Z'" // GitHub App endpoints return a different date format
};
@@ -1044,7 +1042,7 @@ public class GitHub {
MAPPER.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true);
}
/* package */ static final String GITHUB_URL = "https://api.github.com";
static final String GITHUB_URL = "https://api.github.com";
private static final Logger LOGGER = Logger.getLogger(GitHub.class.getName());
}