blob: 07ed51bfca7d91f538883206f373ed1ef014d0cc [file] [log] [blame]
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();
}
});
}
}
}