/* * The MIT License * * Copyright (c) 2010, Kohsuke Kawaguchi * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.kohsuke.github; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.introspect.VisibilityChecker.Std; import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import org.apache.commons.codec.Charsets; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import java.io.*; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Supplier; import java.util.logging.Logger; import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static java.util.logging.Level.FINE; import static org.kohsuke.github.Previews.INERTIA; import static org.kohsuke.github.Previews.MACHINE_MAN; /** * Root of the GitHub API. * *
* 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 & deleting a repository at
* the same time.
*
* @author Kohsuke Kawaguchi
*/
public class GitHub {
final String login;
/**
* Value of the authorization header to be sent with the request.
*/
final String encodedAuthorization;
private final ConcurrentMap
* Several different combinations of the login/oauthAccessToken/password parameters are allowed to represent
* different ways of authentication.
*
*
* All operations that require authentication will fail.
*
* @return the git hub
* @throws IOException
* the io exception
*/
public static GitHub connectAnonymously() throws IOException {
return new GitHubBuilder().build();
}
/**
* Connects to GitHub Enterprise anonymously.
*
* All operations that require authentication will fail.
*
* @param apiUrl
* the api url
* @return the git hub
* @throws IOException
* the io exception
*/
public static GitHub connectToEnterpriseAnonymously(String apiUrl) throws IOException {
return new GitHubBuilder().withEndpoint(apiUrl).build();
}
/**
* An offline-only {@link GitHub} useful for parsing event notification from an unknown source.
*
* All operations that require a connection will fail.
*
* @return An offline-only {@link GitHub}.
*/
public static GitHub offline() {
try {
return new GitHubBuilder().withEndpoint("https://api.github.invalid")
.withConnector(HttpConnector.OFFLINE)
.build();
} catch (IOException e) {
throw new IllegalStateException("The offline implementation constructor should not connect", e);
}
}
/**
* Is this an anonymous connection
*
* @return {@code true} if operations that require authentication will fail.
*/
public boolean isAnonymous() {
return login == null && encodedAuthorization == null;
}
/**
* Is this an always offline "connection".
*
* @return {@code true} if this is an always offline "connection".
*/
public boolean isOffline() {
return connector == HttpConnector.OFFLINE;
}
/**
* Gets connector.
*
* @return the connector
*/
public HttpConnector getConnector() {
return connector;
}
/**
* Gets api url.
*
* @return the api url
*/
public String getApiUrl() {
return apiUrl;
}
/**
* Sets the custom connector used to make requests to GitHub.
*
* @param connector
* the connector
*/
public void setConnector(HttpConnector connector) {
this.connector = connector;
}
void requireCredential() {
if (isAnonymous())
throw new IllegalStateException(
"This operation requires a credential but none is given to the GitHub constructor");
}
URL getApiURL(String tailApiUrl) throws IOException {
if (tailApiUrl.startsWith("/")) {
if ("github.com".equals(apiUrl)) {// backward compatibility
return new URL(GITHUB_URL + tailApiUrl);
} else {
return new URL(apiUrl + tailApiUrl);
}
} else {
return new URL(tailApiUrl);
}
}
Requester retrieve() {
return new Requester(this).method("GET");
}
/**
* Gets the current rate limit.
*
* @return the rate limit
* @throws IOException
* the io exception
*/
public GHRateLimit getRateLimit() throws IOException {
GHRateLimit rateLimit;
try {
rateLimit = retrieve().to("/rate_limit", JsonRateLimit.class).resources;
} catch (FileNotFoundException e) {
// GitHub Enterprise doesn't have the rate limit
// return a default rate limit that
rateLimit = GHRateLimit.Unknown();
}
return this.rateLimit = rateLimit;
}
/**
* 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
*/
void updateCoreRateLimit(@Nonnull GHRateLimit.Record observed) {
synchronized (headerRateLimitLock) {
if (headerRateLimit == null || shouldReplace(observed, headerRateLimit.getCore())) {
headerRateLimit = GHRateLimit.fromHeaderRecord(observed);
LOGGER.log(FINE, "Rate limit now: {0}", headerRateLimit);
}
}
}
/**
* 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.
*
* @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)) {
// Unknown candidate never replaces a regular record
return false;
} 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());
}
}
/**
* Returns the most recently observed rate limit data or {@code null} if either there is no rate limit (for example
* GitHub Enterprise) or if no requests have been made.
*
* @return the most recently observed rate limit data or {@code null}.
*/
@CheckForNull
public GHRateLimit lastRateLimit() {
synchronized (headerRateLimitLock) {
return headerRateLimit;
}
}
/**
* Gets the current rate limit while trying not to actually make any remote requests unless absolutely necessary.
*
* @return the current rate limit data.
* @throws IOException
* if we couldn't get the current rate limit data.
*/
@Nonnull
public GHRateLimit rateLimit() throws IOException {
synchronized (headerRateLimitLock) {
if (headerRateLimit != null && !headerRateLimit.isExpired()) {
return headerRateLimit;
}
}
GHRateLimit rateLimit = this.rateLimit;
if (rateLimit == null || rateLimit.isExpired()) {
rateLimit = getRateLimit();
}
return rateLimit;
}
/**
* Gets the {@link GHUser} that represents yourself.
*
* @return the myself
* @throws IOException
* the io exception
*/
@WithBridgeMethods(GHUser.class)
public GHMyself getMyself() throws IOException {
requireCredential();
synchronized (this) {
if (this.myself != null)
return myself;
GHMyself u = retrieve().to("/user", GHMyself.class);
u.root = this;
this.myself = u;
return u;
}
}
/**
* Obtains the object that represents the named user.
*
* @param login
* the login
* @return the user
* @throws IOException
* the io exception
*/
public GHUser getUser(String login) throws IOException {
GHUser u = users.get(login);
if (u == null) {
u = retrieve().to("/users/" + login, GHUser.class);
u.root = this;
users.put(u.getLogin(), u);
}
return u;
}
/**
* clears all cached data in order for external changes (modifications and del) to be reflected
*/
public void refreshCache() {
users.clear();
orgs.clear();
}
/**
* Interns the given {@link GHUser}.
*
* @param orig
* the orig
* @return the user
*/
protected GHUser getUser(GHUser orig) {
GHUser u = users.get(orig.getLogin());
if (u == null) {
orig.root = this;
users.put(orig.getLogin(), orig);
return orig;
}
return u;
}
/**
* Gets {@link GHOrganization} specified by name.
*
* @param name
* the name
* @return the organization
* @throws IOException
* the io exception
*/
public GHOrganization getOrganization(String name) throws IOException {
GHOrganization o = orgs.get(name);
if (o == null) {
o = retrieve().to("/orgs/" + name, GHOrganization.class).wrapUp(this);
orgs.put(name, o);
}
return o;
}
/**
* Gets a list of all organizations.
*
* @return the paged iterable
*/
public PagedIterable
* To retrieve full organization details, you need to call {@link #getOrganization(String)} TODO: make this
* automatic.
*
* @return the my organizations
* @throws IOException
* the io exception
*/
public Map
* To retrieve full organization details, you need to call {@link #getOrganization(String)}
*
* @param login
* the user to retrieve public Organization membership information for
* @return the public Organization memberships for the user
* @throws IOException
* the io exception
*/
public Map
* 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.
*
* @return the my teams
* @throws IOException
* the io exception
*/
public Map
* 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.
*
* @param
* You use the returned builder to set various properties, then call {@link GHCreateRepositoryBuilder#create()} to
* finally create a repository.
*
*
* To create a repository in an organization, see
* {@link GHOrganization#createRepository(String, String, String, GHTeam, boolean)}
*
* @param name
* the name
* @return the gh create repository builder
*/
public GHCreateRepositoryBuilder createRepository(String name) {
return new GHCreateRepositoryBuilder(this, "/user/repos", name);
}
/**
* Creates a new authorization.
*
* The token created can be then used for {@link GitHub#connectUsingOAuth(String)} in the future.
*
* @param scope
* the scope
* @param note
* the note
* @param noteUrl
* the note url
* @return the gh authorization
* @throws IOException
* the io exception
* @see Documentation
*/
public GHAuthorization createToken(Collection
* 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.
*
* @param scope
* the scope
* @param note
* the note
* @param noteUrl
* the note url
* @param OTP
* the otp
* @return the gh authorization
* @throws IOException
* the io exception
* @see Documentation
*/
public GHAuthorization createToken(Collection
* You must use a JWT to access this endpoint.
*
* @return the app
* @throws IOException
* the io exception
* @see Get the authenticated
* GitHub App
*/
@Preview
@Deprecated
public GHApp getApp() throws IOException {
return retrieve().withPreview(MACHINE_MAN).to("/app", GHApp.class).wrapUp(this);
}
/**
* Ensures that the credential is valid.
*
* @return the boolean
*/
public boolean isCredentialValid() {
try {
retrieve().to("/user", GHUser.class);
return true;
} catch (IOException e) {
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE,
"Exception validating credentials on " + this.apiUrl + " with login '" + this.login + "' " + e,
e);
return false;
}
}
/**
* Provides a list of GitHub's IP addresses.
*
* @return an instance of {@link GHMeta}
* @throws IOException
* if the credentials supplied are invalid or if you're trying to access it as a GitHub App via the JWT
* authentication
* @see Get Meta
*/
public GHMeta getMeta() throws IOException {
return retrieve().to("/meta", GHMeta.class);
}
GHUser intern(GHUser user) throws IOException {
if (user == null)
return user;
// if we already have this user in our map, use it
GHUser u = users.get(user.getLogin());
if (u != null)
return u;
// if not, remember this new user
users.putIfAbsent(user.getLogin(), user);
return user;
}
/**
* Gets project.
*
* @param id
* the id
* @return the project
* @throws IOException
* the io exception
*/
public GHProject getProject(long id) throws IOException {
return retrieve().withPreview(INERTIA).to("/projects/" + id, GHProject.class).wrap(this);
}
/**
* Gets project column.
*
* @param id
* the id
* @return the project column
* @throws IOException
* the io exception
*/
public GHProjectColumn getProjectColumn(long id) throws IOException {
return retrieve().withPreview(INERTIA).to("/projects/columns/" + id, GHProjectColumn.class).wrap(this);
}
/**
* Gets project card.
*
* @param id
* the id
* @return the project card
* @throws IOException
* the io exception
*/
public GHProjectCard getProjectCard(long id) throws IOException {
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");
// make sure that the URL is legitimate
new URL(rate_limit_url);
}
}
/**
* Tests the connection.
*
*
* Verify that the API URL and credentials are valid to access this GitHub.
*
*
* This method returns normally if the endpoint is reachable and verified to be GitHub API URL. Otherwise this
* method throws {@link IOException} to indicate the problem.
*
* @throws IOException
* the io exception
*/
public void checkApiUrlValidity() throws IOException {
try {
retrieve().to("/", GHApiInfo.class).check(apiUrl);
} catch (IOException e) {
if (isPrivateModeEnabled()) {
throw (IOException) new IOException(
"GitHub Enterprise server (" + apiUrl + ") with private mode enabled").initCause(e);
}
throw e;
}
}
/**
* Checks if a GitHub Enterprise server is configured in private mode.
*
* In private mode response looks like:
*
*
* 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).
*
* @param text
* the text
* @return the reader
* @throws IOException
* the io exception
* @see GHRepository#renderMarkdown(String, MarkdownMode) 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");
}
static URL parseURL(String s) {
try {
return s == null ? null : new URL(s);
} catch (MalformedURLException e) {
throw new IllegalStateException("Invalid URL: " + s);
}
}
static Date parseDate(String timestamp) {
if (timestamp == null)
return null;
for (String f : TIME_FORMATS) {
try {
SimpleDateFormat df = new SimpleDateFormat(f);
df.setTimeZone(TimeZone.getTimeZone("GMT"));
return df.parse(timestamp);
} catch (ParseException e) {
// try next
}
}
throw new IllegalStateException("Unable to parse the timestamp: " + timestamp);
}
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);
}
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'",
"yyyy-MM-dd'T'HH:mm:ss.S'Z'" // GitHub App endpoints return a different date format
};
static {
MAPPER.setVisibilityChecker(new Std(NONE, NONE, NONE, NONE, ANY));
MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MAPPER.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true);
}
static final String GITHUB_URL = "https://api.github.com";
private static final Logger LOGGER = Logger.getLogger(GitHub.class.getName());
}
*
*
* @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 /api/v3 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.
* @param oauthAccessToken
* Secret OAuth token.
* @param password
* User's password. Always used in conjunction with the {@code login} parameter
* @param connector
* HttpConnector to use. Pass null to use default connector.
*/
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 (oauthAccessToken != null) {
encodedAuthorization = "token " + oauthAccessToken;
} else {
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);
} else {// anonymous access
encodedAuthorization = null;
}
}
users = new ConcurrentHashMap/api/v3 in the URL. For
* historical reasons, this parameter still accepts the bare domain name, but that's considered
* deprecated.
* @param login
* the login
* @param oauthAccessToken
* the oauth access token
* @return the git hub
* @throws IOException
* the io exception
*/
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.
*
* @param apiUrl
* the api url
* @param login
* the login
* @param password
* the password
* @return the git hub
* @throws IOException
* the io exception
* @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 {
return new GitHubBuilder().withEndpoint(apiUrl).withPassword(login, password).build();
}
/**
* Connect git hub.
*
* @param login
* the login
* @param oauthAccessToken
* the oauth access token
* @return the git hub
* @throws IOException
* the io exception
*/
public static GitHub connect(String login, String oauthAccessToken) throws IOException {
return new GitHubBuilder().withOAuthToken(oauthAccessToken, login).build();
}
/**
* Connect git hub.
*
* @param login
* the login
* @param oauthAccessToken
* the oauth access token
* @param password
* the password
* @return the git hub
* @throws IOException
* the io exception
* @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 {
return new GitHubBuilder().withOAuthToken(oauthAccessToken, login).withPassword(login, password).build();
}
/**
* Connect using password git hub.
*
* @param login
* the login
* @param password
* the password
* @return the git hub
* @throws IOException
* the io exception
*/
public static GitHub connectUsingPassword(String login, String password) throws IOException {
return new GitHubBuilder().withPassword(login, password).build();
}
/**
* Connect using o auth git hub.
*
* @param oauthAccessToken
* the oauth access token
* @return the git hub
* @throws IOException
* the io exception
*/
public static GitHub connectUsingOAuth(String oauthAccessToken) throws IOException {
return new GitHubBuilder().withOAuthToken(oauthAccessToken).build();
}
/**
* Connect using o auth git hub.
*
* @param githubServer
* the github server
* @param oauthAccessToken
* the oauth access token
* @return the git hub
* @throws IOException
* the io exception
*/
public static GitHub connectUsingOAuth(String githubServer, String oauthAccessToken) throws IOException {
return new GitHubBuilder().withEndpoint(githubServer).withOAuthToken(oauthAccessToken).build();
}
/**
* Connects to GitHub anonymously.
*
* $ 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
*
*
* @return {@code true} if private mode is enabled. If it tries to use this method with GitHub, returns {@code
* false}.
*/
private boolean isPrivateModeEnabled() {
try {
HttpURLConnection uc = getConnector().connect(getApiURL("/"));
try {
return uc.getResponseCode() == HTTP_UNAUTHORIZED && uc.getHeaderField("X-GitHub-Media-Type") != null;
} finally {
// ensure that the connection opened by getResponseCode gets closed
try {
IOUtils.closeQuietly(uc.getInputStream());
} catch (IOException ignore) {
// ignore
}
IOUtils.closeQuietly(uc.getErrorStream());
}
} catch (IOException e) {
return false;
}
}
/**
* Search commits.
*
* @return the gh commit search builder
*/
@Preview
@Deprecated
public GHCommitSearchBuilder searchCommits() {
return new GHCommitSearchBuilder(this);
}
/**
* Search issues.
*
* @return the gh issue search builder
*/
public GHIssueSearchBuilder searchIssues() {
return new GHIssueSearchBuilder(this);
}
/**
* Search users.
*
* @return the gh user search builder
*/
public GHUserSearchBuilder searchUsers() {
return new GHUserSearchBuilder(this);
}
/**
* Search repositories.
*
* @return the gh repository search builder
*/
public GHRepositorySearchBuilder searchRepositories() {
return new GHRepositorySearchBuilder(this);
}
/**
* Search content.
*
* @return the gh content search builder
*/
public GHContentSearchBuilder searchContent() {
return new GHContentSearchBuilder(this);
}
/**
* List all the notifications.
*
* @return the gh notification stream
*/
public GHNotificationStream listNotifications() {
return new GHNotificationStream(this, "/notifications");
}
/**
* This provides a dump of every public repository, in the order that they were created.
*
* @return the paged iterable
* @see documentation
*/
public PagedIterable