IDEA-59345 Option to run Mercurial via 'bash -cl'

1. Added a checkbox to the HgConfigurationProjectPanel;
note that in HgConfigurationProjectPanel.validate we take current value of the checkbox, because on may simultaneously change 2 values (radio button of hg executable and bash-option checkbox).
2. Added a flag to HgGlobalSettings and a passthrough method to HgProjectSettings.
3. Added a flag to the ShellCommand: executing in the 'bash -cl' mode. Note that we escape bash control characters (like |<$) and provide --login option to catch .bash_profile.
This commit is contained in:
Kirill Likhodedov
2010-11-15 09:58:18 +03:00
parent df762f1e85
commit d33bbda022
9 changed files with 87 additions and 29 deletions
@@ -39,7 +39,7 @@ public class HgExecutableValidator extends ExecutableValidator {
@Override
public boolean isExecutableValid(String executable) {
return new HgVersionCommand().isValid(executable);
return new HgVersionCommand().isValid(executable, myVcs.getGlobalSettings().isRunViaBash());
}
@Override
@@ -13,7 +13,6 @@
package org.zmlx.hg4idea;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.util.containers.HashMap;
@@ -29,18 +28,32 @@ import java.util.Map;
name = "HgGlobalSettings",
storages = @Storage(id = "HgGlobalSettings", file = "$APP_CONFIG$/vcs.xml")
)
public class HgGlobalSettings implements PersistentStateComponent<HgGlobalSettings> {
public class HgGlobalSettings implements PersistentStateComponent<HgGlobalSettings.State> {
private static final String HG = HgVcs.HG_EXECUTABLE_FILE_NAME;
private static final int FIVE_MINUTES = 300;
private String hgExecutable = HG;
private String myHgExecutable = HG;
// visited URL -> list of logins for this URL. Passwords are remembered in the PasswordSafe.
private Map<String, List<String>> myRememberedUrls = new HashMap<String, List<String>>();
private boolean myRunViaBash;
public static HgGlobalSettings getInstance() {
return ServiceManager.getService(HgGlobalSettings.class);
public static class State {
public String hgExecutable;
public boolean runViaBash;
}
public State getState() {
State s = new State();
s.hgExecutable = myHgExecutable;
s.runViaBash = myRunViaBash;
return s;
}
public void loadState(State state) {
myHgExecutable = state.hgExecutable;
myRunViaBash = state.runViaBash;
}
/**
@@ -77,31 +90,31 @@ public class HgGlobalSettings implements PersistentStateComponent<HgGlobalSettin
}
public String getHgExecutable() {
return hgExecutable;
return myHgExecutable;
}
public void setHgExecutable(String hgExecutable) {
this.hgExecutable = hgExecutable;
this.myHgExecutable = hgExecutable;
}
public boolean isAutodetectHg() {
return HG.equals(hgExecutable);
return HG.equals(myHgExecutable);
}
public void enableAutodetectHg() {
hgExecutable = HG;
myHgExecutable = HG;
}
public static int getIncomingCheckIntervalSeconds() {
return FIVE_MINUTES;
}
public HgGlobalSettings getState() {
return this;
public boolean isRunViaBash() {
return myRunViaBash;
}
public void loadState(HgGlobalSettings state) {
hgExecutable = state.hgExecutable;
public void setRunViaBash(boolean runViaBash) {
myRunViaBash = runViaBash;
}
}
@@ -22,9 +22,14 @@ import com.intellij.openapi.components.Storage;
)
public class HgProjectSettings implements PersistentStateComponent<HgProjectSettings.State> {
private final HgGlobalSettings myAppSettings;
private boolean myCheckIncoming = true;
private boolean myCheckOutgoing = true;
public HgProjectSettings(HgGlobalSettings appSettings) {
myAppSettings = appSettings;
}
public static class State {
public boolean myCheckIncoming = true;
public boolean myCheckOutgoing = true;
@@ -59,19 +64,27 @@ public class HgProjectSettings implements PersistentStateComponent<HgProjectSett
}
public String getHgExecutable() {
return HgGlobalSettings.getInstance().getHgExecutable();
return myAppSettings.getHgExecutable();
}
public boolean isAutodetectHg() {
return HgGlobalSettings.getInstance().isAutodetectHg();
return myAppSettings.isAutodetectHg();
}
public void enableAutodetectHg() {
HgGlobalSettings.getInstance().enableAutodetectHg();
myAppSettings.enableAutodetectHg();
}
public void setHgExecutable(String text) {
HgGlobalSettings.getInstance().setHgExecutable(text);
myAppSettings.setHgExecutable(text);
}
public boolean isRunViaBash() {
return myAppSettings.isRunViaBash();
}
public void setRunViaBash(boolean runViaBash) {
myAppSettings.setRunViaBash(runViaBash);
}
}
@@ -127,7 +127,7 @@ public final class HgCommandService {
if (arguments != null && arguments.size() != 0) {
cmdLine.addAll(arguments);
}
ShellCommand shellCommand = new ShellCommand();
ShellCommand shellCommand = new ShellCommand(mySettings.isRunViaBash());
HgCommandResult result;
try {
String workingDir = repo != null ? repo.getPath() : null;
@@ -154,7 +154,7 @@ public final class HgCommandService {
result.setWarnings(warnings);
// logging to the Version Control console (without extensions and configs)
final String cmdString = String.format("%s %s %s", HgVcs.HG_EXECUTABLE_FILE_NAME, operation,
final String cmdString = String.format("%s %s %s", mySettings.isRunViaBash() ? "bash -c " + HgVcs.HG_EXECUTABLE_FILE_NAME : HgVcs.HG_EXECUTABLE_FILE_NAME, operation,
StringUtils.join(arguments, " "));
myVcs.showMessageInConsole(cmdString, ConsoleViewContentType.USER_INPUT.getAttributes());
if (!silent) {
@@ -20,12 +20,12 @@ import java.util.Arrays;
public class HgVersionCommand {
public boolean isValid(String executable) {
public boolean isValid(String executable, boolean isRunViaBash) {
String hgExecutable = StringUtils.trim(executable);
if (!hgExecutable.endsWith(HgVcs.HG_EXECUTABLE_FILE_NAME)) {
return false;
}
ShellCommand shellCommand = new ShellCommand();
ShellCommand shellCommand = new ShellCommand(isRunViaBash);
try {
return !shellCommand
.execute(Arrays.asList(hgExecutable, "version"), null, Charset.defaultCharset())
@@ -13,9 +13,11 @@
package org.zmlx.hg4idea.command;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
import java.io.*;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
@@ -24,11 +26,28 @@ final class ShellCommand {
private static final Logger LOG = Logger.getInstance(ShellCommand.class.getName());
private static final int BUFFER_SIZE = 1024;
private final boolean myRunViaBash;
public ShellCommand(boolean runViaBash) {
myRunViaBash = runViaBash;
}
public HgCommandResult execute(List<String> commandLine, String dir, Charset charset) throws ShellCommandException, InterruptedException {
if (commandLine == null || commandLine.isEmpty()) {
throw new IllegalArgumentException("commandLine is empty");
}
if (myRunViaBash) {
// run via bash -cl <hg command> => need to escape bash special symbols
// '-l' makes bash execute as a login shell thus reading .bash_profile
String hgCommand = StringUtil.join(commandLine, " ");
hgCommand = StringUtil.escapeStringCharacters(hgCommand.length(), hgCommand, "\b\t\n\f\r|>$\"'&", false, false, new StringBuilder()).toString(); // we don't escape backslash, because we want special unicode characters (\u0017 for hg log)
commandLine = new ArrayList<String>(3);
commandLine.add("bash");
commandLine.add("-cl");
commandLine.add(hgCommand);
}
StringWriter out = new StringWriter();
StringWriter err = new StringWriter();
try {
@@ -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.zmlx.hg4idea.ui.HgConfigurationProjectPanel">
<grid id="27dc6" binding="myMainPanel" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="27dc6" binding="myMainPanel" layout-manager="GridLayoutManager" row-count="4" 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="500" height="400"/>
@@ -80,9 +80,17 @@
</grid>
<vspacer id="4088f">
<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"/>
<grid row="3" 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="71ef3" class="javax.swing.JCheckBox" binding="myRunHgAsBashCheckBox" default-binding="true">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Run hg as 'bash -c &lt;path to hg&gt;'"/>
</properties>
</component>
</children>
</grid>
<buttonGroups>
@@ -34,6 +34,7 @@ public class HgConfigurationProjectPanel {
private JRadioButton myAutoRadioButton;
private JRadioButton mySelectRadioButton;
private TextFieldWithBrowseButton myPathSelector;
private JCheckBox myRunHgAsBashCheckBox;
public HgConfigurationProjectPanel(HgProjectSettings projectSettings) {
myProjectSettings = projectSettings;
@@ -53,12 +54,14 @@ public class HgConfigurationProjectPanel {
? !myPathSelector.getText().equals(myProjectSettings.getHgExecutable())
: myAutoRadioButton.isSelected() != myProjectSettings.isAutodetectHg();
return executableModified || myCheckIncomingCbx.isSelected() != myProjectSettings.isCheckIncoming()
|| myCheckOutgoingCbx.isSelected() != myProjectSettings.isCheckOutgoing();
|| myCheckOutgoingCbx.isSelected() != myProjectSettings.isCheckOutgoing()
|| myRunHgAsBashCheckBox.isSelected() != myProjectSettings.isRunViaBash();
}
public void saveSettings() {
myProjectSettings.setCheckIncoming(myCheckIncomingCbx.isSelected());
myProjectSettings.setCheckOutgoing(myCheckOutgoingCbx.isSelected());
myProjectSettings.setRunViaBash(myRunHgAsBashCheckBox.isSelected());
if (myAutoRadioButton.isSelected()) {
myProjectSettings.enableAutodetectHg();
@@ -70,6 +73,7 @@ public class HgConfigurationProjectPanel {
public void loadSettings() {
myCheckIncomingCbx.setSelected(myProjectSettings.isCheckIncoming());
myCheckOutgoingCbx.setSelected(myProjectSettings.isCheckOutgoing());
myRunHgAsBashCheckBox.setSelected(myProjectSettings.isRunViaBash());
boolean isAutodetectHg = myProjectSettings.isAutodetectHg();
myAutoRadioButton.setSelected(isAutodetectHg);
@@ -94,7 +98,7 @@ public class HgConfigurationProjectPanel {
hgExecutable = myPathSelector.getText();
}
HgVersionCommand command = new HgVersionCommand();
if (!command.isValid(hgExecutable)) {
if (!command.isValid(hgExecutable, myRunHgAsBashCheckBox.isSelected())) {
throw new ConfigurationException(
HgVcsMessages.message("hg4idea.configuration.executable.error", hgExecutable)
);
@@ -102,7 +106,7 @@ public class HgConfigurationProjectPanel {
}
private void createUIComponents() {
myPathSelector = new HgSetExecutablePathPanel();
myPathSelector = new HgSetExecutablePathPanel(myProjectSettings);
}
}
@@ -4,6 +4,7 @@ import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.vfs.VirtualFile;
import org.zmlx.hg4idea.HgProjectSettings;
import org.zmlx.hg4idea.HgVcsMessages;
import org.zmlx.hg4idea.command.HgVersionCommand;
@@ -19,12 +20,12 @@ class HgSetExecutablePathPanel extends TextFieldWithBrowseButton {
private final Set<ActionListener> myOkListeners = new HashSet<ActionListener>();
HgSetExecutablePathPanel() {
HgSetExecutablePathPanel(final HgProjectSettings projectSettings) {
FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, false, false) {
public void validateSelectedFiles(VirtualFile[] files) throws Exception {
HgVersionCommand command = new HgVersionCommand();
String path = files[0].getPath();
if (!command.isValid(path)) {
if (!command.isValid(path, projectSettings.isRunViaBash())) {
throw new ConfigurationException(HgVcsMessages.message("hg4idea.configuration.executable.error", path));
}
for (ActionListener okListener : myOkListeners) {