blob: 3ac118fe6b7f0ea2fe81017448a79b5e0a390a90 [file] [log] [blame]
package com.intellij.remoteServer.util;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.*;
import com.intellij.openapi.vcs.changes.ui.CommitChangeListDialog;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.remoteServer.agent.util.CloudAgentLoggingHandler;
import com.intellij.remoteServer.agent.util.CloudGitApplication;
import com.intellij.remoteServer.configuration.deployment.DeploymentSource;
import com.intellij.remoteServer.runtime.deployment.DeploymentLogManager;
import com.intellij.remoteServer.runtime.deployment.DeploymentTask;
import com.intellij.util.ArrayUtil;
import com.intellij.util.concurrency.Semaphore;
import git4idea.GitPlatformFacade;
import git4idea.GitUtil;
import git4idea.actions.GitInit;
import git4idea.commands.*;
import git4idea.repo.GitRemote;
import git4idea.repo.GitRepository;
import git4idea.repo.GitRepositoryManager;
import git4idea.util.GitFileUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* @author michael.golubev
*/
public class CloudGitDeploymentRuntime extends CloudDeploymentRuntime {
private static final Logger LOG = Logger.getInstance("#" + CloudGitDeploymentRuntime.class.getName());
private static final String COMMIT_MESSAGE = "Deploy";
private static final CommitSession NO_COMMIT = new CommitSession() {
@Nullable
@Override
public JComponent getAdditionalConfigurationUI() {
return null;
}
@Nullable
@Override
public JComponent getAdditionalConfigurationUI(Collection<Change> changes, String commitMessage) {
return null;
}
@Override
public boolean canExecute(Collection<Change> changes, String commitMessage) {
return true;
}
@Override
public void execute(Collection<Change> changes, String commitMessage) {
}
@Override
public void executionCanceled() {
}
@Override
public String getHelpId() {
return null;
}
};
private static final List<CommitExecutor> ourCommitExecutors = Arrays.asList(
new CommitExecutor() {
@Nls
@Override
public String getActionText() {
return "Commit and Push";
}
@NotNull
@Override
public CommitSession createCommitSession() {
return CommitSession.VCS_COMMIT;
}
},
new CommitExecutorBase() {
@Nls
@Override
public String getActionText() {
return "Push without Commit";
}
@NotNull
@Override
public CommitSession createCommitSession() {
return NO_COMMIT;
}
@Override
public boolean areChangesRequired() {
return false;
}
}
);
private final GitRepositoryManager myGitRepositoryManager;
private final Git myGit;
private final VirtualFile myContentRoot;
private final File myRepositoryRootFile;
private final String myDefaultRemoteName;
private final ChangeListManagerEx myChangeListManager;
private String myRemoteName;
private final String myCloudName;
private GitRepository myRepository;
public CloudGitDeploymentRuntime(CloudMultiSourceServerRuntimeInstance serverRuntime,
DeploymentSource source,
File repositoryRoot,
DeploymentTask<? extends CloudDeploymentNameConfiguration> task,
DeploymentLogManager logManager,
String defaultRemoteName,
String cloudName) throws ServerRuntimeException {
super(serverRuntime, source, task, logManager);
myDefaultRemoteName = defaultRemoteName;
myCloudName = cloudName;
myRepositoryRootFile = repositoryRoot;
VirtualFile contentRoot = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(myRepositoryRootFile);
LOG.assertTrue(contentRoot != null, "Repository root is not found");
myContentRoot = contentRoot;
Project project = getProject();
myGitRepositoryManager = GitUtil.getRepositoryManager(project);
myGit = ServiceManager.getService(Git.class);
if (myGit == null) {
throw new ServerRuntimeException("Can't initialize GIT");
}
GitPlatformFacade gitPlatformFacade = ServiceManager.getService(GitPlatformFacade.class);
myChangeListManager = gitPlatformFacade.getChangeListManager(project);
}
@Override
public CloudGitApplication deploy() throws ServerRuntimeException {
CloudGitApplication application = findOrCreateApplication();
deployApplication(application);
return application;
}
private void deployApplication(CloudGitApplication application) throws ServerRuntimeException {
boolean firstDeploy = findRepository() == null;
GitRepository repository = findOrCreateRepository();
addOrResetGitRemote(application, repository);
if (firstDeploy) {
add();
commit();
return;
}
final LocalChangeList activeChangeList = myChangeListManager.getDefaultChangeList();
if (activeChangeList == null) {
add();
commit();
return;
}
Collection<Change> changes = activeChangeList.getChanges();
final List<Change> relevantChanges = new ArrayList<Change>();
for (Change change : changes) {
if (isRelevant(change.getBeforeRevision()) || isRelevant(change.getAfterRevision())) {
relevantChanges.add(change);
}
}
final Semaphore commitSemaphore = new Semaphore();
commitSemaphore.down();
final Ref<Boolean> commitSucceeded = new Ref<Boolean>(false);
Boolean commitStarted = runOnEdt(new Computable<Boolean>() {
@Override
public Boolean compute() {
return CommitChangeListDialog.commitChanges(getProject(),
relevantChanges,
activeChangeList,
ourCommitExecutors,
false,
COMMIT_MESSAGE,
new CommitResultHandler() {
@Override
public void onSuccess(@NotNull String commitMessage) {
commitSucceeded.set(true);
commitSemaphore.up();
}
@Override
public void onFailure() {
commitSemaphore.up();
}
},
false);
}
});
if (commitStarted != null && commitStarted) {
commitSemaphore.waitFor();
if (!commitSucceeded.get()) {
repository.update();
throw new ServerRuntimeException("Commit failed");
}
}
else {
throw new ServerRuntimeException("Deploy interrupted");
}
repository.update();
pushApplication(application);
}
private boolean isRelevant(ContentRevision contentRevision) throws ServerRuntimeException {
if (contentRevision == null) {
return false;
}
GitRepository repository = getRepository();
VirtualFile affectedFile = contentRevision.getFile().getVirtualFile();
return affectedFile != null && VfsUtilCore.isAncestor(repository.getRoot(), affectedFile, false);
}
private static <T> T runOnEdt(final Computable<T> computable) {
final Ref<T> result = new Ref<T>();
ApplicationManager.getApplication().invokeAndWait(new Runnable() {
@Override
public void run() {
result.set(computable.compute());
}
}, ModalityState.any());
return result.get();
}
public boolean isDeployed() throws ServerRuntimeException {
return findApplication() != null;
}
public CloudGitApplication findOrCreateApplication() throws ServerRuntimeException {
CloudGitApplication application = findApplication();
if (application == null) {
application = createApplication();
}
return application;
}
public void addOrResetGitRemote(CloudGitApplication application, GitRepository repository) throws ServerRuntimeException {
String gitUrl = application.getGitUrl();
if (myRemoteName == null) {
for (GitRemote gitRemote : repository.getRemotes()) {
if (gitRemote.getUrls().contains(gitUrl)) {
myRemoteName = gitRemote.getName();
return;
}
}
}
GitRemote gitRemote = GitUtil.findRemoteByName(repository, getRemoteName());
if (gitRemote == null) {
addGitRemote(application);
}
else if (!gitRemote.getUrls().contains(gitUrl)) {
resetGitRemote(application);
}
}
public GitRepository findOrCreateRepository() throws ServerRuntimeException {
GitRepository repository = findRepository();
if (repository == null) {
getLoggingHandler().println("Initializing git repository...");
GitCommandResult gitInitResult = getGit().init(getProject(), getRepositoryRoot(), createGitLineHandlerListener());
checkGitResult(gitInitResult);
refreshApplicationRepository();
repository = getRepository();
}
return repository;
}
public void downloadExistingApplication() throws ServerRuntimeException {
new CloneJobWithRemote().cloneToModule(getApplication().getGitUrl());
getRepository().update();
refreshContentRoot();
}
protected Git getGit() {
return myGit;
}
protected VirtualFile getRepositoryRoot() {
return myContentRoot;
}
protected File getRepositoryRootFile() {
return myRepositoryRootFile;
}
protected static void checkGitResult(GitCommandResult commandResult) throws ServerRuntimeException {
if (!commandResult.success()) {
Throwable exception = commandResult.getException();
if (exception != null) {
LOG.info(exception);
throw new ServerRuntimeException(exception);
}
else {
throw new ServerRuntimeException(commandResult.getErrorOutputAsJoinedString());
}
}
}
protected GitLineHandlerListener createGitLineHandlerListener() {
return new GitLineHandlerAdapter() {
@Override
public void onLineAvailable(String line, Key outputType) {
getLoggingHandler().println(line);
}
};
}
@Override
protected CloudAgentLoggingHandler getLoggingHandler() {
return super.getLoggingHandler();
}
protected void addGitRemote(CloudGitApplication application) throws ServerRuntimeException {
doGitRemote(getRemoteName(), application, "add", CloudBundle.getText("failed.add.remote", getRemoteName()));
}
protected void resetGitRemote(CloudGitApplication application) throws ServerRuntimeException {
doGitRemote(getRemoteName(), application, "set-url", CloudBundle.getText("failed.reset.remote", getRemoteName()));
}
protected void doGitRemote(String remoteName,
CloudGitApplication application,
String subCommand,
String failMessage)
throws ServerRuntimeException {
try {
final GitSimpleHandler handler = new GitSimpleHandler(getProject(), myContentRoot, GitCommand.REMOTE);
handler.setSilent(false);
handler.addParameters(subCommand, remoteName, application.getGitUrl());
handler.run();
getRepository().update();
if (handler.getExitCode() != 0) {
throw new ServerRuntimeException(failMessage);
}
}
catch (VcsException e) {
throw new ServerRuntimeException(e);
}
}
@Nullable
protected GitRepository findRepository() {
if (myRepository != null) {
return myRepository;
}
myRepository = myGitRepositoryManager.getRepositoryForRoot(myContentRoot);
return myRepository;
}
protected void refreshApplicationRepository() {
GitInit.refreshAndConfigureVcsMappings(getProject(), getRepositoryRoot(), getRepositoryRootFile().getAbsolutePath());
}
protected void pushApplication(@NotNull CloudGitApplication application) throws ServerRuntimeException {
push(application, getRepository(), getRemoteName());
}
protected void push(@NotNull CloudGitApplication application, @NotNull GitRepository repository, @NotNull String remote)
throws ServerRuntimeException {
GitCommandResult gitPushResult
= getGit().push(repository, remote, application.getGitUrl(), "master:master", createGitLineHandlerListener());
checkGitResult(gitPushResult);
}
@NotNull
protected GitRepository getRepository() throws ServerRuntimeException {
GitRepository repository = findRepository();
if (repository == null) {
throw new ServerRuntimeException("Unable to find GIT repository for module root: " + myContentRoot);
}
return repository;
}
protected void fetch() throws ServerRuntimeException {
final VirtualFile contentRoot = getRepositoryRoot();
GitRepository repository = getRepository();
final GitLineHandler fetchHandler = new GitLineHandler(getProject(), contentRoot, GitCommand.FETCH);
fetchHandler.setUrl(getApplication().getGitUrl());
fetchHandler.setSilent(false);
fetchHandler.addParameters(getRemoteName());
fetchHandler.addLineListener(createGitLineHandlerListener());
performRemoteGitTask(fetchHandler, CloudBundle.getText("fetching.application", getCloudName()));
repository.update();
}
protected void add() throws ServerRuntimeException {
try {
GitFileUtils.addFiles(getProject(), myContentRoot, myContentRoot);
}
catch (VcsException e) {
throw new ServerRuntimeException(e);
}
}
protected void commit() throws ServerRuntimeException {
try {
if (GitUtil.hasLocalChanges(true, getProject(), myContentRoot)) {
GitSimpleHandler handler = new GitSimpleHandler(getProject(), myContentRoot, GitCommand.COMMIT);
handler.setSilent(false);
handler.setStdoutSuppressed(false);
handler.addParameters("-m", COMMIT_MESSAGE);
handler.endOptions();
handler.run();
}
}
catch (VcsException e) {
throw new ServerRuntimeException(e);
}
}
protected void performRemoteGitTask(final GitLineHandler handler, String title) throws ServerRuntimeException {
final GitTask task = new GitTask(getProject(), handler, title);
task.setProgressAnalyzer(new GitStandardProgressAnalyzer());
final Semaphore semaphore = new Semaphore();
semaphore.down();
final Ref<ServerRuntimeException> errorRef = new Ref<ServerRuntimeException>();
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
task.execute(false, false, new GitTaskResultHandlerAdapter() {
@Override
protected void run(GitTaskResult result) {
super.run(result);
semaphore.up();
}
@Override
protected void onFailure() {
for (VcsException error : handler.errors()) {
getLoggingHandler().println(error.toString());
if (errorRef.isNull()) {
errorRef.set(new ServerRuntimeException(error));
}
}
}
});
}
});
semaphore.waitFor();
if (!errorRef.isNull()) {
throw errorRef.get();
}
}
protected void refreshContentRoot() {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
getRepositoryRoot().refresh(false, true);
}
});
}
public void fetchAndRefresh() throws ServerRuntimeException {
fetch();
refreshContentRoot();
}
private String getRemoteName() {
if (myRemoteName == null) {
myRemoteName = myDefaultRemoteName;
}
return myRemoteName;
}
private String getCloudName() {
return myCloudName;
}
protected CloudGitApplication findApplication() throws ServerRuntimeException {
return getAgentTaskExecutor().execute(new Computable<CloudGitApplication>() {
@Override
public CloudGitApplication compute() {
return getDeployment().findApplication();
}
});
}
protected CloudGitApplication getApplication() throws ServerRuntimeException {
CloudGitApplication application = findApplication();
if (application == null) {
throw new ServerRuntimeException("Can't find the application: " + getApplicationName());
}
return application;
}
protected CloudGitApplication createApplication() throws ServerRuntimeException {
return getAgentTaskExecutor().execute(new Computable<CloudGitApplication>() {
@Override
public CloudGitApplication compute() {
return getDeployment().createApplication();
}
});
}
public CloudGitApplication findApplication4Repository() throws ServerRuntimeException {
final List<String> repositoryUrls = new ArrayList<String>();
for (GitRemote remote : getRepository().getRemotes()) {
for (String url : remote.getUrls()) {
repositoryUrls.add(url);
}
}
return getAgentTaskExecutor().execute(new Computable<CloudGitApplication>() {
@Override
public CloudGitApplication compute() {
return getDeployment().findApplication4Repository(ArrayUtil.toStringArray(repositoryUrls));
}
});
}
public class CloneJob {
public File cloneToTemp(String gitUrl) throws ServerRuntimeException {
File cloneDir;
try {
cloneDir = FileUtil.createTempDirectory("cloud", "clone");
}
catch (IOException e) {
throw new ServerRuntimeException(e);
}
File cloneDirParent = cloneDir.getParentFile();
String cloneDirName = cloneDir.getName();
doClone(cloneDirParent, cloneDirName, gitUrl);
return cloneDir;
}
public void cloneToModule(String gitUrl) throws ServerRuntimeException {
File cloneDir = cloneToTemp(gitUrl);
try {
FileUtil.copyDir(cloneDir, getRepositoryRootFile());
}
catch (IOException e) {
throw new ServerRuntimeException(e);
}
refreshApplicationRepository();
}
public void doClone(File cloneDirParent, String cloneDirName, String gitUrl) throws ServerRuntimeException {
GitCommandResult gitCloneResult
= getGit().clone(getProject(), cloneDirParent, gitUrl, cloneDirName, createGitLineHandlerListener());
checkGitResult(gitCloneResult);
}
}
public class CloneJobWithRemote extends CloneJob {
public void doClone(File cloneDirParent, String cloneDirName, String gitUrl) throws ServerRuntimeException {
final GitLineHandler handler = new GitLineHandler(getProject(), cloneDirParent, GitCommand.CLONE);
handler.setSilent(false);
handler.setStdoutSuppressed(false);
handler.setUrl(gitUrl);
handler.addParameters("--progress");
handler.addParameters(gitUrl);
handler.addParameters(cloneDirName);
handler.addParameters("-o");
handler.addParameters(getRemoteName());
handler.addLineListener(createGitLineHandlerListener());
performRemoteGitTask(handler, CloudBundle.getText("cloning.existing.application", getCloudName()));
}
}
}