Move to GitHub API v3, refactor. IDEA-85769

* Introduce getRequest() and postRequest() methods as entry points to do a query to the API instead of doREST, which requires releasing an HttpMethod and so on.
* Return parsed JsonElement from these methods, since Github API returns the response in JSON format.
* Post request in JSON format as well. Get-methods are not called with parameters yet, so don't bother for them.
* Use these methods from GithubCreateGistAction which already uses API v3.
* Introduce getApiUrl to return the correct API url for github as well as for enterprise installations.
* Introduce GithubUser to encapsulate information about a user.
  Currently it works only for the current user, and stores only its plan to know if private repositories are allowed for the person.
* Rewrite RepositoryInfo to be a structured holder of the information about a repository, without working with DOM elements and so on. Rename some methods to make their return value more obvious.
* Add @Nullable, @NotNull, javadocs,  code style fixes.
* In the GithubShareAction move initial pushing to the background to avoid the modality lock with the dialog asking for the passphrase. This fixes IDEA-86645.
This commit is contained in:
Kirill Likhodedov
2012-06-16 13:12:22 +04:00
parent edb898e391
commit 304e4cca12
8 changed files with 439 additions and 210 deletions
@@ -50,7 +50,7 @@ public class GithubCheckoutProvider implements CheckoutProvider {
Collections.sort(availableRepos, new Comparator<RepositoryInfo>() {
@Override
public int compare(final RepositoryInfo r1, final RepositoryInfo r2) {
final int comparedOwners = r1.getOwner().compareTo(r2.getOwner());
final int comparedOwners = r1.getOwnerName().compareTo(r2.getOwnerName());
return comparedOwners != 0 ? comparedOwners : r1.getName().compareTo(r2.getName());
}
});
@@ -58,7 +58,7 @@ public class GithubCheckoutProvider implements CheckoutProvider {
final GitCloneDialog dialog = new GitCloneDialog(project);
// Add predefined repositories to history
for (int i = availableRepos.size() - 1; i>=0; i--){
dialog.prependToHistory(availableRepos.get(i).getUrl());
dialog.prependToHistory(availableRepos.get(i).getCloneUrl());
}
dialog.show();
if (!dialog.isOK()) {
@@ -17,8 +17,6 @@ package org.jetbrains.plugins.github;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.intellij.ide.BrowserUtil;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener;
@@ -34,9 +32,6 @@ import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vfs.VirtualFile;
import git4idea.GitVcs;
import git4idea.Notificator;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.github.ui.GitHubCreateGistDialog;
@@ -130,41 +125,7 @@ public class GithubCreateGistAction extends DumbAwareAction {
ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
@Override
public void run() {
// Using GitHub Gist API v3: http://developer.github.com/v3/gists/
final HttpClient client = anonymous ? GithubUtil.getHttpClient(null, null) : GithubUtil.getHttpClient(settings.getLogin(), password);
final PostMethod method = new PostMethod("https://api.github.com/gists");
String request = prepareJsonRequest(description, isPrivate, text, file);
String response;
try {
method.setRequestEntity(new StringRequestEntity(request, "application/json", "UTF-8"));
client.executeMethod(method);
response = method.getResponseBodyAsString();
}
catch (IOException e1) {
showError(project, "Failed to create gist", "", null, e1);
return;
}
finally {
method.releaseConnection();
}
JsonObject jsonResponse;
try {
jsonResponse = new JsonParser().parse(response).getAsJsonObject();
}
catch (JsonSyntaxException jse) {
showError(project, "Couldn't parse GitHub response", "", response, jse);
return;
}
JsonElement htmlUrl = jsonResponse.get("html_url");
if (htmlUrl == null) {
showError(project, "Invalid GitHub response", "No html_url property", response, null);
return;
}
url.set(htmlUrl.getAsString());
url.set(createGist(project, settings.getLogin(), password, anonymous, text, isPrivate, file, description));
}
}, "Communicating With GitHub", false, project);
@@ -188,6 +149,40 @@ public class GithubCreateGistAction extends DumbAwareAction {
}
}
@Nullable
private static String createGist(@NotNull Project project, @Nullable String login, @Nullable String password, boolean anonymous,
@NotNull String text, boolean isPrivate, @NotNull VirtualFile file, @NotNull String description) {
if (anonymous) {
login = null;
password = null;
}
String requestBody = prepareJsonRequest(description, isPrivate, text, file);
try {
JsonElement jsonElement = GithubUtil.postRequest("https://api.github.com", login, password, "/gists", requestBody);
if (jsonElement == null) {
LOG.info("Null JSON response returned by GitHub");
showError(project, "Failed to create gist", "Empty JSON response returned by GitHub", null, null);
return null;
}
if (!jsonElement.isJsonObject()) {
LOG.error(String.format("Unexpected JSON result format: %s", jsonElement));
return null;
}
JsonElement htmlUrl = jsonElement.getAsJsonObject().get("html_url");
if (htmlUrl == null) {
LOG.info("Invalid JSON response: " + jsonElement);
showError(project, "Invalid GitHub response", "No html_url property", jsonElement.toString(), null);
return null;
}
return htmlUrl.getAsString();
}
catch (IOException e) {
LOG.info("Exception when creating a Gist", e);
showError(project, "Failed to create gist", "", null, e);
return null;
}
}
private static void showError(@NotNull Project project, @NotNull String title, @NotNull String content,
@Nullable String details, @Nullable Exception e) {
Notificator.getInstance(project).notifyError(title, content);
@@ -139,7 +139,7 @@ public class GithubRebaseAction extends DumbAwareAction {
return;
}
final String parent = repositoryInfo.getParent();
final String parent = repositoryInfo.getParentName();
LOG.assertTrue(parent != null, "Parent repository not found!");
final String parentRepoSuffix = parent + ".git";
final String parentRepoUrl = "git://github.com/" + parentRepoSuffix;
@@ -29,7 +29,6 @@ import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.ui.Messages;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author oleg
@@ -43,13 +42,14 @@ import org.jetbrains.annotations.Nullable;
)
public class GithubSettings implements PersistentStateComponent<Element> {
public static final String DEFAULT_GITHUB_HOST = "github.com";
private static final String GITHUB_SETTINGS_TAG = "GithubSettings";
private static final String LOGIN = "Login";
private static final String HOST = "Host";
private static final String ANONIMOUS_GIST = "Anonymous";
private static final String OPEN_IN_BROWSER_GIST = "OpenInBrowser";
private static final String PRIVATE_GIST = "Private";
private static final String GITHUB = "github.com";
private static final String GITHUB_SETTINGS_PASSWORD_KEY = "GITHUB_SETTINGS_PASSWORD_KEY";
private String myLogin;
@@ -117,7 +117,7 @@ public class GithubSettings implements PersistentStateComponent<Element> {
return myLogin != null ? myLogin : "";
}
@Nullable
@NotNull
public String getPassword() {
LOG.assertTrue(!ProgressManager.getInstance().hasProgressIndicator(), "Password should not be accessed under modal progress");
String password;
@@ -154,7 +154,7 @@ public class GithubSettings implements PersistentStateComponent<Element> {
}
public String getHost() {
return myHost != null ? myHost : GITHUB;
return myHost != null ? myHost : DEFAULT_GITHUB_HOST;
}
public boolean isAnonymous() {
@@ -187,7 +187,7 @@ public class GithubSettings implements PersistentStateComponent<Element> {
}
public void setHost(final String host) {
myHost = host != null ? host : GITHUB;
myHost = host != null ? host : DEFAULT_GITHUB_HOST;
}
public void setAnonymousGist(final boolean anonymousGist) {
@@ -15,6 +15,8 @@
*/
package org.jetbrains.plugins.github;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
@@ -26,11 +28,11 @@ import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.ChangeListManager;
@@ -53,6 +55,7 @@ import git4idea.repo.GitRepository;
import git4idea.repo.GitRepositoryManager;
import git4idea.util.GitFileUtils;
import git4idea.util.GitUIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.github.ui.GithubShareDialog;
import java.io.IOException;
@@ -83,7 +86,7 @@ public class GithubShareAction extends DumbAwareAction {
@Override
public void actionPerformed(final AnActionEvent e) {
final Project project = e.getData(PlatformDataKeys.PROJECT);
if (!GithubUtil.testGitExecutable(project)){
if (project == null || !GithubUtil.testGitExecutable(project)){
return;
}
final VirtualFile root = project.getBaseDir();
@@ -136,22 +139,49 @@ public class GithubShareAction extends DumbAwareAction {
final String description = shareDialog.getDescription();
try {
LOG.info("Creating GitHub repository");
final String escapedDescription = JDOMUtil.escapeText(description, true, true).replace("&#", "%");
GithubUtil.doREST(settings.getHost(), settings.getLogin(), settings.getPassword(),
"/repos/create?name=" + name + "&public=" + (isPrivate ? "0" : "1") + "&description=" + escapedDescription, true).releaseConnection();
LOG.info("Successfully created GitHub repository");
boolean repositoryCreated =
createGithubRepository(settings.getHost(), settings.getLogin(), settings.getPassword(), name, description, isPrivate);
if (repositoryCreated) {
LOG.info("Successfully created GitHub repository");
}
else {
Messages.showErrorDialog(project, "Failed to create new GitHub repository", "Create GitHub Repository");
return;
}
}
catch (final Exception e1) {
Messages.showErrorDialog(e1.getMessage(), "Failed to create new GitHub repository");
return;
}
if (bindToGithub(project, root, gitDetected, settings.getLogin(), name)) {
Notifications.Bus.notify(new Notification("github", "Success", "Successfully created project ''" + name + "'' on github",
NotificationType.INFORMATION));
}
bindToGithub(project, root, gitDetected, settings.getLogin(), name);
}
private boolean bindToGithub(final Project project, final VirtualFile root, final boolean gitDetected, final String login, String name) {
private static boolean createGithubRepository(@NotNull String host, @NotNull String login, @NotNull String password, @NotNull String name,
@NotNull String description, boolean aPrivate) throws IOException {
String path = "/user/repos";
String requestBody = prepareRequest(name, description, aPrivate);
JsonElement result = GithubUtil.postRequest(host, login, password, path, requestBody);
if (result == null) {
return false;
}
if (!result.isJsonObject()) {
LOG.error(String.format("Unexpected JSON result format: %s", result));
return false;
}
return result.getAsJsonObject().has("url");
}
private static String prepareRequest(String name, String description, boolean isPrivate) {
JsonObject json = new JsonObject();
json.addProperty("name", name);
json.addProperty("description", description);
json.addProperty("public", Boolean.toString(!isPrivate));
return json.toString();
}
private void bindToGithub(final Project project, final VirtualFile root, boolean gitDetected, final String login, final String name) {
LOG.info("Binding local project with GitHub");
// creating empty git repo if git isnot initialized
if (!gitDetected) {
@@ -162,7 +192,7 @@ public class GithubShareAction extends DumbAwareAction {
if (!h.errors().isEmpty()) {
GitUIUtil.showOperationErrors(project, h.errors(), "git init");
LOG.info("Failed to create empty git repo: " + h.errors());
return false;
return;
}
final ProgressManager manager = ProgressManager.getInstance();
manager.runProcessWithProgressSynchronously(new Runnable() {
@@ -175,7 +205,7 @@ public class GithubShareAction extends DumbAwareAction {
// In this case we should create sample commit for binding project
if (!performFirstCommitIfRequired(project, root)) {
return false;
return;
}
//git remote add origin git@github.com:login/name.git
@@ -188,36 +218,40 @@ public class GithubShareAction extends DumbAwareAction {
addRemoteHandler.run();
if (addRemoteHandler.getExitCode() != 0) {
Messages.showErrorDialog("Failed to add GitHub repository as remote", "Failed to add GitHub repository as remote");
return false;
return;
}
}
catch (VcsException e) {
Messages.showErrorDialog(e.getMessage(), "Failed to add GitHub repository as remote");
LOG.info("Failed to add GitHub as remote: " + e.getMessage());
return false;
return;
}
//git push origin master
final ProgressManager manager = ProgressManager.getInstance();
final ArrayList<VcsException> errors = new ArrayList<VcsException>();
manager.runProcessWithProgressSynchronously(new Runnable() {
public void run() {
final ProgressIndicator progressIndicator = manager.getProgressIndicator();
if (progressIndicator != null){
progressIndicator.setText("Pushing to GitHub");
}
final GitLineHandler gitPushHandler = new GitLineHandler(project, root, GitCommand.PUSH);
gitPushHandler.addParameters("-u", "origin", "master");
GitPushUtils.trackPushRejectedAsError(gitPushHandler, "Rejected push (" + root.getPresentableUrl() + "): ");
errors.addAll(GitHandlerUtil.doSynchronouslyWithExceptions(gitPushHandler));
new Task.Backgroundable(project, "Pushing to GitHub", false) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
final GitLineHandler gitPushHandler = new GitLineHandler(project, root, GitCommand.PUSH);
gitPushHandler.addParameters("-u", "origin", "master");
GitPushUtils.trackPushRejectedAsError(gitPushHandler, "Rejected push (" + root.getPresentableUrl() + "): ");
errors.addAll(GitHandlerUtil.doSynchronouslyWithExceptions(gitPushHandler));
if (!errors.isEmpty()) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
GitUIUtil.showOperationErrors(project, errors, GitBundle.getString("push.active.pushing"));
}
});
}
}, GitBundle.getString("push.active.pushing"), false, project);
if (!errors.isEmpty()) {
GitUIUtil.showOperationErrors(project, errors, GitBundle.getString("push.active.pushing"));
}
// refresh vcs manually
RefreshAction.doRefresh(project);
return true;
else {
RefreshAction.doRefresh(project);
Notifications.Bus.notify(new Notification("github", "Success", "Successfully created project ''" + name + "'' on github",
NotificationType.INFORMATION));
}
}
}.queue();
}
private boolean performFirstCommitIfRequired(final Project project, final VirtualFile root) {
@@ -0,0 +1,66 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.github;
import org.jetbrains.annotations.NotNull;
/**
* Information about a user on GitHub.
*
* @author Kirill Likhodedov
*/
class GithubUser {
enum Plan {
FREE,
MICRO,
SMALL,
MEDIUM,
BRONZE,
SILVER,
GOLD,
PLATINUM;
public boolean isPrivateRepoAllowed() {
return this != FREE;
}
public static Plan fromString(String name) {
for (Plan plan : values()) {
if (plan.name().equalsIgnoreCase(name)) {
return plan;
}
}
return defaultPlan();
}
private static Plan defaultPlan() {
return FREE;
}
}
@NotNull private final Plan myPlan;
GithubUser(@NotNull Plan plan) {
myPlan = plan;
}
@NotNull
Plan getPlan() {
return myPlan;
}
}
@@ -15,6 +15,10 @@
*/
package org.jetbrains.plugins.github;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
@@ -37,35 +41,41 @@ import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.github.ui.GithubLoginDialog;
import javax.swing.*;
import java.io.InputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Various utility methods for the GutHub plugin.
*
* @author oleg
* @author Kirill Likhodedov
*/
public class GithubUtil {
private static final String API_URL = "/api/v2/xml";
private static final Logger LOG = Logger.getInstance(GithubUtil.class.getName());
public static final Icon GITHUB_ICON = IconLoader.getIcon("/org/jetbrains/plugins/github/github_icon.png");
private static final Logger LOG = Logger.getInstance(GithubUtil.class.getName());
/**
* @deprecated The host may be defined in different formats. Use {@link #getApiUrl(String)} instead.
*/
@Deprecated
public static String getHttpsUrl() {
return "https://" + GithubSettings.getInstance().getHost();
}
public static String getHostByUrl(final String url) {
return url.startsWith("https://") ? url.substring(8) : url.startsWith("http://") ? url.substring(7) : url.startsWith("git@") ? url.substring(4) : url;
}
/**
* @deprecated TODO Use background progress
*/
@Deprecated
public static <T> T accessToGithubWithModalProgress(final Project project, final Computable<T> computable) {
final Ref<T> result = new Ref<T>();
ProgressManager.getInstance().run(new Task.Modal(project, "Access to GitHub", true) {
@@ -76,6 +86,10 @@ public class GithubUtil {
return result.get();
}
/**
* @deprecated TODO Use background progress
*/
@Deprecated
public static void accessToGithubWithModalProgress(final Project project, final Runnable runnable) {
ProgressManager.getInstance().run(new Task.Modal(project, "Access to GitHub", true) {
public void run(@NotNull ProgressIndicator indicator) {
@@ -84,38 +98,113 @@ public class GithubUtil {
});
}
public static boolean testConnection(final String url, final String login, final String password) {
@Nullable
public static JsonElement getRequest(@NotNull String host, @NotNull String login, @NotNull String password,
@NotNull String path) throws IOException {
return request(host, login, password, path, null, false);
}
@Nullable
public static JsonElement postRequest(@NotNull String host, @Nullable String login, @Nullable String password,
@NotNull String path, @Nullable String requestBody) throws IOException {
return request(host, login, password, path, requestBody, true);
}
@Nullable
private static JsonElement request(@NotNull String host, @Nullable String login, @Nullable String password,
@NotNull String path, @Nullable String requestBody, boolean post) throws IOException {
HttpMethod method = null;
try {
method = doREST(url, login, password, "/user/show/" + login, false);
final InputStream stream = method.getResponseBodyAsStream();
final Element element = new SAXBuilder(false).build(stream).getRootElement();
if ("error".equals(element.getName())){
return false;
method = doREST(host, login, password, path, requestBody, post);
String resp = method.getResponseBodyAsString();
if (resp == null) {
LOG.info(String.format("Unexpectedly empty response: %s", resp));
return null;
}
// In case if authentification was successful we should see some extra fields
return element.getChild("total-private-repo-count") != null;
}
catch (Exception e) {
// Ignore
return parseResponse(resp);
}
finally {
if (method!=null) {
if (method != null) {
method.releaseConnection();
}
}
return false;
}
public static HttpMethod doREST(final String url, final String login, final String password, final String request, final boolean post) throws Exception {
@NotNull
private static HttpMethod doREST(@NotNull String host, @Nullable String login, @Nullable String password, @NotNull String path,
@Nullable String requestBody, final boolean post) throws IOException {
final HttpClient client = getHttpClient(login, password);
final String uri = "https://" + getHostByUrl(url) + API_URL + request;
final HttpMethod method = post ? new PostMethod(uri) : new GetMethod(uri);
final String uri = getApiUrl(host) + path;
final HttpMethod method;
if (post) {
method = new PostMethod(uri);
if (requestBody != null) {
((PostMethod)method).setRequestEntity(new StringRequestEntity(requestBody, "application/json", "UTF-8"));
}
}
else {
method = new GetMethod(uri);
}
client.executeMethod(method);
return method;
}
public static HttpClient getHttpClient(@Nullable final String login, @Nullable final String password) {
@NotNull
private static String removeProtocolPrefix(final String url) {
if (url.startsWith("https://")) {
return url.substring(8);
}
else if (url.startsWith("http://")) {
return url.substring(7);
}
else if (url.startsWith("git@")) {
return url.substring(4);
}
else {
return url;
}
}
@NotNull
private static String getApiUrl(@NotNull String urlFromSettings) {
return "https://" + getApiUrlWithoutProtocol(urlFromSettings);
}
/*
All API access is over HTTPS, and accessed from the api.github.com domain
(or through yourdomain.com/api/v3/ for enterprise).
http://developer.github.com/v3/
*/
@NotNull
private static String getApiUrlWithoutProtocol(String urlFromSettings) {
String url = removeTrailingSlash(removeProtocolPrefix(urlFromSettings));
final String API_PREFIX = "api.";
final String ENTERPRISE_API_SUFFIX = "/api/v3";
if (url.equals(GithubSettings.DEFAULT_GITHUB_HOST)) {
return API_PREFIX + url;
}
else if (url.equals(API_PREFIX + GithubSettings.DEFAULT_GITHUB_HOST)) {
return url;
}
else if (url.endsWith(ENTERPRISE_API_SUFFIX)) {
return url;
}
else {
return url + ENTERPRISE_API_SUFFIX;
}
}
private static String removeTrailingSlash(String s) {
if (s.endsWith("/")) {
return s.substring(0, s.length() - 1);
}
return s;
}
@NotNull
private static HttpClient getHttpClient(@Nullable final String login, @Nullable final String password) {
final HttpClient client = new HttpClient();
client.getParams().setContentCharset("UTF-8");
// Configure proxySettings if it is required
@@ -135,87 +224,127 @@ public class GithubUtil {
return client;
}
public static List<RepositoryInfo> getAvailableRepos(final String url, final String login, final String password, final boolean ownOnly) {
HttpMethod method = null;
try {
final String request = (ownOnly ? "/repos/show/" : "/repos/watched/") + login;
method = doREST(url, login, password, request, false);
final InputStream stream = method.getResponseBodyAsStream();
final Element element = new SAXBuilder(false).build(stream).getRootElement();
if ("error".equals(element.getName())){
LOG.warn("Got error element by request: " + request);
return Collections.emptyList();
}
final List repositories = element.getChildren();
final List<RepositoryInfo> result = new ArrayList<RepositoryInfo>();
for (int i = 0; i < repositories.size(); i++) {
final Element repo = (Element)repositories.get(i);
result.add(new RepositoryInfo(repo));
}
return result;
}
catch (Exception e) {
// ignore
}
finally {
if (method != null){
method.releaseConnection();
}
}
return Collections.emptyList();
private static boolean testConnection(final String url, final String login, final String password) {
GithubUser user = retrieveCurrentUserInfo(url, login, password);
return user != null;
}
@Nullable
private static GithubUser retrieveCurrentUserInfo(@NotNull String url, @NotNull String login, @NotNull String password) {
try {
JsonElement result = getRequest(url, login, password, "/user");
return parseUserInfo(result);
}
catch (IOException e) {
LOG.info(e);
return null;
}
}
@Nullable
public static RepositoryInfo getDetailedRepoInfo(final String url, final String login, final String password, final String owner, final String name) {
HttpMethod method = null;
private static GithubUser parseUserInfo(@Nullable JsonElement result) {
if (result == null) {
return null;
}
if (!result.isJsonObject()) {
LOG.error(String.format("Unexpected JSON result format: %s", result));
return null;
}
JsonObject obj = (JsonObject)result;
if (!obj.has("plan")) {
return null;
}
GithubUser.Plan plan = parsePlan(obj.get("plan"));
return new GithubUser(plan);
}
@NotNull
private static GithubUser.Plan parsePlan(JsonElement plan) {
if (!plan.isJsonObject()) {
return GithubUser.Plan.FREE;
}
return GithubUser.Plan.fromString(plan.getAsJsonObject().get("name").getAsString());
}
@NotNull
private static JsonElement parseResponse(@NotNull String githubResponse) throws IOException {
try {
final String request = "/repos/show/" + owner + "/" + name;
method = doREST(url, login, password, request, false);
final InputStream stream = method.getResponseBodyAsStream();
final Element element = new SAXBuilder(false).build(stream).getRootElement();
if ("error".equals(element.getName())){
LOG.warn("Got error element by request: " + request);
return new JsonParser().parse(githubResponse);
}
catch (JsonSyntaxException jse) {
throw new IOException(String.format("Couldn't parse GitHub response:%n%s", githubResponse), jse);
}
}
@NotNull
private static List<RepositoryInfo> getAvailableRepos(@NotNull String url, @NotNull String login, @NotNull String password,
boolean ownOnly) {
final String request = (ownOnly ? "/user/repos" : "/user/watched");
try {
JsonElement result = getRequest(url, login, password, request);
if (result == null) {
return Collections.emptyList();
}
return parseRepositoryInfos(result);
}
catch (IOException e) {
LOG.error(e);
return Collections.emptyList();
}
}
@NotNull
private static List<RepositoryInfo> parseRepositoryInfos(@NotNull JsonElement result) {
if (!result.isJsonArray()) {
LOG.assertTrue(result.isJsonObject(), String.format("Unexpected JSON result format: %s", result));
return Collections.singletonList(parseSingleRepositoryInfo(result.getAsJsonObject()));
}
List<RepositoryInfo> repositories = new ArrayList<RepositoryInfo>();
for (JsonElement element : result.getAsJsonArray()) {
LOG.assertTrue(element.isJsonObject(),
String.format("This element should be a JsonObject: %s%nTotal JSON response: %n%s", element, result));
repositories.add(parseSingleRepositoryInfo(element.getAsJsonObject()));
}
return repositories;
}
@NotNull
private static RepositoryInfo parseSingleRepositoryInfo(@NotNull JsonObject result) {
String name = result.get("name").getAsString();
String cloneUrl = result.get("clone_url").getAsString();
String ownerName = result.get("owner").getAsJsonObject().get("login").getAsString();
String parentName = result.has("parent") ? result.get("parent").getAsJsonObject().get("full_name").getAsString(): null;
boolean fork = result.get("fork").getAsBoolean();
return new RepositoryInfo(name, cloneUrl, ownerName, parentName, fork);
}
@Nullable
private static RepositoryInfo getDetailedRepoInfo(@NotNull String url, @NotNull String login, @NotNull String password,
@NotNull String owner, @NotNull String name) {
try {
final String request = "/repos/" + owner + "/" + name;
JsonElement jsonObject = getRequest(url, login, password, request);
if (jsonObject == null) {
LOG.info(String.format("Information about repository is unavailable. Owner: %s, Name: %s", owner, name));
return null;
}
return (new RepositoryInfo(element));
return parseSingleRepositoryInfo(jsonObject.getAsJsonObject());
}
catch (Exception e) {
// ignore
catch (IOException e) {
LOG.info(String.format("Exception was thrown when trying to retrieve information about repository. Owner: %s, Name: %s",
owner, name));
return null;
}
finally {
if (method != null){
method.releaseConnection();
}
}
return null;
}
public static boolean isPrivateRepoAllowed(final String url, final String login, final String password) {
HttpMethod method = null;
try {
final String request = "/user/show/" + login;
method = doREST(url, login, password, request, false);
final InputStream stream = method.getResponseBodyAsStream();
final Element element = new SAXBuilder(false).build(stream).getRootElement();
if ("error".equals(element.getName())){
LOG.warn("Got error element by request: " + request);
return false;
}
final Element plan = element.getChild("plan");
assert plan != null : "Authentification failed";
final String privateRepos = plan.getChildText("private-repos");
return privateRepos != null && Integer.valueOf(privateRepos) > 0;
GithubUser user = retrieveCurrentUserInfo(url, login, password);
if (user == null) {
return false;
}
catch (Exception e) {
// ignore
}
finally {
if (method != null){
method.releaseConnection();
}
}
return false;
return user.getPlan().isPrivateRepoAllowed();
}
public static boolean checkCredentials(final Project project) {
@@ -291,6 +420,7 @@ public class GithubUtil {
// Otherwise our credentials are valid and they are successfully stored in settings
final String validPassword = settings.getPassword();
return accessToGithubWithModalProgress(project, new Computable<RepositoryInfo>() {
@Nullable
@Override
public RepositoryInfo compute() {
ProgressManager.getInstance().getProgressIndicator().setText("Extracting detailed info about repository ''" + name + "''");
@@ -327,7 +457,7 @@ public class GithubUtil {
}
return null;
}
public static boolean testGitExecutable(final Project project) {
final GitVcsApplicationSettings settings = GitVcsApplicationSettings.getInstance();
final String executable = settings.getPathToGit();
@@ -1,55 +1,59 @@
package org.jetbrains.plugins.github;
import com.intellij.openapi.util.Comparing;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author oleg
* @date 10/21/10
*/
* Information about Github repository.
*
* @author oleg
* @author Kirill Likhodedov
*/
public class RepositoryInfo {
private final Element myRepository;
public RepositoryInfo(final Element repository) {
myRepository = repository;
@NotNull private final String myName;
@NotNull private final String myCloneUrl;
@NotNull private final String myOwnerName;
@Nullable private final String myParentName;
private final boolean myFork;
public RepositoryInfo(@NotNull String name, @NotNull String cloneUrl, @NotNull String ownerName, @Nullable String parentName,
boolean fork) {
myName = name;
myParentName = parentName;
myCloneUrl = cloneUrl;
myOwnerName = ownerName;
myFork = fork;
}
@NotNull
public String getName() {
return myRepository.getChildText("name");
return myName;
}
public String getOwner() {
return myRepository.getChildText("owner");
@NotNull
public String getOwnerName() {
return myOwnerName;
}
public boolean isFork() {
return Boolean.valueOf(myRepository.getChildText("fork"));
return myFork;
}
public String getParent() {
return myRepository.getChildText("parent");
/**
* @return The name of the parent of this repository, or null.
* Null is returned if this repository doesn't have a parent, i. e. is not a fork,
* or if the parent information was not retrieved by the time of constructing of this RepositoryInfo object.
* To be sure use {@link #isFork()}.
*/
@Nullable
public String getParentName() {
return myParentName;
}
public String getId() {
return getOwner() + "/" + getName();
@NotNull
public String getCloneUrl() {
return myCloneUrl;
}
public String getUrl() {
return myRepository.getChildText("url") + ".git";
}
@Override
public int hashCode() {
return myRepository != null ? myRepository.hashCode() : 0;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof RepositoryInfo)){
return false;
}
final RepositoryInfo repositoryInfo = (RepositoryInfo)obj;
return Comparing.equal(getName(), repositoryInfo.getName()) &&
Comparing.equal(getOwner(), repositoryInfo.getOwner());
}
}