| 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.ThrowableComputable; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vcs.VcsException; |
| import com.intellij.openapi.vcs.changes.Change; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.util.Consumer; |
| 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.vcs.log.VcsCommitMetadata; |
| import git4idea.DialogManager; |
| import git4idea.GitCommit; |
| import git4idea.GitLocalBranch; |
| import git4idea.changes.GitChangeUtils; |
| import git4idea.commands.Git; |
| import git4idea.commands.GitCommandResult; |
| import git4idea.history.GitHistoryUtils; |
| import git4idea.repo.GitRemote; |
| import git4idea.repo.GitRepository; |
| import git4idea.ui.branch.GitCompareBranchesDialog; |
| import git4idea.update.GitFetchResult; |
| import git4idea.update.GitFetcher; |
| 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.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; |
| |
| public class GithubCreatePullRequestWorker { |
| private static final Logger LOG = GithubUtil.LOG; |
| private static final String CANNOT_CREATE_PULL_REQUEST = "Can't Create Pull Request"; |
| |
| @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 GithubFullPath mySource; |
| |
| @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) { |
| myProject = project; |
| myGit = git; |
| myGitRepository = gitRepository; |
| myAuthHolder = authHolder; |
| myPath = path; |
| myRemoteName = remoteName; |
| myRemoteUrl = remoteUrl; |
| myCurrentBranch = currentBranch; |
| |
| myForks = new ArrayList<ForkInfo>(); |
| } |
| |
| @NotNull |
| public String getCurrentBranch() { |
| return myCurrentBranch; |
| } |
| |
| @NotNull |
| public List<ForkInfo> getForks() { |
| return myForks; |
| } |
| |
| @Nullable |
| 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.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; |
| } |
| } |
| |
| 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; |
| } |
| catch (IOException e) { |
| GithubNotifications.showWarning(myProject, "Can't load branches for " + path.getFullName(), e); |
| 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; |
| } |
| } |
| |
| try { |
| 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.showWarning(myProject, "Can't load branches for " + path.getFullName(), e); |
| return null; |
| } |
| } |
| |
| private void doLoadForksFromSettings(@NotNull ProgressIndicator indicator) throws IOException { |
| GithubFullPath savedRepo = GithubProjectSettings.getInstance(myProject).getCreatePullRequestDefaultRepo(); |
| if (savedRepo != null) { |
| doAddFork(savedRepo, null, indicator); |
| } |
| } |
| |
| 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; |
| } |
| } |
| } |
| } |
| } |
| |
| 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()); |
| } |
| }); |
| |
| doAddFork(repo, indicator); |
| if (repo.getParent() != null) { |
| doAddFork(repo.getParent(), indicator); |
| } |
| if (repo.getSource() != null) { |
| doAddFork(repo.getSource(), indicator); |
| } |
| |
| mySource = repo.getSource() == null ? repo.getFullPath() : repo.getSource().getFullPath(); |
| } |
| |
| @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; |
| |
| 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(); |
| } |
| }); |
| } |
| } |
| |
| 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; |
| } |
| } |
| |
| private boolean doFetchRemote(@NotNull ForkInfo fork) { |
| if (fork.getRemoteName() == null) return false; |
| |
| 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; |
| } |
| |
| @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); |
| } |
| |
| 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()); |
| } |
| } |
| |
| public void configureRemote(@NotNull final ForkInfo fork) { |
| GithubUtil.computeValueInModal(myProject, "Creating remote..", false, new Consumer<ProgressIndicator>() { |
| @Override |
| public void consume(ProgressIndicator indicator) { |
| doConfigureRemote(fork); |
| } |
| }); |
| } |
| |
| @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; |
| } |
| |
| DiffInfo info; |
| try { |
| 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 (IOException e) { |
| GithubNotifications.showError(myProject, "Can't collect diff data", e); |
| return true; |
| } |
| if (info == null) { |
| return true; |
| } |
| |
| ForkInfo fork = branch.getForkInfo(); |
| |
| String localBranchName = "'" + myCurrentBranch + "'"; |
| String targetBranchName = "'" + fork.getRemoteName() + "/" + branch.getRemoteName() + "'"; |
| if (info.getInfo().getBranchToHeadCommits(myGitRepository).isEmpty()) { |
| 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 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 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) { |
| LOG.info("Pushing current branch"); |
| indicator.setText("Pushing current branch..."); |
| GitCommandResult result = myGit.push(myGitRepository, myRemoteName, myRemoteUrl, myCurrentBranch, true); |
| if (!result.success()) { |
| GithubNotifications.showError(myProject, CANNOT_CREATE_PULL_REQUEST, "Push failed:<br/>" + result.getErrorOutputAsHtmlString()); |
| return; |
| } |
| |
| LOG.info("Creating pull request"); |
| indicator.setText("Creating pull request..."); |
| GithubPullRequest request = doCreatePullRequest(indicator, branch, title, description); |
| if (request == null) { |
| return; |
| } |
| |
| GithubNotifications.showInfoURL(myProject, "Successfully created pull request", |
| "Pull request #" + request.getNumber(), request.getHtmlUrl()); |
| } |
| }.queue(); |
| } |
| |
| @Nullable |
| private GithubPullRequest doCreatePullRequest(@NotNull ProgressIndicator indicator, |
| @NotNull final BranchInfo branch, |
| @NotNull final String title, |
| @NotNull final String description) { |
| final ForkInfo fork = branch.getForkInfo(); |
| |
| final String head = myPath.getUser() + ":" + myCurrentBranch; |
| final String base = branch.getRemoteName(); |
| |
| try { |
| 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(myProject, CANNOT_CREATE_PULL_REQUEST, e); |
| return null; |
| } |
| } |
| |
| 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 { |
| 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 (IOException e) { |
| GithubNotifications.showError(myProject, "Can't collect diff data", e); |
| return; |
| } |
| 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 |
| 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); |
| } |
| }); |
| } |
| |
| Convertor<String, ForkInfo> getForkPath = new Convertor<String, ForkInfo>() { |
| @Nullable |
| @Override |
| 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 List<GithubFullPath> getAvailableForks(@NotNull ProgressIndicator indicator) { |
| try { |
| 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; |
| } |
| } |
| |
| @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; |
| } |
| } |
| |
| try { |
| GithubRepo repo = |
| GithubUtil.runTask(myProject, myAuthHolder, indicator, new ThrowableConvertor<GithubConnection, GithubRepo, IOException>() { |
| @Nullable |
| @Override |
| 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; |
| } |
| } |
| catch (IOException ignore) { |
| // such repo may not exist |
| } |
| |
| return GithubApiUtil.findForkByUser(connection, mySource.getUser(), mySource.getRepository(), user); |
| } |
| }); |
| |
| if (repo == null) return null; |
| return doAddFork(repo, indicator); |
| } |
| catch (IOException e) { |
| GithubNotifications.showError(myProject, "Can't find repository", e); |
| return null; |
| } |
| } |
| |
| 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)); |
| } |
| } |
| |
| @NotNull |
| public GithubFullPath getPath() { |
| return myPath; |
| } |
| |
| @Nullable |
| public String getRemoteName() { |
| return myRemoteName; |
| } |
| |
| @NotNull |
| public String getDefaultBranch() { |
| return myDefaultBranch; |
| } |
| |
| @NotNull |
| public List<BranchInfo> getBranches() { |
| return myBranches; |
| } |
| |
| public void setRemoteName(@NotNull String remoteName) { |
| myRemoteName = remoteName; |
| } |
| |
| public boolean isProposedToCreateRemote() { |
| return myProposedToCreateRemote; |
| } |
| |
| public void setProposedToCreateRemote(boolean proposedToCreateRemote) { |
| myProposedToCreateRemote = proposedToCreateRemote; |
| } |
| |
| @Nullable |
| public MasterFutureTask<Void> getFetchTask() { |
| return myFetchTask; |
| } |
| |
| public void setFetchTask(@NotNull MasterFutureTask<Void> fetchTask) { |
| myFetchTask = fetchTask; |
| } |
| |
| @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; |
| } |
| |
| @Override |
| public int hashCode() { |
| return myPath.hashCode(); |
| } |
| |
| @Override |
| public String toString() { |
| return myPath.getUser() + ":" + myPath.getRepository(); |
| } |
| } |
| |
| public static class BranchInfo { |
| @NotNull public final Object LOCK = new Object(); |
| |
| @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 ForkInfo getForkInfo() { |
| return myForkInfo; |
| } |
| |
| @NotNull |
| public String getRemoteName() { |
| return myRemoteName; |
| } |
| |
| @Nullable |
| public SlaveFutureTask<DiffInfo> getDiffInfoTask() { |
| return myDiffInfoTask; |
| } |
| |
| public void setDiffInfoTask(@NotNull SlaveFutureTask<DiffInfo> diffInfoTask) { |
| myDiffInfoTask = diffInfoTask; |
| } |
| |
| @Nullable |
| public Couple<String> getDefaultMessage() { |
| return myDefaultMessage; |
| } |
| |
| public void setDefaultMessage(@NotNull Couple<String> message) { |
| myDefaultMessage = message; |
| } |
| |
| @Override |
| public String toString() { |
| return myRemoteName; |
| } |
| } |
| |
| public static class DiffInfo { |
| @NotNull private final GitCommitCompareInfo myInfo; |
| @NotNull private final String myFrom; |
| @NotNull private final String myTo; |
| |
| private DiffInfo(@NotNull GitCommitCompareInfo info, @NotNull String from, @NotNull String to) { |
| myInfo = info; |
| myFrom = from; // HEAD |
| myTo = to; // BASE |
| } |
| |
| @NotNull |
| public GitCommitCompareInfo getInfo() { |
| return myInfo; |
| } |
| |
| @NotNull |
| public String getFrom() { |
| return myFrom; |
| } |
| |
| @NotNull |
| public String getTo() { |
| return myTo; |
| } |
| } |
| |
| public static class SlaveFutureTask<T> extends FutureTask<T> { |
| @NotNull private final MasterFutureTask myMaster; |
| |
| public SlaveFutureTask(@NotNull MasterFutureTask master, @NotNull Callable<T> callable) { |
| super(callable); |
| myMaster = master; |
| } |
| |
| @Override |
| public void run() { |
| if (myMaster.isDone()) { |
| super.run(); |
| } |
| else { |
| if (!myMaster.addSlave(this)) { |
| super.run(); |
| } |
| } |
| } |
| |
| public T safeGet() { |
| try { |
| return super.get(); |
| } |
| catch (InterruptedException e) { |
| return null; |
| } |
| catch (CancellationException e) { |
| return null; |
| } |
| catch (ExecutionException e) { |
| return null; |
| } |
| } |
| } |
| |
| 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(); |
| } |
| }); |
| } |
| } |
| } |