mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Merge branch 'createPR'
Conflicts: plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestAction.java plugins/github/src/org/jetbrains/plugins/github/api/GithubApiUtil.java
This commit is contained in:
+6
-579
@@ -17,56 +17,16 @@ package org.jetbrains.plugins.github;
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
import com.intellij.openapi.project.DumbAwareAction;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
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.ThrowableConsumer;
|
||||
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.util.containers.HashSet;
|
||||
import git4idea.DialogManager;
|
||||
import git4idea.GitCommit;
|
||||
import git4idea.GitLocalBranch;
|
||||
import git4idea.GitRemoteBranch;
|
||||
import git4idea.branch.GitBranchUtil;
|
||||
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 icons.GithubIcons;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.plugins.github.api.*;
|
||||
import org.jetbrains.plugins.github.exceptions.GithubAuthenticationCanceledException;
|
||||
import org.jetbrains.plugins.github.ui.GithubCreatePullRequestDialog;
|
||||
import org.jetbrains.plugins.github.util.GithubAuthData;
|
||||
import org.jetbrains.plugins.github.util.GithubNotifications;
|
||||
import org.jetbrains.plugins.github.util.GithubUrlUtil;
|
||||
import org.jetbrains.plugins.github.util.GithubUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.jetbrains.plugins.github.util.*;
|
||||
|
||||
import static org.jetbrains.plugins.github.util.GithubUtil.setVisibleEnabled;
|
||||
|
||||
@@ -74,9 +34,6 @@ import static org.jetbrains.plugins.github.util.GithubUtil.setVisibleEnabled;
|
||||
* @author Aleksey Pivovarov
|
||||
*/
|
||||
public class GithubCreatePullRequestAction extends DumbAwareAction {
|
||||
private static final Logger LOG = GithubUtil.LOG;
|
||||
private static final String CANNOT_CREATE_PULL_REQUEST = "Can't create pull request";
|
||||
|
||||
public GithubCreatePullRequestAction() {
|
||||
super("Create Pull Request", "Create pull request from current branch", GithubIcons.Github_icon);
|
||||
}
|
||||
@@ -115,543 +72,13 @@ public class GithubCreatePullRequestAction extends DumbAwareAction {
|
||||
createPullRequest(project, file);
|
||||
}
|
||||
|
||||
static void createPullRequest(@NotNull final Project project, @Nullable final VirtualFile file) {
|
||||
final Git git = ServiceManager.getService(Git.class);
|
||||
|
||||
final GitRepository repository = GithubUtil.getGitRepository(project, file);
|
||||
if (repository == null) {
|
||||
GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't find git repository");
|
||||
return;
|
||||
}
|
||||
repository.update();
|
||||
|
||||
Pair<GitRemote, String> remote = GithubUtil.findGithubRemote(repository);
|
||||
if (remote == null) {
|
||||
GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't find GitHub remote");
|
||||
return;
|
||||
}
|
||||
final String remoteUrl = remote.getSecond();
|
||||
final String remoteName = remote.getFirst().getName();
|
||||
|
||||
GithubFullPath userAndRepo = GithubUrlUtil.getUserAndRepositoryFromRemoteUrl(remoteUrl);
|
||||
if (userAndRepo == null) {
|
||||
GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't process remote: " + remoteUrl);
|
||||
static void createPullRequest(@NotNull Project project, @Nullable VirtualFile file) {
|
||||
GithubCreatePullRequestWorker worker = GithubCreatePullRequestWorker.createPullRequestWorker(project, file);
|
||||
if (worker == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final GitLocalBranch currentBranch = repository.getCurrentBranch();
|
||||
if (currentBranch == null) {
|
||||
GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "No current branch");
|
||||
return;
|
||||
}
|
||||
|
||||
String upstreamUrl = GithubUtil.findUpstreamRemote(repository);
|
||||
GithubFullPath upstreamUserAndRepo =
|
||||
upstreamUrl == null || !GithubUrlUtil.isGithubUrl(upstreamUrl) ? null : GithubUrlUtil.getUserAndRepositoryFromRemoteUrl(upstreamUrl);
|
||||
|
||||
final Map<String, String> forks = new HashMap<String, String>();
|
||||
final Set<RemoteBranch> branches = new HashSet<RemoteBranch>();
|
||||
addAvailableBranchesFromGit(repository, forks, branches);
|
||||
GithubInfo info = loadGithubInfoAndBranchesWithModal(project, userAndRepo, upstreamUserAndRepo, forks, branches);
|
||||
if (info == null) {
|
||||
return;
|
||||
}
|
||||
final GithubRepoDetailed repo = info.getRepo();
|
||||
final GithubAuthData auth = info.getAuthData();
|
||||
|
||||
GithubRepo parent = repo.getParent();
|
||||
String defaultBranch =
|
||||
parent == null || parent.getDefaultBranch() == null ? null : parent.getUserName() + ":" + parent.getDefaultBranch();
|
||||
Collection<String> suggestions = ContainerUtil.map(branches, new Function<RemoteBranch, String>() {
|
||||
@Override
|
||||
public String fun(RemoteBranch remoteBranch) {
|
||||
return remoteBranch.getReference();
|
||||
}
|
||||
});
|
||||
Consumer<String> showDiff = new Consumer<String>() {
|
||||
@Override
|
||||
public void consume(String ref) {
|
||||
showDiffByRef(project, ref, repository, currentBranch.getName(), auth, forks, branches, repo.getSource());
|
||||
}
|
||||
};
|
||||
final GithubCreatePullRequestDialog dialog = new GithubCreatePullRequestDialog(project, suggestions, defaultBranch, showDiff);
|
||||
GithubCreatePullRequestDialog dialog = new GithubCreatePullRequestDialog(worker);
|
||||
DialogManager.show(dialog);
|
||||
if (!dialog.isOK()) {
|
||||
return;
|
||||
}
|
||||
|
||||
new Task.Backgroundable(project, "Creating pull request...") {
|
||||
@Override
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
LOG.info("Pushing current branch");
|
||||
indicator.setText("Pushing current branch...");
|
||||
GitCommandResult result = git.push(repository, remoteName, remoteUrl, currentBranch.getName(), true);
|
||||
if (!result.success()) {
|
||||
GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Push failed:<br/>" + result.getErrorOutputAsHtmlString());
|
||||
return;
|
||||
}
|
||||
|
||||
String from = repo.getUserName() + ":" + currentBranch.getName();
|
||||
String onto = dialog.getTargetBranch();
|
||||
String targetUser = onto.substring(0, onto.indexOf(':'));
|
||||
|
||||
GithubFullPath targetRepo = findRepositoryByUser(project, targetUser, forks, auth, repo.getSource());
|
||||
if (targetRepo == null) {
|
||||
GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't find repository for specified branch: " + onto);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.info("Creating pull request");
|
||||
indicator.setText("Creating pull request...");
|
||||
GithubPullRequest request =
|
||||
createPullRequest(project, auth, targetRepo, dialog.getRequestTitle(), dialog.getDescription(), from, onto);
|
||||
if (request == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GithubNotifications
|
||||
.showInfoURL(project, "Successfully created pull request", "Pull Request #" + request.getNumber(), request.getHtmlUrl());
|
||||
}
|
||||
}.queue();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static GithubInfo loadGithubInfoAndBranchesWithModal(@NotNull final Project project,
|
||||
@NotNull final GithubFullPath userAndRepo,
|
||||
@Nullable final GithubFullPath upstreamUserAndRepo,
|
||||
@NotNull final Map<String, String> forks,
|
||||
@NotNull final Set<RemoteBranch> branches) {
|
||||
try {
|
||||
return GithubUtil
|
||||
.computeValueInModal(project, "Access to GitHub", new ThrowableConvertor<ProgressIndicator, GithubInfo, IOException>() {
|
||||
@Override
|
||||
public GithubInfo convert(ProgressIndicator indicator) throws IOException {
|
||||
final AtomicReference<GithubRepoDetailed> reposRef = new AtomicReference<GithubRepoDetailed>();
|
||||
final GithubAuthData auth =
|
||||
GithubUtil.runAndGetValidAuth(project, indicator, new ThrowableConsumer<GithubAuthData, IOException>() {
|
||||
@Override
|
||||
public void consume(GithubAuthData authData) throws IOException {
|
||||
reposRef.set(GithubApiUtil.getDetailedRepoInfo(authData, userAndRepo.getUser(), userAndRepo.getRepository()));
|
||||
}
|
||||
});
|
||||
addAvailableBranchesFromGithub(project, auth, reposRef.get(), upstreamUserAndRepo, forks, branches);
|
||||
return new GithubInfo(auth, reposRef.get());
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (GithubAuthenticationCanceledException e) {
|
||||
return null;
|
||||
}
|
||||
catch (IOException e) {
|
||||
GithubNotifications.showErrorDialog(project, CANNOT_CREATE_PULL_REQUEST, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static GithubFullPath findRepositoryByUser(@NotNull Project project,
|
||||
@NotNull String user,
|
||||
@NotNull Map<String, String> forks,
|
||||
@NotNull GithubAuthData auth,
|
||||
@Nullable GithubRepo source) {
|
||||
for (Map.Entry<String, String> entry : forks.entrySet()) {
|
||||
if (StringUtil.equalsIgnoreCase(user, entry.getKey())) {
|
||||
return new GithubFullPath(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (source != null) {
|
||||
try {
|
||||
GithubRepoDetailed target = GithubApiUtil.getDetailedRepoInfo(auth, user, source.getName());
|
||||
if (target.getSource() != null && StringUtil.equals(target.getSource().getUserName(), source.getUserName())) {
|
||||
forks.put(target.getUserName(), target.getName());
|
||||
return target.getFullPath();
|
||||
}
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
// such repo may not exist
|
||||
}
|
||||
|
||||
try {
|
||||
GithubRepo fork = GithubApiUtil.findForkByUser(auth, source.getUserName(), source.getName(), user);
|
||||
if (fork != null) {
|
||||
forks.put(fork.getUserName(), fork.getName());
|
||||
return fork.getFullPath();
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static GithubPullRequest createPullRequest(@NotNull Project project,
|
||||
@NotNull GithubAuthData auth,
|
||||
@NotNull GithubFullPath targetRepo,
|
||||
@NotNull String title,
|
||||
@NotNull String description,
|
||||
@NotNull String from,
|
||||
@NotNull String onto) {
|
||||
try {
|
||||
return GithubApiUtil.createPullRequest(auth, targetRepo.getUser(), targetRepo.getRepository(), title, description, from, onto);
|
||||
}
|
||||
catch (IOException e) {
|
||||
GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void addAvailableBranchesFromGit(@NotNull GitRepository gitRepository,
|
||||
@NotNull Map<String, String> forks,
|
||||
@NotNull Set<RemoteBranch> branches) {
|
||||
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.put(path.getUser(), path.getRepository());
|
||||
branches.add(new RemoteBranch(path.getUser(), remoteBranch.getNameForRemoteOperations(), remoteBranch));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addAvailableBranchesFromGithub(@NotNull final Project project,
|
||||
@NotNull final GithubAuthData auth,
|
||||
@NotNull final GithubRepoDetailed repo,
|
||||
@Nullable final GithubFullPath upstreamPath,
|
||||
@NotNull Map<String, String> forks,
|
||||
@NotNull Set<RemoteBranch> branches) {
|
||||
try {
|
||||
final GithubRepo parent = repo.getParent();
|
||||
final GithubRepo source = repo.getSource();
|
||||
|
||||
forks.put(repo.getUserName(), repo.getName());
|
||||
branches.addAll(getBranches(auth, repo.getUserName(), repo.getName()));
|
||||
|
||||
if (parent != null) {
|
||||
forks.put(parent.getUserName(), parent.getName());
|
||||
branches.addAll(getBranches(auth, parent.getUserName(), parent.getName()));
|
||||
}
|
||||
|
||||
if (source != null && !equals(source, parent)) {
|
||||
forks.put(source.getUserName(), source.getName());
|
||||
branches.addAll(getBranches(auth, source.getUserName(), source.getName()));
|
||||
}
|
||||
|
||||
if (upstreamPath != null && !equals(upstreamPath, repo) && !equals(upstreamPath, parent) && !equals(upstreamPath, source)) {
|
||||
forks.put(upstreamPath.getUser(), upstreamPath.getRepository());
|
||||
branches.addAll(getBranches(auth, upstreamPath.getUser(), upstreamPath.getRepository()));
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
GithubNotifications.showError(project, "Can't load available branches", e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<RemoteBranch> getBranches(@NotNull GithubAuthData auth, @NotNull final String user, @NotNull final String repo)
|
||||
throws IOException {
|
||||
List<GithubBranch> branches = GithubApiUtil.getRepoBranches(auth, user, repo);
|
||||
return ContainerUtil.map(branches, new Function<GithubBranch, RemoteBranch>() {
|
||||
@Override
|
||||
public RemoteBranch fun(GithubBranch branch) {
|
||||
return new RemoteBranch(user, branch.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean equals(@NotNull GithubRepo repo1, @Nullable GithubRepo repo2) {
|
||||
if (repo2 == null) {
|
||||
return false;
|
||||
}
|
||||
return StringUtil.equals(repo1.getUserName(), repo2.getUserName());
|
||||
}
|
||||
|
||||
private static boolean equals(@NotNull GithubFullPath repo1, @Nullable GithubRepo repo2) {
|
||||
if (repo2 == null) {
|
||||
return false;
|
||||
}
|
||||
return StringUtil.equals(repo1.getUser(), repo2.getUserName());
|
||||
}
|
||||
|
||||
private static void showDiffByRef(@NotNull final Project project,
|
||||
@Nullable final String ref,
|
||||
@NotNull final GitRepository gitRepository,
|
||||
@NotNull final String currentBranch,
|
||||
@NotNull final GithubAuthData auth,
|
||||
@NotNull final Map<String, String> forks,
|
||||
@NotNull final Set<RemoteBranch> branches,
|
||||
@Nullable final GithubRepo source) {
|
||||
if (ref == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
DiffInfo info = GithubUtil.computeValueInModal(project, "Collecting diff data...", new Convertor<ProgressIndicator, DiffInfo>() {
|
||||
@Override
|
||||
@Nullable
|
||||
public DiffInfo convert(ProgressIndicator indicator) {
|
||||
List<String> list = StringUtil.split(ref, ":");
|
||||
assert list.size() == 2 : ref;
|
||||
final String user = list.get(0);
|
||||
final String branch = list.get(1);
|
||||
|
||||
TargetBranchInfo targetBranchInfo;
|
||||
RemoteBranch remoteBranch = findRemoteBranch(branches, user, branch);
|
||||
if (remoteBranch != null && remoteBranch.getRemoteBranch() != null) {
|
||||
targetBranchInfo = getTargetBranchInfo(remoteBranch.getRemoteBranch());
|
||||
}
|
||||
else {
|
||||
GithubFullPath forkPath = findRepositoryByUser(project, user, forks, auth, source);
|
||||
if (forkPath == null) {
|
||||
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GithubNotifications.showErrorDialog(project, "Can't show diff", "Can't find fork for user '" + user + "'");
|
||||
}
|
||||
}, indicator.getModalityState());
|
||||
return null;
|
||||
}
|
||||
|
||||
targetBranchInfo = findRemote(branch, gitRepository, forkPath);
|
||||
if (targetBranchInfo == null) {
|
||||
final AtomicReference<Integer> responseRef = new AtomicReference<Integer>();
|
||||
ApplicationManager.getApplication().invokeAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
responseRef.set(GithubNotifications.showYesNoDialog(project, "Can't find remote", "Configure remote for '" + user + "'?"));
|
||||
}
|
||||
}, indicator.getModalityState());
|
||||
if (responseRef.get() != Messages.YES) {
|
||||
return null;
|
||||
}
|
||||
|
||||
targetBranchInfo = configureRemote(project, user, branch, gitRepository, forkPath);
|
||||
}
|
||||
}
|
||||
if (targetBranchInfo == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
GitFetchResult result = new GitFetcher(project, indicator, false)
|
||||
.fetch(gitRepository.getRoot(), targetBranchInfo.getRemote(), targetBranchInfo.getBranchNameForRemoteOperations());
|
||||
if (!result.isSuccess()) {
|
||||
GitFetcher.displayFetchResult(project, result, null, result.getErrors());
|
||||
return null;
|
||||
}
|
||||
|
||||
DiffInfo info = getDiffInfo(project, gitRepository, currentBranch, targetBranchInfo.getBranchNameForLocalOperations());
|
||||
if (info == null) {
|
||||
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GithubNotifications.showErrorDialog(project, "Can't show diff", "Can't get diff info");
|
||||
}
|
||||
}, indicator.getModalityState());
|
||||
return null;
|
||||
}
|
||||
return info;
|
||||
}
|
||||
});
|
||||
if (info == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GitCompareBranchesDialog dialog = new GitCompareBranchesDialog(project, info.getTo(), info.getFrom(), info.getInfo(), gitRepository);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private static TargetBranchInfo getTargetBranchInfo(@NotNull GitRemoteBranch remoteBranch) {
|
||||
return new TargetBranchInfo(remoteBranch.getRemote().getName(), remoteBranch.getNameForRemoteOperations());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static TargetBranchInfo findRemote(@NotNull String branch,
|
||||
@NotNull GitRepository gitRepository,
|
||||
@NotNull GithubFullPath forkPath) {
|
||||
GitRemote remote = GithubUtil.findGithubRemote(gitRepository, forkPath);
|
||||
return remote == null ? null : new TargetBranchInfo(remote.getName(), branch);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static TargetBranchInfo configureRemote(@NotNull Project project,
|
||||
@NotNull String user,
|
||||
@NotNull String branch,
|
||||
@NotNull GitRepository gitRepository,
|
||||
@NotNull GithubFullPath forkPath) {
|
||||
String url = GithubUrlUtil.getCloneUrl(forkPath);
|
||||
|
||||
if (GithubUtil.addGithubRemote(project, gitRepository, user, url)) {
|
||||
return new TargetBranchInfo(user, branch);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static RemoteBranch findRemoteBranch(@NotNull Set<RemoteBranch> branches, @NotNull String user, @NotNull String branch) {
|
||||
for (RemoteBranch remoteBranch : branches) {
|
||||
if (StringUtil.equalsIgnoreCase(user, remoteBranch.getUser()) && StringUtil.equals(branch, remoteBranch.getBranch())) {
|
||||
return remoteBranch;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static DiffInfo getDiffInfo(@NotNull final Project project,
|
||||
@NotNull final GitRepository repository,
|
||||
@NotNull final String currentBranch,
|
||||
@NotNull final String targetBranch) {
|
||||
try {
|
||||
List<GitCommit> commits = 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, Pair.<List<GitCommit>, List<GitCommit>>create(new ArrayList<GitCommit>(), commits));
|
||||
return new DiffInfo(info, currentBranch, targetBranch);
|
||||
}
|
||||
catch (VcsException e) {
|
||||
LOG.info(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class RemoteBranch {
|
||||
@NotNull final String myUser;
|
||||
@NotNull final String myBranch;
|
||||
|
||||
@Nullable final GitRemoteBranch myRemoteBranch;
|
||||
|
||||
private RemoteBranch(@NotNull String user, @NotNull String branch) {
|
||||
this(user, branch, null);
|
||||
}
|
||||
|
||||
public RemoteBranch(@NotNull String user, @NotNull String branch, @Nullable GitRemoteBranch localBranch) {
|
||||
myUser = user;
|
||||
myBranch = branch;
|
||||
myRemoteBranch = localBranch;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getReference() {
|
||||
return myUser + ":" + myBranch;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getUser() {
|
||||
return myUser;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getBranch() {
|
||||
return myBranch;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public GitRemoteBranch getRemoteBranch() {
|
||||
return myRemoteBranch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
RemoteBranch that = (RemoteBranch)o;
|
||||
|
||||
if (!StringUtil.equals(myUser, that.myUser)) return false;
|
||||
if (!StringUtil.equals(myBranch, that.myBranch)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = myUser.hashCode();
|
||||
result = 31 * result + myBranch.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static class GithubInfo {
|
||||
@NotNull private final GithubRepoDetailed myRepo;
|
||||
@NotNull private final GithubAuthData myAuthData;
|
||||
|
||||
private GithubInfo(@NotNull GithubAuthData authData, @NotNull GithubRepoDetailed repo) {
|
||||
myAuthData = authData;
|
||||
myRepo = repo;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public GithubRepoDetailed getRepo() {
|
||||
return myRepo;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public GithubAuthData getAuthData() {
|
||||
return myAuthData;
|
||||
}
|
||||
}
|
||||
|
||||
private 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;
|
||||
myTo = to;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public GitCommitCompareInfo getInfo() {
|
||||
return myInfo;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getFrom() {
|
||||
return myFrom;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getTo() {
|
||||
return myTo;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TargetBranchInfo {
|
||||
@NotNull private final String myRemote;
|
||||
@NotNull private final String myName;
|
||||
@NotNull private final String myNameAtRemote;
|
||||
|
||||
private TargetBranchInfo(@NotNull String remote, @NotNull String nameAtRemote) {
|
||||
myRemote = remote;
|
||||
myNameAtRemote = GitBranchUtil.stripRefsPrefix(nameAtRemote);
|
||||
myName = myRemote + "/" + myNameAtRemote;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getRemote() {
|
||||
return myRemote;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getBranchNameForLocalOperations() {
|
||||
return myName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getBranchNameForRemoteOperations() {
|
||||
return myNameAtRemote;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,623 @@
|
||||
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.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
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.*;
|
||||
import com.intellij.util.containers.HashMap;
|
||||
import git4idea.DialogManager;
|
||||
import git4idea.GitCommit;
|
||||
import git4idea.GitLocalBranch;
|
||||
import git4idea.GitRemoteBranch;
|
||||
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.GithubAuthenticationCanceledException;
|
||||
import org.jetbrains.plugins.github.ui.GithubSelectForkDialog;
|
||||
import org.jetbrains.plugins.github.util.GithubAuthData;
|
||||
import org.jetbrains.plugins.github.util.GithubNotifications;
|
||||
import org.jetbrains.plugins.github.util.GithubUrlUtil;
|
||||
import org.jetbrains.plugins.github.util.GithubUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* @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";
|
||||
|
||||
@NotNull private final Project myProject;
|
||||
@NotNull private final Git myGit;
|
||||
@NotNull private final GitRepository myGitRepository;
|
||||
@NotNull private final GithubFullPath myPath;
|
||||
@NotNull private final String myRemoteName;
|
||||
@NotNull private final String myRemoteUrl;
|
||||
@NotNull private final String myCurrentBranch;
|
||||
@NotNull private final GithubAuthData myAuth;
|
||||
|
||||
@NotNull private final Map<String, FutureTask<DiffInfo>> myDiffInfos;
|
||||
|
||||
private volatile GithubFullPath myForkPath;
|
||||
private volatile String myTargetRemote;
|
||||
|
||||
private GithubCreatePullRequestWorker(@NotNull Project project,
|
||||
@NotNull Git git,
|
||||
@NotNull GitRepository gitRepository,
|
||||
@NotNull GithubFullPath path,
|
||||
@NotNull String remoteName,
|
||||
@NotNull String remoteUrl,
|
||||
@NotNull String currentBranch,
|
||||
@NotNull GithubAuthData auth) {
|
||||
myProject = project;
|
||||
myGit = git;
|
||||
myGitRepository = gitRepository;
|
||||
myPath = path;
|
||||
myRemoteName = remoteName;
|
||||
myRemoteUrl = remoteUrl;
|
||||
myCurrentBranch = currentBranch;
|
||||
myAuth = auth;
|
||||
|
||||
myDiffInfos = new HashMap<String, FutureTask<DiffInfo>>();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Project getProject() {
|
||||
return myProject;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getCurrentBranch() {
|
||||
return myCurrentBranch;
|
||||
}
|
||||
|
||||
public boolean canShowDiff() {
|
||||
return myTargetRemote != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static GithubCreatePullRequestWorker createPullRequestWorker(@NotNull final Project project, @Nullable final VirtualFile file) {
|
||||
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;
|
||||
}
|
||||
|
||||
GithubAuthData auth;
|
||||
try {
|
||||
auth = GithubUtil
|
||||
.computeValueInModal(project, "Access to GitHub", new ThrowableConvertor<ProgressIndicator, GithubAuthData, IOException>() {
|
||||
@Override
|
||||
public GithubAuthData convert(ProgressIndicator indicator) throws IOException {
|
||||
return GithubUtil.getValidAuthDataFromConfig(project, indicator);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (GithubAuthenticationCanceledException e) {
|
||||
return null;
|
||||
}
|
||||
catch (IOException e) {
|
||||
GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new GithubCreatePullRequestWorker(project, git, gitRepository, path, remoteName, remoteUrl, currentBranch.getName(), auth);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public GithubTargetInfo setTarget(@NotNull final GithubFullPath forkPath) {
|
||||
try {
|
||||
GithubInfo info =
|
||||
GithubUtil.computeValueInModal(myProject, "Access to GitHub", new ThrowableConvertor<ProgressIndicator, GithubInfo, IOException>() {
|
||||
@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 AtomicReference<Integer> responseRef = new AtomicReference<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);
|
||||
}
|
||||
}
|
||||
|
||||
// load available branches
|
||||
List<String> branches = ContainerUtil.map(GithubApiUtil.getRepoBranches(myAuth, forkPath.getUser(), forkPath.getRepository()),
|
||||
new Function<GithubBranch, String>() {
|
||||
@Override
|
||||
public String fun(GithubBranch githubBranch) {
|
||||
return githubBranch.getName();
|
||||
}
|
||||
});
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
return new GithubInfo(branches, targetRemoteName);
|
||||
}
|
||||
});
|
||||
|
||||
myForkPath = forkPath;
|
||||
myTargetRemote = info.getTargetRemote();
|
||||
|
||||
myDiffInfos.clear();
|
||||
if (canShowDiff()) {
|
||||
for (final String branch : info.getBranches()) {
|
||||
myDiffInfos.put(branch, new FutureTask<DiffInfo>(new Callable<DiffInfo>() {
|
||||
@Override
|
||||
public DiffInfo call() throws Exception {
|
||||
return loadDiffInfo(myProject, myGitRepository, myCurrentBranch, myTargetRemote + "/" + branch);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return new GithubTargetInfo(info.getBranches());
|
||||
}
|
||||
catch (GithubAuthenticationCanceledException e) {
|
||||
return null;
|
||||
}
|
||||
catch (IOException e) {
|
||||
GithubNotifications.showErrorDialog(myProject, CANNOT_CREATE_PULL_REQUEST, e);
|
||||
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;
|
||||
}
|
||||
|
||||
GitCompareBranchesDialog dialog =
|
||||
new GitCompareBranchesDialog(myProject, info.getTo(), info.getFrom(), info.getInfo(), myGitRepository);
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public GithubFullPath showTargetDialog() {
|
||||
final GithubInfo2 info = getAvailableForksInModal(myProject, myGitRepository, myAuth, myPath);
|
||||
if (info == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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
|
||||
@Override
|
||||
public GithubFullPath convert(ProgressIndicator o) {
|
||||
return findRepositoryByUser(myProject, user, info.getForks(), myAuth, info.getSource());
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
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);
|
||||
if (info == null) {
|
||||
return true;
|
||||
}
|
||||
if (info.getInfo().getBranchToHeadCommits(myGitRepository).isEmpty()) {
|
||||
GithubNotifications.showWarningDialog(myProject, CANNOT_CREATE_PULL_REQUEST,
|
||||
"Can't create empty pull request: the branch" + getCurrentBranch() + " in fully merged to the branch " + targetBranch + ".");
|
||||
return false;
|
||||
}
|
||||
if (info.getInfo().getHeadToBranchCommits(myGitRepository).isEmpty()) {
|
||||
return GithubNotifications
|
||||
.showYesNoDialog(myProject, "The branch" + targetBranch + " in not fully merged to the branch " + getCurrentBranch(),
|
||||
"Do you want to proceed anyway?") == Messages.YES;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void performAction(@NotNull final String title, @NotNull final String description, @NotNull final String targetBranch) {
|
||||
@NotNull final Project project = myProject;
|
||||
|
||||
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(project, 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, myAuth, myForkPath, title, description, headBranch, targetBranch);
|
||||
if (request == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GithubNotifications
|
||||
.showInfoURL(project, "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);
|
||||
|
||||
if (GithubUtil.addGithubRemote(project, gitRepository, forkPath.getUser(), url)) {
|
||||
return forkPath.getUser();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static GithubPullRequest createPullRequest(@NotNull Project project,
|
||||
@NotNull GithubAuthData auth,
|
||||
@NotNull GithubFullPath targetRepo,
|
||||
@NotNull String title,
|
||||
@NotNull String description,
|
||||
@NotNull String head,
|
||||
@NotNull String base) {
|
||||
try {
|
||||
return GithubApiUtil.createPullRequest(auth, targetRepo.getUser(), targetRepo.getRepository(), title, description, head, base);
|
||||
}
|
||||
catch (IOException e) {
|
||||
GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DiffInfo getDiffInfo(@NotNull String branch) {
|
||||
try {
|
||||
FutureTask<DiffInfo> future = myDiffInfos.get(branch);
|
||||
if (future == null) {
|
||||
return null;
|
||||
}
|
||||
future.run();
|
||||
return future.get();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
LOG.error(e);
|
||||
return null;
|
||||
}
|
||||
catch (ExecutionException e) {
|
||||
LOG.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@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 void getDiffDescriptionInPooledThread(@NotNull final String branch, @NotNull final Consumer<DiffDescription> after) {
|
||||
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
after.consume(getDefaultDescriptionMessage(branch, getDiffInfo(branch), myGitRepository));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static DiffInfo loadDiffInfo(@NotNull final Project project,
|
||||
@NotNull final GitRepository repository,
|
||||
@NotNull final String currentBranch,
|
||||
@NotNull final String targetBranch) {
|
||||
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, Pair.create(commits1, commits2));
|
||||
return new DiffInfo(info, currentBranch, targetBranch);
|
||||
}
|
||||
catch (VcsException e) {
|
||||
LOG.info(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);
|
||||
}
|
||||
|
||||
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 GithubAuthData auth,
|
||||
@NotNull final GithubFullPath path) {
|
||||
try {
|
||||
return GithubUtil
|
||||
.computeValueInModal(project, "Access to GitHub", new ThrowableConvertor<ProgressIndicator, GithubInfo2, IOException>() {
|
||||
@Override
|
||||
public GithubInfo2 convert(ProgressIndicator indicator) throws IOException {
|
||||
final Set<GithubFullPath> forks = new HashSet<GithubFullPath>();
|
||||
|
||||
// GitHub
|
||||
GithubRepoDetailed repo = GithubApiUtil.getDetailedRepoInfo(auth, path.getUser(), path.getRepository());
|
||||
forks.add(path);
|
||||
if (repo.getParent() != null) {
|
||||
forks.add(repo.getParent().getFullPath());
|
||||
}
|
||||
if (repo.getSource() != null) {
|
||||
forks.add(repo.getSource().getFullPath());
|
||||
}
|
||||
|
||||
// Git
|
||||
forks.addAll(getAvailableForksFromGit(gitRepository));
|
||||
|
||||
GithubRepo forkTreeRoot = repo.getSource() == null ? repo : repo.getSource();
|
||||
return new GithubInfo2(forks, forkTreeRoot);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (GithubAuthenticationCanceledException e) {
|
||||
return null;
|
||||
}
|
||||
catch (IOException e) {
|
||||
GithubNotifications.showErrorDialog(project, CANNOT_CREATE_PULL_REQUEST, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return forks;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static GithubFullPath findRepositoryByUser(@NotNull Project project,
|
||||
@NotNull String user,
|
||||
@NotNull Set<GithubFullPath> forks,
|
||||
@NotNull GithubAuthData auth,
|
||||
@NotNull GithubRepo source) {
|
||||
for (GithubFullPath path : forks) {
|
||||
if (StringUtil.equalsIgnoreCase(user, path.getUser())) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
try {
|
||||
GithubRepo fork = GithubApiUtil.findForkByUser(auth, source.getUserName(), source.getName(), user);
|
||||
if (fork != null) {
|
||||
return fork.getFullPath();
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class GithubInfo {
|
||||
@NotNull private final List<String> myBranches;
|
||||
@Nullable private final String myTargetRemote;
|
||||
|
||||
private GithubInfo(@NotNull List<String> repo, @Nullable String targetRemote) {
|
||||
myBranches = repo;
|
||||
myTargetRemote = targetRemote;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<String> getBranches() {
|
||||
return myBranches;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getTargetRemote() {
|
||||
return myTargetRemote;
|
||||
}
|
||||
}
|
||||
|
||||
private static class GithubInfo2 {
|
||||
@NotNull private final Set<GithubFullPath> myForks;
|
||||
@NotNull private final GithubRepo mySource;
|
||||
|
||||
private GithubInfo2(@NotNull Set<GithubFullPath> forks, @NotNull GithubRepo source) {
|
||||
myForks = forks;
|
||||
mySource = source;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Set<GithubFullPath> getForks() {
|
||||
return myForks;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public GithubRepo getSource() {
|
||||
return mySource;
|
||||
}
|
||||
}
|
||||
|
||||
public static class GithubTargetInfo {
|
||||
@NotNull private final List<String> myBranches;
|
||||
|
||||
private GithubTargetInfo(@NotNull List<String> branches) {
|
||||
myBranches = branches;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<String> getBranches() {
|
||||
return myBranches;
|
||||
}
|
||||
}
|
||||
|
||||
private 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 DiffDescription {
|
||||
@NotNull private final String myBranch;
|
||||
@Nullable private final String myTitle;
|
||||
@Nullable private final String myDescription;
|
||||
|
||||
public DiffDescription(@NotNull String branch, @Nullable String title, @Nullable String description) {
|
||||
myBranch = branch;
|
||||
myTitle = title;
|
||||
myDescription = description;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getBranch() {
|
||||
return myBranch;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getTitle() {
|
||||
return myTitle;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getDescription() {
|
||||
return myDescription;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -597,10 +597,10 @@ public class GithubApiUtil {
|
||||
@NotNull String repo,
|
||||
@NotNull String title,
|
||||
@NotNull String description,
|
||||
@NotNull String from,
|
||||
@NotNull String onto) throws IOException {
|
||||
@NotNull String head,
|
||||
@NotNull String base) throws IOException {
|
||||
try {
|
||||
String request = gson.toJson(new GithubPullRequestRequest(title, description, from, onto));
|
||||
String request = gson.toJson(new GithubPullRequestRequest(title, description, head, base));
|
||||
return createDataFromRaw(fromJson(postRequest(auth, "/repos/" + user + "/" + repo + "/pulls", request), GithubPullRequestRaw.class),
|
||||
GithubPullRequest.class);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public class GithubFullPath {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String toString() {
|
||||
public String getFullName() {
|
||||
return myUserName + '/' + myRepositoryName;
|
||||
}
|
||||
|
||||
|
||||
+133
-32
@@ -15,43 +15,140 @@
|
||||
*/
|
||||
package org.jetbrains.plugins.github.ui;
|
||||
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.ui.ValidationInfo;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.Consumer;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
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.GithubProjectSettings;
|
||||
|
||||
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.regex.Pattern;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* @author Aleksey Pivovarov
|
||||
*/
|
||||
public class GithubCreatePullRequestDialog extends DialogWrapper {
|
||||
@NotNull private final GithubCreatePullRequestPanel myGithubCreatePullRequestPanel;
|
||||
@NotNull private static final Pattern GITHUB_REPO_PATTERN = Pattern.compile("[a-zA-Z0-9_.-]+:[a-zA-Z0-9_.-]+");
|
||||
@NotNull private final Project myProject;
|
||||
@NotNull private final GithubCreatePullRequestWorker myWorker;
|
||||
@NotNull private final GithubProjectSettings myProjectSettings;
|
||||
|
||||
public GithubCreatePullRequestDialog(@NotNull final Project project,
|
||||
@NotNull Collection<String> branches,
|
||||
@Nullable String suggestedBranch,
|
||||
@NotNull Consumer<String> showDiff) {
|
||||
super(project, true);
|
||||
myGithubCreatePullRequestPanel = new GithubCreatePullRequestPanel(showDiff);
|
||||
public GithubCreatePullRequestDialog(@NotNull GithubCreatePullRequestWorker worker) {
|
||||
super(worker.getProject(), true);
|
||||
myWorker = worker;
|
||||
|
||||
myProjectSettings = GithubProjectSettings.getInstance(myWorker.getProject());
|
||||
|
||||
myGithubCreatePullRequestPanel = new GithubCreatePullRequestPanel();
|
||||
|
||||
myGithubCreatePullRequestPanel.getShowDiffButton().addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
myWorker.showDiffDialog(myGithubCreatePullRequestPanel.getBranch());
|
||||
}
|
||||
});
|
||||
myGithubCreatePullRequestPanel.getSelectForkButton().addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
showTargetDialog();
|
||||
}
|
||||
});
|
||||
myGithubCreatePullRequestPanel.getBranchComboBox().addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setTitle("Create Pull Request - " + myWorker.getCurrentBranch());
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
GithubFullPath defaultForkPath = myProjectSettings.getCreatePullRequestDefaultRepo();
|
||||
if (defaultForkPath != null) {
|
||||
setTarget(defaultForkPath);
|
||||
}
|
||||
else {
|
||||
if (!showTargetDialog()) {
|
||||
close(CANCEL_EXIT_CODE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.show();
|
||||
}
|
||||
|
||||
private boolean showTargetDialog() {
|
||||
GithubFullPath forkPath = myWorker.showTargetDialog();
|
||||
if (forkPath == null) {
|
||||
return false;
|
||||
}
|
||||
return setTarget(forkPath);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private void updateBranches(@NotNull Collection<String> branches, @NotNull GithubFullPath forkPath) {
|
||||
myGithubCreatePullRequestPanel.setBranches(branches);
|
||||
|
||||
myProject = project;
|
||||
String configBranch = GithubProjectSettings.getInstance(myProject).getCreatePullRequestDefaultBranch();
|
||||
myGithubCreatePullRequestPanel.setSelectedBranch(configBranch != null ? configBranch : suggestedBranch);
|
||||
String configBranch = myProjectSettings.getCreatePullRequestDefaultBranch();
|
||||
if (configBranch != null) myGithubCreatePullRequestPanel.setSelectedBranch(configBranch);
|
||||
|
||||
setTitle("Create Pull Request");
|
||||
init();
|
||||
myGithubCreatePullRequestPanel.setForkName(forkPath.getFullName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doOKAction() {
|
||||
if (myWorker.checkAction(getTargetBranch())) {
|
||||
myProjectSettings.setCreatePullRequestDefaultBranch(getTargetBranch());
|
||||
myWorker.performAction(getRequestTitle(), getDescription(), getTargetBranch());
|
||||
super.doOKAction();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -77,47 +174,51 @@ public class GithubCreatePullRequestDialog extends DialogWrapper {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getRequestTitle() {
|
||||
private String getRequestTitle() {
|
||||
return myGithubCreatePullRequestPanel.getTitle();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getDescription() {
|
||||
private String getDescription() {
|
||||
return myGithubCreatePullRequestPanel.getDescription();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getTargetBranch() {
|
||||
private String getTargetBranch() {
|
||||
return myGithubCreatePullRequestPanel.getBranch();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doOKAction() {
|
||||
super.doOKAction();
|
||||
GithubProjectSettings.getInstance(myProject).setCreatePullRequestDefaultBranch(getTargetBranch());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected ValidationInfo doValidate() {
|
||||
if (StringUtil.isEmptyOrSpaces(getRequestTitle())) {
|
||||
return new ValidationInfo("Title can't be empty'", myGithubCreatePullRequestPanel.getTitleTextField());
|
||||
}
|
||||
|
||||
if (!GITHUB_REPO_PATTERN.matcher(getTargetBranch()).matches()) {
|
||||
return new ValidationInfo("Branch must be specified like 'username:branch'", myGithubCreatePullRequestPanel.getBranchEditor());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public void setRequestTitle(String title) {
|
||||
public void testSetRequestTitle(String title) {
|
||||
myGithubCreatePullRequestPanel.setTitle(title);
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public void setBranch(String branch) {
|
||||
myGithubCreatePullRequestPanel.setSelectedBranch(branch);
|
||||
public void testSetBranch(String branch) {
|
||||
myGithubCreatePullRequestPanel.setBranches(Collections.singleton(branch));
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public void testCreatePullRequest() {
|
||||
myWorker.performAction(getRequestTitle(), getDescription(), getTargetBranch());
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public void testSetTarget(@NotNull GithubFullPath forkPath) {
|
||||
GithubCreatePullRequestWorker.GithubTargetInfo forkInfo = myWorker.setTarget(forkPath);
|
||||
if (forkInfo == null) {
|
||||
doCancelAction();
|
||||
return;
|
||||
}
|
||||
myGithubCreatePullRequestPanel.setDiffEnabled(myWorker.canShowDiff());
|
||||
}
|
||||
}
|
||||
|
||||
+48
-18
@@ -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="4" column-count="3" 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="4" 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"/>
|
||||
@@ -10,15 +10,15 @@
|
||||
<children>
|
||||
<component id="3f5fd" class="com.intellij.ui.components.JBLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
<grid row="1" column="0" 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="Target branch:"/>
|
||||
<text value="Base branch:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="d2b10" class="com.intellij.ui.components.JBLabel">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
<grid row="2" column="0" 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="Title:"/>
|
||||
@@ -26,7 +26,7 @@
|
||||
</component>
|
||||
<component id="f7387" class="com.intellij.ui.components.JBLabel">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
<grid row="3" column="0" 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="Description:"/>
|
||||
@@ -34,29 +34,20 @@
|
||||
</component>
|
||||
<component id="996e9" class="javax.swing.JTextField" binding="myTitleTextField">
|
||||
<constraints>
|
||||
<grid row="1" column="1" row-span="1" col-span="2" 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="3" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="78a64" class="com.intellij.openapi.ui.ComboBox" binding="myBranchComboBox">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<editable value="true"/>
|
||||
<toolTipText value="Target branch in format 'user:branch', where user - owner of the target repository"/>
|
||||
</properties>
|
||||
</component>
|
||||
<scrollpane id="61e54" class="com.intellij.ui.components.JBScrollPane">
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="3" 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="4" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false">
|
||||
<minimum-size width="150" height="50"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<border type="empty"/>
|
||||
<children>
|
||||
<component id="21aa4" class="javax.swing.JTextArea" binding="myDescriptionTextArea">
|
||||
<constraints/>
|
||||
@@ -66,12 +57,51 @@
|
||||
</scrollpane>
|
||||
<component id="90c93" class="javax.swing.JButton" binding="myShowDiffButton">
|
||||
<constraints>
|
||||
<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"/>
|
||||
<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"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Show Diff"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="fb73f" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" 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="Base fork:"/>
|
||||
</properties>
|
||||
</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"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Select Base 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"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<editable value="false"/>
|
||||
<toolTipText value=""/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
|
||||
+71
-13
@@ -17,14 +17,15 @@ package org.jetbrains.plugins.github.ui;
|
||||
|
||||
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.Consumer;
|
||||
import com.intellij.util.ui.AsyncProcessIcon;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
|
||||
@@ -38,8 +39,13 @@ public class GithubCreatePullRequestPanel {
|
||||
private SortedComboBoxModel<String> myBranchModel;
|
||||
private JPanel myPanel;
|
||||
private JButton myShowDiffButton;
|
||||
private JButton mySelectForkButton;
|
||||
private JLabel myForkLabel;
|
||||
private AsyncProcessIcon myBusyIcon;
|
||||
|
||||
public GithubCreatePullRequestPanel(@NotNull final Consumer<String> showDiff) {
|
||||
private boolean myTitleDescriptionUserModified = false;
|
||||
|
||||
public GithubCreatePullRequestPanel() {
|
||||
myDescriptionTextArea.setBorder(BorderFactory.createEtchedBorder());
|
||||
myBranchModel = new SortedComboBoxModel<String>(new Comparator<String>() {
|
||||
@Override
|
||||
@@ -48,12 +54,15 @@ public class GithubCreatePullRequestPanel {
|
||||
}
|
||||
});
|
||||
myBranchComboBox.setModel(myBranchModel);
|
||||
myShowDiffButton.addActionListener(new ActionListener() {
|
||||
|
||||
DocumentListener userModifiedDocumentListener = new DocumentAdapter() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
showDiff.consume(getBranch());
|
||||
protected void textChanged(DocumentEvent e) {
|
||||
myTitleDescriptionUserModified = true;
|
||||
}
|
||||
});
|
||||
};
|
||||
myTitleTextField.getDocument().addDocumentListener(userModifiedDocumentListener);
|
||||
myDescriptionTextArea.getDocument().addDocumentListener(userModifiedDocumentListener);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -71,21 +80,24 @@ public class GithubCreatePullRequestPanel {
|
||||
return myBranchComboBox.getSelectedItem().toString();
|
||||
}
|
||||
|
||||
public void setDiffEnabled(boolean enabled) {
|
||||
myShowDiffButton.setEnabled(enabled);
|
||||
}
|
||||
|
||||
public void setSelectedBranch(@Nullable String branch) {
|
||||
if (StringUtil.isEmptyOrSpaces(branch)) {
|
||||
myBranchComboBox.setSelectedItem("");
|
||||
return;
|
||||
}
|
||||
|
||||
if (myBranchModel.indexOf(branch) == -1) {
|
||||
myBranchModel.add(branch);
|
||||
}
|
||||
myBranchComboBox.setSelectedItem(branch);
|
||||
}
|
||||
|
||||
public void setBranches(@NotNull Collection<String> branches) {
|
||||
myBranchModel.clear();
|
||||
myBranchModel.addAll(branches);
|
||||
if (branches.size() > 0) {
|
||||
myBranchComboBox.setSelectedIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
public JPanel getPanel() {
|
||||
@@ -97,15 +109,61 @@ public class GithubCreatePullRequestPanel {
|
||||
return myTitleTextField;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public JComponent getBranchEditor() {
|
||||
return myBranchComboBox;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public JComponent getTitleTextField() {
|
||||
return myTitleTextField;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
@NotNull
|
||||
public JButton getShowDiffButton() {
|
||||
return myShowDiffButton;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public JButton getSelectForkButton() {
|
||||
return mySelectForkButton;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ComboBox getBranchComboBox() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
private void createUIComponents() {
|
||||
myBusyIcon = new AsyncProcessIcon("Loading diff...");
|
||||
myBusyIcon.suspend();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2000-2013 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.ui;
|
||||
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.Convertor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
import org.jetbrains.plugins.github.api.GithubFullPath;
|
||||
import org.jetbrains.plugins.github.util.GithubNotifications;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Aleksey Pivovarov
|
||||
*/
|
||||
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;
|
||||
|
||||
|
||||
public GithubSelectForkDialog(@NotNull Project project,
|
||||
@NotNull Set<GithubFullPath> forks,
|
||||
@NotNull Convertor<String, GithubFullPath> 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();
|
||||
}
|
||||
}));
|
||||
|
||||
setTitle("Select Base Fork Repository");
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doOKAction() {
|
||||
GithubFullPath path = myCheckFork.convert(myPanel.getUser());
|
||||
if (path == null) {
|
||||
GithubNotifications.showErrorDialog(myProject, "Can't Find Repository", "Can't find fork for selected user");
|
||||
}
|
||||
else {
|
||||
myFullPath = path;
|
||||
super.doOKAction();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
return myPanel.getPanel();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public GithubFullPath getPath() {
|
||||
return myFullPath;
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public void testSetUser(@NotNull String user) {
|
||||
myPanel.setSelectedUser(user);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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.GithubSelectForkPanel">
|
||||
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="2" column-count="2" 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"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="42560" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" 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="Fork owner:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<vspacer id="406">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
<component id="b3c77" class="com.intellij.openapi.ui.ComboBox" binding="myComboBox">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<editable value="true"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2000-2013 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.ui;
|
||||
|
||||
import com.intellij.openapi.ui.ComboBox;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.ui.SortedComboBoxModel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* @author Aleksey Pivovarov
|
||||
*/
|
||||
public class GithubSelectForkPanel {
|
||||
private final SortedComboBoxModel<String> myModel;
|
||||
private JPanel myPanel;
|
||||
private ComboBox myComboBox;
|
||||
|
||||
public GithubSelectForkPanel() {
|
||||
myModel = new SortedComboBoxModel<String>(new Comparator<String>() {
|
||||
@Override
|
||||
public int compare(String o1, String o2) {
|
||||
return StringUtil.naturalCompare(o1, o2);
|
||||
}
|
||||
});
|
||||
|
||||
myComboBox.setModel(myModel);
|
||||
}
|
||||
|
||||
public void setUsers(@NotNull Collection<String> users) {
|
||||
myModel.clear();
|
||||
myModel.addAll(users);
|
||||
if (users.size() > 0) {
|
||||
myComboBox.setSelectedIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getUser() {
|
||||
return myComboBox.getSelectedItem().toString();
|
||||
}
|
||||
|
||||
public void setSelectedUser(@Nullable String user) {
|
||||
if (StringUtil.isEmptyOrSpaces(user)) {
|
||||
return;
|
||||
}
|
||||
|
||||
myComboBox.setSelectedItem(user);
|
||||
}
|
||||
|
||||
public JPanel getPanel() {
|
||||
return myPanel;
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import com.intellij.openapi.components.*;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.plugins.github.api.GithubFullPath;
|
||||
|
||||
/**
|
||||
* @author Aleksey Pivovarov
|
||||
@@ -38,8 +39,14 @@ public class GithubProjectSettings implements PersistentStateComponent<GithubPro
|
||||
myState = state;
|
||||
}
|
||||
|
||||
public static GithubProjectSettings getInstance(@NotNull Project project) {
|
||||
return ServiceManager.getService(project, GithubProjectSettings.class);
|
||||
}
|
||||
|
||||
public static class State {
|
||||
@Nullable public String CREATE_PULL_REQUEST_DEFAULT_BRANCH = null;
|
||||
@Nullable public String CREATE_PULL_REQUEST_DEFAULT_REPO_USER = null;
|
||||
@Nullable public String CREATE_PULL_REQUEST_DEFAULT_REPO_NAME = null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -51,7 +58,16 @@ public class GithubProjectSettings implements PersistentStateComponent<GithubPro
|
||||
myState.CREATE_PULL_REQUEST_DEFAULT_BRANCH = branch;
|
||||
}
|
||||
|
||||
public static GithubProjectSettings getInstance(@NotNull Project project) {
|
||||
return ServiceManager.getService(project, GithubProjectSettings.class);
|
||||
@Nullable
|
||||
public GithubFullPath getCreatePullRequestDefaultRepo() {
|
||||
if (myState.CREATE_PULL_REQUEST_DEFAULT_REPO_USER == null || myState.CREATE_PULL_REQUEST_DEFAULT_REPO_NAME == null) {
|
||||
return null;
|
||||
}
|
||||
return new GithubFullPath(myState.CREATE_PULL_REQUEST_DEFAULT_REPO_USER, myState.CREATE_PULL_REQUEST_DEFAULT_REPO_NAME);
|
||||
}
|
||||
|
||||
public void setCreatePullRequestDefaultRepo(@NotNull GithubFullPath repo) {
|
||||
myState.CREATE_PULL_REQUEST_DEFAULT_REPO_USER = repo.getUser();
|
||||
myState.CREATE_PULL_REQUEST_DEFAULT_REPO_NAME = repo.getRepository();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import com.intellij.notification.NotificationType;
|
||||
*/
|
||||
public class GithubCreatePullRequestTest extends GithubCreatePullRequestTestBase {
|
||||
public void testSimple() throws Exception {
|
||||
registerDefaultCreatePullRequestDialogHandler(myLogin1 + ":master");
|
||||
registerDefaultCreatePullRequestDialogHandler("master", myLogin1);
|
||||
|
||||
GithubCreatePullRequestAction.createPullRequest(myProject, myProjectRoot);
|
||||
|
||||
@@ -32,17 +32,8 @@ public class GithubCreatePullRequestTest extends GithubCreatePullRequestTestBase
|
||||
}
|
||||
|
||||
public void testParent() throws Exception {
|
||||
registerDefaultCreatePullRequestDialogHandler(myLogin2 + ":file2");
|
||||
|
||||
GithubCreatePullRequestAction.createPullRequest(myProject, myProjectRoot);
|
||||
|
||||
checkNotification(NotificationType.INFORMATION, "Successfully created pull request", null);
|
||||
checkRemoteConfigured();
|
||||
checkLastCommitPushed();
|
||||
}
|
||||
|
||||
public void testCommitRef1() throws Exception {
|
||||
registerDefaultCreatePullRequestDialogHandler(myLogin1 + ":refs/heads/master");
|
||||
registerDefaultCreatePullRequestDialogHandler("file2", myLogin2);
|
||||
addRemote(myLogin2);
|
||||
|
||||
GithubCreatePullRequestAction.createPullRequest(myProject, myProjectRoot);
|
||||
|
||||
|
||||
+13
-3
@@ -24,8 +24,11 @@ import git4idea.actions.GitInit;
|
||||
import git4idea.commands.Git;
|
||||
import git4idea.repo.GitRepository;
|
||||
import git4idea.test.TestDialogHandler;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.plugins.github.api.GithubFullPath;
|
||||
import org.jetbrains.plugins.github.test.GithubTest;
|
||||
import org.jetbrains.plugins.github.ui.GithubCreatePullRequestDialog;
|
||||
import org.jetbrains.plugins.github.util.GithubUrlUtil;
|
||||
import org.jetbrains.plugins.github.util.GithubUtil;
|
||||
|
||||
import java.util.Random;
|
||||
@@ -38,6 +41,7 @@ import static git4idea.test.GitExecutor.git;
|
||||
*/
|
||||
public abstract class GithubCreatePullRequestTestBase extends GithubTest {
|
||||
protected static final String PROJECT_URL = "https://github.com/ideatest1/PullRequestTest";
|
||||
protected static final String PROJECT_NAME = "PullRequestTest";
|
||||
protected String BRANCH_NAME;
|
||||
|
||||
@Override
|
||||
@@ -67,12 +71,14 @@ public abstract class GithubCreatePullRequestTestBase extends GithubTest {
|
||||
}
|
||||
}
|
||||
|
||||
protected void registerDefaultCreatePullRequestDialogHandler(final String branch) {
|
||||
protected void registerDefaultCreatePullRequestDialogHandler(@NotNull final String branch, @NotNull final String user) {
|
||||
myDialogManager.registerDialogHandler(GithubCreatePullRequestDialog.class, new TestDialogHandler<GithubCreatePullRequestDialog>() {
|
||||
@Override
|
||||
public int handleDialog(GithubCreatePullRequestDialog dialog) {
|
||||
dialog.setRequestTitle(BRANCH_NAME);
|
||||
dialog.setBranch(branch);
|
||||
dialog.testSetRequestTitle(BRANCH_NAME);
|
||||
dialog.testSetBranch(branch);
|
||||
dialog.testSetTarget(new GithubFullPath(user, PROJECT_NAME));
|
||||
dialog.testCreatePullRequest();
|
||||
return DialogWrapper.OK_EXIT_CODE;
|
||||
}
|
||||
});
|
||||
@@ -84,6 +90,10 @@ public abstract class GithubCreatePullRequestTestBase extends GithubTest {
|
||||
GitInit.refreshAndConfigureVcsMappings(myProject, myProjectRoot, myProjectRoot.getPath());
|
||||
}
|
||||
|
||||
protected void addRemote(@NotNull String user) {
|
||||
git("remote add somename " + GithubUrlUtil.getCloneUrl(new GithubFullPath(user, PROJECT_NAME)));
|
||||
}
|
||||
|
||||
protected void createBranch() {
|
||||
git("branch " + BRANCH_NAME);
|
||||
git("checkout " + BRANCH_NAME);
|
||||
|
||||
Reference in New Issue
Block a user