IDEA-172253 git: support xmlrpc callbacks with WSL executable

GitOrigin-RevId: 60d678ae57d007d045b7203c10fca3368c7f2ffa
This commit is contained in:
Aleksey Pivovarov
2020-05-17 12:59:24 +00:00
committed by intellij-monorepo-bot
parent 3cf2ecbd13
commit c41b29aba3
7 changed files with 113 additions and 84 deletions
@@ -8,7 +8,6 @@ import org.jetbrains.git4idea.GitExternalApp;
import java.io.File;
import java.util.Arrays;
import java.util.Locale;
import java.util.Vector;
/**
@@ -30,10 +29,6 @@ public class GitRebaseEditorMain implements GitExternalApp {
* Rebase editor handler name
*/
@NonNls static final String HANDLER_NAME = "Git4ideaRebaseEditorHandler";
/**
* The prefix for cygwin files
*/
private static final String CYGDRIVE_PREFIX = "/cygdrive/";
/**
* A private constructor for static class
@@ -68,16 +63,14 @@ public class GitRebaseEditorMain implements GitExternalApp {
System.exit(ERROR_EXIT_CODE);
}
String file = args[1];
try {
XmlRpcClientLite client = new XmlRpcClientLite("127.0.0.1", port);
Vector<Object> params = new Vector<>();
params.add(handlerId);
if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows") && file.startsWith(CYGDRIVE_PREFIX)) {
int p = CYGDRIVE_PREFIX.length();
file = file.charAt(p) + ":" + file.substring(p + 1);
}
params.add(new File(file).getAbsolutePath());
params.add(args[1]);
params.add(new File("").getAbsolutePath());
Integer exitCode = (Integer)client.execute(HANDLER_NAME + ".editCommits", params);
if (exitCode == null) {
exitCode = ERROR_EXIT_CODE;
@@ -9,6 +9,7 @@ import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import git4idea.GitUtil;
import git4idea.config.GitExecutable;
import git4idea.config.GitVcsApplicationSettings;
import git4idea.config.GitVersion;
import git4idea.config.GitVersionSpecialty;
@@ -170,10 +171,12 @@ public class GitHandlerAuthenticationManager implements AutoCloseable {
private void addHandlerPathToEnvironment(@NotNull String env,
@NotNull GitXmlRpcHandlerService service) throws IOException {
GitExecutable executable = myHandler.getExecutable();
boolean useBatchFile = SystemInfo.isWindows &&
executable.isLocal() &&
(!Registry.is("git.use.shell.script.on.windows") ||
!GitVersionSpecialty.CAN_USE_SHELL_HELPER_SCRIPT_ON_WINDOWS.existsIn(myVersion));
File scriptFile = service.getScriptPath(useBatchFile);
File scriptFile = service.getScriptPath(executable, useBatchFile);
myHandler.addCustomEnvironmentVariable(env, scriptFile);
}
@@ -4,10 +4,18 @@ package git4idea.config
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.util.ExecUtil
import com.intellij.execution.wsl.WSLDistribution
import com.intellij.openapi.util.SystemInfo
import git4idea.commands.GitHandler
import org.jetbrains.annotations.NonNls
import java.io.File
sealed class GitExecutable {
companion object {
@NonNls
private const val CYGDRIVE_PREFIX = "/cygdrive/"
}
abstract val id: String
abstract val exePath: String
abstract val isLocal: Boolean
@@ -15,15 +23,30 @@ sealed class GitExecutable {
* Convert absolute file path into a form, that can be passed into executable arguments.
*/
abstract fun convertFilePath(file: File): String
/**
* Convert file path, returned by git, to be used by IDE.
*/
abstract fun convertFilePathBack(path: String, workingDir: File): File
abstract fun patchCommandLine(handler: GitHandler, commandLine: GeneralCommandLine, withLowPriority: Boolean, withNoTty: Boolean)
data class Local(override val exePath: String)
: GitExecutable() {
override val id: String = "local"
override val isLocal: Boolean = true
override fun toString(): String = exePath
override fun convertFilePath(file: File): String = file.absolutePath
override fun convertFilePathBack(path: String, workingDir: File): File {
if (SystemInfo.isWindows && path.startsWith(CYGDRIVE_PREFIX)) {
val prefixSize = CYGDRIVE_PREFIX.length
val localPath = path.substring(prefixSize, prefixSize + 1) + ":" + path.substring(prefixSize + 1)
return File(localPath)
}
return workingDir.resolve(path)
}
override fun patchCommandLine(handler: GitHandler, commandLine: GeneralCommandLine, withLowPriority: Boolean, withNoTty: Boolean) {
if (withLowPriority) ExecUtil.setupLowPriorityExecution(commandLine)
if (withNoTty) ExecUtil.setupNoTtyExecution(commandLine)
@@ -33,6 +56,7 @@ sealed class GitExecutable {
data class Wsl(override val exePath: String,
val distribution: WSLDistribution)
: GitExecutable() {
override val id: String = "wsl-${distribution.id}"
override val isLocal: Boolean = false
override fun toString(): String = "${distribution.presentableName}: $exePath"
@@ -41,6 +65,11 @@ sealed class GitExecutable {
return distribution.getWslPath(path) ?: path
}
override fun convertFilePathBack(path: String, workingDir: File): File {
val localPath = distribution.getWindowsPath(path) ?: path
return File(localPath)
}
override fun patchCommandLine(handler: GitHandler, commandLine: GeneralCommandLine, withLowPriority: Boolean, withNoTty: Boolean) {
// Handling 'withNoTty' is not needed, as tty can't leak into WSL executable
@@ -2,7 +2,6 @@
package git4idea.rebase;
import git4idea.GitUtil;
import git4idea.commands.GitCommand;
import git4idea.commands.GitHandler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -38,8 +37,8 @@ public class GitHandlerRebaseEditorManager implements AutoCloseable {
private void prepareEditor() {
if (myHandler.containsCustomEnvironmentVariable(GIT_EDITOR_ENV)) return;
myHandlerId = myService.registerHandler(myEditorHandler);
myHandler.addCustomEnvironmentVariable(GitCommand.GIT_EDITOR_ENV, myService.getEditorCommand());
myHandlerId = myService.registerHandler(myHandler, myEditorHandler);
myHandler.addCustomEnvironmentVariable(GIT_EDITOR_ENV, myService.getEditorCommand(myHandler.getExecutable()));
myHandler.addCustomEnvironmentVariable(GitRebaseEditorMain.IDEA_REBASE_HANDER_NO, myHandlerId.toString());
}
@@ -5,7 +5,9 @@ import com.intellij.ide.XmlRpcServer;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.Pair;
import git4idea.commands.GitHandler;
import git4idea.config.GitExecutable;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.git4idea.util.ScriptGenerator;
@@ -20,12 +22,6 @@ import java.util.UUID;
* The service that generates editor script for
*/
public class GitRebaseEditorService implements Disposable {
private static final String CYGDRIVE_PREFIX = "/cygdrive/";
/**
* The editor command that is set to env variable
*/
private String myEditorCommand;
/**
* The lock object
*/
@@ -33,7 +29,7 @@ public class GitRebaseEditorService implements Disposable {
/**
* The handlers to use
*/
private final Map<UUID, GitRebaseEditorHandler> myHandlers = new HashMap<>();
private final Map<UUID, Pair<GitRebaseEditorHandler, GitExecutable>> myHandlers = new HashMap<>();
/**
* The lock for the handlers
*/
@@ -76,14 +72,11 @@ public class GitRebaseEditorService implements Disposable {
* @return the editor command
*/
@NotNull
public synchronized String getEditorCommand() {
public synchronized String getEditorCommand(@NotNull GitExecutable executable) {
synchronized (myScriptLock) {
if (myEditorCommand == null) {
ScriptGenerator generator = new ScriptGenerator(GIT_REBASE_EDITOR_PREFIX, GitRebaseEditorMain.class);
generator.addInternal(Integer.toString(BuiltInServerManager.getInstance().waitForStart().getPort()));
myEditorCommand = generator.commandLine();
}
return myEditorCommand;
ScriptGenerator generator = new ScriptGenerator(GIT_REBASE_EDITOR_PREFIX, GitRebaseEditorMain.class);
generator.addInternal(Integer.toString(BuiltInServerManager.getInstance().waitForStart().getPort()));
return generator.commandLine(executable);
}
}
@@ -94,11 +87,11 @@ public class GitRebaseEditorService implements Disposable {
* @return the handler identifier
*/
@NotNull
public UUID registerHandler(@NotNull GitRebaseEditorHandler handler) {
public UUID registerHandler(@NotNull GitHandler handler, @NotNull GitRebaseEditorHandler editorHandler) {
addInternalHandler();
synchronized (myHandlersLock) {
UUID key = UUID.randomUUID();
myHandlers.put(key, handler);
myHandlers.put(key, Pair.create(editorHandler, handler.getExecutable()));
return key;
}
}
@@ -122,13 +115,13 @@ public class GitRebaseEditorService implements Disposable {
* @param handlerNo the handler number.
*/
@NotNull
GitRebaseEditorHandler getHandler(@NotNull UUID handlerNo) {
Pair<GitRebaseEditorHandler, GitExecutable> getHandler(@NotNull UUID handlerNo) {
synchronized (myHandlersLock) {
GitRebaseEditorHandler h = myHandlers.get(handlerNo);
if (h == null) {
Pair<GitRebaseEditorHandler, GitExecutable> pair = myHandlers.get(handlerNo);
if (pair == null) {
throw new IllegalStateException("The handler " + handlerNo + " is not registered");
}
return h;
return pair;
}
}
@@ -144,22 +137,14 @@ public class GitRebaseEditorService implements Disposable {
* @return exit code
*/
@SuppressWarnings({"UnusedDeclaration"})
public int editCommits(@NotNull String handlerNo, String path) {
GitRebaseEditorHandler editor = getHandler(UUID.fromString(handlerNo));
public int editCommits(@NotNull String handlerNo, String path, String workingDir) {
Pair<GitRebaseEditorHandler, GitExecutable> pair = getHandler(UUID.fromString(handlerNo));
GitExecutable executable = pair.second;
GitRebaseEditorHandler editorHandler = pair.first;
String localPath = adjustFilePath(path);
File file = new File(localPath);
File file = executable.convertFilePathBack(path, new File(workingDir));
return editor.editCommits(file);
return editorHandler.editCommits(file);
}
}
@NotNull
private static String adjustFilePath(@NotNull String file) {
if (SystemInfo.isWindows && file.startsWith(CYGDRIVE_PREFIX)) {
final int prefixSize = CYGDRIVE_PREFIX.length();
return file.charAt(prefixSize) + ":" + file.substring(prefixSize + 1);
}
return file;
}
}
@@ -5,15 +5,17 @@ import com.intellij.ide.XmlRpcServer;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import git4idea.config.GitExecutable;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.git4idea.GitExternalApp;
import org.jetbrains.git4idea.util.ScriptGenerator;
import org.jetbrains.ide.BuiltInServerManager;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import static com.intellij.openapi.diagnostic.Logger.getInstance;
@@ -42,8 +44,7 @@ public abstract class GitXmlRpcHandlerService<T> implements Disposable {
@NotNull private final String myHandlerName;
@NotNull private final Class<? extends GitExternalApp> myScriptMainClass;
@Nullable private File myBatchScriptPath;
@Nullable private File myShellScriptPath;
@NotNull private final Map<String, File> myScriptPaths = new HashMap<>();
@NotNull private final Object SCRIPT_FILE_LOCK = new Object();
@NotNull private final THashMap<UUID, T> handlers = new THashMap<>();
@@ -74,22 +75,16 @@ public abstract class GitXmlRpcHandlerService<T> implements Disposable {
* @throws IOException if script cannot be generated
*/
@NotNull
public File getScriptPath(boolean useBatchFile) throws IOException {
ScriptGenerator generator = new ScriptGenerator(myScriptTempFilePrefix, myScriptMainClass);
public File getScriptPath(@NotNull GitExecutable executable, boolean useBatchFile) throws IOException {
synchronized (SCRIPT_FILE_LOCK) {
if (useBatchFile) {
if (myBatchScriptPath == null || !myBatchScriptPath.exists()) {
myBatchScriptPath = generator.generate(useBatchFile);
}
return myBatchScriptPath;
}
else {
if (myShellScriptPath == null || !myShellScriptPath.exists()) {
myShellScriptPath = generator.generate(useBatchFile);
}
return myShellScriptPath;
String id = executable.getId() + (useBatchFile ? "-bat" : "");
File scriptPath = myScriptPaths.get(id);
if (scriptPath == null || !scriptPath.exists()) {
ScriptGenerator generator = new ScriptGenerator(myScriptTempFilePrefix + "-" + executable.getId(), myScriptMainClass);
scriptPath = generator.generate(executable, useBatchFile);
myScriptPaths.put(id, scriptPath);
}
return scriptPath;
}
}
@@ -4,16 +4,22 @@ package org.jetbrains.git4idea.util;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.util.text.StringUtilRt;
import com.intellij.util.PathUtil;
import com.intellij.util.containers.ContainerUtil;
import git4idea.config.GitExecutable;
import git4idea.rebase.GitRebaseEditorMain;
import org.apache.commons.codec.DecoderException;
import org.apache.xmlrpc.XmlRpcClientLite;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.git4idea.http.GitAskPassXmlRpcHandler;
import org.jetbrains.git4idea.nativessh.GitNativeSshAskPassXmlRpcHandler;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Script generator utility class. It uses to generate a temporary scripts that
@@ -31,7 +37,7 @@ public class ScriptGenerator {
/**
* The class paths for the script
*/
private final ArrayList<String> myPaths = new ArrayList<>();
private final ArrayList<File> myPaths = new ArrayList<>();
/**
* The internal parameters for the script
*/
@@ -57,7 +63,7 @@ public class ScriptGenerator {
*/
private void addClasses(final Class... classes) {
for (Class<?> c : classes) {
String classPath = PathUtil.getJarPathForClass(c);
File classPath = new File(PathUtil.getJarPathForClass(c));
if (!myPaths.contains(classPath)) {
// the size of path is expected to be quite small, so no optimization is done here
myPaths.add(classPath);
@@ -104,8 +110,8 @@ public class ScriptGenerator {
}
@NotNull
public File generate(boolean useBatchFile) throws IOException {
String commandLine = commandLine();
public File generate(@NotNull GitExecutable executable, boolean useBatchFile) throws IOException {
String commandLine = commandLine(executable);
return useBatchFile ? generateBatch(myPrefix, commandLine)
: generateShell(myPrefix, commandLine);
}
@@ -113,26 +119,45 @@ public class ScriptGenerator {
/**
* @return a command line for the the executable program
*/
public String commandLine() {
public String commandLine(@NotNull GitExecutable executable) {
StringBuilder cmd = new StringBuilder();
cmd.append('\"').append(System.getProperty("java.home")).append(File.separatorChar).append("bin").append(File.separatorChar)
.append("java\" -cp \"");
boolean first = true;
for (String p : myPaths) {
if (!first) {
cmd.append(File.pathSeparatorChar);
}
else {
first = false;
}
cmd.append(p);
if (executable instanceof GitExecutable.Wsl) {
List<String> envs = ContainerUtil.newArrayList(
GitNativeSshAskPassXmlRpcHandler.IJ_HANDLER_ENV,
GitNativeSshAskPassXmlRpcHandler.IJ_PORT_ENV,
GitAskPassXmlRpcHandler.GIT_ASK_PASS_HANDLER_ENV,
GitAskPassXmlRpcHandler.GIT_ASK_PASS_PORT_ENV,
GitRebaseEditorMain.IDEA_REBASE_HANDER_NO);
cmd.append("export WSLENV=");
cmd.append(StringUtil.join(envs, it -> it + "/w", ":"));
cmd.append("\n");
cmd.append('"');
File javaExecutable = new File(String.format("%s\\bin\\java.exe", System.getProperty("java.home")));
cmd.append(executable.convertFilePath(javaExecutable));
cmd.append('"');
}
cmd.append("\" ");
else {
cmd.append('"');
cmd.append(String.format("%s/bin/java", System.getProperty("java.home")));
cmd.append('"');
}
cmd.append(" -cp ");
cmd.append('"');
String classpathSeparator = String.valueOf(File.pathSeparatorChar);
cmd.append(StringUtil.join(myPaths, file -> file.getPath(), classpathSeparator));
cmd.append('"');
cmd.append(' ');
cmd.append(myMainClass.getName());
for (String p : myInternalParameters) {
cmd.append(' ');
cmd.append(p);
}
String line = cmd.toString();
if (SystemInfo.isWindows) {
line = line.replace('\\', '/');