summaryrefslogtreecommitdiff
path: root/plugins/github/src/org/jetbrains
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/github/src/org/jetbrains')
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/GithubCreateGistAction.java7
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestAction.java6
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestWorker.java1083
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/GithubRebaseAction.java7
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/GithubShareAction.java15
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/api/GithubApiUtil.java632
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/api/GithubChangeIssueStateRequest.java12
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/api/GithubConnection.java491
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/api/GithubFullPath.java9
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/api/GithubUser.java10
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/api/GithubUserDetailed.java4
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/api/GithubUserRaw.java4
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/extensions/GithubCheckoutProvider.java7
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/tasks/GithubComment.java12
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/tasks/GithubRepository.java118
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/tasks/GithubRepositoryEditor.java9
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/tasks/GithubRepositoryType.java7
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestDialog.java249
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestPanel.form32
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestPanel.java138
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/ui/GithubSelectForkDialog.java41
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/ui/GithubSelectForkPanel.java8
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/ui/GithubSettingsPanel.java13
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/util/GithubNotifications.java26
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/util/GithubSettings.java11
-rw-r--r--plugins/github/src/org/jetbrains/plugins/github/util/GithubUtil.java177
26 files changed, 1910 insertions, 1218 deletions
diff --git a/plugins/github/src/org/jetbrains/plugins/github/GithubCreateGistAction.java b/plugins/github/src/org/jetbrains/plugins/github/GithubCreateGistAction.java
index 43753142a5b2..ae7b271ce3d6 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/GithubCreateGistAction.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/GithubCreateGistAction.java
@@ -38,6 +38,7 @@ import icons.GithubIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.github.api.GithubApiUtil;
+import org.jetbrains.plugins.github.api.GithubConnection;
import org.jetbrains.plugins.github.api.GithubGist;
import org.jetbrains.plugins.github.exceptions.GithubOperationCanceledException;
import org.jetbrains.plugins.github.ui.GithubCreateGistDialog;
@@ -219,11 +220,11 @@ public class GithubCreateGistAction extends DumbAwareAction {
}
try {
final List<FileContent> finalContents = contents;
- return GithubUtil.runTask(project, auth, indicator, new ThrowableConvertor<GithubAuthData, GithubGist, IOException>() {
+ return GithubUtil.runTask(project, auth, indicator, new ThrowableConvertor<GithubConnection, GithubGist, IOException>() {
@NotNull
@Override
- public GithubGist convert(@NotNull GithubAuthData auth) throws IOException {
- return GithubApiUtil.createGist(auth, finalContents, description, isPrivate);
+ public GithubGist convert(@NotNull GithubConnection connection) throws IOException {
+ return GithubApiUtil.createGist(connection, finalContents, description, isPrivate);
}
}).getHtmlUrl();
}
diff --git a/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestAction.java b/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestAction.java
index 636415e5fcb6..d3fa265dc389 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestAction.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestAction.java
@@ -26,7 +26,7 @@ import icons.GithubIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.github.ui.GithubCreatePullRequestDialog;
-import org.jetbrains.plugins.github.util.*;
+import org.jetbrains.plugins.github.util.GithubUtil;
import static org.jetbrains.plugins.github.util.GithubUtil.setVisibleEnabled;
@@ -73,12 +73,12 @@ public class GithubCreatePullRequestAction extends DumbAwareAction {
}
static void createPullRequest(@NotNull Project project, @Nullable VirtualFile file) {
- GithubCreatePullRequestWorker worker = GithubCreatePullRequestWorker.createPullRequestWorker(project, file);
+ GithubCreatePullRequestWorker worker = GithubCreatePullRequestWorker.create(project, file);
if (worker == null) {
return;
}
- GithubCreatePullRequestDialog dialog = new GithubCreatePullRequestDialog(worker);
+ GithubCreatePullRequestDialog dialog = new GithubCreatePullRequestDialog(project, worker);
DialogManager.show(dialog);
}
} \ No newline at end of file
diff --git a/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestWorker.java b/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestWorker.java
index bf9803b6f46f..07ed51bfca7d 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestWorker.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestWorker.java
@@ -1,30 +1,16 @@
-/*
- * Copyright 2000-2014 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 com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.EmptyProgressIndicator;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.Pair;
-import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
@@ -34,11 +20,10 @@ import com.intellij.util.Function;
import com.intellij.util.ThrowableConvertor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Convertor;
-import com.intellij.util.containers.HashMap;
+import com.intellij.vcs.log.VcsCommitMetadata;
import git4idea.DialogManager;
import git4idea.GitCommit;
import git4idea.GitLocalBranch;
-import git4idea.GitRemoteBranch;
import git4idea.changes.GitChangeUtils;
import git4idea.commands.Git;
import git4idea.commands.GitCommandResult;
@@ -52,18 +37,19 @@ import git4idea.util.GitCommitCompareInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.github.api.*;
+import org.jetbrains.plugins.github.exceptions.GithubOperationCanceledException;
import org.jetbrains.plugins.github.ui.GithubSelectForkDialog;
import org.jetbrains.plugins.github.util.*;
import java.io.IOException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
-/**
- * @author Aleksey Pivovarov
- */
public class GithubCreatePullRequestWorker {
private static final Logger LOG = GithubUtil.LOG;
private static final String CANNOT_CREATE_PULL_REQUEST = "Can't Create Pull Request";
@@ -71,40 +57,36 @@ public class GithubCreatePullRequestWorker {
@NotNull private final Project myProject;
@NotNull private final Git myGit;
@NotNull private final GitRepository myGitRepository;
+ @NotNull private final GithubAuthDataHolder myAuthHolder;
+
@NotNull private final GithubFullPath myPath;
@NotNull private final String myRemoteName;
@NotNull private final String myRemoteUrl;
@NotNull private final String myCurrentBranch;
- @NotNull private final GithubAuthDataHolder myAuthHolder;
- @NotNull private final Map<String, FutureTask<DiffInfo>> myDiffInfos;
+ @NotNull private GithubFullPath mySource;
- private volatile GithubFullPath myForkPath;
- private volatile String myTargetRemote;
+ @NotNull private final List<ForkInfo> myForks;
+ @Nullable private List<GithubFullPath> myAvailableForks;
private GithubCreatePullRequestWorker(@NotNull Project project,
@NotNull Git git,
@NotNull GitRepository gitRepository,
+ @NotNull GithubAuthDataHolder authHolder,
@NotNull GithubFullPath path,
@NotNull String remoteName,
@NotNull String remoteUrl,
- @NotNull String currentBranch,
- @NotNull GithubAuthDataHolder authHolder) {
+ @NotNull String currentBranch) {
myProject = project;
myGit = git;
myGitRepository = gitRepository;
+ myAuthHolder = authHolder;
myPath = path;
myRemoteName = remoteName;
myRemoteUrl = remoteUrl;
myCurrentBranch = currentBranch;
- myAuthHolder = authHolder;
-
- myDiffInfos = new HashMap<String, FutureTask<DiffInfo>>();
- }
- @NotNull
- public Project getProject() {
- return myProject;
+ myForks = new ArrayList<ForkInfo>();
}
@NotNull
@@ -112,231 +94,416 @@ public class GithubCreatePullRequestWorker {
return myCurrentBranch;
}
- public boolean canShowDiff() {
- return myTargetRemote != null;
+ @NotNull
+ public List<ForkInfo> getForks() {
+ return myForks;
}
@Nullable
- public static GithubCreatePullRequestWorker createPullRequestWorker(@NotNull final Project project, @Nullable final VirtualFile file) {
- Git git = ServiceManager.getService(Git.class);
+ public static GithubCreatePullRequestWorker create(@NotNull final Project project, @Nullable final VirtualFile file) {
+ return GithubUtil.computeValueInModal(project, "Loading data...", new Convertor<ProgressIndicator, GithubCreatePullRequestWorker>() {
+ @Override
+ public GithubCreatePullRequestWorker convert(ProgressIndicator indicator) {
+ Git git = ServiceManager.getService(Git.class);
- GitRepository gitRepository = GithubUtil.getGitRepository(project, file);
- if (gitRepository == null) {
- GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't find git repository");
- return null;
+ GitRepository gitRepository = GithubUtil.getGitRepository(project, file);
+ if (gitRepository == null) {
+ GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't find git repository");
+ return null;
+ }
+ gitRepository.update();
+
+ Pair<GitRemote, String> remote = GithubUtil.findGithubRemote(gitRepository);
+ if (remote == null) {
+ GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't find GitHub remote");
+ return null;
+ }
+ String remoteName = remote.getFirst().getName();
+ String remoteUrl = remote.getSecond();
+
+ GithubFullPath path = GithubUrlUtil.getUserAndRepositoryFromRemoteUrl(remoteUrl);
+ if (path == null) {
+ GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't process remote: " + remoteUrl);
+ return null;
+ }
+
+ GitLocalBranch currentBranch = gitRepository.getCurrentBranch();
+ if (currentBranch == null) {
+ GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "No current branch");
+ return null;
+ }
+
+ GithubAuthDataHolder authHolder;
+ try {
+ authHolder = GithubUtil.getValidAuthDataHolderFromConfig(project, indicator);
+ }
+ catch (IOException e) {
+ GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e);
+ return null;
+ }
+
+ GithubCreatePullRequestWorker worker =
+ new GithubCreatePullRequestWorker(project, git, gitRepository, authHolder, path, remoteName, remoteUrl, currentBranch.getName());
+
+ try {
+ worker.initForks(indicator);
+ }
+ catch (IOException e) {
+ GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e);
+ return null;
+ }
+
+ return worker;
+ }
+ });
+ }
+
+ private void initForks(@NotNull ProgressIndicator indicator) throws IOException {
+ doLoadForksFromGithub(indicator);
+ doLoadForksFromGit(indicator);
+ doLoadForksFromSettings(indicator);
+ }
+
+ @Nullable
+ private ForkInfo doAddFork(@NotNull GithubFullPath path,
+ @Nullable String remoteName,
+ @NotNull ProgressIndicator indicator) {
+ for (ForkInfo fork : myForks) {
+ if (fork.getPath().equals(path)) {
+ if (fork.getRemoteName() == null && remoteName != null) {
+ fork.setRemoteName(remoteName);
+ }
+ return fork;
+ }
}
- gitRepository.update();
- Pair<GitRemote, String> remote = GithubUtil.findGithubRemote(gitRepository);
- if (remote == null) {
- GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't find GitHub remote");
- return null;
+ try {
+ List<String> branches = loadBranches(path, indicator);
+ String defaultBranch = doLoadDefaultBranch(path, indicator);
+
+ ForkInfo fork = new ForkInfo(path, branches, defaultBranch);
+ myForks.add(fork);
+ if (remoteName != null) {
+ fork.setRemoteName(remoteName);
+ }
+ return fork;
}
- String remoteName = remote.getFirst().getName();
- String remoteUrl = remote.getSecond();
- GithubFullPath path = GithubUrlUtil.getUserAndRepositoryFromRemoteUrl(remoteUrl);
- if (path == null) {
- GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't process remote: " + remoteUrl);
+ catch (IOException e) {
+ GithubNotifications.showWarning(myProject, "Can't load branches for " + path.getFullName(), e);
return null;
}
+ }
- GitLocalBranch currentBranch = gitRepository.getCurrentBranch();
- if (currentBranch == null) {
- GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "No current branch");
- return null;
+ @Nullable
+ private ForkInfo doAddFork(@NotNull GithubRepo repo, @NotNull ProgressIndicator indicator) {
+ GithubFullPath path = repo.getFullPath();
+ for (ForkInfo fork : myForks) {
+ if (fork.getPath().equals(path)) {
+ return fork;
+ }
}
- GithubAuthDataHolder authHolder;
try {
- authHolder = GithubUtil
- .computeValueInModal(project, "Access to GitHub", new ThrowableConvertor<ProgressIndicator, GithubAuthDataHolder, IOException>() {
- @NotNull
- @Override
- public GithubAuthDataHolder convert(ProgressIndicator indicator) throws IOException {
- return GithubUtil.getValidAuthDataHolderFromConfig(project, indicator);
- }
- });
+ List<String> branches = loadBranches(path, indicator);
+ String defaultBranch = repo.getDefaultBranch();
+
+ ForkInfo fork = new ForkInfo(path, branches, defaultBranch);
+ myForks.add(fork);
+ return fork;
}
catch (IOException e) {
- GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e);
+ GithubNotifications.showWarning(myProject, "Can't load branches for " + path.getFullName(), e);
return null;
}
+ }
- return new GithubCreatePullRequestWorker(project, git, gitRepository, path, remoteName, remoteUrl, currentBranch.getName(), authHolder);
+ private void doLoadForksFromSettings(@NotNull ProgressIndicator indicator) throws IOException {
+ GithubFullPath savedRepo = GithubProjectSettings.getInstance(myProject).getCreatePullRequestDefaultRepo();
+ if (savedRepo != null) {
+ doAddFork(savedRepo, null, indicator);
+ }
}
- @Nullable
- public GithubTargetInfo setTarget(@NotNull final GithubFullPath forkPath) {
- try {
- GithubInfo info =
- GithubUtil.computeValueInModal(myProject, "Access to GitHub", new ThrowableConvertor<ProgressIndicator, GithubInfo, IOException>() {
- @NotNull
- @Override
- public GithubInfo convert(ProgressIndicator indicator) throws IOException {
- // configure remote
- GitRemote targetRemote = GithubUtil.findGithubRemote(myGitRepository, forkPath);
- String targetRemoteName = targetRemote == null ? null : targetRemote.getName();
- if (targetRemoteName == null) {
- final Ref<Integer> responseRef = new Ref<Integer>();
- ApplicationManager.getApplication().invokeAndWait(new Runnable() {
- @Override
- public void run() {
- responseRef.set(GithubNotifications
- .showYesNoDialog(myProject, "Can't Find Remote", "Configure remote for '" + forkPath.getUser() + "'?"));
- }
- }, indicator.getModalityState());
- if (responseRef.get() == Messages.YES) {
- targetRemoteName = configureRemote(myProject, myGitRepository, forkPath);
- }
- }
+ private void doLoadForksFromGit(@NotNull ProgressIndicator indicator) {
+ for (GitRemote remote : myGitRepository.getRemotes()) {
+ for (String url : remote.getUrls()) {
+ if (GithubUrlUtil.isGithubUrl(url)) {
+ GithubFullPath path = GithubUrlUtil.getUserAndRepositoryFromRemoteUrl(url);
+ if (path != null) {
+ doAddFork(path, remote.getName(), indicator);
+ break;
+ }
+ }
+ }
+ }
+ }
- // load available branches
- List<String> branches = ContainerUtil.map(GithubUtil.runTask(myProject, myAuthHolder, indicator,
- new ThrowableConvertor<GithubAuthData, List<GithubBranch>, IOException>() {
- @Override
- public List<GithubBranch> convert(@NotNull GithubAuthData auth)
- throws IOException {
- return GithubApiUtil.getRepoBranches(auth, forkPath.getUser(),
- forkPath.getRepository());
- }
- }
- ), new Function<GithubBranch, String>() {
- @Override
- public String fun(GithubBranch githubBranch) {
- return githubBranch.getName();
- }
- });
+ private void doLoadForksFromGithub(@NotNull ProgressIndicator indicator) throws IOException {
+ GithubRepoDetailed repo =
+ GithubUtil.runTask(myProject, myAuthHolder, indicator, new ThrowableConvertor<GithubConnection, GithubRepoDetailed, IOException>() {
+ @NotNull
+ @Override
+ public GithubRepoDetailed convert(@NotNull GithubConnection connection) throws IOException {
+ return GithubApiUtil.getDetailedRepoInfo(connection, myPath.getUser(), myPath.getRepository());
+ }
+ });
- // fetch
- if (targetRemoteName != null) {
- GitFetchResult result = new GitFetcher(myProject, indicator, false).fetch(myGitRepository.getRoot(), targetRemoteName, null);
- if (!result.isSuccess()) {
- GitFetcher.displayFetchResult(myProject, result, null, result.getErrors());
- targetRemoteName = null;
- }
- }
+ doAddFork(repo, indicator);
+ if (repo.getParent() != null) {
+ doAddFork(repo.getParent(), indicator);
+ }
+ if (repo.getSource() != null) {
+ doAddFork(repo.getSource(), indicator);
+ }
- return new GithubInfo(branches, targetRemoteName);
- }
- });
+ mySource = repo.getSource() == null ? repo.getFullPath() : repo.getSource().getFullPath();
+ }
- myForkPath = forkPath;
- myTargetRemote = info.getTargetRemote();
-
- myDiffInfos.clear();
- if (canShowDiff()) {
- for (final String branch : info.getBranches()) {
- myDiffInfos.put(branch, new FutureTask<DiffInfo>(new Callable<DiffInfo>() {
- @Nullable
- @Override
- public DiffInfo call() throws Exception {
- return loadDiffInfo(myProject, myGitRepository, myCurrentBranch, myTargetRemote + "/" + branch);
- }
- }));
+ @NotNull
+ private List<String> loadBranches(@NotNull final GithubFullPath fork, @NotNull ProgressIndicator indicator) throws IOException {
+ return ContainerUtil.map(
+ GithubUtil.runTask(myProject, myAuthHolder, indicator, new ThrowableConvertor<GithubConnection, List<GithubBranch>, IOException>() {
+ @Override
+ public List<GithubBranch> convert(@NotNull GithubConnection connection) throws IOException {
+ return GithubApiUtil.getRepoBranches(connection, fork.getUser(), fork.getRepository());
+ }
+ }),
+ new Function<GithubBranch, String>() {
+ @Override
+ public String fun(@NotNull GithubBranch branch) {
+ return branch.getName();
}
}
+ );
+ }
+
+ @Nullable
+ private String doLoadDefaultBranch(@NotNull final GithubFullPath fork, @NotNull ProgressIndicator indicator) throws IOException {
+ GithubRepo repo =
+ GithubUtil.runTask(myProject, myAuthHolder, indicator, new ThrowableConvertor<GithubConnection, GithubRepo, IOException>() {
+ @Override
+ public GithubRepo convert(@NotNull GithubConnection connection) throws IOException {
+ return GithubApiUtil.getDetailedRepoInfo(connection, fork.getUser(), fork.getRepository());
+ }
+ });
+ return repo.getDefaultBranch();
+ }
+
+ public void launchFetchRemote(@NotNull final ForkInfo fork) {
+ if (fork.getRemoteName() == null) return;
+
+ if (fork.getFetchTask() != null) return;
+ synchronized (fork.LOCK) {
+ if (fork.getFetchTask() != null) return;
- return new GithubTargetInfo(info.getBranches());
+ final MasterFutureTask<Void> task = new MasterFutureTask<Void>(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ doFetchRemote(fork);
+ return null;
+ }
+ });
+ fork.setFetchTask(task);
+
+ ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
+ @Override
+ public void run() {
+ task.run();
+ }
+ });
}
- catch (IOException e) {
- GithubNotifications.showErrorDialog(myProject, CANNOT_CREATE_PULL_REQUEST, e);
+ }
+
+ public void launchLoadDiffInfo(@NotNull final BranchInfo branch) {
+ if (branch.getForkInfo().getRemoteName() == null) return;
+
+ if (branch.getDiffInfoTask() != null) return;
+ synchronized (branch.LOCK) {
+ if (branch.getDiffInfoTask() != null) return;
+
+ launchFetchRemote(branch.getForkInfo());
+ MasterFutureTask<Void> masterTask = branch.getForkInfo().getFetchTask();
+ assert masterTask != null;
+
+ final SlaveFutureTask<DiffInfo> task = new SlaveFutureTask<DiffInfo>(masterTask, new Callable<DiffInfo>() {
+ @Override
+ public DiffInfo call() throws VcsException {
+ return doLoadDiffInfo(branch);
+ }
+ });
+ branch.setDiffInfoTask(task);
+
+ ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
+ @Override
+ public void run() {
+ task.run();
+ }
+ });
+ }
+ }
+
+ @Nullable
+ public DiffInfo getDiffInfo(@NotNull final BranchInfo branch) throws IOException {
+ if (branch.getForkInfo().getRemoteName() == null) return null;
+
+ launchLoadDiffInfo(branch);
+
+ assert branch.getDiffInfoTask() != null;
+ try {
+ return branch.getDiffInfoTask().get();
+ }
+ catch (InterruptedException e) {
+ throw new GithubOperationCanceledException(e);
+ }
+ catch (ExecutionException e) {
+ Throwable ex = e.getCause();
+ if (ex instanceof VcsException) throw new IOException(ex);
+ LOG.error(ex);
return null;
}
}
- public void showDiffDialog(@NotNull String branch) {
- if (canShowDiff()) {
- DiffInfo info = getDiffInfoWithModal(branch);
- if (info == null) {
- GithubNotifications.showErrorDialog(myProject, "Can't Show Diff", "Can't get diff info");
- return;
- }
+ private boolean doFetchRemote(@NotNull ForkInfo fork) {
+ if (fork.getRemoteName() == null) return false;
- GitCompareBranchesDialog dialog =
- new GitCompareBranchesDialog(myProject, info.getTo(), info.getFrom(), info.getInfo(), myGitRepository);
- dialog.show();
+ GitFetchResult result =
+ new GitFetcher(myProject, new EmptyProgressIndicator(), false).fetch(myGitRepository.getRoot(), fork.getRemoteName(), null);
+ if (!result.isSuccess()) {
+ GitFetcher.displayFetchResult(myProject, result, null, result.getErrors());
+ return false;
}
+ return true;
}
- @Nullable
- public GithubFullPath showTargetDialog() {
- return showTargetDialog(false);
+ @NotNull
+ private DiffInfo doLoadDiffInfo(@NotNull final BranchInfo branch) throws VcsException {
+ // TODO: make cancelable and abort old speculative requests (when git4idea will allow to do so)
+ String currentBranch = myCurrentBranch;
+ String targetBranch = branch.getForkInfo().getRemoteName() + "/" + branch.getRemoteName();
+
+ List<GitCommit> commits1 = GitHistoryUtils.history(myProject, myGitRepository.getRoot(), ".." + targetBranch);
+ List<GitCommit> commits2 = GitHistoryUtils.history(myProject, myGitRepository.getRoot(), targetBranch + "..");
+ Collection<Change> diff = GitChangeUtils.getDiff(myProject, myGitRepository.getRoot(), targetBranch, myCurrentBranch, null);
+ GitCommitCompareInfo info = new GitCommitCompareInfo(GitCommitCompareInfo.InfoType.BRANCH_TO_HEAD);
+ info.put(myGitRepository, diff);
+ info.put(myGitRepository, Couple.of(commits1, commits2));
+
+ return new DiffInfo(info, currentBranch, targetBranch);
}
- @Nullable
- public GithubFullPath showTargetDialog(boolean firstTime) {
- final GithubInfo2 info = getAvailableForksInModal(myProject, myGitRepository, myAuthHolder, myPath);
- if (info == null) {
- return null;
+ private void doConfigureRemote(@NotNull ForkInfo fork) {
+ if (fork.getRemoteName() != null) return;
+
+ GithubFullPath path = fork.getPath();
+ String url = GithubUrlUtil.getCloneUrl(path);
+
+ if (GithubUtil.addGithubRemote(myProject, myGitRepository, path.getUser(), url)) {
+ fork.setRemoteName(path.getUser());
}
+ }
- if (firstTime) {
- if (info.getForks().size() == 1) {
- return info.getForks().iterator().next();
+ public void configureRemote(@NotNull final ForkInfo fork) {
+ GithubUtil.computeValueInModal(myProject, "Creating remote..", false, new Consumer<ProgressIndicator>() {
+ @Override
+ public void consume(ProgressIndicator indicator) {
+ doConfigureRemote(fork);
}
- if (info.getForks().size() == 2) {
- Iterator<GithubFullPath> it = info.getForks().iterator();
- GithubFullPath path1 = it.next();
- GithubFullPath path2 = it.next();
+ });
+ }
- if (myPath.equals(path1)) {
- return path2;
- }
- if (myPath.equals(path2)) {
- return path1;
+ @NotNull
+ public Couple<String> getDefaultDescriptionMessage(@NotNull final BranchInfo branch) {
+ Couple<String> message = branch.getDefaultMessage();
+ if (message != null) return message;
+
+ if (branch.getForkInfo().getRemoteName() == null) {
+ return getSimpleDefaultDescriptionMessage(branch);
+ }
+
+ return GithubUtil
+ .computeValueInModal(myProject, "Collecting additional data...", false, new Convertor<ProgressIndicator, Couple<String>>() {
+ @Override
+ public Couple<String> convert(ProgressIndicator o) {
+ String localBranch = myCurrentBranch;
+ String targetBranch = branch.getForkInfo().getRemoteName() + "/" + branch.getRemoteName();
+ try {
+ List<VcsCommitMetadata> commits =
+ GitHistoryUtils.readLastCommits(myProject, myGitRepository.getRoot(), localBranch, targetBranch);
+ if (commits == null) return getSimpleDefaultDescriptionMessage(branch);
+
+ VcsCommitMetadata localCommit = commits.get(0);
+ VcsCommitMetadata targetCommit = commits.get(1);
+
+ if (localCommit.getParents().contains(targetCommit.getId())) {
+ return Couple.of(localCommit.getSubject(), localCommit.getFullMessage());
+ }
+ return getSimpleDefaultDescriptionMessage(branch);
+ }
+ catch (VcsException e) {
+ GithubNotifications.showWarning(myProject, "Can't collect additional data", e);
+ return getSimpleDefaultDescriptionMessage(branch);
+ }
}
- }
+ });
+ }
+
+ @NotNull
+ public Couple<String> getSimpleDefaultDescriptionMessage(@NotNull final BranchInfo branch) {
+ Couple<String> message = Couple.of(myCurrentBranch, "");
+ branch.setDefaultMessage(message);
+ return message;
+ }
+
+ public boolean checkAction(@Nullable final BranchInfo branch) {
+ if (branch == null) {
+ GithubNotifications.showWarningDialog(myProject, CANNOT_CREATE_PULL_REQUEST, "Target branch is not selected");
+ return false;
}
- Convertor<String, GithubFullPath> getForkPath = new Convertor<String, GithubFullPath>() {
- @Nullable
- @Override
- public GithubFullPath convert(@NotNull final String user) {
- return GithubUtil.computeValueInModal(myProject, "Access to GitHub", new Convertor<ProgressIndicator, GithubFullPath>() {
- @Nullable
+ DiffInfo info;
+ try {
+ info = GithubUtil
+ .computeValueInModal(myProject, "Collecting diff data...", new ThrowableConvertor<ProgressIndicator, DiffInfo, IOException>() {
@Override
- public GithubFullPath convert(ProgressIndicator indicator) {
- return findRepositoryByUser(myProject, myAuthHolder, indicator, user, info.getForks(), info.getSource());
+ public DiffInfo convert(ProgressIndicator indicator) throws IOException {
+ return GithubUtil.runInterruptable(indicator, new ThrowableComputable<DiffInfo, IOException>() {
+ @Override
+ public DiffInfo compute() throws IOException {
+ return getDiffInfo(branch);
+ }
+ });
}
});
- }
- };
- GithubSelectForkDialog dialog = new GithubSelectForkDialog(myProject, info.getForks(), getForkPath);
- DialogManager.show(dialog);
- if (!dialog.isOK()) {
- return null;
}
- return dialog.getPath();
- }
-
- public boolean checkAction(@NotNull String targetBranch) {
- DiffInfo info = getDiffInfoWithModal(targetBranch);
+ catch (IOException e) {
+ GithubNotifications.showError(myProject, "Can't collect diff data", e);
+ return true;
+ }
if (info == null) {
return true;
}
- String localBranchName = "'" + getCurrentBranch() + "'";
- String targetBranchName = "'" + myTargetRemote + ":" + targetBranch + "'";
+ ForkInfo fork = branch.getForkInfo();
+
+ String localBranchName = "'" + myCurrentBranch + "'";
+ String targetBranchName = "'" + fork.getRemoteName() + "/" + branch.getRemoteName() + "'";
if (info.getInfo().getBranchToHeadCommits(myGitRepository).isEmpty()) {
- GithubNotifications
- .showWarningDialog(myProject, CANNOT_CREATE_PULL_REQUEST,
- "Can't create empty pull request: the branch " + localBranchName +
- " is fully merged to the branch " + targetBranchName
- );
- return false;
+ return Messages.YES == GithubNotifications
+ .showYesNoDialog(myProject, "Do you want to proceed anyway?",
+ "Empty pull request: the branch " + localBranchName + " is fully merged to the branch " + targetBranchName);
}
if (!info.getInfo().getHeadToBranchCommits(myGitRepository).isEmpty()) {
- return GithubNotifications
- .showYesNoDialog(myProject, "Do you want to proceed anyway?",
- "The branch " + targetBranchName + " is not fully merged to the branch " + localBranchName) == Messages.YES;
+ return Messages.YES == GithubNotifications
+ .showYesNoDialog(myProject, "Do you want to proceed anyway?",
+ "The branch " + targetBranchName + " is not fully merged to the branch " + localBranchName);
}
return true;
}
- public void performAction(@NotNull final String title, @NotNull final String description, @NotNull final String targetBranch) {
- @NotNull final Project project = myProject;
-
+ public void createPullRequest(@NotNull final BranchInfo branch,
+ @NotNull final String title,
+ @NotNull final String description) {
new Task.Backgroundable(myProject, "Creating pull request...") {
@Override
public void run(@NotNull ProgressIndicator indicator) {
@@ -344,298 +511,322 @@ public class GithubCreatePullRequestWorker {
indicator.setText("Pushing current branch...");
GitCommandResult result = myGit.push(myGitRepository, myRemoteName, myRemoteUrl, myCurrentBranch, true);
if (!result.success()) {
- GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Push failed:<br/>" + result.getErrorOutputAsHtmlString());
+ GithubNotifications.showError(myProject, CANNOT_CREATE_PULL_REQUEST, "Push failed:<br/>" + result.getErrorOutputAsHtmlString());
return;
}
- String headBranch = myPath.getUser() + ":" + myCurrentBranch;
-
LOG.info("Creating pull request");
indicator.setText("Creating pull request...");
- GithubPullRequest request =
- createPullRequest(project, myAuthHolder, indicator, myForkPath, title, description, headBranch, targetBranch);
+ GithubPullRequest request = doCreatePullRequest(indicator, branch, title, description);
if (request == null) {
return;
}
- GithubNotifications
- .showInfoURL(project, "Successfully created pull request", "Pull request #" + request.getNumber(), request.getHtmlUrl());
+ GithubNotifications.showInfoURL(myProject, "Successfully created pull request",
+ "Pull request #" + request.getNumber(), request.getHtmlUrl());
}
}.queue();
}
@Nullable
- private static String configureRemote(@NotNull Project project, @NotNull GitRepository gitRepository, @NotNull GithubFullPath forkPath) {
- String url = GithubUrlUtil.getCloneUrl(forkPath);
+ private GithubPullRequest doCreatePullRequest(@NotNull ProgressIndicator indicator,
+ @NotNull final BranchInfo branch,
+ @NotNull final String title,
+ @NotNull final String description) {
+ final ForkInfo fork = branch.getForkInfo();
- if (GithubUtil.addGithubRemote(project, gitRepository, forkPath.getUser(), url)) {
- return forkPath.getUser();
- }
- else {
- return null;
- }
- }
+ final String head = myPath.getUser() + ":" + myCurrentBranch;
+ final String base = branch.getRemoteName();
- @Nullable
- private static GithubPullRequest createPullRequest(@NotNull Project project,
- @NotNull GithubAuthDataHolder authHolder,
- @NotNull ProgressIndicator indicator,
- @NotNull final GithubFullPath targetRepo,
- @NotNull final String title,
- @NotNull final String description,
- @NotNull final String head,
- @NotNull final String base) {
try {
- return GithubUtil.runTask(project, authHolder, indicator, new ThrowableConvertor<GithubAuthData, GithubPullRequest, IOException>() {
- @NotNull
- @Override
- public GithubPullRequest convert(@NotNull GithubAuthData auth) throws IOException {
- return GithubApiUtil.createPullRequest(auth, targetRepo.getUser(), targetRepo.getRepository(), title, description, head, base);
- }
- });
+ return GithubUtil
+ .runTask(myProject, myAuthHolder, indicator, new ThrowableConvertor<GithubConnection, GithubPullRequest, IOException>() {
+ @NotNull
+ @Override
+ public GithubPullRequest convert(@NotNull GithubConnection connection) throws IOException {
+ return GithubApiUtil
+ .createPullRequest(connection, fork.getPath().getUser(), fork.getPath().getRepository(), title, description, head, base);
+ }
+ });
}
catch (IOException e) {
- GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e);
+ GithubNotifications.showError(myProject, CANNOT_CREATE_PULL_REQUEST, e);
return null;
}
}
- @Nullable
- private DiffInfo getDiffInfo(@NotNull String branch) {
+ public void showDiffDialog(@Nullable final BranchInfo branch) {
+ if (branch == null) {
+ GithubNotifications.showWarningDialog(myProject, "Can't Show Diff", "Target branch is not selected");
+ return;
+ }
+
+ DiffInfo info;
try {
- FutureTask<DiffInfo> future = myDiffInfos.get(branch);
- if (future == null) {
- return null;
- }
- future.run();
- return future.get();
+ info = GithubUtil
+ .computeValueInModal(myProject, "Collecting diff data...", new ThrowableConvertor<ProgressIndicator, DiffInfo, IOException>() {
+ @Override
+ public DiffInfo convert(ProgressIndicator indicator) throws IOException {
+ return GithubUtil.runInterruptable(indicator, new ThrowableComputable<DiffInfo, IOException>() {
+ @Override
+ public DiffInfo compute() throws IOException {
+ return getDiffInfo(branch);
+ }
+ });
+ }
+ });
}
- catch (InterruptedException e) {
- LOG.error(e);
- return null;
+ catch (IOException e) {
+ GithubNotifications.showError(myProject, "Can't collect diff data", e);
+ return;
}
- catch (ExecutionException e) {
- LOG.error(e);
- return null;
+ if (info == null) {
+ GithubNotifications.showErrorDialog(myProject, "Can't Show Diff", "Can't collect diff data");
+ return;
}
+
+ GitCompareBranchesDialog dialog =
+ new GitCompareBranchesDialog(myProject, info.getTo(), info.getFrom(), info.getInfo(), myGitRepository, true);
+ dialog.show();
}
@Nullable
- private DiffInfo getDiffInfoWithModal(@NotNull final String branch) {
- return GithubUtil.computeValueInModal(myProject, "Collecting diff data...", new Convertor<ProgressIndicator, DiffInfo>() {
- @Override
- @Nullable
- public DiffInfo convert(ProgressIndicator indicator) {
- return getDiffInfo(branch);
- }
- });
- }
+ public ForkInfo showTargetDialog() {
+ if (myAvailableForks == null) {
+ myAvailableForks = GithubUtil
+ .computeValueInModal(myProject, myCurrentBranch, new Convertor<ProgressIndicator, List<GithubFullPath>>() {
+ @Override
+ public List<GithubFullPath> convert(ProgressIndicator indicator) {
+ return getAvailableForks(indicator);
+ }
+ });
+ }
- public void getDiffDescriptionInPooledThread(@NotNull final String branch, @NotNull final Consumer<DiffDescription> after) {
- ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
+ Convertor<String, ForkInfo> getForkPath = new Convertor<String, ForkInfo>() {
+ @Nullable
@Override
- public void run() {
- after.consume(getDefaultDescriptionMessage(branch, getDiffInfo(branch), myGitRepository));
+ public ForkInfo convert(@NotNull final String user) {
+ return GithubUtil.computeValueInModal(myProject, "Access to GitHub", new Convertor<ProgressIndicator, ForkInfo>() {
+ @Nullable
+ @Override
+ public ForkInfo convert(ProgressIndicator indicator) {
+ return findRepositoryByUser(indicator, user);
+ }
+ });
}
- });
+ };
+ GithubSelectForkDialog dialog = new GithubSelectForkDialog(myProject, myAvailableForks, getForkPath);
+ DialogManager.show(dialog);
+ if (!dialog.isOK()) {
+ return null;
+ }
+ return dialog.getPath();
}
@Nullable
- private static DiffInfo loadDiffInfo(@NotNull final Project project,
- @NotNull final GitRepository repository,
- @NotNull final String currentBranch,
- @NotNull final String targetBranch) {
+ private List<GithubFullPath> getAvailableForks(@NotNull ProgressIndicator indicator) {
try {
- List<GitCommit> commits1 = GitHistoryUtils.history(project, repository.getRoot(), ".." + targetBranch);
- List<GitCommit> commits2 = GitHistoryUtils.history(project, repository.getRoot(), targetBranch + "..");
- Collection<Change> diff = GitChangeUtils.getDiff(repository.getProject(), repository.getRoot(), targetBranch, currentBranch, null);
- GitCommitCompareInfo info = new GitCommitCompareInfo(GitCommitCompareInfo.InfoType.BRANCH_TO_HEAD);
- info.put(repository, diff);
- info.put(repository, Couple.of(commits1, commits2));
- return new DiffInfo(info, currentBranch, targetBranch);
- }
- catch (VcsException e) {
- LOG.info(e);
+ List<GithubFullPath> forks = ContainerUtil.map(
+ GithubUtil.runTask(myProject, myAuthHolder, indicator,
+ new ThrowableConvertor<GithubConnection, List<GithubRepo>, IOException>() {
+ @NotNull
+ @Override
+ public List<GithubRepo> convert(@NotNull GithubConnection connection)
+ throws IOException {
+ return GithubApiUtil.getForks(connection, mySource.getUser(), mySource.getRepository());
+ }
+ }
+ ),
+ new Function<GithubRepo, GithubFullPath>() {
+ @Override
+ public GithubFullPath fun(GithubRepo repo) {
+ return repo.getFullPath();
+ }
+ }
+ );
+ if (!forks.contains(mySource)) forks.add(mySource);
+ return forks;
+ }
+ catch (IOException e) {
+ GithubNotifications.showWarning(myProject, "Can't load available forks", e);
return null;
}
}
- @NotNull
- private static DiffDescription getDefaultDescriptionMessage(@NotNull String branch,
- @Nullable DiffInfo info,
- @NotNull GitRepository gitRepository) {
- if (info == null) {
- return new DiffDescription(branch, null, null);
- }
-
- if (info.getInfo().getBranchToHeadCommits(gitRepository).size() != 1) {
- return new DiffDescription(branch, info.getFrom(), null);
+ @Nullable
+ private ForkInfo findRepositoryByUser(@NotNull final ProgressIndicator indicator, @NotNull final String user) {
+ for (ForkInfo fork : myForks) {
+ if (StringUtil.equalsIgnoreCase(user, fork.getPath().getUser())) {
+ return fork;
+ }
}
- GitCommit commit = info.getInfo().getBranchToHeadCommits(gitRepository).get(0);
- return new DiffDescription(branch, commit.getSubject(), commit.getFullMessage());
- }
-
- @Nullable
- private static GithubInfo2 getAvailableForksInModal(@NotNull final Project project,
- @NotNull final GitRepository gitRepository,
- @NotNull final GithubAuthDataHolder authHolder,
- @NotNull final GithubFullPath path) {
try {
- return GithubUtil
- .computeValueInModal(project, "Access to GitHub", new ThrowableConvertor<ProgressIndicator, GithubInfo2, IOException>() {
- @NotNull
+ GithubRepo repo =
+ GithubUtil.runTask(myProject, myAuthHolder, indicator, new ThrowableConvertor<GithubConnection, GithubRepo, IOException>() {
+ @Nullable
@Override
- public GithubInfo2 convert(ProgressIndicator indicator) throws IOException {
- final Set<GithubFullPath> forks = new HashSet<GithubFullPath>();
-
- // GitHub
- GithubRepoDetailed repo =
- GithubUtil.runTask(project, authHolder, indicator, new ThrowableConvertor<GithubAuthData, GithubRepoDetailed, IOException>() {
- @NotNull
- @Override
- public GithubRepoDetailed convert(@NotNull GithubAuthData auth) throws IOException {
- return GithubApiUtil.getDetailedRepoInfo(auth, path.getUser(), path.getRepository());
- }
- });
-
- forks.add(path);
- if (repo.getParent() != null) {
- forks.add(repo.getParent().getFullPath());
+ public GithubRepo convert(@NotNull GithubConnection connection) throws IOException {
+ try {
+ GithubRepoDetailed target = GithubApiUtil.getDetailedRepoInfo(connection, user, mySource.getRepository());
+ if (target.getSource() != null && StringUtil.equals(target.getSource().getUserName(), mySource.getUser())) {
+ return target;
+ }
}
- if (repo.getSource() != null) {
- forks.add(repo.getSource().getFullPath());
+ catch (IOException ignore) {
+ // such repo may not exist
}
- // Git
- forks.addAll(getAvailableForksFromGit(gitRepository));
-
- GithubRepo forkTreeRoot = repo.getSource() == null ? repo : repo.getSource();
- return new GithubInfo2(forks, forkTreeRoot);
+ return GithubApiUtil.findForkByUser(connection, mySource.getUser(), mySource.getRepository(), user);
}
});
+
+ if (repo == null) return null;
+ return doAddFork(repo, indicator);
}
catch (IOException e) {
- GithubNotifications.showErrorDialog(project, CANNOT_CREATE_PULL_REQUEST, e);
+ GithubNotifications.showError(myProject, "Can't find repository", e);
return null;
}
}
- @NotNull
- private static List<GithubFullPath> getAvailableForksFromGit(@NotNull GitRepository gitRepository) {
- List<GithubFullPath> forks = new ArrayList<GithubFullPath>();
- for (GitRemoteBranch remoteBranch : gitRepository.getBranches().getRemoteBranches()) {
- for (String url : remoteBranch.getRemote().getUrls()) {
- if (GithubUrlUtil.isGithubUrl(url)) {
- GithubFullPath path = GithubUrlUtil.getUserAndRepositoryFromRemoteUrl(url);
- if (path != null) {
- forks.add(path);
- break;
- }
- }
+ public static class ForkInfo {
+ @NotNull public final Object LOCK = new Object();
+
+ // initial loading
+ @NotNull private final GithubFullPath myPath;
+
+ @NotNull private final String myDefaultBranch;
+ @NotNull private final List<BranchInfo> myBranches;
+
+ @Nullable private String myRemoteName;
+ private boolean myProposedToCreateRemote;
+
+ @Nullable private MasterFutureTask<Void> myFetchTask;
+
+ public ForkInfo(@NotNull GithubFullPath path, @NotNull List<String> branches, @Nullable String defaultBranch) {
+ myPath = path;
+ myDefaultBranch = defaultBranch == null ? "master" : defaultBranch;
+ myBranches = new ArrayList<BranchInfo>();
+ for (String branchName : branches) {
+ myBranches.add(new BranchInfo(branchName, this));
}
}
- return forks;
- }
- @Nullable
- private static GithubFullPath findRepositoryByUser(@NotNull Project project,
- @NotNull GithubAuthDataHolder authHolder,
- @NotNull ProgressIndicator indicator,
- @NotNull final String user,
- @NotNull Set<GithubFullPath> forks,
- @NotNull final GithubRepo source) {
- for (GithubFullPath path : forks) {
- if (StringUtil.equalsIgnoreCase(user, path.getUser())) {
- return path;
- }
+ @NotNull
+ public GithubFullPath getPath() {
+ return myPath;
}
- try {
- return GithubUtil.runTask(project, authHolder, indicator, new ThrowableConvertor<GithubAuthData, GithubFullPath, IOException>() {
- @Nullable
- @Override
- public GithubFullPath convert(@NotNull GithubAuthData auth) throws IOException {
- try {
- GithubRepoDetailed target = GithubApiUtil.getDetailedRepoInfo(auth, user, source.getName());
- if (target.getSource() != null && StringUtil.equals(target.getSource().getUserName(), source.getUserName())) {
- return target.getFullPath();
- }
- }
- catch (IOException ignore) {
- // such repo may not exist
- }
+ @Nullable
+ public String getRemoteName() {
+ return myRemoteName;
+ }
- GithubRepo fork = GithubApiUtil.findForkByUser(auth, source.getUserName(), source.getName(), user);
- if (fork != null) {
- return fork.getFullPath();
- }
+ @NotNull
+ public String getDefaultBranch() {
+ return myDefaultBranch;
+ }
- return null;
- }
- });
+ @NotNull
+ public List<BranchInfo> getBranches() {
+ return myBranches;
}
- catch (IOException e) {
- GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e);
+
+ public void setRemoteName(@NotNull String remoteName) {
+ myRemoteName = remoteName;
}
- return null;
- }
+ public boolean isProposedToCreateRemote() {
+ return myProposedToCreateRemote;
+ }
+
+ public void setProposedToCreateRemote(boolean proposedToCreateRemote) {
+ myProposedToCreateRemote = proposedToCreateRemote;
+ }
- private static class GithubInfo {
- @NotNull private final List<String> myBranches;
- @Nullable private final String myTargetRemote;
+ @Nullable
+ public MasterFutureTask<Void> getFetchTask() {
+ return myFetchTask;
+ }
- private GithubInfo(@NotNull List<String> repo, @Nullable String targetRemote) {
- myBranches = repo;
- myTargetRemote = targetRemote;
+ public void setFetchTask(@NotNull MasterFutureTask<Void> fetchTask) {
+ myFetchTask = fetchTask;
}
- @NotNull
- public List<String> getBranches() {
- return myBranches;
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ForkInfo info = (ForkInfo)o;
+
+ if (!myPath.equals(info.myPath)) return false;
+
+ return true;
}
- @Nullable
- public String getTargetRemote() {
- return myTargetRemote;
+ @Override
+ public int hashCode() {
+ return myPath.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return myPath.getUser() + ":" + myPath.getRepository();
}
}
- private static class GithubInfo2 {
- @NotNull private final Set<GithubFullPath> myForks;
- @NotNull private final GithubRepo mySource;
+ public static class BranchInfo {
+ @NotNull public final Object LOCK = new Object();
- private GithubInfo2(@NotNull Set<GithubFullPath> forks, @NotNull GithubRepo source) {
- myForks = forks;
- mySource = source;
+ @NotNull private final ForkInfo myForkInfo;
+ @NotNull private final String myRemoteName;
+
+ @Nullable private SlaveFutureTask<DiffInfo> myDiffInfoTask;
+
+ @Nullable private Couple<String> myDefaultMessage;
+
+ public BranchInfo(@NotNull String remoteName, @NotNull ForkInfo fork) {
+ myRemoteName = remoteName;
+ myForkInfo = fork;
}
@NotNull
- public Set<GithubFullPath> getForks() {
- return myForks;
+ public ForkInfo getForkInfo() {
+ return myForkInfo;
}
@NotNull
- public GithubRepo getSource() {
- return mySource;
+ public String getRemoteName() {
+ return myRemoteName;
}
- }
- public static class GithubTargetInfo {
- @NotNull private final List<String> myBranches;
+ @Nullable
+ public SlaveFutureTask<DiffInfo> getDiffInfoTask() {
+ return myDiffInfoTask;
+ }
- private GithubTargetInfo(@NotNull List<String> branches) {
- myBranches = branches;
+ public void setDiffInfoTask(@NotNull SlaveFutureTask<DiffInfo> diffInfoTask) {
+ myDiffInfoTask = diffInfoTask;
}
- @NotNull
- public List<String> getBranches() {
- return myBranches;
+ @Nullable
+ public Couple<String> getDefaultMessage() {
+ return myDefaultMessage;
+ }
+
+ public void setDefaultMessage(@NotNull Couple<String> message) {
+ myDefaultMessage = message;
+ }
+
+ @Override
+ public String toString() {
+ return myRemoteName;
}
}
- private static class DiffInfo {
+ public static class DiffInfo {
@NotNull private final GitCommitCompareInfo myInfo;
@NotNull private final String myFrom;
@NotNull private final String myTo;
@@ -662,30 +853,86 @@ public class GithubCreatePullRequestWorker {
}
}
- public static class DiffDescription {
- @NotNull private final String myBranch;
- @Nullable private final String myTitle;
- @Nullable private final String myDescription;
+ public static class SlaveFutureTask<T> extends FutureTask<T> {
+ @NotNull private final MasterFutureTask myMaster;
- public DiffDescription(@NotNull String branch, @Nullable String title, @Nullable String description) {
- myBranch = branch;
- myTitle = title;
- myDescription = description;
+ public SlaveFutureTask(@NotNull MasterFutureTask master, @NotNull Callable<T> callable) {
+ super(callable);
+ myMaster = master;
}
- @NotNull
- public String getBranch() {
- return myBranch;
+ @Override
+ public void run() {
+ if (myMaster.isDone()) {
+ super.run();
+ }
+ else {
+ if (!myMaster.addSlave(this)) {
+ super.run();
+ }
+ }
}
- @Nullable
- public String getTitle() {
- return myTitle;
+ public T safeGet() {
+ try {
+ return super.get();
+ }
+ catch (InterruptedException e) {
+ return null;
+ }
+ catch (CancellationException e) {
+ return null;
+ }
+ catch (ExecutionException e) {
+ return null;
+ }
}
+ }
- @Nullable
- public String getDescription() {
- return myDescription;
+ public static class MasterFutureTask<T> extends FutureTask<T> {
+ @NotNull private final Object LOCK = new Object();
+ private boolean myDone = false;
+
+ @Nullable private List<SlaveFutureTask> mySlaves;
+
+ public MasterFutureTask(@NotNull Callable<T> callable) {
+ super(callable);
+ }
+
+ boolean addSlave(@NotNull SlaveFutureTask slave) {
+ if (isDone()) {
+ return false;
+ }
+ else {
+ synchronized (LOCK) {
+ if (myDone) return false;
+ if (mySlaves == null) mySlaves = new ArrayList<SlaveFutureTask>();
+ mySlaves.add(slave);
+ return true;
+ }
+ }
+ }
+
+ @Override
+ protected void done() {
+ synchronized (LOCK) {
+ myDone = true;
+ if (mySlaves != null) {
+ for (final SlaveFutureTask slave : mySlaves) {
+ runSlave(slave);
+ }
+ mySlaves = null;
+ }
+ }
+ }
+
+ protected void runSlave(@NotNull final SlaveFutureTask slave) {
+ ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
+ @Override
+ public void run() {
+ slave.run();
+ }
+ });
}
}
}
diff --git a/plugins/github/src/org/jetbrains/plugins/github/GithubRebaseAction.java b/plugins/github/src/org/jetbrains/plugins/github/GithubRebaseAction.java
index 719f8b956432..23d34e3178d3 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/GithubRebaseAction.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/GithubRebaseAction.java
@@ -41,6 +41,7 @@ import icons.GithubIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.github.api.GithubApiUtil;
+import org.jetbrains.plugins.github.api.GithubConnection;
import org.jetbrains.plugins.github.api.GithubFullPath;
import org.jetbrains.plugins.github.api.GithubRepoDetailed;
import org.jetbrains.plugins.github.util.*;
@@ -197,11 +198,11 @@ public class GithubRebaseAction extends DumbAwareAction {
try {
return GithubUtil.runTask(project, GithubAuthDataHolder.createFromSettings(), indicator,
- new ThrowableConvertor<GithubAuthData, GithubRepoDetailed, IOException>() {
+ new ThrowableConvertor<GithubConnection, GithubRepoDetailed, IOException>() {
@NotNull
@Override
- public GithubRepoDetailed convert(@NotNull GithubAuthData auth) throws IOException {
- return GithubApiUtil.getDetailedRepoInfo(auth, userAndRepo.getUser(), userAndRepo.getRepository());
+ public GithubRepoDetailed convert(@NotNull GithubConnection connection) throws IOException {
+ return GithubApiUtil.getDetailedRepoInfo(connection, userAndRepo.getUser(), userAndRepo.getRepository());
}
});
}
diff --git a/plugins/github/src/org/jetbrains/plugins/github/GithubShareAction.java b/plugins/github/src/org/jetbrains/plugins/github/GithubShareAction.java
index cca5afa0f5e5..c372b09805a1 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/GithubShareAction.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/GithubShareAction.java
@@ -52,6 +52,7 @@ import icons.GithubIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.github.api.GithubApiUtil;
+import org.jetbrains.plugins.github.api.GithubConnection;
import org.jetbrains.plugins.github.api.GithubRepo;
import org.jetbrains.plugins.github.api.GithubUserDetailed;
import org.jetbrains.plugins.github.ui.GithubShareDialog;
@@ -211,15 +212,15 @@ public class GithubShareAction extends DumbAwareAction {
@Override
public GithubInfo convert(ProgressIndicator indicator) throws IOException {
// get existing github repos (network) and validate auth data
- return GithubUtil.runTask(project, authHolder, indicator, new ThrowableConvertor<GithubAuthData, GithubInfo, IOException>() {
+ return GithubUtil.runTask(project, authHolder, indicator, new ThrowableConvertor<GithubConnection, GithubInfo, IOException>() {
@NotNull
@Override
- public GithubInfo convert(@NotNull GithubAuthData auth) throws IOException {
+ public GithubInfo convert(@NotNull GithubConnection connection) throws IOException {
// check access to private repos (network)
- GithubUserDetailed userInfo = GithubApiUtil.getCurrentUserDetailed(auth);
+ GithubUserDetailed userInfo = GithubApiUtil.getCurrentUserDetailed(connection);
HashSet<String> names = new HashSet<String>();
- for (GithubRepo info : GithubApiUtil.getUserRepos(auth)) {
+ for (GithubRepo info : GithubApiUtil.getUserRepos(connection)) {
names.add(info.getName());
}
return new GithubInfo(userInfo, names);
@@ -243,11 +244,11 @@ public class GithubShareAction extends DumbAwareAction {
final boolean isPrivate) {
try {
- return GithubUtil.runTask(project, authHolder, indicator, new ThrowableConvertor<GithubAuthData, GithubRepo, IOException>() {
+ return GithubUtil.runTask(project, authHolder, indicator, new ThrowableConvertor<GithubConnection, GithubRepo, IOException>() {
@NotNull
@Override
- public GithubRepo convert(@NotNull GithubAuthData auth) throws IOException {
- return GithubApiUtil.createRepo(auth, name, description, isPrivate);
+ public GithubRepo convert(@NotNull GithubConnection connection) throws IOException {
+ return GithubApiUtil.createRepo(connection, name, description, isPrivate);
}
}).getHtmlUrl();
}
diff --git a/plugins/github/src/org/jetbrains/plugins/github/api/GithubApiUtil.java b/plugins/github/src/org/jetbrains/plugins/github/api/GithubApiUtil.java
index b8cf537f7709..063ad3bc7bb6 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/api/GithubApiUtil.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/api/GithubApiUtil.java
@@ -1,60 +1,32 @@
-/*
- * Copyright 2000-2014 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.api;
import com.google.gson.*;
-import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.util.net.HttpConfigurable;
-import org.apache.commons.httpclient.*;
-import org.apache.commons.httpclient.auth.AuthScope;
-import org.apache.commons.httpclient.methods.*;
-import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
+import org.apache.http.Header;
+import org.apache.http.HeaderElement;
+import org.apache.http.message.BasicHeader;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import org.jetbrains.plugins.github.exceptions.*;
-import org.jetbrains.plugins.github.util.GithubAuthData;
-import org.jetbrains.plugins.github.util.GithubSettings;
-import org.jetbrains.plugins.github.util.GithubUrlUtil;
+import org.jetbrains.plugins.github.api.GithubConnection.PagedRequest;
+import org.jetbrains.plugins.github.exceptions.GithubConfusingException;
+import org.jetbrains.plugins.github.exceptions.GithubJsonException;
+import org.jetbrains.plugins.github.exceptions.GithubStatusCodeException;
import org.jetbrains.plugins.github.util.GithubUtil;
-import sun.security.validator.ValidatorException;
-import javax.net.ssl.SSLHandshakeException;
-import java.awt.*;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
import java.net.URLEncoder;
import java.util.*;
-import java.util.List;
-/**
- * @author Kirill Likhodedov
- */
public class GithubApiUtil {
+ private static final Logger LOG = GithubUtil.LOG;
public static final String DEFAULT_GITHUB_HOST = "github.com";
private static final String PER_PAGE = "per_page=100";
- private static final Logger LOG = GithubUtil.LOG;
- private static final Header ACCEPT_V3_JSON_HTML_MARKUP = new Header("Accept", "application/vnd.github.v3.html+json");
- private static final Header ACCEPT_V3_JSON = new Header("Accept", "application/vnd.github.v3+json");
+ private static final Header ACCEPT_V3_JSON_HTML_MARKUP = new BasicHeader("Accept", "application/vnd.github.v3.html+json");
+ private static final Header ACCEPT_V3_JSON = new BasicHeader("Accept", "application/vnd.github.v3+json");
@NotNull private static final Gson gson = initGson();
@@ -65,344 +37,8 @@ public class GithubApiUtil {
return builder.create();
}
- private enum HttpVerb {
- GET, POST, DELETE, HEAD, PATCH
- }
-
- @Nullable
- private static JsonElement postRequest(@NotNull GithubAuthData auth,
- @NotNull String path,
- @Nullable String requestBody,
- @NotNull Header... headers) throws IOException {
- return request(auth, path, requestBody, Arrays.asList(headers), HttpVerb.POST).getJsonElement();
- }
-
- @Nullable
- private static JsonElement patchRequest(@NotNull GithubAuthData auth,
- @NotNull String path,
- @Nullable String requestBody,
- @NotNull Header... headers) throws IOException {
- return request(auth, path, requestBody, Arrays.asList(headers), HttpVerb.PATCH).getJsonElement();
- }
-
- @Nullable
- private static JsonElement deleteRequest(@NotNull GithubAuthData auth, @NotNull String path, @NotNull Header... headers)
- throws IOException {
- return request(auth, path, null, Arrays.asList(headers), HttpVerb.DELETE).getJsonElement();
- }
-
- @Nullable
- private static JsonElement getRequest(@NotNull GithubAuthData auth, @NotNull String path, @NotNull Header... headers) throws IOException {
- return request(auth, path, null, Arrays.asList(headers), HttpVerb.GET).getJsonElement();
- }
-
- @NotNull
- private static ResponsePage request(@NotNull GithubAuthData auth,
- @NotNull String path,
- @Nullable String requestBody,
- @NotNull Collection<Header> headers,
- @NotNull HttpVerb verb) throws IOException {
- if (EventQueue.isDispatchThread() && !ApplicationManager.getApplication().isUnitTestMode()) {
- LOG.warn("Network operation in EDT"); // TODO: fix
- }
-
- HttpMethod method = null;
- try {
- String uri = GithubUrlUtil.getApiUrl(auth.getHost()) + path;
- method = doREST(auth, uri, requestBody, headers, verb);
-
- checkStatusCode(method, requestBody);
-
- InputStream resp = method.getResponseBodyAsStream();
- if (resp == null) {
- return new ResponsePage();
- }
-
- JsonElement ret = parseResponse(resp);
- if (ret.isJsonNull()) {
- return new ResponsePage();
- }
-
- Header header = method.getResponseHeader("Link");
- if (header != null) {
- String value = header.getValue();
- int end = value.indexOf(">; rel=\"next\"");
- int begin = value.lastIndexOf('<', end);
- if (begin >= 0 && end >= 0) {
- String newPath = GithubUrlUtil.removeProtocolPrefix(value.substring(begin + 1, end));
- int index = newPath.indexOf('/');
-
- return new ResponsePage(ret, newPath.substring(index));
- }
- }
-
- return new ResponsePage(ret);
- }
- finally {
- if (method != null) {
- method.releaseConnection();
- }
- }
- }
-
- @NotNull
- private static HttpMethod doREST(@NotNull final GithubAuthData auth,
- @NotNull final String uri,
- @Nullable final String requestBody,
- @NotNull final Collection<Header> headers,
- @NotNull final HttpVerb verb) throws IOException {
- HttpClient client = getHttpClient(auth.getBasicAuth(), auth.isUseProxy());
- HttpMethod method;
- switch (verb) {
- case POST:
- method = new PostMethod(uri);
- if (requestBody != null) {
- ((PostMethod)method).setRequestEntity(new StringRequestEntity(requestBody, "application/json", "UTF-8"));
- }
- break;
- case PATCH:
- method = new PostMethod(uri) { // TODO: httpclient 4.x
- @Override
- public String getName() {
- return "PATCH";
- }
- };
- if (requestBody != null) {
- ((PostMethod)method).setRequestEntity(new StringRequestEntity(requestBody, "application/json", "UTF-8"));
- }
- break;
- case GET:
- method = new GetMethod(uri);
- break;
- case DELETE:
- method = new DeleteMethod(uri);
- break;
- case HEAD:
- method = new HeadMethod(uri);
- break;
- default:
- throw new IllegalStateException("Wrong HttpVerb: unknown method: " + verb.toString());
- }
-
- GithubAuthData.TokenAuth tokenAuth = auth.getTokenAuth();
- if (tokenAuth != null) {
- method.addRequestHeader("Authorization", "token " + tokenAuth.getToken());
- }
- GithubAuthData.BasicAuth basicAuth = auth.getBasicAuth();
- if (basicAuth != null && basicAuth.getCode() != null) {
- method.addRequestHeader("X-GitHub-OTP", basicAuth.getCode());
- }
- for (Header header : headers) {
- method.addRequestHeader(header);
- }
-
- try {
- client.executeMethod(method);
- }
- catch (SSLHandshakeException e) { // User canceled operation from CertificateManager
- if (e.getCause() instanceof ValidatorException) {
- LOG.info("Host SSL certificate is not trusted", e);
- throw new GithubOperationCanceledException("Host SSL certificate is not trusted", e);
- }
- throw e;
- }
- return method;
- }
-
- @NotNull
- private static HttpClient getHttpClient(@Nullable GithubAuthData.BasicAuth basicAuth, boolean useProxy) {
- int timeout = GithubSettings.getInstance().getConnectionTimeout();
- final HttpClient client = new HttpClient();
- HttpConnectionManagerParams params = client.getHttpConnectionManager().getParams();
- params.setConnectionTimeout(timeout); //set connection timeout (how long it takes to connect to remote host)
- params.setSoTimeout(timeout); //set socket timeout (how long it takes to retrieve data from remote host)
-
- client.getParams().setContentCharset("UTF-8");
- // Configure proxySettings if it is required
- final HttpConfigurable proxySettings = HttpConfigurable.getInstance();
- if (useProxy && proxySettings.USE_HTTP_PROXY && !StringUtil.isEmptyOrSpaces(proxySettings.PROXY_HOST)) {
- client.getHostConfiguration().setProxy(proxySettings.PROXY_HOST, proxySettings.PROXY_PORT);
- if (proxySettings.PROXY_AUTHENTICATION) {
- client.getState().setProxyCredentials(AuthScope.ANY, new UsernamePasswordCredentials(proxySettings.PROXY_LOGIN,
- proxySettings.getPlainProxyPassword()));
- }
- }
- if (basicAuth != null) {
- client.getParams().setCredentialCharset("UTF-8");
- client.getParams().setAuthenticationPreemptive(true);
- client.getState().setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(basicAuth.getLogin(), basicAuth.getPassword()));
- }
- return client;
- }
-
- private static void checkStatusCode(@NotNull HttpMethod method, @Nullable String body) throws IOException {
- int code = method.getStatusCode();
- switch (code) {
- case HttpStatus.SC_OK:
- case HttpStatus.SC_CREATED:
- case HttpStatus.SC_ACCEPTED:
- case HttpStatus.SC_NO_CONTENT:
- return;
- case HttpStatus.SC_UNAUTHORIZED:
- case HttpStatus.SC_PAYMENT_REQUIRED:
- case HttpStatus.SC_FORBIDDEN:
- String message = getErrorMessage(method);
-
- Header headerOTP = method.getResponseHeader("X-GitHub-OTP");
- if (headerOTP != null) {
- if (headerOTP.getValue().startsWith("required")) {
- throw new GithubTwoFactorAuthenticationException(message);
- }
- }
-
- if (message.contains("API rate limit exceeded")) {
- throw new GithubRateLimitExceededException(message);
- }
-
- throw new GithubAuthenticationException("Request response: " + message);
- case HttpStatus.SC_BAD_REQUEST:
- case HttpStatus.SC_UNPROCESSABLE_ENTITY:
- if (body != null) {
- LOG.info(body);
- }
- throw new GithubStatusCodeException(code + ": " + getErrorMessage(method), code);
- default:
- throw new GithubStatusCodeException(code + ": " + getErrorMessage(method), code);
- }
- }
-
- @NotNull
- private static String getErrorMessage(@NotNull HttpMethod method) {
- try {
- InputStream resp = method.getResponseBodyAsStream();
- if (resp != null) {
- GithubErrorMessageRaw error = fromJson(parseResponse(resp), GithubErrorMessageRaw.class);
- return method.getStatusText() + " - " + error.getMessage();
- }
- }
- catch (IOException e) {
- LOG.info(e);
- }
- return method.getStatusText();
- }
-
- @NotNull
- private static JsonElement parseResponse(@NotNull InputStream githubResponse) throws IOException {
- Reader reader = new InputStreamReader(githubResponse, "UTF-8");
- try {
- return new JsonParser().parse(reader);
- }
- catch (JsonParseException jse) {
- throw new GithubJsonException("Couldn't parse GitHub response", jse);
- }
- finally {
- reader.close();
- }
- }
-
- private static class ResponsePage {
- @Nullable private final JsonElement response;
- @Nullable private final String nextPage;
-
- public ResponsePage() {
- this(null, null);
- }
-
- public ResponsePage(@Nullable JsonElement response) {
- this(response, null);
- }
-
- public ResponsePage(@Nullable JsonElement response, @Nullable String next) {
- this.response = response;
- this.nextPage = next;
- }
-
- @Nullable
- public JsonElement getJsonElement() {
- return response;
- }
-
- @Nullable
- public String getNextPage() {
- return nextPage;
- }
- }
-
- /*
- * Json API
- */
-
- static <Raw extends DataConstructor, Result> Result createDataFromRaw(@NotNull Raw rawObject, @NotNull Class<Result> resultClass)
- throws GithubJsonException {
- try {
- return rawObject.create(resultClass);
- }
- catch (Exception e) {
- throw new GithubJsonException("Json parse error", e);
- }
- }
-
- public static class PagedRequest<T> {
- @Nullable private String myNextPage;
- @NotNull private final Collection<Header> myHeaders;
- @NotNull private final Class<T> myResult;
- @NotNull private final Class<? extends DataConstructor[]> myRawArray;
-
- @SuppressWarnings("NullableProblems")
- public PagedRequest(@NotNull String path,
- @NotNull Class<T> result,
- @NotNull Class<? extends DataConstructor[]> rawArray,
- @NotNull Header... headers) {
- myNextPage = path;
- myResult = result;
- myRawArray = rawArray;
- myHeaders = Arrays.asList(headers);
- }
-
- @NotNull
- public List<T> next(@NotNull GithubAuthData auth) throws IOException {
- if (myNextPage == null) {
- throw new NoSuchElementException();
- }
-
- String page = myNextPage;
- myNextPage = null;
-
- ResponsePage response = request(auth, page, null, myHeaders, HttpVerb.GET);
-
- if (response.getJsonElement() == null) {
- throw new HttpException("Empty response");
- }
-
- if (!response.getJsonElement().isJsonArray()) {
- throw new GithubJsonException("Wrong json type: expected JsonArray", new Exception(response.getJsonElement().toString()));
- }
-
- myNextPage = response.getNextPage();
-
- List<T> result = new ArrayList<T>();
- for (DataConstructor raw : fromJson(response.getJsonElement().getAsJsonArray(), myRawArray)) {
- result.add(createDataFromRaw(raw, myResult));
- }
- return result;
- }
-
- public boolean hasNext() {
- return myNextPage != null;
- }
-
- @NotNull
- public List<T> getAll(@NotNull GithubAuthData auth) throws IOException {
- List<T> result = new ArrayList<T>();
- while (hasNext()) {
- result.addAll(next(auth));
- }
- return result;
- }
- }
-
@NotNull
- private static <T> T fromJson(@Nullable JsonElement json, @NotNull Class<T> classT) throws IOException {
+ public static <T> T fromJson(@Nullable JsonElement json, @NotNull Class<T> classT) throws IOException {
if (json == null) {
throw new GithubJsonException("Unexpected empty response");
}
@@ -425,13 +61,24 @@ public class GithubApiUtil {
return res;
}
- /*
- * Github API
+ @NotNull
+ public static <Raw extends DataConstructor, Result> Result createDataFromRaw(@NotNull Raw rawObject, @NotNull Class<Result> resultClass)
+ throws GithubJsonException {
+ try {
+ return rawObject.create(resultClass);
+ }
+ catch (Exception e) {
+ throw new GithubJsonException("Json parse error", e);
+ }
+ }
+
+ /*
+ * Operations
*/
- public static void askForTwoFactorCodeSMS(@NotNull GithubAuthData auth) {
+ public static void askForTwoFactorCodeSMS(@NotNull GithubConnection connection) {
try {
- postRequest(auth, "/authorizations", null, ACCEPT_V3_JSON);
+ connection.postRequest("/authorizations", null, ACCEPT_V3_JSON);
}
catch (IOException e) {
LOG.info(e);
@@ -439,47 +86,42 @@ public class GithubApiUtil {
}
@NotNull
- public static Collection<String> getTokenScopes(@NotNull GithubAuthData auth) throws IOException {
- HttpMethod method = null;
- try {
- String uri = GithubUrlUtil.getApiUrl(auth.getHost()) + "/user";
- method = doREST(auth, uri, null, Collections.<Header>emptyList(), HttpVerb.HEAD);
-
- checkStatusCode(method, null);
+ public static Collection<String> getTokenScopes(@NotNull GithubConnection connection) throws IOException {
+ Header[] headers = connection.headRequest("/user", ACCEPT_V3_JSON);
- Header header = method.getResponseHeader("X-OAuth-Scopes");
- if (header == null) {
- throw new HttpException("No scopes header");
- }
-
- Collection<String> scopes = new ArrayList<String>();
- for (HeaderElement elem : header.getElements()) {
- scopes.add(elem.getName());
+ Header scopesHeader = null;
+ for (Header header : headers) {
+ if (header.getName().equals("X-OAuth-Scopes")) {
+ scopesHeader = header;
+ break;
}
- return scopes;
}
- finally {
- if (method != null) {
- method.releaseConnection();
- }
+ if (scopesHeader == null) {
+ throw new GithubConfusingException("No scopes header");
}
+
+ Collection<String> scopes = new ArrayList<String>();
+ for (HeaderElement elem : scopesHeader.getElements()) {
+ scopes.add(elem.getName());
+ }
+ return scopes;
}
@NotNull
- public static String getScopedToken(@NotNull GithubAuthData auth, @NotNull Collection<String> scopes, @NotNull String note)
+ public static String getScopedToken(@NotNull GithubConnection connection, @NotNull Collection<String> scopes, @NotNull String note)
throws IOException {
- GithubAuthorization token = findToken(auth, note);
+ GithubAuthorization token = findToken(connection, note);
if (token == null) {
- return getNewScopedToken(auth, scopes, note).getToken();
+ return getNewScopedToken(connection, scopes, note).getToken();
}
if (token.getScopes().containsAll(scopes)) {
return token.getToken();
}
- return updateTokenScopes(auth, token, scopes).getToken();
+ return updateTokenScopes(connection, token, scopes).getToken();
}
@NotNull
- private static GithubAuthorization updateTokenScopes(@NotNull GithubAuthData auth,
+ private static GithubAuthorization updateTokenScopes(@NotNull GithubConnection connection,
@NotNull GithubAuthorization token,
@NotNull Collection<String> scopes) throws IOException {
try {
@@ -487,7 +129,7 @@ public class GithubApiUtil {
GithubAuthorizationUpdateRequest request = new GithubAuthorizationUpdateRequest(new ArrayList<String>(scopes));
- return createDataFromRaw(fromJson(patchRequest(auth, path, gson.toJson(request), ACCEPT_V3_JSON), GithubAuthorizationRaw.class),
+ return createDataFromRaw(fromJson(connection.patchRequest(path, gson.toJson(request), ACCEPT_V3_JSON), GithubAuthorizationRaw.class),
GithubAuthorization.class);
}
catch (GithubConfusingException e) {
@@ -497,7 +139,7 @@ public class GithubApiUtil {
}
@NotNull
- private static GithubAuthorization getNewScopedToken(@NotNull GithubAuthData auth,
+ private static GithubAuthorization getNewScopedToken(@NotNull GithubConnection connection,
@NotNull Collection<String> scopes,
@NotNull String note)
throws IOException {
@@ -506,7 +148,7 @@ public class GithubApiUtil {
GithubAuthorizationCreateRequest request = new GithubAuthorizationCreateRequest(new ArrayList<String>(scopes), note, null);
- return createDataFromRaw(fromJson(postRequest(auth, path, gson.toJson(request), ACCEPT_V3_JSON), GithubAuthorizationRaw.class),
+ return createDataFromRaw(fromJson(connection.postRequest(path, gson.toJson(request), ACCEPT_V3_JSON), GithubAuthorizationRaw.class),
GithubAuthorization.class);
}
catch (GithubConfusingException e) {
@@ -516,14 +158,14 @@ public class GithubApiUtil {
}
@Nullable
- private static GithubAuthorization findToken(@NotNull GithubAuthData auth, @NotNull String note) throws IOException {
+ private static GithubAuthorization findToken(@NotNull GithubConnection connection, @NotNull String note) throws IOException {
try {
String path = "/authorizations";
PagedRequest<GithubAuthorization> request =
new PagedRequest<GithubAuthorization>(path, GithubAuthorization.class, GithubAuthorizationRaw[].class, ACCEPT_V3_JSON);
- List<GithubAuthorization> tokens = request.getAll(auth);
+ List<GithubAuthorization> tokens = request.getAll(connection);
for (GithubAuthorization token : tokens) {
if (note.equals(token.getNote())) return token;
@@ -537,29 +179,32 @@ public class GithubApiUtil {
}
@NotNull
- public static String getMasterToken(@NotNull GithubAuthData auth, @NotNull String note) throws IOException {
+ public static String getMasterToken(@NotNull GithubConnection connection, @NotNull String note) throws IOException {
// "repo" - read/write access to public/private repositories
// "gist" - create/delete gists
List<String> scopes = Arrays.asList("repo", "gist");
- return getScopedToken(auth, scopes, note);
+ return getScopedToken(connection, scopes, note);
}
@NotNull
- public static String getReadOnlyToken(@NotNull GithubAuthData auth, @NotNull String user, @NotNull String repo, @NotNull String note)
+ public static String getReadOnlyToken(@NotNull GithubConnection connection,
+ @NotNull String user,
+ @NotNull String repo,
+ @NotNull String note)
throws IOException {
- GithubRepo repository = getDetailedRepoInfo(auth, user, repo);
+ GithubRepo repository = getDetailedRepoInfo(connection, user, repo);
// TODO: use read-only token for private repos when it will be available
List<String> scopes = repository.isPrivate() ? Collections.singletonList("repo") : Collections.<String>emptyList();
- return getScopedToken(auth, scopes, note);
+ return getScopedToken(connection, scopes, note);
}
@NotNull
- public static GithubUser getCurrentUser(@NotNull GithubAuthData auth) throws IOException {
+ public static GithubUser getCurrentUser(@NotNull GithubConnection connection) throws IOException {
try {
- JsonElement result = getRequest(auth, "/user", ACCEPT_V3_JSON);
+ JsonElement result = connection.getRequest("/user", ACCEPT_V3_JSON);
return createDataFromRaw(fromJson(result, GithubUserRaw.class), GithubUser.class);
}
catch (GithubConfusingException e) {
@@ -569,9 +214,9 @@ public class GithubApiUtil {
}
@NotNull
- public static GithubUserDetailed getCurrentUserDetailed(@NotNull GithubAuthData auth) throws IOException {
+ public static GithubUserDetailed getCurrentUserDetailed(@NotNull GithubConnection connection) throws IOException {
try {
- JsonElement result = getRequest(auth, "/user", ACCEPT_V3_JSON);
+ JsonElement result = connection.getRequest("/user", ACCEPT_V3_JSON);
return createDataFromRaw(fromJson(result, GithubUserRaw.class), GithubUserDetailed.class);
}
catch (GithubConfusingException e) {
@@ -581,13 +226,13 @@ public class GithubApiUtil {
}
@NotNull
- public static List<GithubRepo> getUserRepos(@NotNull GithubAuthData auth) throws IOException {
+ public static List<GithubRepo> getUserRepos(@NotNull GithubConnection connection) throws IOException {
try {
String path = "/user/repos?" + PER_PAGE;
PagedRequest<GithubRepo> request = new PagedRequest<GithubRepo>(path, GithubRepo.class, GithubRepoRaw[].class, ACCEPT_V3_JSON);
- return request.getAll(auth);
+ return request.getAll(connection);
}
catch (GithubConfusingException e) {
e.setDetails("Can't get user repositories");
@@ -596,13 +241,13 @@ public class GithubApiUtil {
}
@NotNull
- public static List<GithubRepo> getUserRepos(@NotNull GithubAuthData auth, @NotNull String user) throws IOException {
+ public static List<GithubRepo> getUserRepos(@NotNull GithubConnection connection, @NotNull String user) throws IOException {
try {
String path = "/users/" + user + "/repos?" + PER_PAGE;
PagedRequest<GithubRepo> request = new PagedRequest<GithubRepo>(path, GithubRepo.class, GithubRepoRaw[].class, ACCEPT_V3_JSON);
- return request.getAll(auth);
+ return request.getAll(connection);
}
catch (GithubConfusingException e) {
e.setDetails("Can't get user repositories: " + user);
@@ -611,21 +256,21 @@ public class GithubApiUtil {
}
@NotNull
- public static List<GithubRepo> getAvailableRepos(@NotNull GithubAuthData auth) throws IOException {
+ public static List<GithubRepo> getAvailableRepos(@NotNull GithubConnection connection) throws IOException {
try {
List<GithubRepo> repos = new ArrayList<GithubRepo>();
- repos.addAll(getUserRepos(auth));
+ repos.addAll(getUserRepos(connection));
// We already can return something useful from getUserRepos, so let's ignore errors.
// One of this may not exist in GitHub enterprise
try {
- repos.addAll(getMembershipRepos(auth));
+ repos.addAll(getMembershipRepos(connection));
}
catch (GithubStatusCodeException ignore) {
}
try {
- repos.addAll(getWatchedRepos(auth));
+ repos.addAll(getWatchedRepos(connection));
}
catch (GithubStatusCodeException ignore) {
}
@@ -639,36 +284,36 @@ public class GithubApiUtil {
}
@NotNull
- public static List<GithubRepoOrg> getMembershipRepos(@NotNull GithubAuthData auth) throws IOException {
+ public static List<GithubRepoOrg> getMembershipRepos(@NotNull GithubConnection connection) throws IOException {
String orgsPath = "/user/orgs?" + PER_PAGE;
PagedRequest<GithubOrg> orgsRequest = new PagedRequest<GithubOrg>(orgsPath, GithubOrg.class, GithubOrgRaw[].class);
List<GithubRepoOrg> repos = new ArrayList<GithubRepoOrg>();
- for (GithubOrg org : orgsRequest.getAll(auth)) {
+ for (GithubOrg org : orgsRequest.getAll(connection)) {
String path = "/orgs/" + org.getLogin() + "/repos?type=member&" + PER_PAGE;
PagedRequest<GithubRepoOrg> request =
new PagedRequest<GithubRepoOrg>(path, GithubRepoOrg.class, GithubRepoRaw[].class, ACCEPT_V3_JSON);
- repos.addAll(request.getAll(auth));
+ repos.addAll(request.getAll(connection));
}
return repos;
}
@NotNull
- public static List<GithubRepo> getWatchedRepos(@NotNull GithubAuthData auth) throws IOException {
+ public static List<GithubRepo> getWatchedRepos(@NotNull GithubConnection connection) throws IOException {
String pathWatched = "/user/subscriptions?" + PER_PAGE;
PagedRequest<GithubRepo> requestWatched =
new PagedRequest<GithubRepo>(pathWatched, GithubRepo.class, GithubRepoRaw[].class, ACCEPT_V3_JSON);
- return requestWatched.getAll(auth);
+ return requestWatched.getAll(connection);
}
@NotNull
- public static GithubRepoDetailed getDetailedRepoInfo(@NotNull GithubAuthData auth, @NotNull String owner, @NotNull String name)
+ public static GithubRepoDetailed getDetailedRepoInfo(@NotNull GithubConnection connection, @NotNull String owner, @NotNull String name)
throws IOException {
try {
final String request = "/repos/" + owner + "/" + name;
- JsonElement jsonObject = getRequest(auth, request, ACCEPT_V3_JSON);
+ JsonElement jsonObject = connection.getRequest(request, ACCEPT_V3_JSON);
return createDataFromRaw(fromJson(jsonObject, GithubRepoRaw.class), GithubRepoDetailed.class);
}
@@ -678,11 +323,11 @@ public class GithubApiUtil {
}
}
- public static void deleteGithubRepository(@NotNull GithubAuthData auth, @NotNull String username, @NotNull String repo)
+ public static void deleteGithubRepository(@NotNull GithubConnection connection, @NotNull String username, @NotNull String repo)
throws IOException {
try {
String path = "/repos/" + username + "/" + repo;
- deleteRequest(auth, path);
+ connection.deleteRequest(path);
}
catch (GithubConfusingException e) {
e.setDetails("Can't delete repository: " + username + "/" + repo);
@@ -690,10 +335,10 @@ public class GithubApiUtil {
}
}
- public static void deleteGist(@NotNull GithubAuthData auth, @NotNull String id) throws IOException {
+ public static void deleteGist(@NotNull GithubConnection connection, @NotNull String id) throws IOException {
try {
String path = "/gists/" + id;
- deleteRequest(auth, path);
+ connection.deleteRequest(path);
}
catch (GithubConfusingException e) {
e.setDetails("Can't delete gist: id - " + id);
@@ -702,10 +347,10 @@ public class GithubApiUtil {
}
@NotNull
- public static GithubGist getGist(@NotNull GithubAuthData auth, @NotNull String id) throws IOException {
+ public static GithubGist getGist(@NotNull GithubConnection connection, @NotNull String id) throws IOException {
try {
String path = "/gists/" + id;
- JsonElement result = getRequest(auth, path, ACCEPT_V3_JSON);
+ JsonElement result = connection.getRequest(path, ACCEPT_V3_JSON);
return createDataFromRaw(fromJson(result, GithubGistRaw.class), GithubGist.class);
}
@@ -716,13 +361,13 @@ public class GithubApiUtil {
}
@NotNull
- public static GithubGist createGist(@NotNull GithubAuthData auth,
+ public static GithubGist createGist(@NotNull GithubConnection connection,
@NotNull List<GithubGist.FileContent> contents,
@NotNull String description,
boolean isPrivate) throws IOException {
try {
String request = gson.toJson(new GithubGistRequest(contents, description, !isPrivate));
- return createDataFromRaw(fromJson(postRequest(auth, "/gists", request, ACCEPT_V3_JSON), GithubGistRaw.class), GithubGist.class);
+ return createDataFromRaw(fromJson(connection.postRequest("/gists", request, ACCEPT_V3_JSON), GithubGistRaw.class), GithubGist.class);
}
catch (GithubConfusingException e) {
e.setDetails("Can't create gist");
@@ -731,7 +376,16 @@ public class GithubApiUtil {
}
@NotNull
- public static GithubPullRequest createPullRequest(@NotNull GithubAuthData auth,
+ public static List<GithubRepo> getForks(@NotNull GithubConnection connection, @NotNull String owner, @NotNull String name)
+ throws IOException {
+ String path = "/repos/" + owner + "/" + name + "/forks?" + PER_PAGE;
+ PagedRequest<GithubRepo> requestWatched =
+ new PagedRequest<GithubRepo>(path, GithubRepo.class, GithubRepoRaw[].class, ACCEPT_V3_JSON);
+ return requestWatched.getAll(connection);
+ }
+
+ @NotNull
+ public static GithubPullRequest createPullRequest(@NotNull GithubConnection connection,
@NotNull String user,
@NotNull String repo,
@NotNull String title,
@@ -741,7 +395,7 @@ public class GithubApiUtil {
try {
String request = gson.toJson(new GithubPullRequestRequest(title, description, head, base));
return createDataFromRaw(
- fromJson(postRequest(auth, "/repos/" + user + "/" + repo + "/pulls", request, ACCEPT_V3_JSON), GithubPullRequestRaw.class),
+ fromJson(connection.postRequest("/repos/" + user + "/" + repo + "/pulls", request, ACCEPT_V3_JSON), GithubPullRequestRaw.class),
GithubPullRequest.class);
}
catch (GithubConfusingException e) {
@@ -751,14 +405,17 @@ public class GithubApiUtil {
}
@NotNull
- public static GithubRepo createRepo(@NotNull GithubAuthData auth, @NotNull String name, @NotNull String description, boolean isPrivate)
+ public static GithubRepo createRepo(@NotNull GithubConnection connection,
+ @NotNull String name,
+ @NotNull String description,
+ boolean isPrivate)
throws IOException {
try {
String path = "/user/repos";
GithubRepoRequest request = new GithubRepoRequest(name, description, isPrivate);
- return createDataFromRaw(fromJson(postRequest(auth, path, gson.toJson(request), ACCEPT_V3_JSON), GithubRepoRaw.class),
+ return createDataFromRaw(fromJson(connection.postRequest(path, gson.toJson(request), ACCEPT_V3_JSON), GithubRepoRaw.class),
GithubRepo.class);
}
catch (GithubConfusingException e) {
@@ -771,7 +428,7 @@ public class GithubApiUtil {
* Open issues only
*/
@NotNull
- public static List<GithubIssue> getIssuesAssigned(@NotNull GithubAuthData auth,
+ public static List<GithubIssue> getIssuesAssigned(@NotNull GithubConnection connection,
@NotNull String user,
@NotNull String repo,
@Nullable String assigned,
@@ -791,7 +448,7 @@ public class GithubApiUtil {
List<GithubIssue> result = new ArrayList<GithubIssue>();
while (request.hasNext() && max > result.size()) {
- result.addAll(request.next(auth));
+ result.addAll(request.next(connection));
}
return result;
}
@@ -805,7 +462,7 @@ public class GithubApiUtil {
/*
* All issues - open and closed
*/
- public static List<GithubIssue> getIssuesQueried(@NotNull GithubAuthData auth,
+ public static List<GithubIssue> getIssuesQueried(@NotNull GithubConnection connection,
@NotNull String user,
@NotNull String repo,
@Nullable String query,
@@ -816,7 +473,7 @@ public class GithubApiUtil {
String path = "/search/issues?q=" + query;
//TODO: Use bodyHtml for issues - GitHub does not support this feature for SearchApi yet
- JsonElement result = getRequest(auth, path, ACCEPT_V3_JSON);
+ JsonElement result = connection.getRequest(path, ACCEPT_V3_JSON);
return createDataFromRaw(fromJson(result, GithubIssuesSearchResultRaw.class), GithubIssuesSearchResult.class).getIssues();
}
@@ -827,12 +484,12 @@ public class GithubApiUtil {
}
@NotNull
- public static GithubIssue getIssue(@NotNull GithubAuthData auth, @NotNull String user, @NotNull String repo, @NotNull String id)
+ public static GithubIssue getIssue(@NotNull GithubConnection connection, @NotNull String user, @NotNull String repo, @NotNull String id)
throws IOException {
try {
String path = "/repos/" + user + "/" + repo + "/issues/" + id;
- JsonElement result = getRequest(auth, path, ACCEPT_V3_JSON);
+ JsonElement result = connection.getRequest(path, ACCEPT_V3_JSON);
return createDataFromRaw(fromJson(result, GithubIssueRaw.class), GithubIssue.class);
}
@@ -843,7 +500,10 @@ public class GithubApiUtil {
}
@NotNull
- public static List<GithubIssueComment> getIssueComments(@NotNull GithubAuthData auth, @NotNull String user, @NotNull String repo, long id)
+ public static List<GithubIssueComment> getIssueComments(@NotNull GithubConnection connection,
+ @NotNull String user,
+ @NotNull String repo,
+ long id)
throws IOException {
try {
String path = "/repos/" + user + "/" + repo + "/issues/" + id + "/comments?" + PER_PAGE;
@@ -851,7 +511,7 @@ public class GithubApiUtil {
PagedRequest<GithubIssueComment> request =
new PagedRequest<GithubIssueComment>(path, GithubIssueComment.class, GithubIssueCommentRaw[].class, ACCEPT_V3_JSON_HTML_MARKUP);
- return request.getAll(auth);
+ return request.getAll(connection);
}
catch (GithubConfusingException e) {
e.setDetails("Can't get issue comments: " + user + "/" + repo + " - " + id);
@@ -859,15 +519,37 @@ public class GithubApiUtil {
}
}
+ public static void setIssueState(@NotNull GithubConnection connection,
+ @NotNull String user,
+ @NotNull String repo,
+ @NotNull String id,
+ boolean open)
+ throws IOException {
+ try {
+ String path = "/repos/" + user + "/" + repo + "/issues/" + id;
+
+ GithubChangeIssueStateRequest request = new GithubChangeIssueStateRequest(open ? "open" : "closed");
+
+ JsonElement result = connection.patchRequest(path, gson.toJson(request), ACCEPT_V3_JSON);
+
+ createDataFromRaw(fromJson(result, GithubIssueRaw.class), GithubIssue.class);
+ }
+ catch (GithubConfusingException e) {
+ e.setDetails("Can't set issue state: " + user + "/" + repo + " - " + id + "@" + (open ? "open" : "closed"));
+ throw e;
+ }
+ }
+
+
@NotNull
- public static GithubCommitDetailed getCommit(@NotNull GithubAuthData auth,
+ public static GithubCommitDetailed getCommit(@NotNull GithubConnection connection,
@NotNull String user,
@NotNull String repo,
@NotNull String sha) throws IOException {
try {
String path = "/repos/" + user + "/" + repo + "/commits/" + sha;
- JsonElement result = getRequest(auth, path, ACCEPT_V3_JSON);
+ JsonElement result = connection.getRequest(path, ACCEPT_V3_JSON);
return createDataFromRaw(fromJson(result, GithubCommitRaw.class), GithubCommitDetailed.class);
}
catch (GithubConfusingException e) {
@@ -877,7 +559,7 @@ public class GithubApiUtil {
}
@NotNull
- public static List<GithubCommitComment> getCommitComments(@NotNull GithubAuthData auth,
+ public static List<GithubCommitComment> getCommitComments(@NotNull GithubConnection connection,
@NotNull String user,
@NotNull String repo,
@NotNull String sha) throws IOException {
@@ -887,7 +569,7 @@ public class GithubApiUtil {
PagedRequest<GithubCommitComment> request =
new PagedRequest<GithubCommitComment>(path, GithubCommitComment.class, GithubCommitCommentRaw[].class, ACCEPT_V3_JSON_HTML_MARKUP);
- return request.getAll(auth);
+ return request.getAll(connection);
}
catch (GithubConfusingException e) {
e.setDetails("Can't get commit comments: " + user + "/" + repo + " - " + sha);
@@ -896,7 +578,7 @@ public class GithubApiUtil {
}
@NotNull
- public static List<GithubCommitComment> getPullRequestComments(@NotNull GithubAuthData auth,
+ public static List<GithubCommitComment> getPullRequestComments(@NotNull GithubConnection connection,
@NotNull String user,
@NotNull String repo,
long id) throws IOException {
@@ -906,7 +588,7 @@ public class GithubApiUtil {
PagedRequest<GithubCommitComment> request =
new PagedRequest<GithubCommitComment>(path, GithubCommitComment.class, GithubCommitCommentRaw[].class, ACCEPT_V3_JSON_HTML_MARKUP);
- return request.getAll(auth);
+ return request.getAll(connection);
}
catch (GithubConfusingException e) {
e.setDetails("Can't get pull request comments: " + user + "/" + repo + " - " + id);
@@ -915,11 +597,11 @@ public class GithubApiUtil {
}
@NotNull
- public static GithubPullRequest getPullRequest(@NotNull GithubAuthData auth, @NotNull String user, @NotNull String repo, int id)
+ public static GithubPullRequest getPullRequest(@NotNull GithubConnection connection, @NotNull String user, @NotNull String repo, int id)
throws IOException {
try {
String path = "/repos/" + user + "/" + repo + "/pulls/" + id;
- return createDataFromRaw(fromJson(getRequest(auth, path, ACCEPT_V3_JSON_HTML_MARKUP), GithubPullRequestRaw.class),
+ return createDataFromRaw(fromJson(connection.getRequest(path, ACCEPT_V3_JSON_HTML_MARKUP), GithubPullRequestRaw.class),
GithubPullRequest.class);
}
catch (GithubConfusingException e) {
@@ -929,7 +611,7 @@ public class GithubApiUtil {
}
@NotNull
- public static List<GithubPullRequest> getPullRequests(@NotNull GithubAuthData auth, @NotNull String user, @NotNull String repo)
+ public static List<GithubPullRequest> getPullRequests(@NotNull GithubConnection connection, @NotNull String user, @NotNull String repo)
throws IOException {
try {
String path = "/repos/" + user + "/" + repo + "/pulls?" + PER_PAGE;
@@ -937,7 +619,7 @@ public class GithubApiUtil {
PagedRequest<GithubPullRequest> request =
new PagedRequest<GithubPullRequest>(path, GithubPullRequest.class, GithubPullRequestRaw[].class, ACCEPT_V3_JSON_HTML_MARKUP);
- return request.getAll(auth);
+ return request.getAll(connection);
}
catch (GithubConfusingException e) {
e.setDetails("Can't get pull requests" + user + "/" + repo);
@@ -953,7 +635,10 @@ public class GithubApiUtil {
}
@NotNull
- public static List<GithubCommit> getPullRequestCommits(@NotNull GithubAuthData auth, @NotNull String user, @NotNull String repo, long id)
+ public static List<GithubCommit> getPullRequestCommits(@NotNull GithubConnection connection,
+ @NotNull String user,
+ @NotNull String repo,
+ long id)
throws IOException {
try {
String path = "/repos/" + user + "/" + repo + "/pulls/" + id + "/commits?" + PER_PAGE;
@@ -961,7 +646,7 @@ public class GithubApiUtil {
PagedRequest<GithubCommit> request =
new PagedRequest<GithubCommit>(path, GithubCommit.class, GithubCommitRaw[].class, ACCEPT_V3_JSON);
- return request.getAll(auth);
+ return request.getAll(connection);
}
catch (GithubConfusingException e) {
e.setDetails("Can't get pull request commits: " + user + "/" + repo + " - " + id);
@@ -970,14 +655,17 @@ public class GithubApiUtil {
}
@NotNull
- public static List<GithubFile> getPullRequestFiles(@NotNull GithubAuthData auth, @NotNull String user, @NotNull String repo, long id)
+ public static List<GithubFile> getPullRequestFiles(@NotNull GithubConnection connection,
+ @NotNull String user,
+ @NotNull String repo,
+ long id)
throws IOException {
try {
String path = "/repos/" + user + "/" + repo + "/pulls/" + id + "/files?" + PER_PAGE;
PagedRequest<GithubFile> request = new PagedRequest<GithubFile>(path, GithubFile.class, GithubFileRaw[].class, ACCEPT_V3_JSON);
- return request.getAll(auth);
+ return request.getAll(connection);
}
catch (GithubConfusingException e) {
e.setDetails("Can't get pull request files: " + user + "/" + repo + " - " + id);
@@ -986,7 +674,7 @@ public class GithubApiUtil {
}
@NotNull
- public static List<GithubBranch> getRepoBranches(@NotNull GithubAuthData auth, @NotNull String user, @NotNull String repo)
+ public static List<GithubBranch> getRepoBranches(@NotNull GithubConnection connection, @NotNull String user, @NotNull String repo)
throws IOException {
try {
String path = "/repos/" + user + "/" + repo + "/branches?" + PER_PAGE;
@@ -994,7 +682,7 @@ public class GithubApiUtil {
PagedRequest<GithubBranch> request =
new PagedRequest<GithubBranch>(path, GithubBranch.class, GithubBranchRaw[].class, ACCEPT_V3_JSON);
- return request.getAll(auth);
+ return request.getAll(connection);
}
catch (GithubConfusingException e) {
e.setDetails("Can't get repository branches: " + user + "/" + repo);
@@ -1003,7 +691,7 @@ public class GithubApiUtil {
}
@Nullable
- public static GithubRepo findForkByUser(@NotNull GithubAuthData auth,
+ public static GithubRepo findForkByUser(@NotNull GithubConnection connection,
@NotNull String user,
@NotNull String repo,
@NotNull String forkUser) throws IOException {
@@ -1013,7 +701,7 @@ public class GithubApiUtil {
PagedRequest<GithubRepo> request = new PagedRequest<GithubRepo>(path, GithubRepo.class, GithubRepoRaw[].class, ACCEPT_V3_JSON);
while (request.hasNext()) {
- for (GithubRepo fork : request.next(auth)) {
+ for (GithubRepo fork : request.next(connection)) {
if (StringUtil.equalsIgnoreCase(fork.getUserName(), forkUser)) {
return fork;
}
@@ -1027,4 +715,4 @@ public class GithubApiUtil {
throw e;
}
}
-} \ No newline at end of file
+}
diff --git a/plugins/github/src/org/jetbrains/plugins/github/api/GithubChangeIssueStateRequest.java b/plugins/github/src/org/jetbrains/plugins/github/api/GithubChangeIssueStateRequest.java
new file mode 100644
index 000000000000..9e21f64046e5
--- /dev/null
+++ b/plugins/github/src/org/jetbrains/plugins/github/api/GithubChangeIssueStateRequest.java
@@ -0,0 +1,12 @@
+package org.jetbrains.plugins.github.api;
+
+import org.jetbrains.annotations.NotNull;
+
+@SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
+public class GithubChangeIssueStateRequest {
+ @NotNull private final String state;
+
+ public GithubChangeIssueStateRequest(@NotNull String state) {
+ this.state = state;
+ }
+}
diff --git a/plugins/github/src/org/jetbrains/plugins/github/api/GithubConnection.java b/plugins/github/src/org/jetbrains/plugins/github/api/GithubConnection.java
new file mode 100644
index 000000000000..6358b79e38ee
--- /dev/null
+++ b/plugins/github/src/org/jetbrains/plugins/github/api/GithubConnection.java
@@ -0,0 +1,491 @@
+package org.jetbrains.plugins.github.api;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonParser;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.net.HttpConfigurable;
+import com.intellij.util.net.ssl.CertificateManager;
+import org.apache.http.*;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.*;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.config.ConnectionConfig;
+import org.apache.http.conn.ssl.X509HostnameVerifier;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.auth.BasicScheme;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.protocol.HttpContext;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
+import org.jetbrains.plugins.github.exceptions.*;
+import org.jetbrains.plugins.github.util.GithubAuthData;
+import org.jetbrains.plugins.github.util.GithubSettings;
+import org.jetbrains.plugins.github.util.GithubUrlUtil;
+import org.jetbrains.plugins.github.util.GithubUtil;
+import sun.security.validator.ValidatorException;
+
+import javax.net.ssl.SSLHandshakeException;
+import java.awt.*;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.*;
+import java.util.List;
+
+import static org.jetbrains.plugins.github.api.GithubApiUtil.createDataFromRaw;
+import static org.jetbrains.plugins.github.api.GithubApiUtil.fromJson;
+
+public class GithubConnection {
+ private static final Logger LOG = GithubUtil.LOG;
+
+ private static final HttpRequestInterceptor PREEMPTIVE_BASIC_AUTH = new PreemptiveBasicAuthInterceptor();
+
+ @NotNull private final String myHost;
+ @NotNull private final CloseableHttpClient myClient;
+ private final boolean myReusable;
+
+ private volatile HttpUriRequest myRequest;
+ private volatile boolean myAborted;
+
+ @TestOnly
+ public GithubConnection(@NotNull GithubAuthData auth) {
+ this(auth, false);
+ }
+
+ public GithubConnection(@NotNull GithubAuthData auth, boolean reusable) {
+ myHost = auth.getHost();
+ myClient = createClient(auth);
+ myReusable = reusable;
+ }
+
+ private enum HttpVerb {
+ GET, POST, DELETE, HEAD, PATCH
+ }
+
+ @Nullable
+ public JsonElement getRequest(@NotNull String path,
+ @NotNull Header... headers) throws IOException {
+ return request(path, null, Arrays.asList(headers), HttpVerb.GET).getJsonElement();
+ }
+
+ @Nullable
+ public JsonElement postRequest(@NotNull String path,
+ @Nullable String requestBody,
+ @NotNull Header... headers) throws IOException {
+ return request(path, requestBody, Arrays.asList(headers), HttpVerb.POST).getJsonElement();
+ }
+
+ @Nullable
+ public JsonElement patchRequest(@NotNull String path,
+ @Nullable String requestBody,
+ @NotNull Header... headers) throws IOException {
+ return request(path, requestBody, Arrays.asList(headers), HttpVerb.PATCH).getJsonElement();
+ }
+
+ @Nullable
+ public JsonElement deleteRequest(@NotNull String path,
+ @NotNull Header... headers) throws IOException {
+ return request(path, null, Arrays.asList(headers), HttpVerb.DELETE).getJsonElement();
+ }
+
+ @NotNull
+ public Header[] headRequest(@NotNull String path,
+ @NotNull Header... headers) throws IOException {
+ return request(path, null, Arrays.asList(headers), HttpVerb.HEAD).getHeaders();
+ }
+
+ public void abort() {
+ if (myAborted) return;
+ myAborted = true;
+
+ HttpUriRequest request = myRequest;
+ if (request != null) request.abort();
+ }
+
+ public void close() throws IOException {
+ myClient.close();
+ }
+
+ @NotNull
+ private static CloseableHttpClient createClient(@NotNull GithubAuthData auth) {
+ HttpClientBuilder builder = HttpClients.custom();
+
+ return builder
+ .setDefaultRequestConfig(createRequestConfig(auth))
+ .setDefaultConnectionConfig(createConnectionConfig(auth))
+ .setDefaultCredentialsProvider(createCredentialsProvider(auth))
+ .setDefaultHeaders(createHeaders(auth))
+ .addInterceptorFirst(PREEMPTIVE_BASIC_AUTH)
+ .setSslcontext(CertificateManager.getInstance().getSslContext())
+ .setHostnameVerifier((X509HostnameVerifier)CertificateManager.HOSTNAME_VERIFIER)
+ .build();
+ }
+
+ @NotNull
+ private static RequestConfig createRequestConfig(@NotNull GithubAuthData auth) {
+ RequestConfig.Builder builder = RequestConfig.custom();
+
+ int timeout = GithubSettings.getInstance().getConnectionTimeout();
+ builder
+ .setConnectTimeout(timeout)
+ .setSocketTimeout(timeout);
+
+ final HttpConfigurable proxySettings = HttpConfigurable.getInstance();
+ if (auth.isUseProxy() && proxySettings.USE_HTTP_PROXY && !StringUtil.isEmptyOrSpaces(proxySettings.PROXY_HOST)) {
+ builder
+ .setProxy(new HttpHost(proxySettings.PROXY_HOST, proxySettings.PROXY_PORT));
+ }
+
+ return builder.build();
+ }
+
+ @NotNull
+ private static ConnectionConfig createConnectionConfig(@NotNull GithubAuthData auth) {
+ return ConnectionConfig.custom()
+ .setCharset(Consts.UTF_8)
+ .build();
+ }
+
+
+ @NotNull
+ private static CredentialsProvider createCredentialsProvider(@NotNull GithubAuthData auth) {
+ CredentialsProvider provider = new BasicCredentialsProvider();
+ // Basic authentication
+ GithubAuthData.BasicAuth basicAuth = auth.getBasicAuth();
+ if (basicAuth != null) {
+ provider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(basicAuth.getLogin(), basicAuth.getPassword()));
+ }
+
+ final HttpConfigurable proxySettings = HttpConfigurable.getInstance();
+ //proxySettings.USE_HTTP_PROXY
+ if (auth.isUseProxy() && proxySettings.USE_HTTP_PROXY && !StringUtil.isEmptyOrSpaces(proxySettings.PROXY_HOST)) {
+ if (proxySettings.PROXY_AUTHENTICATION) {
+ provider.setCredentials(new AuthScope(proxySettings.PROXY_HOST, proxySettings.PROXY_PORT),
+ new UsernamePasswordCredentials(proxySettings.PROXY_LOGIN, proxySettings.getPlainProxyPassword()));
+ }
+ }
+ return provider;
+ }
+
+ @NotNull
+ private static Collection<? extends Header> createHeaders(@NotNull GithubAuthData auth) {
+ List<Header> headers = new ArrayList<Header>();
+ GithubAuthData.TokenAuth tokenAuth = auth.getTokenAuth();
+ if (tokenAuth != null) {
+ headers.add(new BasicHeader("Authorization", "token " + tokenAuth.getToken()));
+ }
+ GithubAuthData.BasicAuth basicAuth = auth.getBasicAuth();
+ if (basicAuth != null && basicAuth.getCode() != null) {
+ headers.add(new BasicHeader("X-GitHub-OTP", basicAuth.getCode()));
+ }
+ return headers;
+ }
+
+ @NotNull
+ private ResponsePage request(@NotNull String path,
+ @Nullable String requestBody,
+ @NotNull Collection<Header> headers,
+ @NotNull HttpVerb verb) throws IOException {
+ if (myAborted) throw new GithubOperationCanceledException();
+
+ if (EventQueue.isDispatchThread() && !ApplicationManager.getApplication().isUnitTestMode()) {
+ LOG.warn("Network operation in EDT"); // TODO: fix
+ }
+
+ CloseableHttpResponse response = null;
+ try {
+ String uri = GithubUrlUtil.getApiUrl(myHost) + path;
+ response = doREST(uri, requestBody, headers, verb);
+
+ if (myAborted) throw new GithubOperationCanceledException();
+
+ checkStatusCode(response, requestBody);
+
+ HttpEntity entity = response.getEntity();
+ if (entity == null) {
+ return createResponse(response);
+ }
+
+ JsonElement ret = parseResponse(entity.getContent());
+ if (ret.isJsonNull()) {
+ return createResponse(response);
+ }
+
+ String newPath = null;
+ Header pageHeader = response.getFirstHeader("Link");
+ if (pageHeader != null) {
+ for (HeaderElement element : pageHeader.getElements()) {
+ NameValuePair rel = element.getParameterByName("rel");
+ if (rel != null && "next".equals(rel.getValue())) {
+ String urlString = element.toString();
+ int begin = urlString.indexOf('<');
+ int end = urlString.lastIndexOf('>');
+ if (begin == -1 || end == -1) {
+ LOG.error("Invalid 'Link' header", "{" + pageHeader.toString() + "}");
+ break;
+ }
+
+ String url = urlString.substring(begin + 1, end);
+ String newUrl = GithubUrlUtil.removeProtocolPrefix(url);
+ int index = newUrl.indexOf('/');
+ newPath = newUrl.substring(index);
+ break;
+ }
+ }
+ }
+
+ return createResponse(ret, newPath, response);
+ }
+ catch (SSLHandshakeException e) { // User canceled operation from CertificateManager
+ if (e.getCause() instanceof ValidatorException) {
+ LOG.info("Host SSL certificate is not trusted", e);
+ throw new GithubOperationCanceledException("Host SSL certificate is not trusted", e);
+ }
+ throw e;
+ }
+ catch (IOException e) {
+ if (myAborted) throw new GithubOperationCanceledException("Operation canceled", e);
+ throw e;
+ }
+ finally {
+ myRequest = null;
+ if (response != null) {
+ response.close();
+ }
+ if (!myReusable) {
+ myClient.close();
+ }
+ }
+ }
+
+ @NotNull
+ private CloseableHttpResponse doREST(@NotNull final String uri,
+ @Nullable final String requestBody,
+ @NotNull final Collection<Header> headers,
+ @NotNull final HttpVerb verb) throws IOException {
+ HttpRequestBase request;
+ switch (verb) {
+ case POST:
+ request = new HttpPost(uri);
+ if (requestBody != null) {
+ ((HttpPost)request).setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
+ }
+ break;
+ case PATCH:
+ request = new HttpPatch(uri);
+ if (requestBody != null) {
+ ((HttpPatch)request).setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
+ }
+ break;
+ case GET:
+ request = new HttpGet(uri);
+ break;
+ case DELETE:
+ request = new HttpDelete(uri);
+ break;
+ case HEAD:
+ request = new HttpHead(uri);
+ break;
+ default:
+ throw new IllegalStateException("Unknown HttpVerb: " + verb.toString());
+ }
+
+ for (Header header : headers) {
+ request.addHeader(header);
+ }
+
+ myRequest = request;
+ return myClient.execute(request);
+ }
+
+ private static void checkStatusCode(@NotNull CloseableHttpResponse response, @Nullable String body) throws IOException {
+ int code = response.getStatusLine().getStatusCode();
+ switch (code) {
+ case HttpStatus.SC_OK:
+ case HttpStatus.SC_CREATED:
+ case HttpStatus.SC_ACCEPTED:
+ case HttpStatus.SC_NO_CONTENT:
+ return;
+ case HttpStatus.SC_UNAUTHORIZED:
+ case HttpStatus.SC_PAYMENT_REQUIRED:
+ case HttpStatus.SC_FORBIDDEN:
+ String message = getErrorMessage(response);
+
+ Header headerOTP = response.getFirstHeader("X-GitHub-OTP");
+ if (headerOTP != null) {
+ for (HeaderElement element : headerOTP.getElements()) {
+ if ("required".equals(element.getName())) {
+ throw new GithubTwoFactorAuthenticationException(message);
+ }
+ }
+ }
+
+ if (message.contains("API rate limit exceeded")) {
+ throw new GithubRateLimitExceededException(message);
+ }
+
+ throw new GithubAuthenticationException("Request response: " + message);
+ case HttpStatus.SC_BAD_REQUEST:
+ case HttpStatus.SC_UNPROCESSABLE_ENTITY:
+ if (body != null) {
+ LOG.info(body);
+ }
+ throw new GithubStatusCodeException(code + ": " + getErrorMessage(response), code);
+ default:
+ throw new GithubStatusCodeException(code + ": " + getErrorMessage(response), code);
+ }
+ }
+
+ @NotNull
+ private static String getErrorMessage(@NotNull CloseableHttpResponse response) {
+ try {
+ HttpEntity entity = response.getEntity();
+ if (entity != null) {
+ GithubErrorMessageRaw error = fromJson(parseResponse(entity.getContent()), GithubErrorMessageRaw.class);
+ return response.getStatusLine().getReasonPhrase() + " - " + error.getMessage();
+ }
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ return response.getStatusLine().getReasonPhrase();
+ }
+
+ @NotNull
+ private static JsonElement parseResponse(@NotNull InputStream githubResponse) throws IOException {
+ Reader reader = new InputStreamReader(githubResponse, "UTF-8");
+ try {
+ return new JsonParser().parse(reader);
+ }
+ catch (JsonParseException jse) {
+ throw new GithubJsonException("Couldn't parse GitHub response", jse);
+ }
+ finally {
+ reader.close();
+ }
+ }
+
+ public static class PagedRequest<T> {
+ @Nullable private String myNextPage;
+ @NotNull private final Collection<Header> myHeaders;
+ @NotNull private final Class<T> myResult;
+ @NotNull private final Class<? extends DataConstructor[]> myRawArray;
+
+ @SuppressWarnings("NullableProblems")
+ public PagedRequest(@NotNull String path,
+ @NotNull Class<T> result,
+ @NotNull Class<? extends DataConstructor[]> rawArray,
+ @NotNull Header... headers) {
+ myNextPage = path;
+ myResult = result;
+ myRawArray = rawArray;
+ myHeaders = Arrays.asList(headers);
+ }
+
+ @NotNull
+ public List<T> next(@NotNull GithubConnection connection) throws IOException {
+ if (myNextPage == null) {
+ throw new NoSuchElementException();
+ }
+
+ String page = myNextPage;
+ myNextPage = null;
+
+ ResponsePage response = connection.request(page, null, myHeaders, HttpVerb.GET);
+
+ if (response.getJsonElement() == null) {
+ throw new GithubConfusingException("Empty response");
+ }
+
+ if (!response.getJsonElement().isJsonArray()) {
+ throw new GithubJsonException("Wrong json type: expected JsonArray", new Exception(response.getJsonElement().toString()));
+ }
+
+ myNextPage = response.getNextPage();
+
+ List<T> result = new ArrayList<T>();
+ for (DataConstructor raw : fromJson(response.getJsonElement().getAsJsonArray(), myRawArray)) {
+ result.add(createDataFromRaw(raw, myResult));
+ }
+ return result;
+ }
+
+ public boolean hasNext() {
+ return myNextPage != null;
+ }
+
+ @NotNull
+ public List<T> getAll(@NotNull GithubConnection connection) throws IOException {
+ List<T> result = new ArrayList<T>();
+ while (hasNext()) {
+ result.addAll(next(connection));
+ }
+ return result;
+ }
+ }
+
+ private ResponsePage createResponse(@NotNull CloseableHttpResponse response) throws GithubOperationCanceledException {
+ if (myAborted) throw new GithubOperationCanceledException();
+
+ return new ResponsePage(null, null, response.getAllHeaders());
+ }
+
+ private ResponsePage createResponse(@NotNull JsonElement ret, @Nullable String path, @NotNull CloseableHttpResponse response)
+ throws GithubOperationCanceledException {
+ if (myAborted) throw new GithubOperationCanceledException();
+
+ return new ResponsePage(ret, path, response.getAllHeaders());
+ }
+
+ private static class ResponsePage {
+ @Nullable private final JsonElement myResponse;
+ @Nullable private final String myNextPage;
+ @NotNull private final Header[] myHeaders;
+
+ public ResponsePage(@Nullable JsonElement response, @Nullable String next, @NotNull Header[] headers) {
+ myResponse = response;
+ myNextPage = next;
+ myHeaders = headers;
+ }
+
+ @Nullable
+ public JsonElement getJsonElement() {
+ return myResponse;
+ }
+
+ @Nullable
+ public String getNextPage() {
+ return myNextPage;
+ }
+
+ @NotNull
+ public Header[] getHeaders() {
+ return myHeaders;
+ }
+ }
+
+ private static class PreemptiveBasicAuthInterceptor implements HttpRequestInterceptor {
+ @Override
+ public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
+ CredentialsProvider provider = (CredentialsProvider)context.getAttribute(HttpClientContext.CREDS_PROVIDER);
+ Credentials credentials = provider.getCredentials(AuthScope.ANY);
+ if (credentials != null) {
+ request.addHeader(new BasicScheme(Consts.UTF_8).authenticate(credentials, request, context));
+ }
+ }
+ }
+}
diff --git a/plugins/github/src/org/jetbrains/plugins/github/api/GithubFullPath.java b/plugins/github/src/org/jetbrains/plugins/github/api/GithubFullPath.java
index ee0134e4fbc3..1575caee25db 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/api/GithubFullPath.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/api/GithubFullPath.java
@@ -47,14 +47,19 @@ public class GithubFullPath {
}
@Override
+ public String toString() {
+ return "'" + getFullName() + "'";
+ }
+
+ @Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GithubFullPath that = (GithubFullPath)o;
- if (!StringUtil.endsWithIgnoreCase(myRepositoryName, that.myRepositoryName)) return false;
- if (!StringUtil.endsWithIgnoreCase(myUserName, that.myUserName)) return false;
+ if (!StringUtil.equalsIgnoreCase(myRepositoryName, that.myRepositoryName)) return false;
+ if (!StringUtil.equalsIgnoreCase(myUserName, that.myUserName)) return false;
return true;
}
diff --git a/plugins/github/src/org/jetbrains/plugins/github/api/GithubUser.java b/plugins/github/src/org/jetbrains/plugins/github/api/GithubUser.java
index 29ef1acdfd78..bf81b8d62502 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/api/GithubUser.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/api/GithubUser.java
@@ -26,12 +26,12 @@ public class GithubUser {
@NotNull private final String myHtmlUrl;
- @Nullable private final String myGravatarId;
+ @Nullable private final String myAvatarUrl;
- public GithubUser(@NotNull String login, @NotNull String htmlUrl, @Nullable String gravatarId) {
+ public GithubUser(@NotNull String login, @NotNull String htmlUrl, @Nullable String avatarUrl) {
myLogin = login;
myHtmlUrl = htmlUrl;
- myGravatarId = gravatarId;
+ myAvatarUrl = avatarUrl;
}
@NotNull
@@ -45,7 +45,7 @@ public class GithubUser {
}
@Nullable
- public String getGravatarId() {
- return myGravatarId;
+ public String getAvatarUrl() {
+ return myAvatarUrl;
}
}
diff --git a/plugins/github/src/org/jetbrains/plugins/github/api/GithubUserDetailed.java b/plugins/github/src/org/jetbrains/plugins/github/api/GithubUserDetailed.java
index 2c82c078e339..3940b80f360a 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/api/GithubUserDetailed.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/api/GithubUserDetailed.java
@@ -55,13 +55,13 @@ public class GithubUserDetailed extends GithubUser {
public GithubUserDetailed(@NotNull String login,
@NotNull String htmlUrl,
- @Nullable String gravatarId,
+ @Nullable String avatarUrl,
@Nullable String name,
@Nullable String email,
@Nullable Integer ownedPrivateRepos,
@Nullable String type,
@Nullable UserPlan plan) {
- super(login, htmlUrl, gravatarId);
+ super(login, htmlUrl, avatarUrl);
myName = name;
myEmail = email;
myOwnedPrivateRepos = ownedPrivateRepos;
diff --git a/plugins/github/src/org/jetbrains/plugins/github/api/GithubUserRaw.java b/plugins/github/src/org/jetbrains/plugins/github/api/GithubUserRaw.java
index 04d642f8eb2e..0bc673299847 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/api/GithubUserRaw.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/api/GithubUserRaw.java
@@ -74,14 +74,14 @@ class GithubUserRaw implements DataConstructor {
@SuppressWarnings("ConstantConditions")
@NotNull
public GithubUser createUser() {
- return new GithubUser(login, htmlUrl, gravatarId);
+ return new GithubUser(login, htmlUrl, avatarUrl);
}
@SuppressWarnings("ConstantConditions")
@NotNull
public GithubUserDetailed createUserDetailed() {
GithubUserDetailed.UserPlan plan = this.plan == null ? null : this.plan.create();
- return new GithubUserDetailed(login, htmlUrl, gravatarId, name, email, ownedPrivateRepos, type, plan);
+ return new GithubUserDetailed(login, htmlUrl, avatarUrl, name, email, ownedPrivateRepos, type, plan);
}
@SuppressWarnings("unchecked")
diff --git a/plugins/github/src/org/jetbrains/plugins/github/extensions/GithubCheckoutProvider.java b/plugins/github/src/org/jetbrains/plugins/github/extensions/GithubCheckoutProvider.java
index 6b3f73f5f8be..911314c4f72d 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/extensions/GithubCheckoutProvider.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/extensions/GithubCheckoutProvider.java
@@ -29,6 +29,7 @@ import git4idea.commands.Git;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.github.api.GithubApiUtil;
+import org.jetbrains.plugins.github.api.GithubConnection;
import org.jetbrains.plugins.github.api.GithubRepo;
import org.jetbrains.plugins.github.exceptions.GithubOperationCanceledException;
import org.jetbrains.plugins.github.util.GithubAuthData;
@@ -62,11 +63,11 @@ public class GithubCheckoutProvider implements CheckoutProvider {
@Override
public List<GithubRepo> convert(ProgressIndicator indicator) throws IOException {
return GithubUtil.runTask(project, GithubAuthDataHolder.createFromSettings(), indicator,
- new ThrowableConvertor<GithubAuthData, List<GithubRepo>, IOException>() {
+ new ThrowableConvertor<GithubConnection, List<GithubRepo>, IOException>() {
@NotNull
@Override
- public List<GithubRepo> convert(@NotNull GithubAuthData auth) throws IOException {
- return GithubApiUtil.getAvailableRepos(auth);
+ public List<GithubRepo> convert(@NotNull GithubConnection connection) throws IOException {
+ return GithubApiUtil.getAvailableRepos(connection);
}
}
);
diff --git a/plugins/github/src/org/jetbrains/plugins/github/tasks/GithubComment.java b/plugins/github/src/org/jetbrains/plugins/github/tasks/GithubComment.java
index d65a95044a9c..e3115f71975c 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/tasks/GithubComment.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/tasks/GithubComment.java
@@ -26,16 +26,16 @@ import java.util.Date;
* @author Dennis.Ushakov
*/
public class GithubComment extends SimpleComment {
- @Nullable private final String myGravatarId;
+ @Nullable private final String myAvatarUrl;
@NotNull private final String myUserHtmlUrl;
public GithubComment(@Nullable Date date,
@Nullable String author,
@NotNull String text,
- @Nullable String gravatarId,
+ @Nullable String avatarUrl,
@NotNull String userHtmlUrl) {
super(date, author, text);
- myGravatarId = gravatarId;
+ myAvatarUrl = avatarUrl;
myUserHtmlUrl = userHtmlUrl;
}
@@ -43,9 +43,9 @@ public class GithubComment extends SimpleComment {
builder.append("<hr>");
builder.append("<table>");
builder.append("<tr><td>");
- if (myGravatarId != null) {
- builder.append("<img src=\"").append("http://www.gravatar.com/avatar/").append(myGravatarId).append("?s=40\"/><br>");
- }
+ if (myAvatarUrl != null) {
+ builder.append("<img src=\"").append(myAvatarUrl).append("\" height=\"40\" width=\"40\"/><br>");
+ }
builder.append("</td><td>");
if (getAuthor() != null) {
builder.append("<b>Author:</b> <a href=\"").append(myUserHtmlUrl).append("\">").append(getAuthor()).append("</a><br>");
diff --git a/plugins/github/src/org/jetbrains/plugins/github/tasks/GithubRepository.java b/plugins/github/src/org/jetbrains/plugins/github/tasks/GithubRepository.java
index 7ad3238f8a12..a8445b7f76ac 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/tasks/GithubRepository.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/tasks/GithubRepository.java
@@ -5,10 +5,7 @@ import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.PasswordUtil;
import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.tasks.Comment;
-import com.intellij.tasks.Task;
-import com.intellij.tasks.TaskRepository;
-import com.intellij.tasks.TaskType;
+import com.intellij.tasks.*;
import com.intellij.tasks.impl.BaseRepository;
import com.intellij.tasks.impl.BaseRepositoryImpl;
import com.intellij.util.Function;
@@ -19,12 +16,10 @@ import icons.TasksIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.github.api.GithubApiUtil;
+import org.jetbrains.plugins.github.api.GithubConnection;
import org.jetbrains.plugins.github.api.GithubIssue;
import org.jetbrains.plugins.github.api.GithubIssueComment;
-import org.jetbrains.plugins.github.exceptions.GithubAuthenticationException;
-import org.jetbrains.plugins.github.exceptions.GithubJsonException;
-import org.jetbrains.plugins.github.exceptions.GithubRateLimitExceededException;
-import org.jetbrains.plugins.github.exceptions.GithubStatusCodeException;
+import org.jetbrains.plugins.github.exceptions.*;
import org.jetbrains.plugins.github.util.GithubAuthData;
import org.jetbrains.plugins.github.util.GithubUtil;
@@ -67,14 +62,20 @@ public class GithubRepository extends BaseRepositoryImpl {
@Override
public CancellableConnection createCancellableConnection() {
return new CancellableConnection() {
+ private final GithubConnection myConnection = new GithubConnection(getAuthData(), false);
+
@Override
protected void doTest() throws Exception {
- getIssues("", 10, false);
+ try {
+ GithubApiUtil.getIssuesQueried(myConnection, getRepoAuthor(), getRepoName(), "", false);
+ }
+ catch (GithubOperationCanceledException ignore) {
+ }
}
@Override
public void cancel() {
- // TODO
+ myConnection.abort();
}
};
}
@@ -122,23 +123,30 @@ public class GithubRepository extends BaseRepositoryImpl {
@NotNull
private Task[] getIssues(@Nullable String query, int max, boolean withClosed) throws Exception {
- List<GithubIssue> issues;
- if (StringUtil.isEmptyOrSpaces(query)) {
- if (StringUtil.isEmptyOrSpaces(myUser)) {
- myUser = GithubApiUtil.getCurrentUser(getAuthData()).getLogin();
+ GithubConnection connection = getConnection();
+
+ try {
+ List<GithubIssue> issues;
+ if (StringUtil.isEmptyOrSpaces(query)) {
+ if (StringUtil.isEmptyOrSpaces(myUser)) {
+ myUser = GithubApiUtil.getCurrentUser(connection).getLogin();
+ }
+ issues = GithubApiUtil.getIssuesAssigned(connection, getRepoAuthor(), getRepoName(), myUser, max, withClosed);
+ }
+ else {
+ issues = GithubApiUtil.getIssuesQueried(connection, getRepoAuthor(), getRepoName(), query, withClosed);
}
- issues = GithubApiUtil.getIssuesAssigned(getAuthData(), getRepoAuthor(), getRepoName(), myUser, max, withClosed);
+
+ return ContainerUtil.map2Array(issues, Task.class, new Function<GithubIssue, Task>() {
+ @Override
+ public Task fun(GithubIssue issue) {
+ return createTask(issue);
+ }
+ });
}
- else {
- issues = GithubApiUtil.getIssuesQueried(getAuthData(), getRepoAuthor(), getRepoName(), query, withClosed);
+ finally {
+ connection.close();
}
-
- return ContainerUtil.map2Array(issues, Task.class, new Function<GithubIssue, Task>() {
- @Override
- public Task fun(GithubIssue issue) {
- return createTask(issue);
- }
- });
}
@NotNull
@@ -224,16 +232,22 @@ public class GithubRepository extends BaseRepositoryImpl {
}
private Comment[] fetchComments(final long id) throws Exception {
- List<GithubIssueComment> result = GithubApiUtil.getIssueComments(getAuthData(), getRepoAuthor(), getRepoName(), id);
-
- return ContainerUtil.map2Array(result, Comment.class, new Function<GithubIssueComment, Comment>() {
- @Override
- public Comment fun(GithubIssueComment comment) {
- return new GithubComment(comment.getCreatedAt(), comment.getUser().getLogin(), comment.getBodyHtml(),
- comment.getUser().getGravatarId(),
- comment.getUser().getHtmlUrl());
- }
- });
+ GithubConnection connection = getConnection();
+ try {
+ List<GithubIssueComment> result = GithubApiUtil.getIssueComments(connection, getRepoAuthor(), getRepoName(), id);
+
+ return ContainerUtil.map2Array(result, Comment.class, new Function<GithubIssueComment, Comment>() {
+ @Override
+ public Comment fun(GithubIssueComment comment) {
+ return new GithubComment(comment.getCreatedAt(), comment.getUser().getLogin(), comment.getBodyHtml(),
+ comment.getUser().getAvatarUrl(),
+ comment.getUser().getHtmlUrl());
+ }
+ });
+ }
+ finally {
+ connection.close();
+ }
}
@Nullable
@@ -245,7 +259,35 @@ public class GithubRepository extends BaseRepositoryImpl {
@Nullable
@Override
public Task findTask(@NotNull String id) throws Exception {
- return createTask(GithubApiUtil.getIssue(getAuthData(), getRepoAuthor(), getRepoName(), id));
+ GithubConnection connection = getConnection();
+ try {
+ return createTask(GithubApiUtil.getIssue(connection, getRepoAuthor(), getRepoName(), id));
+ }
+ finally {
+ connection.close();
+ }
+ }
+
+ @Override
+ public void setTaskState(@NotNull Task task, @NotNull TaskState state) throws Exception {
+ GithubConnection connection = getConnection();
+ try {
+ boolean isOpen;
+ switch (state) {
+ case OPEN:
+ isOpen = true;
+ break;
+ case RESOLVED:
+ isOpen = false;
+ break;
+ default:
+ throw new IllegalStateException("Unknown state: " + state);
+ }
+ GithubApiUtil.setIssueState(connection, getRepoAuthor(), getRepoName(), task.getNumber(), isOpen);
+ }
+ finally {
+ connection.close();
+ }
}
@NotNull
@@ -311,6 +353,10 @@ public class GithubRepository extends BaseRepositoryImpl {
return GithubAuthData.createTokenAuth(getUrl(), getToken(), isUseProxy());
}
+ private GithubConnection getConnection() {
+ return new GithubConnection(getAuthData(), true);
+ }
+
@Override
public boolean equals(Object o) {
if (!super.equals(o)) return false;
@@ -326,6 +372,6 @@ public class GithubRepository extends BaseRepositoryImpl {
@Override
protected int getFeatures() {
- return super.getFeatures() | BASIC_HTTP_AUTHORIZATION;
+ return super.getFeatures() | STATE_UPDATING;
}
}
diff --git a/plugins/github/src/org/jetbrains/plugins/github/tasks/GithubRepositoryEditor.java b/plugins/github/src/org/jetbrains/plugins/github/tasks/GithubRepositoryEditor.java
index 5333d82e3baf..ba863621c969 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/tasks/GithubRepositoryEditor.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/tasks/GithubRepositoryEditor.java
@@ -14,8 +14,7 @@ import com.intellij.util.ui.GridBag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.github.api.GithubApiUtil;
-import org.jetbrains.plugins.github.exceptions.GithubOperationCanceledException;
-import org.jetbrains.plugins.github.util.GithubAuthData;
+import org.jetbrains.plugins.github.api.GithubConnection;
import org.jetbrains.plugins.github.util.GithubAuthDataHolder;
import org.jetbrains.plugins.github.util.GithubNotifications;
import org.jetbrains.plugins.github.util.GithubUtil;
@@ -126,12 +125,12 @@ public class GithubRepositoryEditor extends BaseRepositoryEditor<GithubRepositor
public String convert(ProgressIndicator indicator) throws IOException {
return GithubUtil
.runTaskWithBasicAuthForHost(myProject, GithubAuthDataHolder.createFromSettings(), indicator, getHost(),
- new ThrowableConvertor<GithubAuthData, String, IOException>() {
+ new ThrowableConvertor<GithubConnection, String, IOException>() {
@NotNull
@Override
- public String convert(@NotNull GithubAuthData auth) throws IOException {
+ public String convert(@NotNull GithubConnection connection) throws IOException {
return GithubApiUtil
- .getReadOnlyToken(auth, getRepoAuthor(), getRepoName(), "IntelliJ tasks plugin");
+ .getReadOnlyToken(connection, getRepoAuthor(), getRepoName(), "IntelliJ tasks plugin");
}
}
);
diff --git a/plugins/github/src/org/jetbrains/plugins/github/tasks/GithubRepositoryType.java b/plugins/github/src/org/jetbrains/plugins/github/tasks/GithubRepositoryType.java
index a677fad0d781..eefab9065add 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/tasks/GithubRepositoryType.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/tasks/GithubRepositoryType.java
@@ -2,6 +2,7 @@ package org.jetbrains.plugins.github.tasks;
import com.intellij.openapi.project.Project;
import com.intellij.tasks.TaskRepository;
+import com.intellij.tasks.TaskState;
import com.intellij.tasks.config.TaskRepositoryEditor;
import com.intellij.tasks.impl.BaseRepositoryType;
import com.intellij.util.Consumer;
@@ -9,6 +10,7 @@ import icons.TasksIcons;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
+import java.util.EnumSet;
/**
* @author Dennis.Ushakov
@@ -45,4 +47,9 @@ public class GithubRepositoryType extends BaseRepositoryType<GithubRepository> {
Consumer<GithubRepository> changeListener) {
return new GithubRepositoryEditor(project, repository, changeListener);
}
+
+ public EnumSet<TaskState> getPossibleTaskStates() {
+ return EnumSet.of(TaskState.OPEN, TaskState.RESOLVED);
+ }
+
}
diff --git a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestDialog.java b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestDialog.java
index f062742d191c..482ef9492fdc 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestDialog.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestDialog.java
@@ -15,142 +15,159 @@
*/
package org.jetbrains.plugins.github.ui;
+import com.intellij.CommonBundle;
+import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.ValidationInfo;
+import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.util.Consumer;
-import com.intellij.util.ui.UIUtil;
+import com.intellij.util.ThreeState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.jetbrains.plugins.github.GithubCreatePullRequestWorker;
import org.jetbrains.plugins.github.api.GithubFullPath;
+import org.jetbrains.plugins.github.util.GithubNotifications;
import org.jetbrains.plugins.github.util.GithubProjectSettings;
+import org.jetbrains.plugins.github.util.GithubSettings;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
-import java.util.Collection;
import java.util.Collections;
+import static org.jetbrains.plugins.github.GithubCreatePullRequestWorker.BranchInfo;
+import static org.jetbrains.plugins.github.GithubCreatePullRequestWorker.ForkInfo;
+
/**
* @author Aleksey Pivovarov
*/
public class GithubCreatePullRequestDialog extends DialogWrapper {
- @NotNull private final GithubCreatePullRequestPanel myGithubCreatePullRequestPanel;
+ @NotNull private final GithubCreatePullRequestPanel myPanel;
@NotNull private final GithubCreatePullRequestWorker myWorker;
@NotNull private final GithubProjectSettings myProjectSettings;
+ @NotNull private static final CreateRemoteDoNotAskOption ourDoNotAskOption = new CreateRemoteDoNotAskOption();
- public GithubCreatePullRequestDialog(@NotNull GithubCreatePullRequestWorker worker) {
- super(worker.getProject(), true);
+ public GithubCreatePullRequestDialog(@NotNull final Project project, @NotNull GithubCreatePullRequestWorker worker) {
+ super(project, true);
myWorker = worker;
- myProjectSettings = GithubProjectSettings.getInstance(myWorker.getProject());
-
- myGithubCreatePullRequestPanel = new GithubCreatePullRequestPanel();
+ myProjectSettings = GithubProjectSettings.getInstance(project);
- myGithubCreatePullRequestPanel.getShowDiffButton().addActionListener(new ActionListener() {
+ myPanel = new GithubCreatePullRequestPanel();
+ myPanel.getShowDiffButton().addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
- myWorker.showDiffDialog(myGithubCreatePullRequestPanel.getBranch());
+ myWorker.showDiffDialog(myPanel.getSelectedBranch());
}
});
- myGithubCreatePullRequestPanel.getSelectForkButton().addActionListener(new ActionListener() {
+ myPanel.getSelectForkButton().addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
- showTargetDialog();
+ ForkInfo fork = myWorker.showTargetDialog();
+ if (fork != null) {
+ myPanel.setForks(myWorker.getForks());
+ myPanel.setSelectedFork(fork.getPath());
+ }
}
});
- myGithubCreatePullRequestPanel.getBranchComboBox().addItemListener(new ItemListener() {
+
+ myPanel.getForkComboBox().addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
+ if (e.getStateChange() == ItemEvent.DESELECTED) {
+ myPanel.setBranches(Collections.<BranchInfo>emptyList());
+ }
if (e.getStateChange() == ItemEvent.SELECTED) {
- if (myWorker.canShowDiff()) {
- myGithubCreatePullRequestPanel.setBusy(true);
- myWorker.getDiffDescriptionInPooledThread(getTargetBranch(), new Consumer<GithubCreatePullRequestWorker.DiffDescription>() {
- @Override
- public void consume(final GithubCreatePullRequestWorker.DiffDescription info) {
- UIUtil.invokeLaterIfNeeded(new Runnable() {
- @Override
- public void run() {
- if (info == null) {
- myGithubCreatePullRequestPanel.setBusy(false);
- return;
- }
- if (getTargetBranch().equals(info.getBranch())) {
- myGithubCreatePullRequestPanel.setBusy(false);
- if (myGithubCreatePullRequestPanel.isTitleDescriptionEmptyOrNotModified()) {
- myGithubCreatePullRequestPanel.setTitle(info.getTitle());
- myGithubCreatePullRequestPanel.setDescription(info.getDescription());
- }
- }
- }
- });
- }
- });
+ final ForkInfo fork = (ForkInfo)e.getItem();
+ if (fork == null) return;
+
+ myPanel.setBranches(fork.getBranches());
+ myPanel.setSelectedBranch(fork.getDefaultBranch());
+
+ if (fork.getRemoteName() == null && !fork.isProposedToCreateRemote()) {
+ fork.setProposedToCreateRemote(true);
+ boolean createRemote = false;
+
+ switch (GithubSettings.getInstance().getCreatePullRequestCreateRemote()) {
+ case YES:
+ createRemote = true;
+ break;
+ case NO:
+ createRemote = false;
+ break;
+ case UNSURE:
+ createRemote = GithubNotifications.showYesNoDialog(project,
+ "Can't Find Remote",
+ "Configure remote for '" + fork.getPath().getUser() + "'?",
+ ourDoNotAskOption) == Messages.YES;
+ break;
+ }
+
+ if (createRemote) {
+ myWorker.configureRemote(fork);
+ }
+ }
+
+ if (fork.getRemoteName() == null) {
+ myPanel.setDiffEnabled(false);
+ }
+ else {
+ myPanel.setDiffEnabled(true);
+ myWorker.launchFetchRemote(fork);
}
}
}
});
- setTitle("Create Pull Request - " + myWorker.getCurrentBranch());
- init();
- }
-
- @Override
- public void show() {
- GithubFullPath defaultForkPath = myProjectSettings.getCreatePullRequestDefaultRepo();
- if (defaultForkPath != null) {
- setTarget(defaultForkPath);
- }
- else {
- if (!showTargetDialog(true)) {
- close(CANCEL_EXIT_CODE);
- return;
- }
- }
- super.show();
- }
-
- private boolean showTargetDialog() {
- return showTargetDialog(false);
- }
+ myPanel.getBranchComboBox().addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ if (e.getStateChange() == ItemEvent.SELECTED) {
+ BranchInfo branch = (BranchInfo)e.getItem();
+ if (branch == null) return;
- private boolean showTargetDialog(boolean firstTime) {
- GithubFullPath forkPath = myWorker.showTargetDialog(firstTime);
- if (forkPath == null) {
- return false;
- }
- return setTarget(forkPath);
- }
+ if (branch.getForkInfo().getRemoteName() != null) {
+ if (branch.getDiffInfoTask() != null && branch.getDiffInfoTask().isDone() && branch.getDiffInfoTask().safeGet() == null) {
+ myPanel.setDiffEnabled(false);
+ }
+ else {
+ myPanel.setDiffEnabled(true);
+ }
+ }
- private boolean setTarget(@NotNull GithubFullPath forkPath) {
- GithubCreatePullRequestWorker.GithubTargetInfo forkInfo = myWorker.setTarget(forkPath);
- if (forkInfo == null) {
- return false;
- }
- myProjectSettings.setCreatePullRequestDefaultRepo(forkPath);
- myGithubCreatePullRequestPanel.setDiffEnabled(myWorker.canShowDiff());
- updateBranches(forkInfo.getBranches(), forkPath);
- return true;
- }
+ if (myPanel.isTitleDescriptionEmptyOrNotModified()) {
+ Pair<String, String> description = myWorker.getDefaultDescriptionMessage(branch);
+ myPanel.setTitle(description.getFirst());
+ myPanel.setDescription(description.getSecond());
+ }
- private void updateBranches(@NotNull Collection<String> branches, @NotNull GithubFullPath forkPath) {
- myGithubCreatePullRequestPanel.setBranches(branches);
+ myWorker.launchLoadDiffInfo(branch);
+ }
+ }
+ });
- String configBranch = myProjectSettings.getCreatePullRequestDefaultBranch();
- if (configBranch != null) myGithubCreatePullRequestPanel.setSelectedBranch(configBranch);
+ myPanel.setForks(myWorker.getForks());
+ myPanel.setSelectedFork(myProjectSettings.getCreatePullRequestDefaultRepo());
+ myPanel.setSelectedBranch(myProjectSettings.getCreatePullRequestDefaultBranch());
- myGithubCreatePullRequestPanel.setForkName(forkPath.getFullName());
+ setTitle("Create Pull Request - " + myWorker.getCurrentBranch());
+ init();
}
@Override
protected void doOKAction() {
- if (myWorker.checkAction(getTargetBranch())) {
- myProjectSettings.setCreatePullRequestDefaultBranch(getTargetBranch());
- myWorker.performAction(getRequestTitle(), getDescription(), getTargetBranch());
+ BranchInfo branch = myPanel.getSelectedBranch();
+ if (myWorker.checkAction(branch)) {
+ assert branch != null;
+ myWorker.createPullRequest(branch, getRequestTitle(), getDescription());
+
+ myProjectSettings.setCreatePullRequestDefaultBranch(branch.getRemoteName());
+ myProjectSettings.setCreatePullRequestDefaultRepo(branch.getForkInfo().getPath());
+
super.doOKAction();
}
}
@@ -158,13 +175,13 @@ public class GithubCreatePullRequestDialog extends DialogWrapper {
@Nullable
@Override
protected JComponent createCenterPanel() {
- return myGithubCreatePullRequestPanel.getPanel();
+ return myPanel.getPanel();
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
- return myGithubCreatePullRequestPanel.getPreferredComponent();
+ return myPanel.getPreferredComponent();
}
@Override
@@ -179,50 +196,76 @@ public class GithubCreatePullRequestDialog extends DialogWrapper {
@NotNull
private String getRequestTitle() {
- return myGithubCreatePullRequestPanel.getTitle();
+ return myPanel.getTitle();
}
@NotNull
private String getDescription() {
- return myGithubCreatePullRequestPanel.getDescription();
- }
-
- @NotNull
- private String getTargetBranch() {
- return myGithubCreatePullRequestPanel.getBranch();
+ return myPanel.getDescription();
}
@Nullable
@Override
protected ValidationInfo doValidate() {
if (StringUtil.isEmptyOrSpaces(getRequestTitle())) {
- return new ValidationInfo("Title can't be empty'", myGithubCreatePullRequestPanel.getTitleTextField());
+ return new ValidationInfo("Title can't be empty'", myPanel.getTitleTextField());
}
return null;
}
@TestOnly
public void testSetRequestTitle(String title) {
- myGithubCreatePullRequestPanel.setTitle(title);
+ myPanel.setTitle(title);
}
@TestOnly
public void testSetBranch(String branch) {
- myGithubCreatePullRequestPanel.setBranches(Collections.singleton(branch));
+ myPanel.setSelectedBranch(branch);
}
@TestOnly
public void testCreatePullRequest() {
- myWorker.performAction(getRequestTitle(), getDescription(), getTargetBranch());
+ myWorker.createPullRequest(myPanel.getSelectedBranch(), getRequestTitle(), getDescription());
}
@TestOnly
- public void testSetTarget(@NotNull GithubFullPath forkPath) {
- GithubCreatePullRequestWorker.GithubTargetInfo forkInfo = myWorker.setTarget(forkPath);
- if (forkInfo == null) {
- doCancelAction();
- return;
+ public void testSetFork(@NotNull GithubFullPath forkPath) {
+ myPanel.setSelectedFork(forkPath);
+ }
+
+ private static class CreateRemoteDoNotAskOption implements DoNotAskOption {
+ @Override
+ public boolean isToBeShown() {
+ return true;
+ }
+
+ @Override
+ public void setToBeShown(boolean value, int exitCode) {
+ if (value) {
+ GithubSettings.getInstance().setCreatePullRequestCreateRemote(ThreeState.UNSURE);
+ }
+ else if (exitCode == DialogWrapper.OK_EXIT_CODE) {
+ GithubSettings.getInstance().setCreatePullRequestCreateRemote(ThreeState.YES);
+ }
+ else {
+ GithubSettings.getInstance().setCreatePullRequestCreateRemote(ThreeState.NO);
+ }
+ }
+
+ @Override
+ public boolean canBeHidden() {
+ return true;
+ }
+
+ @Override
+ public boolean shouldSaveOptionsOnCancel() {
+ return false;
+ }
+
+ @NotNull
+ @Override
+ public String getDoNotShowMessage() {
+ return CommonBundle.message("dialog.options.do.not.ask");
}
- myGithubCreatePullRequestPanel.setDiffEnabled(myWorker.canShowDiff());
}
}
diff --git a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestPanel.form b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestPanel.form
index e19b8601fa01..342fcfaecde7 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestPanel.form
+++ b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestPanel.form
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.plugins.github.ui.GithubCreatePullRequestPanel">
- <grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="5" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="5" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="500" height="400"/>
@@ -34,7 +34,7 @@
</component>
<component id="996e9" class="javax.swing.JTextField" binding="myTitleTextField">
<constraints>
- <grid row="2" column="1" row-span="1" col-span="3" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <grid row="2" column="1" row-span="1" col-span="2" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
@@ -42,7 +42,7 @@
</component>
<scrollpane id="61e54" class="com.intellij.ui.components.JBScrollPane">
<constraints>
- <grid row="4" column="0" row-span="1" col-span="4" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false">
+ <grid row="4" column="0" row-span="1" col-span="3" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false">
<minimum-size width="150" height="50"/>
</grid>
</constraints>
@@ -57,7 +57,7 @@
</scrollpane>
<component id="90c93" class="javax.swing.JButton" binding="myShowDiffButton">
<constraints>
- <grid row="1" column="3" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ <grid row="1" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Show Diff"/>
@@ -73,26 +73,12 @@
</component>
<component id="a5106" class="javax.swing.JButton" binding="mySelectForkButton">
<constraints>
- <grid row="0" column="3" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ <grid row="0" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
- <text value="Select Base Fork"/>
+ <text value="Select Other Fork"/>
</properties>
</component>
- <component id="e5548" class="javax.swing.JLabel" binding="myForkLabel">
- <constraints>
- <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
- </constraints>
- <properties>
- <text value="repo"/>
- </properties>
- </component>
- <component id="e5988" class="com.intellij.util.ui.AsyncProcessIcon" binding="myBusyIcon" custom-create="true">
- <constraints>
- <grid row="1" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
- </constraints>
- <properties/>
- </component>
<component id="78a64" class="com.intellij.openapi.ui.ComboBox" binding="myBranchComboBox">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
@@ -102,6 +88,12 @@
<toolTipText value=""/>
</properties>
</component>
+ <component id="952ec" class="com.intellij.openapi.ui.ComboBox" binding="myForkComboBox">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
</children>
</grid>
</form>
diff --git a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestPanel.java b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestPanel.java
index 41512465eeea..f17562f12600 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestPanel.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestPanel.java
@@ -19,9 +19,9 @@ import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.SortedComboBoxModel;
-import com.intellij.util.ui.AsyncProcessIcon;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.github.api.GithubFullPath;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
@@ -29,6 +29,9 @@ import javax.swing.event.DocumentListener;
import java.util.Collection;
import java.util.Comparator;
+import static org.jetbrains.plugins.github.GithubCreatePullRequestWorker.BranchInfo;
+import static org.jetbrains.plugins.github.GithubCreatePullRequestWorker.ForkInfo;
+
/**
* @author Aleksey Pivovarov
*/
@@ -36,25 +39,35 @@ public class GithubCreatePullRequestPanel {
private JTextField myTitleTextField;
private JTextArea myDescriptionTextArea;
private ComboBox myBranchComboBox;
- private SortedComboBoxModel<String> myBranchModel;
+ private SortedComboBoxModel<ForkInfo> myForkModel;
+ private SortedComboBoxModel<BranchInfo> myBranchModel;
private JPanel myPanel;
private JButton myShowDiffButton;
private JButton mySelectForkButton;
private JLabel myForkLabel;
- private AsyncProcessIcon myBusyIcon;
+ private ComboBox myForkComboBox;
private boolean myTitleDescriptionUserModified = false;
public GithubCreatePullRequestPanel() {
myDescriptionTextArea.setBorder(BorderFactory.createEtchedBorder());
- myBranchModel = new SortedComboBoxModel<String>(new Comparator<String>() {
+
+ myBranchModel = new SortedComboBoxModel<BranchInfo>(new Comparator<BranchInfo>() {
@Override
- public int compare(String o1, String o2) {
- return StringUtil.naturalCompare(o1, o2);
+ public int compare(BranchInfo o1, BranchInfo o2) {
+ return StringUtil.naturalCompare(o1.getRemoteName(), o2.getRemoteName());
}
});
myBranchComboBox.setModel(myBranchModel);
+ myForkModel = new SortedComboBoxModel<ForkInfo>(new Comparator<ForkInfo>() {
+ @Override
+ public int compare(ForkInfo o1, ForkInfo o2) {
+ return StringUtil.naturalCompare(o1.getPath().getUser(), o2.getPath().getUser());
+ }
+ });
+ myForkComboBox.setModel(myForkModel);
+
DocumentListener userModifiedDocumentListener = new DocumentAdapter() {
@Override
protected void textChanged(DocumentEvent e) {
@@ -75,43 +88,69 @@ public class GithubCreatePullRequestPanel {
return myDescriptionTextArea.getText();
}
- @NotNull
- public String getBranch() {
- return myBranchComboBox.getSelectedItem().toString();
+ @Nullable
+ public ForkInfo getSelectedFork() {
+ return myForkModel.getSelectedItem();
}
- public void setDiffEnabled(boolean enabled) {
- myShowDiffButton.setEnabled(enabled);
+ @Nullable
+ public BranchInfo getSelectedBranch() {
+ return myBranchModel.getSelectedItem();
}
- public void setSelectedBranch(@Nullable String branch) {
- if (StringUtil.isEmptyOrSpaces(branch)) {
- return;
+ public void setSelectedFork(@Nullable GithubFullPath path) {
+ if (path != null) {
+ for (ForkInfo info : myForkModel.getItems()) {
+ if (path.equals(info.getPath())) {
+ myForkModel.setSelectedItem(info);
+ return;
+ }
+ }
}
- myBranchComboBox.setSelectedItem(branch);
+ if (myForkModel.getSize() > 0) myForkModel.setSelectedItem(myForkModel.get(0));
}
- public void setBranches(@NotNull Collection<String> branches) {
- myBranchModel.clear();
- myBranchModel.addAll(branches);
- if (branches.size() > 0) {
- myBranchComboBox.setSelectedIndex(0);
+ public void setSelectedBranch(@Nullable String branch) {
+ if (branch != null) {
+ for (BranchInfo info : myBranchModel.getItems()) {
+ if (branch.equals(info.getRemoteName())) {
+ myBranchModel.setSelectedItem(info);
+ return;
+ }
+ }
}
+
+ if (myBranchModel.getSize() > 0) myBranchModel.setSelectedItem(myBranchModel.get(0));
}
- public JPanel getPanel() {
- return myPanel;
+ public void setForks(@NotNull Collection<ForkInfo> forks) {
+ myForkModel.setSelectedItem(null);
+ myForkModel.setAll(forks);
}
- @NotNull
- public JComponent getPreferredComponent() {
- return myTitleTextField;
+ public void setBranches(@NotNull Collection<BranchInfo> branches) {
+ myBranchModel.setSelectedItem(null);
+ myBranchModel.setAll(branches);
}
- @NotNull
- public JComponent getBranchEditor() {
- return myBranchComboBox;
+ public void setTitle(@Nullable String title) {
+ myTitleTextField.setText(title);
+ myTitleDescriptionUserModified = false;
+ }
+
+ public void setDescription(@Nullable String title) {
+ myDescriptionTextArea.setText(title);
+ myTitleDescriptionUserModified = false;
+ }
+
+ public boolean isTitleDescriptionEmptyOrNotModified() {
+ return !myTitleDescriptionUserModified ||
+ (StringUtil.isEmptyOrSpaces(myTitleTextField.getText()) && StringUtil.isEmptyOrSpaces(myDescriptionTextArea.getText()));
+ }
+
+ public void setDiffEnabled(boolean enabled) {
+ myShowDiffButton.setEnabled(enabled);
}
@NotNull
@@ -120,13 +159,18 @@ public class GithubCreatePullRequestPanel {
}
@NotNull
+ public JButton getSelectForkButton() {
+ return mySelectForkButton;
+ }
+
+ @NotNull
public JButton getShowDiffButton() {
return myShowDiffButton;
}
@NotNull
- public JButton getSelectForkButton() {
- return mySelectForkButton;
+ public ComboBox getForkComboBox() {
+ return myForkComboBox;
}
@NotNull
@@ -134,36 +178,12 @@ public class GithubCreatePullRequestPanel {
return myBranchComboBox;
}
- public void setTitle(@Nullable String title) {
- myTitleTextField.setText(title);
- myTitleDescriptionUserModified = false;
- }
-
- public void setDescription(@Nullable String title) {
- myDescriptionTextArea.setText(title);
- myTitleDescriptionUserModified = false;
- }
-
- public boolean isTitleDescriptionEmptyOrNotModified() {
- return !myTitleDescriptionUserModified ||
- (StringUtil.isEmptyOrSpaces(myTitleTextField.getText()) && StringUtil.isEmptyOrSpaces(myDescriptionTextArea.getText()));
- }
-
- public void setForkName(@NotNull String forkName) {
- myForkLabel.setText(forkName);
- }
-
- public void setBusy(boolean enabled) {
- if (enabled) {
- myBusyIcon.resume();
- }
- else {
- myBusyIcon.suspend();
- }
+ public JPanel getPanel() {
+ return myPanel;
}
- private void createUIComponents() {
- myBusyIcon = new AsyncProcessIcon("Loading diff...");
- myBusyIcon.suspend();
+ @NotNull
+ public JComponent getPreferredComponent() {
+ return myTitleTextField;
}
}
diff --git a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSelectForkDialog.java b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSelectForkDialog.java
index d8ce684eeb38..cea8d206a845 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSelectForkDialog.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSelectForkDialog.java
@@ -27,7 +27,9 @@ import org.jetbrains.plugins.github.api.GithubFullPath;
import org.jetbrains.plugins.github.util.GithubNotifications;
import javax.swing.*;
-import java.util.Set;
+import java.util.List;
+
+import static org.jetbrains.plugins.github.GithubCreatePullRequestWorker.ForkInfo;
/**
* @author Aleksey Pivovarov
@@ -35,25 +37,27 @@ import java.util.Set;
public class GithubSelectForkDialog extends DialogWrapper {
@NotNull private final GithubSelectForkPanel myPanel;
@NotNull private final Project myProject;
- @NotNull private final Convertor<String, GithubFullPath> myCheckFork;
- private GithubFullPath myFullPath;
+ @NotNull private final Convertor<String, ForkInfo> myCheckFork;
+ private ForkInfo mySelectedFork;
public GithubSelectForkDialog(@NotNull Project project,
- @NotNull Set<GithubFullPath> forks,
- @NotNull Convertor<String, GithubFullPath> checkFork) {
+ @Nullable List<GithubFullPath> forks,
+ @NotNull Convertor<String, ForkInfo> checkFork) {
super(project);
myProject = project;
myCheckFork = checkFork;
myPanel = new GithubSelectForkPanel();
- myPanel.setUsers(ContainerUtil.map(forks, new Function<GithubFullPath, String>() {
- @Override
- public String fun(GithubFullPath path) {
- return path.getUser();
- }
- }));
+ if (forks != null) {
+ myPanel.setUsers(ContainerUtil.map(forks, new Function<GithubFullPath, String>() {
+ @Override
+ public String fun(GithubFullPath path) {
+ return path.getUser();
+ }
+ }));
+ }
setTitle("Select Base Fork Repository");
init();
@@ -61,12 +65,12 @@ public class GithubSelectForkDialog extends DialogWrapper {
@Override
protected void doOKAction() {
- GithubFullPath path = myCheckFork.convert(myPanel.getUser());
- if (path == null) {
+ ForkInfo fork = myCheckFork.convert(myPanel.getUser());
+ if (fork == null) {
GithubNotifications.showErrorDialog(myProject, "Can't Find Repository", "Can't find fork for selected user");
}
else {
- myFullPath = path;
+ mySelectedFork = fork;
super.doOKAction();
}
}
@@ -78,12 +82,7 @@ public class GithubSelectForkDialog extends DialogWrapper {
}
@NotNull
- public GithubFullPath getPath() {
- return myFullPath;
- }
-
- @TestOnly
- public void testSetUser(@NotNull String user) {
- myPanel.setSelectedUser(user);
+ public ForkInfo getPath() {
+ return mySelectedFork;
}
}
diff --git a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSelectForkPanel.java b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSelectForkPanel.java
index 754afc617057..cb3e014ee7dc 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSelectForkPanel.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSelectForkPanel.java
@@ -57,14 +57,6 @@ public class GithubSelectForkPanel {
return myComboBox.getSelectedItem().toString();
}
- public void setSelectedUser(@Nullable String user) {
- if (StringUtil.isEmptyOrSpaces(user)) {
- return;
- }
-
- myComboBox.setSelectedItem(user);
- }
-
public JPanel getPanel() {
return myPanel;
}
diff --git a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSettingsPanel.java b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSettingsPanel.java
index 7de0dae30d69..9bcf2aa78fc8 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSettingsPanel.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSettingsPanel.java
@@ -30,6 +30,7 @@ import com.intellij.util.ThrowableConvertor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.github.api.GithubApiUtil;
+import org.jetbrains.plugins.github.api.GithubConnection;
import org.jetbrains.plugins.github.api.GithubUser;
import org.jetbrains.plugins.github.exceptions.GithubAuthenticationException;
import org.jetbrains.plugins.github.exceptions.GithubOperationCanceledException;
@@ -112,11 +113,10 @@ public class GithubSettingsPanel {
}
}
catch (GithubAuthenticationException ex) {
- GithubNotifications.showErrorDialog(myPane, "Login Failure", "Can't login using given credentials: " + ex.getMessage());
+ GithubNotifications.showErrorDialog(myPane, "Login Failure", "Can't login using given credentials: ", ex);
}
catch (IOException ex) {
- LOG.info(ex);
- GithubNotifications.showErrorDialog(myPane, "Login Failure", "Can't login: " + GithubUtil.getErrorTextFromException(ex));
+ GithubNotifications.showErrorDialog(myPane, "Login Failure", "Can't login: ", ex);
}
}
});
@@ -131,11 +131,12 @@ public class GithubSettingsPanel {
@Override
public String convert(ProgressIndicator indicator) throws IOException {
return GithubUtil.runTaskWithBasicAuthForHost(project, GithubAuthDataHolder.createFromSettings(), indicator, getHost(),
- new ThrowableConvertor<GithubAuthData, String, IOException>() {
+ new ThrowableConvertor<GithubConnection, String, IOException>() {
@NotNull
@Override
- public String convert(@NotNull GithubAuthData auth) throws IOException {
- return GithubApiUtil.getMasterToken(auth, "IntelliJ plugin");
+ public String convert(@NotNull GithubConnection connection)
+ throws IOException {
+ return GithubApiUtil.getMasterToken(connection, "IntelliJ plugin");
}
}
);
diff --git a/plugins/github/src/org/jetbrains/plugins/github/util/GithubNotifications.java b/plugins/github/src/org/jetbrains/plugins/github/util/GithubNotifications.java
index 767c620c93a4..d34441bf1ba3 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/util/GithubNotifications.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/util/GithubNotifications.java
@@ -18,6 +18,7 @@ package org.jetbrains.plugins.github.util;
import com.intellij.notification.NotificationListener;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vcs.VcsNotifier;
import org.jetbrains.annotations.NotNull;
@@ -41,6 +42,12 @@ public class GithubNotifications {
VcsNotifier.getInstance(project).notifyImportantWarning(title, message);
}
+ public static void showWarning(@NotNull Project project, @NotNull String title, @NotNull Exception e) {
+ LOG.info(title + "; ", e);
+ if (e instanceof GithubOperationCanceledException) return;
+ VcsNotifier.getInstance(project).notifyImportantWarning(title, getErrorTextFromException(e));
+ }
+
public static void showError(@NotNull Project project, @NotNull String title, @NotNull String message) {
LOG.info(title + "; " + message);
VcsNotifier.getInstance(project).notifyError(title, message);
@@ -116,19 +123,28 @@ public class GithubNotifications {
Messages.showErrorDialog(project, getErrorTextFromException(e), title);
}
- public static void showErrorDialog(@NotNull Component component, @NotNull String title, @NotNull String message) {
- LOG.info(title + "; " + message);
- Messages.showErrorDialog(component, message, title);
- }
-
public static void showErrorDialog(@NotNull Component component, @NotNull String title, @NotNull Exception e) {
LOG.info(title, e);
if (e instanceof GithubOperationCanceledException) return;
Messages.showErrorDialog(component, getErrorTextFromException(e), title);
}
+ public static void showErrorDialog(@NotNull Component component, @NotNull String title, @NotNull String prefix, @NotNull Exception e) {
+ LOG.info(title, e);
+ if (e instanceof GithubOperationCanceledException) return;
+ Messages.showErrorDialog(component, prefix + getErrorTextFromException(e), title);
+ }
+
@Messages.YesNoResult
public static int showYesNoDialog(@Nullable Project project, @NotNull String title, @NotNull String message) {
return Messages.showYesNoDialog(project, message, title, Messages.getQuestionIcon());
}
+
+ @Messages.YesNoResult
+ public static int showYesNoDialog(@Nullable Project project,
+ @NotNull String title,
+ @NotNull String message,
+ @NotNull DialogWrapper.DoNotAskOption doNotAskOption) {
+ return Messages.showYesNoDialog(project, message, title, Messages.getQuestionIcon(), doNotAskOption);
+ }
}
diff --git a/plugins/github/src/org/jetbrains/plugins/github/util/GithubSettings.java b/plugins/github/src/org/jetbrains/plugins/github/util/GithubSettings.java
index 3523e412a95e..7e98d47d8220 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/util/GithubSettings.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/util/GithubSettings.java
@@ -22,6 +22,7 @@ import com.intellij.ide.passwordSafe.impl.PasswordSafeImpl;
import com.intellij.openapi.components.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.ThreeState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.github.api.GithubApiUtil;
@@ -60,6 +61,7 @@ public class GithubSettings implements PersistentStateComponent<GithubSettings.S
public boolean SAVE_PASSWORD = true;
public int CONNECTION_TIMEOUT = 5000;
public boolean VALID_GIT_AUTH = true;
+ public ThreeState CREATE_PULL_REQUEST_CREATE_REMOTE = ThreeState.UNSURE;
}
public static GithubSettings getInstance() {
@@ -130,6 +132,15 @@ public class GithubSettings implements PersistentStateComponent<GithubSettings.S
return passwordSafe.getSettings().getProviderType() == PasswordSafeSettings.ProviderType.MASTER_PASSWORD;
}
+ @NotNull
+ public ThreeState getCreatePullRequestCreateRemote() {
+ return myState.CREATE_PULL_REQUEST_CREATE_REMOTE;
+ }
+
+ public void setCreatePullRequestCreateRemote(@NotNull ThreeState value) {
+ myState.CREATE_PULL_REQUEST_CREATE_REMOTE = value;
+ }
+
public void setAnonymousGist(final boolean anonymousGist) {
myState.ANONYMOUS_GIST = anonymousGist;
}
diff --git a/plugins/github/src/org/jetbrains/plugins/github/util/GithubUtil.java b/plugins/github/src/org/jetbrains/plugins/github/util/GithubUtil.java
index edd12ab85ab6..34c53da7a97f 100644
--- a/plugins/github/src/org/jetbrains/plugins/github/util/GithubUtil.java
+++ b/plugins/github/src/org/jetbrains/plugins/github/util/GithubUtil.java
@@ -15,6 +15,7 @@
*/
package org.jetbrains.plugins.github.util;
+import com.intellij.concurrency.JobScheduler;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
@@ -23,12 +24,14 @@ import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.Consumer;
import com.intellij.util.ThrowableConsumer;
import com.intellij.util.ThrowableConvertor;
@@ -46,6 +49,7 @@ import git4idea.repo.GitRepositoryManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.github.api.GithubApiUtil;
+import org.jetbrains.plugins.github.api.GithubConnection;
import org.jetbrains.plugins.github.api.GithubFullPath;
import org.jetbrains.plugins.github.api.GithubUserDetailed;
import org.jetbrains.plugins.github.exceptions.GithubAuthenticationException;
@@ -57,6 +61,8 @@ import org.jetbrains.plugins.github.ui.GithubLoginDialog;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.List;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
/**
* Various utility methods for the GutHub plugin.
@@ -72,11 +78,21 @@ public class GithubUtil {
// TODO: Consider sharing of GithubAuthData between actions (as member of GithubSettings)
public static <T> T runTask(@NotNull Project project,
@NotNull GithubAuthDataHolder authHolder,
- @NotNull ProgressIndicator indicator,
- @NotNull ThrowableConvertor<GithubAuthData, T, IOException> task) throws IOException {
+ @NotNull final ProgressIndicator indicator,
+ @NotNull ThrowableConvertor<GithubConnection, T, IOException> task) throws IOException {
GithubAuthData auth = authHolder.getAuthData();
try {
- return task.convert(auth);
+ final GithubConnection connection = new GithubConnection(auth, true);
+ ScheduledFuture<?> future = null;
+
+ try {
+ future = addCancellationListener(indicator, connection);
+ return task.convert(connection);
+ }
+ finally {
+ connection.close();
+ if (future != null) future.cancel(true);
+ }
}
catch (GithubTwoFactorAuthenticationException e) {
getTwoFactorAuthData(project, authHolder, indicator, auth);
@@ -90,11 +106,21 @@ public class GithubUtil {
public static void runTask(@NotNull Project project,
@NotNull GithubAuthDataHolder authHolder,
- @NotNull ProgressIndicator indicator,
- @NotNull ThrowableConsumer<GithubAuthData, IOException> task) throws IOException {
+ @NotNull final ProgressIndicator indicator,
+ @NotNull ThrowableConsumer<GithubConnection, IOException> task) throws IOException {
GithubAuthData auth = authHolder.getAuthData();
try {
- task.consume(auth);
+ final GithubConnection connection = new GithubConnection(auth, true);
+ ScheduledFuture<?> future = null;
+
+ try {
+ future = addCancellationListener(indicator, connection);
+ task.consume(connection);
+ }
+ finally {
+ connection.close();
+ if (future != null) future.cancel(true);
+ }
}
catch (GithubTwoFactorAuthenticationException e) {
getTwoFactorAuthData(project, authHolder, indicator, auth);
@@ -108,15 +134,26 @@ public class GithubUtil {
public static <T> T runTaskWithBasicAuthForHost(@NotNull Project project,
@NotNull GithubAuthDataHolder authHolder,
- @NotNull ProgressIndicator indicator,
+ @NotNull final ProgressIndicator indicator,
@NotNull String host,
- @NotNull ThrowableConvertor<GithubAuthData, T, IOException> task) throws IOException {
+ @NotNull ThrowableConvertor<GithubConnection, T, IOException> task) throws IOException {
GithubAuthData auth = authHolder.getAuthData();
try {
if (auth.getAuthType() != GithubAuthData.AuthType.BASIC) {
throw new GithubAuthenticationException("Expected basic authentication");
}
- return task.convert(auth);
+
+ final GithubConnection connection = new GithubConnection(auth, true);
+ ScheduledFuture<?> future = null;
+
+ try {
+ future = addCancellationListener(indicator, connection);
+ return task.convert(connection);
+ }
+ finally {
+ connection.close();
+ if (future != null) future.cancel(true);
+ }
}
catch (GithubTwoFactorAuthenticationException e) {
getTwoFactorAuthData(project, authHolder, indicator, auth);
@@ -131,10 +168,20 @@ public class GithubUtil {
@NotNull
private static GithubUserDetailed testConnection(@NotNull Project project,
@NotNull GithubAuthDataHolder authHolder,
- @NotNull ProgressIndicator indicator) throws IOException {
+ @NotNull final ProgressIndicator indicator) throws IOException {
GithubAuthData auth = authHolder.getAuthData();
try {
- return GithubApiUtil.getCurrentUserDetailed(auth);
+ final GithubConnection connection = new GithubConnection(auth, true);
+ ScheduledFuture<?> future = null;
+
+ try {
+ future = addCancellationListener(indicator, connection);
+ return GithubApiUtil.getCurrentUserDetailed(connection);
+ }
+ finally {
+ connection.close();
+ if (future != null) future.cancel(true);
+ }
}
catch (GithubTwoFactorAuthenticationException e) {
getTwoFactorAuthData(project, authHolder, indicator, auth);
@@ -142,6 +189,33 @@ public class GithubUtil {
}
}
+ @NotNull
+ private static ScheduledFuture<?> addCancellationListener(@NotNull Runnable run) {
+ return JobScheduler.getScheduler().scheduleWithFixedDelay(run, 1000, 300, TimeUnit.MILLISECONDS);
+ }
+
+ @NotNull
+ private static ScheduledFuture<?> addCancellationListener(@NotNull final ProgressIndicator indicator,
+ @NotNull final GithubConnection connection) {
+ return addCancellationListener(new Runnable() {
+ @Override
+ public void run() {
+ if (indicator.isCanceled()) connection.abort();
+ }
+ });
+ }
+
+ @NotNull
+ private static ScheduledFuture<?> addCancellationListener(@NotNull final ProgressIndicator indicator,
+ @NotNull final Thread thread) {
+ return addCancellationListener(new Runnable() {
+ @Override
+ public void run() {
+ if (indicator.isCanceled()) thread.interrupt();
+ }
+ });
+ }
+
public static void getValidAuthData(@NotNull final Project project,
@NotNull final GithubAuthDataHolder authHolder,
@NotNull final ProgressIndicator indicator,
@@ -220,7 +294,7 @@ public class GithubUtil {
throw new GithubOperationCanceledException("Two factor authentication can be used only with Login/Password");
}
- GithubApiUtil.askForTwoFactorCodeSMS(oldAuth);
+ GithubApiUtil.askForTwoFactorCodeSMS(new GithubConnection(oldAuth, false));
final Ref<String> codeRef = new Ref<String>();
ApplicationManager.getApplication().invokeAndWait(new Runnable() {
@@ -301,13 +375,7 @@ public class GithubUtil {
try {
dataRef.set(task.convert(indicator));
}
- catch (IOException e) {
- exceptionRef.set(e);
- }
- catch (Error e) {
- exceptionRef.set(e);
- }
- catch (RuntimeException e) {
+ catch (Throwable e) {
exceptionRef.set(e);
}
}
@@ -325,17 +393,21 @@ public class GithubUtil {
public static <T> T computeValueInModal(@NotNull Project project,
@NotNull String caption,
@NotNull final Convertor<ProgressIndicator, T> task) {
+ return computeValueInModal(project, caption, true, task);
+ }
+
+ public static <T> T computeValueInModal(@NotNull Project project,
+ @NotNull String caption,
+ boolean canBeCancelled,
+ @NotNull final Convertor<ProgressIndicator, T> task) {
final Ref<T> dataRef = new Ref<T>();
final Ref<Throwable> exceptionRef = new Ref<Throwable>();
- ProgressManager.getInstance().run(new Task.Modal(project, caption, true) {
+ ProgressManager.getInstance().run(new Task.Modal(project, caption, canBeCancelled) {
public void run(@NotNull ProgressIndicator indicator) {
try {
dataRef.set(task.convert(indicator));
}
- catch (Error e) {
- exceptionRef.set(e);
- }
- catch (RuntimeException e) {
+ catch (Throwable e) {
exceptionRef.set(e);
}
}
@@ -352,16 +424,20 @@ public class GithubUtil {
public static void computeValueInModal(@NotNull Project project,
@NotNull String caption,
@NotNull final Consumer<ProgressIndicator> task) {
+ computeValueInModal(project, caption, true, task);
+ }
+
+ public static void computeValueInModal(@NotNull Project project,
+ @NotNull String caption,
+ boolean canBeCancelled,
+ @NotNull final Consumer<ProgressIndicator> task) {
final Ref<Throwable> exceptionRef = new Ref<Throwable>();
- ProgressManager.getInstance().run(new Task.Modal(project, caption, true) {
+ ProgressManager.getInstance().run(new Task.Modal(project, caption, canBeCancelled) {
public void run(@NotNull ProgressIndicator indicator) {
try {
task.consume(indicator);
}
- catch (Error e) {
- exceptionRef.set(e);
- }
- catch (RuntimeException e) {
+ catch (Throwable e) {
exceptionRef.set(e);
}
}
@@ -374,6 +450,49 @@ public class GithubUtil {
}
}
+ public static <T> T runInterruptable(@NotNull final ProgressIndicator indicator,
+ @NotNull ThrowableComputable<T, IOException> task) throws IOException {
+ ScheduledFuture<?> future = null;
+ try {
+ final Thread thread = Thread.currentThread();
+ future = addCancellationListener(indicator, thread);
+
+ return task.compute();
+ }
+ finally {
+ if (future != null) future.cancel(true);
+ Thread.interrupted();
+ }
+ }
+
+ public static <T> T runInterruptable(@NotNull final ProgressIndicator indicator, @NotNull Computable<T> task) {
+ ScheduledFuture<?> future = null;
+ try {
+ final Thread thread = Thread.currentThread();
+ future = addCancellationListener(indicator, thread);
+
+ return task.compute();
+ }
+ finally {
+ if (future != null) future.cancel(true);
+ Thread.interrupted();
+ }
+ }
+
+ public static void runInterruptable(@NotNull final ProgressIndicator indicator, @NotNull Runnable task) {
+ ScheduledFuture<?> future = null;
+ try {
+ final Thread thread = Thread.currentThread();
+ future = addCancellationListener(indicator, thread);
+
+ task.run();
+ }
+ finally {
+ if (future != null) future.cancel(true);
+ Thread.interrupted();
+ }
+ }
+
/*
* Git utils
*/