Merge branch 'executable_validator'

This commit is contained in:
Kirill Likhodedov
2011-10-27 14:31:07 +04:00
22 changed files with 279 additions and 471 deletions
@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.execution.util.ExecutableDialog">
<grid id="27dc6" binding="myCenterPanel" layout-manager="GridLayoutManager" row-count="3" column-count="1" 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="564" height="400"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="2e454" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myExecutablePath" custom-create="true">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="7" anchor="1" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="400" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<component id="a1421" class="javax.swing.JLabel" binding="myInfoLabel">
<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=""/>
</properties>
</component>
<vspacer id="4c273">
<constraints>
<grid row="2" 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>
</children>
</grid>
</form>
@@ -1,76 +0,0 @@
// Copyright 2008-2010 Victor Iacoban
//
// 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 com.intellij.execution.util;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.ui.DocumentAdapter;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
public class ExecutableDialog extends DialogWrapper {
private JPanel myCenterPanel;
private TextFieldWithBrowseButton myExecutablePath;
private JLabel myInfoLabel;
private final ExecutableValidator myExecutableValidator;
private String myErrorNotValidText;
public ExecutableDialog(Project project, ExecutableValidator executableValidator) {
super(project, false);
myExecutableValidator = executableValidator;
myErrorNotValidText = executableValidator.getDialogErrorText();
init();
updateUI();
setTitle(myExecutableValidator.getDialogTitle());
myInfoLabel.setText(myExecutableValidator.getDialogDescription());
}
@Override
protected JComponent createCenterPanel() {
return myCenterPanel;
}
// disable OK button if current executable is not valid
private void updateUI() {
final String path = myExecutablePath.getText();
if (myExecutableValidator.isExecutableValid(path)) {
setErrorText(null);
getOKAction().setEnabled(true);
} else {
setErrorText(myErrorNotValidText);
getOKAction().setEnabled(false);
}
}
private void createUIComponents() {
myExecutablePath = new TextFieldWithBrowseButton();
myExecutablePath.setText(myExecutableValidator.getCurrentExecutable());
FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor();
myExecutablePath.addBrowseFolderListener(myExecutableValidator.getFileChooserTitle(), myExecutableValidator.getFileChooserDescription(),
null, descriptor);
myExecutablePath.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(DocumentEvent e) {
updateUI();
}
});
}
public String getPath() {
return myExecutablePath.getText();
}
}
@@ -1,229 +0,0 @@
/*
* Copyright 2000-2010 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 com.intellij.execution.util;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.CapturingProcessHandler;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.event.HyperlinkEvent;
/**
* Validates the supplied external executable.
* Shows notification or a dialog to fix it.
* @author Kirill Likhodedov
*/
public abstract class ExecutableValidator {
private Notification myNotification;
protected final Project myProject;
private final String myNotificationGroupId;
private String myNotificationErrorTitle = "Executable not valid";
private String myNotificationErrorDescription = "You haven't configured a valid executable. <a href=''>Fix</a>";
private String myDialogTitle = "Executable";
private String myDialogDescription = "Specify the full path to the executable";
private String myDialogErrorText = "It doesn't appear to be a valid executable";
private String myFileChooserTitle = "Executable";
private String myFileChooserDescription = "Specify the full path to the executable";
public ExecutableValidator(Project project, String notificationGroupId) {
myProject = project;
myNotificationGroupId = notificationGroupId;
}
/**
* Configures notification and dialog by setting text messages and titles specific to the whoever uses the validator.
* @param notificationErrorTitle title of the notification about not valid executable.
* @param notificationErrorDescription description of this notification with a link to fix it (link action is defined by
* {@link #notificationHyperlinkUpdate(com.intellij.notification.Notification, javax.swing.event.HyperlinkEvent)}
* @param dialogTitle
* @param dialogDescription
* @param dialogErrorText
* @param fileChooserTitle
*/
public void setMessagesAndTitles(String notificationErrorTitle, String notificationErrorDescription,
String dialogTitle, String dialogDescription, String dialogErrorText,
String fileChooserTitle, String fileChooserDescription) {
myNotificationErrorTitle = notificationErrorTitle;
myNotificationErrorDescription = notificationErrorDescription;
myDialogTitle = dialogTitle;
myDialogDescription = dialogDescription;
myDialogErrorText = dialogErrorText;
myFileChooserTitle = fileChooserTitle;
myFileChooserDescription = fileChooserDescription;
}
/**
* Returns current executable persisted in the settings or elsewhere.
* @return Path to current executable.
*/
protected abstract String getCurrentExecutable();
/**
* Override this to save new (correct) executable path entered in the dialog.
* @param executable
*/
protected void saveCurrentExecutable(String executable) {
}
/**
* Returns the configurable page for the vcs containing settings for executable.
* This configurable will be opened if user presses "Fix" on the notification about invalid executable.
* May return null - in this case the settings dialog won't be displayed.
*/
@Nullable
protected Configurable getConfigurable(Project project) {
return null;
}
/**
* Returns true if the supplied executable is valid.
* Default implementation: try to execute the given executable and test if output returned errors.
* @param executable Path to executable.
* @return true if process with the supplied executable completed without errors and with exit code 0.
*/
@SuppressWarnings({"MethodMayBeStatic"})
public boolean isExecutableValid(String executable) {
try {
GeneralCommandLine commandLine = new GeneralCommandLine();
commandLine.setExePath(executable);
CapturingProcessHandler handler = new CapturingProcessHandler(commandLine.createProcess(), CharsetToolkit.getDefaultSystemCharset());
ProcessOutput result = handler.runProcess(60 * 1000);
return !result.isTimeout() && (result.getExitCode() == 0) && result.getStderr().isEmpty();
} catch (Throwable e) {
return false;
}
}
public boolean showDialog() {
final ExecutableDialog dialog = new ExecutableDialog(myProject, this);
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
dialog.show();
}
});
if (dialog.isOK()) {
saveCurrentExecutable(dialog.getPath());
return true;
} else { // user pressed cancel
showExecutableNotConfiguredNotification();
return false;
}
}
public boolean checkExecutableAndShowDialogIfNeeded() {
if (!isExecutableValid(getCurrentExecutable())) {
return showDialog();
}
return true;
}
/**
* Shows a notification about not configured executable with a link to the Settings to fix it.
* Expires the notification if user fixes the path from the opened Settings dialog.
* Makes sure that there is always only one notification about the problem in the stack of notifications.
*/
public void showExecutableNotConfiguredNotification() {
if (ApplicationManager.getApplication().isUnitTestMode() || ApplicationManager.getApplication().isHeadlessEnvironment()) {
return;
}
final Notification newNotification = new Notification(myNotificationGroupId, myNotificationErrorTitle,
myNotificationErrorDescription, NotificationType.ERROR,
new NotificationListener() {
public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
notificationHyperlinkUpdate(notification, event);
}
});
// expire() needs to be called from AWT thread.
// we also want to be sure that previous notification expires before new one is shown (and assigned to myNotification).
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
if (myNotification != null && !myNotification.isExpired()) {
// don't store this notification twice, but redisplay it again so that popup appears.
myNotification.expire();
}
myNotification = newNotification;
Notifications.Bus.notify(myNotification, myProject.isDefault() ? null : myProject);
}
});
}
/**
* The action which will be executed when the user presses "Fix" link in the notification description.
* By default it opens the Settings dialog on the page correspondent to the supplied configurable (e.g. Git configurable)
* and expires the notification after dialog is closed if executable was fixed in that dialog.
* One may override the method.
* Parameters are the same as in {@link com.intellij.notification.NotificationListener#hyperlinkUpdate(com.intellij.notification.Notification, javax.swing.event.HyperlinkEvent)}
*/
protected void notificationHyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
Configurable configurable = getConfigurable(myProject);
if (configurable != null) {
ShowSettingsUtil.getInstance().showSettingsDialog(myProject, configurable);
if (isExecutableValid(getCurrentExecutable())) {
notification.expire();
}
}
}
/**
* Checks if executable is valid and displays the notification if not.
* @return true if executable was valid, false - if not valid (and notification is shown in that case).
*/
public boolean checkExecutableAndNotifyIfNeeded() {
if (!isExecutableValid(getCurrentExecutable())) {
showExecutableNotConfiguredNotification();
return false;
}
return true;
}
String getDialogTitle() {
return myDialogTitle;
}
String getDialogErrorText() {
return myDialogErrorText;
}
String getFileChooserDescription() {
return myFileChooserDescription;
}
String getFileChooserTitle() {
return myFileChooserTitle;
}
public String getDialogDescription() {
return myDialogDescription;
}
}
@@ -0,0 +1,147 @@
/*
* Copyright 2000-2011 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 com.intellij.execution;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.CapturingProcessHandler;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.notification.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.wm.ToolWindowId;
import org.jetbrains.annotations.NotNull;
import javax.swing.event.HyperlinkEvent;
/**
* Validates the given external executable.
* If it is not valid, shows notification to fix it.
* Notification balloon in the upper right corner is shown only the first time. After it is shown in the toolwindow group.
* This is done, because it is hard to discover whether notification balloon is showing or not, and showing several balloons about a single
* problem is a bad idea.
*
* @author Kirill Likhodedov
*/
public abstract class ExecutableValidator {
public static final String NOTIFICATION_ID = "External Executable Critical Failures";
private final NotificationGroup myNotificationGroup = new NotificationGroup(NOTIFICATION_ID, NotificationDisplayType.TOOL_WINDOW, true,
ToolWindowId.VCS);
private final Project myProject;
private final String myNotificationErrorTitle;
private final String myNotificationErrorDescription;
/**
* Configures notification and dialog by setting text messages and titles specific to the whoever uses the validator.
* @param notificationErrorTitle title of the notification about not valid executable.
* @param notificationErrorDescription description of this notification with a link to fix it (link action is defined by
* {@link #showSettingsAndExpireIfFixed(com.intellij.notification.Notification)}
*/
public ExecutableValidator(Project project, String notificationErrorTitle, String notificationErrorDescription) {
myProject = project;
myNotificationErrorTitle = notificationErrorTitle;
myNotificationErrorDescription = notificationErrorDescription;
}
/**
* @return path to current executable.
*/
protected abstract String getCurrentExecutable();
/**
* @return the settings configurable where the executable is shown and can be fixed.
* This configurable will be opened if user presses "Fix" on the notification about invalid executable.
*/
@NotNull
protected abstract Configurable getConfigurable();
/**
* Returns true if the supplied executable is valid.
* Default implementation: try to execute the given executable and test if output returned errors.
* This can take a long time since it spawns external process.
* @param executable Path to executable.
* @return true if process with the supplied executable completed without errors and with exit code 0.
*/
protected boolean isExecutableValid(String executable) {
try {
GeneralCommandLine commandLine = new GeneralCommandLine();
commandLine.setExePath(executable);
CapturingProcessHandler handler = new CapturingProcessHandler(commandLine.createProcess(), CharsetToolkit.getDefaultSystemCharset());
ProcessOutput result = handler.runProcess(60 * 1000);
return !result.isTimeout() && (result.getExitCode() == 0) && result.getStderr().isEmpty();
} catch (Throwable e) {
return false;
}
}
/**
* Shows a notification about not configured executable with a link to the Settings to fix it.
* Expires the notification if user fixes the path from the opened Settings dialog.
* Makes sure that there is always only one notification about the problem in the stack of notifications.
*/
private void showExecutableNotConfiguredNotification() {
if (ApplicationManager.getApplication().isUnitTestMode() || ApplicationManager.getApplication().isHeadlessEnvironment()) {
return;
}
myNotificationGroup.createNotification("", prepareDescription(), NotificationType.ERROR,
new NotificationListener() {
public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
showSettingsAndExpireIfFixed(notification);
}
}
).notify(myProject.isDefault() ? null : myProject);
}
@NotNull
private String prepareDescription() {
String executable = getCurrentExecutable();
if (executable.isEmpty()) {
return String.format("<b>%s</b>%s", myNotificationErrorTitle, myNotificationErrorDescription);
} else {
return String.format("<b>%s:</b> <code>%s</code><br/>%s", myNotificationErrorTitle, executable, myNotificationErrorDescription);
}
}
private void showSettingsAndExpireIfFixed(@NotNull Notification notification) {
Configurable configurable = getConfigurable();
ShowSettingsUtil.getInstance().showSettingsDialog(myProject, configurable);
if (isExecutableValid(getCurrentExecutable())) {
notification.expire();
}
}
/**
* Checks if executable is valid and displays the notification if not.
* @return true if executable was valid, false - if not valid (and notification was shown in that case).
*/
public boolean checkExecutableAndNotifyIfNeeded() {
if (!isExecutableValid(getCurrentExecutable())) {
showExecutableNotConfiguredNotification();
return false;
}
return true;
}
public boolean isExecutableValid() {
return isExecutableValid(getCurrentExecutable());
}
}
@@ -127,12 +127,10 @@ public class EventLog implements Notifications {
if (content.startsWith("<") && !content.startsWith("<a ")) {
showMore = true;
}
else {
if (StringUtil.isNotEmpty(mainText)) {
mainText += ": ";
}
mainText += content;
if (StringUtil.isNotEmpty(mainText)) {
mainText += ": ";
}
mainText += content;
}
int nlIndex = eolIndex(mainText);
+2 -2
View File
@@ -49,9 +49,9 @@ public class Git {
* // TODO use common format
*/
public static void init(Project project, VirtualFile root) throws VcsException {
GitLineHandler h = new GitLineHandler(project, root, GitCommand.INIT);
GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.INIT);
h.setNoSSH(true);
GitHandlerUtil.runInCurrentThread(h, null);
h.run();
if (!h.errors().isEmpty()) {
throw h.errors().get(0);
}
+16 -15
View File
@@ -128,10 +128,10 @@ public class GitVcs extends AbstractVcs<CommittedChangeList> {
private final GitCommitAndPushExecutor myCommitAndPushExecutor;
private GitReferenceTracker myReferenceTracker;
private boolean isActivated; // If true, the vcs was activated
private GitExecutableValidator myExecutableValidator;
private final GitExecutableValidator myExecutableValidator;
private GitBranchWidget myBranchWidget;
private GitVersion myVersion; // version of Git which this plugin uses.
private GitVersion myVersion = GitVersion.NULL; // version of Git which this plugin uses.
@Nullable
public static GitVcs getInstance(Project project) {
@@ -169,6 +169,7 @@ public class GitVcs extends AbstractVcs<CommittedChangeList> {
myCommitAndPushExecutor = new GitCommitAndPushExecutor(myCheckinEnvironment);
myReferenceTracker = new GitReferenceTracker(myProject, this, myReferenceListeners.getMulticaster());
myTaskQueue = new BackgroundTaskQueue(myProject, GitBundle.getString("task.queue.title"));
myExecutableValidator = new GitExecutableValidator(myProject, this);
}
@@ -345,11 +346,11 @@ public class GitVcs extends AbstractVcs<CommittedChangeList> {
@Override
protected void activate() {
isActivated = true;
myExecutableValidator = new GitExecutableValidator(myProject);
if (!ApplicationManager.getApplication().isHeadlessEnvironment()) {
myExecutableValidator.checkExecutableAndShowDialogIfNeeded();
checkVersion();
if (myExecutableValidator.checkExecutableAndNotifyIfNeeded()) {
checkVersion();
}
}
if (!myProject.isDefault() && myRootTracker == null) {
@@ -483,23 +484,22 @@ public class GitVcs extends AbstractVcs<CommittedChangeList> {
VcsBalloonProblemNotifier.showOverVersionControlView(myProject, message, MessageType.ERROR);
}
} catch (Exception e) {
final String reason = (e.getCause() != null ? e.getCause() : e).getMessage();
String message = GitBundle.message("vcs.unable.to.run.git", executable, reason);
if (!myProject.isDefault()) {
showMessage(message, ConsoleViewContentType.SYSTEM_OUTPUT.getAttributes());
if (getExecutableValidator().checkExecutableAndNotifyIfNeeded()) { // check executable before notifying error
final String reason = (e.getCause() != null ? e.getCause() : e).getMessage();
String message = GitBundle.message("vcs.unable.to.run.git", executable, reason);
if (!myProject.isDefault()) {
showMessage(message, ConsoleViewContentType.SYSTEM_OUTPUT.getAttributes());
}
VcsBalloonProblemNotifier.showOverVersionControlView(myProject, message, MessageType.ERROR);
}
VcsBalloonProblemNotifier.showOverVersionControlView(myProject, message, MessageType.ERROR);
}
}
/**
* @return the version number of Git, which is used by IDEA. <code>null</code> can be returned if the GitVcs hasn't yet activated.
* @return the version number of Git, which is used by IDEA. Or {@link GitVersion#NULL} if version info is unavailable yet.
*/
@Nullable
@NotNull
public GitVersion getVersion() {
if (myVersion == null) {
checkVersion();
}
return myVersion;
}
@@ -590,6 +590,7 @@ public class GitVcs extends AbstractVcs<CommittedChangeList> {
return isActivated;
}
@NotNull
public GitExecutableValidator getExecutableValidator() {
return myExecutableValidator;
}
@@ -74,10 +74,13 @@ public class GitInit extends DumbAwareAction {
}
}
GitVcs vcs = GitVcs.getInstance(project);
try {
Git.init(project, root);
} catch (VcsException ex) {
GitUIUtil.showOperationErrors(project, Collections.singleton(ex), "git init");
if (vcs == null || vcs.getExecutableValidator().isExecutableValid()) {
GitUIUtil.showOperationErrors(project, Collections.singleton(ex), "git init");
}
return;
}
@@ -89,8 +92,7 @@ public class GitInit extends DumbAwareAction {
}
final String path = root.equals(baseDir) ? "" : root.getPath();
final Project finalProject = project;
GitVcs.getInstance(project).runInBackground(new Task.Backgroundable(finalProject, GitBundle.getString("common.refreshing")) {
GitVcs.runInBackground(new Task.Backgroundable(finalProject, GitBundle.getString("common.refreshing")) {
public void run(@NotNull ProgressIndicator indicator) {
refreshAndConfigureVcsMappings(finalProject, root, path);
}
@@ -19,6 +19,7 @@ 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.util.io.FileUtil;
import com.intellij.openapi.vcs.CheckoutProvider;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
@@ -26,6 +27,7 @@ import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.vcsUtil.VcsUtil;
import git4idea.Git;
import git4idea.GitVcs;
import git4idea.actions.BasicAction;
import git4idea.commands.GitCommand;
import git4idea.commands.GitSimpleHandler;
@@ -102,15 +104,27 @@ public class GitCheckoutProvider implements CheckoutProvider {
}
private static boolean doClone(ProgressIndicator indicator, Project project, String directoryName, String parentDirectory, String sourceRepositoryURL) {
final VirtualFile root = mkdir(project, directoryName, parentDirectory);
if (root == null) { return false; }
if (!init(project, root)) { return false; }
if (!addRemote(project, root, sourceRepositoryURL)) { return false; }
if (!fetch(project, root, indicator)) { return false; }
return checkout(project, root);
File dir = mkdir(project, directoryName, parentDirectory);
if (dir == null) {
return false;
}
VirtualFile root = VcsUtil.getVirtualFileWithRefresh(dir);
if (root != null &&
init(project, root) &&
addRemote(project, root, sourceRepositoryURL) &&
fetch(project, root, indicator) &&
checkout(project, root)) {
return true;
}
cleanup(dir);
return false;
}
private static @Nullable VirtualFile mkdir(Project project, String directoryName, String parentDirectory) {
private static void cleanup(@NotNull File dir) {
FileUtil.delete(dir);
}
private static @Nullable File mkdir(Project project, String directoryName, String parentDirectory) {
final File dir = new File(parentDirectory, directoryName);
if (dir.exists()) {
GitUIUtil.notifyError(project, "Couldn't clone", "Directory <code>" + dir + "</code> already exists.");
@@ -120,8 +134,7 @@ public class GitCheckoutProvider implements CheckoutProvider {
GitUIUtil.notifyError(project, "Couldn't clone", "Can't create directory <code>" + dir + "</code>");
return null;
}
return VcsUtil.getVirtualFileWithRefresh(dir);
return dir;
}
private static boolean init(Project project, VirtualFile root) {
@@ -129,7 +142,10 @@ public class GitCheckoutProvider implements CheckoutProvider {
Git.init(project, root);
} catch (VcsException e) {
LOG.info("init ", e);
GitUIUtil.notifyError(project, "Couldn't clone", "Couldn't <code>git init</code> in <code>" + root.getPresentableUrl() + "</code>", true, e);
GitVcs vcs = GitVcs.getInstance(project);
if (vcs == null || vcs.getExecutableValidator().isExecutableValid()) { // invalid executable will be notified in GitHandler.start()
GitUIUtil.notifyError(project, "Couldn't clone", "Couldn't <code>git init</code> in <code>" + root.getPresentableUrl() + "</code>", true, e);
}
return false;
}
return true;
@@ -325,19 +325,4 @@ public class GitCloneDialog extends DialogWrapper {
return "reference.VersionControl.Git.CloneRepository";
}
/**
* Prepare check repository handler. To do this ls-remote command is executed and attempts to match
* master tag. This will likely return only single entry or none, if there is no master
* branch. Stdout output is ignored. Stderr is used to construct exception message and shown
* in error message box if exit is negative.
*
* @param project the project
* @param url the url to check
* @return a simple handler that does the task
*/
private static GitSimpleHandler checkRepository(Project project, final String url) {
GitSimpleHandler handler = new GitSimpleHandler(project, new File("."), GitCommand.LS_REMOTE);
handler.addParameters(url, "master");
return handler;
}
}
@@ -412,6 +412,7 @@ public abstract class GitHandler {
startHandlingStreams();
}
catch (Throwable t) {
myVcs.getExecutableValidator().checkExecutableAndNotifyIfNeeded();
cleanupEnv();
myListeners.getMulticaster().startFailed(t);
}
@@ -15,16 +15,18 @@
*/
package git4idea.config;
import com.intellij.execution.ExecutableValidator;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.CapturingProcessHandler;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.execution.util.ExecutableValidator;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vfs.CharsetToolkit;
import git4idea.GitVcs;
import git4idea.i18n.GitBundle;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Project service that is used to check whether currently set git executable is valid (just calls 'git version' and parses the output),
@@ -35,30 +37,25 @@ public class GitExecutableValidator extends ExecutableValidator {
private GitVcs myVcs;
public GitExecutableValidator(Project project) {
super(project, GitVcs.NOTIFICATION_GROUP_ID.getDisplayId());
myVcs = GitVcs.getInstance(project);
setMessagesAndTitles(GitBundle.message("git.executable.notification.title"),
GitBundle.message("git.executable.notification.description"),
GitBundle.message("git.executable.dialog.title"),
GitBundle.message("git.executable.dialog.description"),
GitBundle.message("git.executable.dialog.error"),
GitBundle.message("git.executable.filechooser.title"),
GitBundle.message("git.executable.filechooser.description"));
public GitExecutableValidator(@NotNull Project project, @Nullable GitVcs vcs) {
super(project,
GitBundle.message("git.executable.notification.title"), GitBundle.message("git.executable.notification.description"));
myVcs = vcs;
}
@Override
protected String getCurrentExecutable() {
return myVcs.getAppSettings().getPathToGit();
return GitVcsApplicationSettings.getInstance().getPathToGit();
}
@NotNull
@Override
protected Configurable getConfigurable() {
return myVcs.getConfigurable();
}
@Override
protected Configurable getConfigurable(Project project) {
return myVcs == null ? null : myVcs.getConfigurable();
}
@Override
public boolean isExecutableValid(String executable) {
public boolean isExecutableValid(@NotNull String executable) {
try {
GeneralCommandLine commandLine = new GeneralCommandLine();
commandLine.setExePath(executable);
@@ -71,12 +68,6 @@ public class GitExecutableValidator extends ExecutableValidator {
}
}
@Override
protected void saveCurrentExecutable(String executable) {
myVcs.getAppSettings().setPathToGit(executable);
myVcs.checkVersion();
}
/**
* Checks if git executable is valid. If not (which is a common case for low-level vcs exceptions), shows the
* notification. Otherwise throws the exception.
@@ -21,7 +21,7 @@ import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.util.SystemInfo;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
import java.io.File;
@@ -60,6 +60,7 @@ public class GitVcsApplicationSettings implements PersistentStateComponent<GitVc
/**
* @return the default executable name depending on the platform
*/
@NotNull
public String defaultGit() {
if (myState.myPathToGit == null) {
String[] paths;
@@ -86,7 +87,7 @@ public class GitVcsApplicationSettings implements PersistentStateComponent<GitVc
return myState.myPathToGit;
}
@Nullable
@NotNull
public String getPathToGit() {
return myState.myPathToGit == null ? defaultGit() : myState.myPathToGit;
}
@@ -45,7 +45,13 @@ public final class GitVersion implements Comparable<GitVersion> {
* Type UNDEFINED means that the type doesn't matter in certain condition.
*/
public static enum Type {
UNDEFINED, UNIX, MSYS, CYGWIN
UNIX,
MSYS,
CYGWIN,
/** The type doesn't matter or couldn't be detected. */
UNDEFINED,
/** Information about Git version is unavailable because the GitVcs hasn't fully initialized yet, or because Git executable is invalid. */
NULL
}
/**
@@ -53,13 +59,19 @@ public final class GitVersion implements Comparable<GitVersion> {
*/
public static final GitVersion MIN = new GitVersion(1, 6, 0, 0);
/**
* Special version with a special Type which indicates, that Git version information is unavailable.
* Probably, because of invalid executable, or when GitVcs hasn't fully initialized yet.
*/
public static final GitVersion NULL = new GitVersion(0, 0, 0, 0, Type.NULL);
private static final Pattern FORMAT = Pattern.compile("git version (\\d+)\\.(\\d+)\\.(\\d+)(?:\\.(\\d+))?(?:\\.(msysgit))?[\\.\\-\\d\\w]*", Pattern.CASE_INSENSITIVE);
private static final Logger LOG = Logger.getInstance(GitVersion.class.getName());
private final int myMajor; // Major version number
private final int myMinor; // Minor version number
private final int myRevision; // Revision number
private final int myPatchLevel; // Patch level
private final int myMajor;
private final int myMinor;
private final int myRevision;
private final int myPatchLevel;
private final Type myType;
private final int myHashCode;
@@ -145,7 +157,7 @@ public final class GitVersion implements Comparable<GitVersion> {
* @return true if the version is supported by the plugin
*/
public boolean isSupported() {
return compareTo(MIN) >= 0;
return getType() != Type.NULL && compareTo(MIN) >= 0;
}
/**
@@ -183,8 +195,13 @@ public final class GitVersion implements Comparable<GitVersion> {
* (msys git 1.7.3).compareTo(cygwin git 1.7.3) == 0
* BUT
* (msys git 1.7.3).equals(cygwin git 1.7.3) == false
*
* {@link GitVersion#NULL} is less than any other not-NULL version.
*/
public int compareTo(final GitVersion o) {
public int compareTo(@NotNull GitVersion o) {
if (o.getType() == Type.NULL) {
return (getType() == Type.NULL ? 0 : 1);
}
int d = myMajor - o.myMajor;
if (d != 0) {
return d;
@@ -224,4 +241,8 @@ public final class GitVersion implements Comparable<GitVersion> {
return myType;
}
public boolean isNull() {
return getType() == Type.NULL;
}
}
@@ -183,17 +183,21 @@ public class GitHistoryProvider implements VcsHistoryProvider, VcsCacheableHisto
final VcsAbstractHistorySession emptySession = createSession(path, Collections.<VcsFileRevision>emptyList(), null);
partner.reportCreatedEmptySession(emptySession);
final GitExecutableValidator validator = GitVcs.getInstance(myProject).getExecutableValidator();
GitHistoryUtils.history(myProject, path, null, new Consumer<GitFileRevision>() {
public void consume(GitFileRevision gitFileRevision) {
partner.acceptRevision(gitFileRevision);
}
}, new Consumer<VcsException>() {
public void consume(VcsException e) {
if (validator.checkExecutableAndNotifyIfNeeded()) {
partner.reportException(e);
try {
GitHistoryUtils.history(myProject, path, null, new Consumer<GitFileRevision>() {
public void consume(GitFileRevision gitFileRevision) {
partner.acceptRevision(gitFileRevision);
}
}
});
}, new Consumer<VcsException>() {
public void consume(VcsException e) {
if (validator.checkExecutableAndNotifyIfNeeded()) {
partner.reportException(e);
}
}
});
} catch (VcsException e) {
validator.showNotificationOrThrow(e);
}
}
/**
@@ -233,7 +233,7 @@ public class SelectRepositoryAndShowLogAction extends AnAction {
@Override
public void onSuccess() {
if (myVersion.myVersion == null) return;
if (myVersion.myVersion.isNull()) return;
if (myProject.isDisposed()) return;
new MyDialog(myProject, myCorrectRoots).show();
}
@@ -249,12 +249,8 @@ public class SelectRepositoryAndShowLogAction extends AnAction {
@Override
public void run(@NotNull ProgressIndicator indicator) {
GitVcs vcs = GitVcs.getInstance(myProject);
if (vcs == null) { return; }
myVersion = vcs.getVersion();
if (myVersion == null) {
vcs.checkVersion();
}
myVersion = vcs.getVersion();
if (myVersion == null) return;
}
}
@@ -495,8 +495,8 @@ executable.error.description = Git couldn't be started. Probably the path to Git
git.commit.message.empty=Please specify commit message
git.commit.message.empty.title=Commit message is empty
git.executable.notification.title=Git executable problem
git.executable.notification.description=Git couldn't be started. Probably the path to Git executable is not valid. <a href="">Fix it.</a>
git.executable.notification.title=Can't start Git
git.executable.notification.description=Probably the path to Git executable is not valid. <a href=''>Fix it.</a>
git.executable.dialog.title=Git executable
git.executable.dialog.description=Specify the full path to Git executable
git.executable.dialog.error=It doesn't appear to be a valid Git executable
@@ -112,9 +112,6 @@ public class GitChangeProvider implements ChangeProvider {
return false;
}
final GitVersion version = vcs.getVersion();
if (version == null) {
return false;
}
return GitVersionSpecialty.KNOWS_STATUS_PORCELAIN.existsIn(version);
}
@@ -66,7 +66,7 @@ public class GitUIUtil {
desc += "<hr/>" + StringUtil.join(messages, "<br/>");
}
NotificationGroup group = important ? GitVcs.IMPORTANT_ERROR_NOTIFICATION : GitVcs.NOTIFICATION_GROUP_ID;
group.createNotification(title, desc, type, null).notify(project);
group.createNotification(title, desc, type, null).notify(project.isDefault() ? null : project);
}
public static void notifyMessage(Project project, @Nullable String title, @Nullable String description, NotificationType type, boolean important, @Nullable Collection<VcsException> errors) {
@@ -55,13 +55,8 @@ hg4idea.commit.partial.merge.message=<html>Partial commits are not allowed durin
hg4idea.commit.partial.merge.title=Partial Merge Commit
hg4idea.commit.error.messageEmpty=Please provide a commit message
hg4idea.executable.notification.title=Mercurial executable is not valid
hg4idea.executable.notification.description=You haven't configured a valid executable. Mercurial integration won't work. <a href=''>Fix</a>
hg4idea.executable.dialog.title=Mercurial executable
hg4idea.executable.dialog.description=Specify the full path to hg executable
hg4idea.executable.dialog.error=It doesn't appear to be a valid hg executable
hg4idea.executable.filechooser.title=Mercurial executable
hg4idea.executable.filechooser.description=Specify the full path to hg executable
hg4idea.executable.notification.title=Can't start Mercurial
hg4idea.executable.notification.description=Probably the path to hg executable is not valid. <a href=''>Fix it.</a>
hg4idea.init.dialog.title=Create Mercurial repository
hg4idea.init.dialog.incorrect.path=The specified path is incorrect
@@ -12,25 +12,21 @@
// limitations under the License.
package org.zmlx.hg4idea;
import com.intellij.execution.util.ExecutableValidator;
import com.intellij.execution.ExecutableValidator;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.zmlx.hg4idea.command.HgVersionCommand;
public class HgExecutableValidator extends ExecutableValidator {
private final HgVcs myVcs;
public HgExecutableValidator(Project project) {
super(project, HgVcs.NOTIFICATION_GROUP_ID);
myVcs = HgVcs.getInstance(project);
setMessagesAndTitles(HgVcsMessages.message("hg4idea.executable.notification.title"),
HgVcsMessages.message("hg4idea.executable.notification.description"),
HgVcsMessages.message("hg4idea.executable.dialog.title"),
HgVcsMessages.message("hg4idea.executable.dialog.description"),
HgVcsMessages.message("hg4idea.executable.dialog.error"),
HgVcsMessages.message("hg4idea.executable.filechooser.title"),
HgVcsMessages.message("hg4idea.executable.filechooser.description"));
public HgExecutableValidator(@NotNull Project project, @NotNull HgVcs vcs) {
super(project,
HgVcsMessages.message("hg4idea.executable.notification.title"),
HgVcsMessages.message("hg4idea.executable.notification.description"));
myVcs = vcs;
}
@Override
@@ -38,9 +34,10 @@ public class HgExecutableValidator extends ExecutableValidator {
return myVcs.getHgExecutable();
}
@NotNull
@Override
protected Configurable getConfigurable(Project project) {
return myVcs == null ? null : myVcs.getConfigurable();
protected Configurable getConfigurable() {
return myVcs.getConfigurable();
}
@Override
@@ -48,9 +45,4 @@ public class HgExecutableValidator extends ExecutableValidator {
return new HgVersionCommand().isValid(executable, myVcs.getGlobalSettings().isRunViaBash());
}
@Override
protected void saveCurrentExecutable(String executable) {
myVcs.getGlobalSettings().setHgExecutable(executable);
}
}
@@ -260,7 +260,7 @@ public class HgVcs extends AbstractVcs<CommittedChangeList> {
public void activate() {
// validate hg executable on start
if (!ApplicationManager.getApplication().isUnitTestMode()) {
getExecutableValidator().checkExecutableAndShowDialogIfNeeded();
getExecutableValidator().checkExecutableAndNotifyIfNeeded();
}
// status bar
@@ -372,7 +372,7 @@ public class HgVcs extends AbstractVcs<CommittedChangeList> {
public HgExecutableValidator getExecutableValidator() {
synchronized (myExecutableValidatorLock) {
if (myExecutableValidator == null) {
myExecutableValidator = new HgExecutableValidator(myProject);
myExecutableValidator = new HgExecutableValidator(myProject, this);
}
return myExecutableValidator;
}