From 28682938fb1a15ccb66fa1cda19b4265963c86d1 Mon Sep 17 00:00:00 2001 From: Konstantin Kolosovsky Date: Tue, 25 Mar 2014 23:05:18 +0400 Subject: [PATCH 01/14] IDEA-122281 Do not explicitly specify "--force-interactive" for svn commands under terminal (they will be interactive by default if under terminal) --- .../src/org/jetbrains/idea/svn/commandLine/CommandRuntime.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandRuntime.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandRuntime.java index bd6c69c5e8d9..649b204ac790 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandRuntime.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandRuntime.java @@ -206,7 +206,8 @@ public class CommandRuntime { executor = new CommandExecutor(exePath, command); } else { - command.put("--force-interactive"); + // do not explicitly specify "--force-interactive" as it is not supported in svn 1.7 - commands will be interactive by default as + // running under terminal executor = new TerminalExecutor(exePath, command); ((TerminalExecutor)executor).addInteractiveListener(new TerminalSshModule(this, executor)); } From 9465f77dcb305faa0bd85ed72f1f1b7196c5f8e3 Mon Sep 17 00:00:00 2001 From: Konstantin Kolosovsky Date: Thu, 27 Mar 2014 13:24:48 +0400 Subject: [PATCH 02/14] IDEA-121752 Get cached authentication data through special SVNKit authentication provider and not directly from runtime storage (to utilize logic for creating appropriate storage key) --- .../idea/svn/SvnAuthenticationManager.java | 14 ++++++++++++++ .../org/jetbrains/idea/svn/SvnConfiguration.java | 2 +- .../svn/dialogs/SvnAuthenticationProvider.java | 10 +++++----- .../jetbrains/idea/svn/SvnAuthenticationTest.java | 2 +- .../idea/svn16/SvnAuthenticationTest.java | 2 +- 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationManager.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationManager.java index cc94b50963d4..06005731a860 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationManager.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationManager.java @@ -73,6 +73,7 @@ public class SvnAuthenticationManager extends DefaultSVNAuthenticationManager im public static final String HTTP_PROXY_PASSWORD = "http-proxy-password"; private Project myProject; private File myConfigDirectory; + private ISVNAuthenticationProvider myRuntimeCacheProvider; private PersistentAuthenticationProviderProxy myPersistentAuthenticationProviderProxy; private SvnConfiguration myConfig; private static final ThreadLocal ourJustEntered = new ThreadLocal(); @@ -119,6 +120,19 @@ public class SvnAuthenticationManager extends DefaultSVNAuthenticationManager im } }); + // This is not the same instance as DefaultSVNAuthenticationManager.myProviders[1], but currently + // DefaultSVNAuthenticationManager.CacheAuthenticationProvider uses only its outer class state - so we utilize necessary logic with + // this new instance. + myRuntimeCacheProvider = createRuntimeAuthenticationProvider(); + } + + public SVNAuthentication requestFromCache(String kind, + SVNURL url, + String realm, + SVNErrorMessage errorMessage, + SVNAuthentication previousAuth, + boolean authMayBeStored) { + return myRuntimeCacheProvider.requestClientAuthentication(kind, url, realm, errorMessage, previousAuth, authMayBeStored); } public String getDefaultUsername(String kind, SVNURL url) { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfiguration.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfiguration.java index 824bd1b7c7a4..6c0be5f0a3dc 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfiguration.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfiguration.java @@ -350,7 +350,7 @@ public class SvnConfiguration implements PersistentStateComponent ourForceInteractive = new HashSet(); public SvnAuthenticationProvider(final SvnVcs svnVcs, final ISVNAuthenticationProvider provider, - final ISVNAuthenticationStorage authenticationStorage) { - myAuthenticationStorage = authenticationStorage; + final SvnAuthenticationManager authenticationManager) { + myAuthenticationManager = authenticationManager; myProject = svnVcs.getProject(); myAuthenticationNotifier = svnVcs.getAuthNotifier(); mySvnInteractiveAuthenticationProvider = provider; @@ -73,7 +73,7 @@ public class SvnAuthenticationProvider implements ISVNAuthenticationProvider { return mySvnInteractiveAuthenticationProvider.requestClientAuthentication(kind, url, realm, errorMessage, previousAuth, authMayBeStored); } else { if (myAuthenticationNotifier.ensureNotify(obj)) { - return (SVNAuthentication) myAuthenticationStorage.getData(kind, realm); + return myAuthenticationManager.requestFromCache(kind, url, realm, errorMessage, previousAuth, authMayBeStored); } } return null; diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnAuthenticationTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnAuthenticationTest.java index 3700343842e3..f3552e99c392 100644 --- a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnAuthenticationTest.java +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnAuthenticationTest.java @@ -80,7 +80,7 @@ public class SvnAuthenticationTest extends PlatformTestCase { myAuthenticationManager = new SvnAuthenticationManager(myProject, configFile); myInteractiveProvider = new SvnTestInteractiveAuthentication(myAuthenticationManager); - myAuthenticationManager.setAuthenticationProvider(new SvnAuthenticationProvider(vcs, myInteractiveProvider, SvnConfiguration.RUNTIME_AUTH_CACHE)); + myAuthenticationManager.setAuthenticationProvider(new SvnAuthenticationProvider(vcs, myInteractiveProvider, myAuthenticationManager)); myAuthenticationManager.setRuntimeStorage(SvnConfiguration.RUNTIME_AUTH_CACHE); myTestInteraction = new TestInteraction(); diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn16/SvnAuthenticationTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn16/SvnAuthenticationTest.java index 624cfbb25635..9652a1f25c8a 100644 --- a/plugins/svn4idea/testSource/org/jetbrains/idea/svn16/SvnAuthenticationTest.java +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn16/SvnAuthenticationTest.java @@ -84,7 +84,7 @@ public class SvnAuthenticationTest extends PlatformTestCase { myAuthenticationManager = new SvnAuthenticationManager(myProject, configFile); myInteractiveProvider = new SvnTestInteractiveAuthentication(myAuthenticationManager); - myAuthenticationManager.setAuthenticationProvider(new SvnAuthenticationProvider(vcs, myInteractiveProvider, SvnConfiguration.RUNTIME_AUTH_CACHE)); + myAuthenticationManager.setAuthenticationProvider(new SvnAuthenticationProvider(vcs, myInteractiveProvider, myAuthenticationManager)); myAuthenticationManager.setRuntimeStorage(SvnConfiguration.RUNTIME_AUTH_CACHE); myTestInteraction = new TestInteraction(); From 1881195c4fe9e9c5aee8668d1b2fa47007b97cb1 Mon Sep 17 00:00:00 2001 From: Konstantin Kolosovsky Date: Thu, 27 Mar 2014 13:27:22 +0400 Subject: [PATCH 03/14] svn: Refactored SvnAuthenticationProvider - removed unused code --- .../idea/svn/dialogs/SvnAuthenticationProvider.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnAuthenticationProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnAuthenticationProvider.java index b81d2639897b..04868ae990b8 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnAuthenticationProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnAuthenticationProvider.java @@ -16,7 +16,6 @@ package org.jetbrains.idea.svn.dialogs; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.util.SystemProperties; import org.jetbrains.idea.svn.SvnAuthenticationManager; @@ -36,7 +35,7 @@ import java.util.Set; * @author alex */ public class SvnAuthenticationProvider implements ISVNAuthenticationProvider { - private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.dialogs.SvnAuthenticationProvider"); + private final Project myProject; private final SvnAuthenticationNotifier myAuthenticationNotifier; private final ISVNAuthenticationProvider mySvnInteractiveAuthenticationProvider; @@ -51,10 +50,6 @@ public class SvnAuthenticationProvider implements ISVNAuthenticationProvider { mySvnInteractiveAuthenticationProvider = provider; } - private void log(final String s) { - LOG.debug(s); - } - public SVNAuthentication requestClientAuthentication(final String kind, final SVNURL url, final String realm, From 33790eb6dca45fe383f894fc7b029c910b83ac6c Mon Sep 17 00:00:00 2001 From: Konstantin Kolosovsky Date: Thu, 27 Mar 2014 14:27:37 +0400 Subject: [PATCH 04/14] svn: Make Command.myRepositoryUrl volatile - as it is accessed from several threads when in terminal mode --- .../src/org/jetbrains/idea/svn/commandLine/Command.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/Command.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/Command.java index 4facdd367034..b6979140a466 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/Command.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/Command.java @@ -28,7 +28,7 @@ public class Command { private File workingDirectory; @Nullable private File myConfigDir; @Nullable private LineCommandListener myResultBuilder; - @Nullable private SVNURL myRepositoryUrl; + @Nullable private volatile SVNURL myRepositoryUrl; @NotNull private SvnTarget myTarget; @Nullable private ISVNCanceller myCanceller; From b4871374fa793963e99cc8ecd1622e5771244b9d Mon Sep 17 00:00:00 2001 From: Konstantin Kolosovsky Date: Thu, 27 Mar 2014 15:12:32 +0400 Subject: [PATCH 05/14] IDEA-122394 Use ModalityState.any() to prompt user if executing svn commands in terminal mode (as currently these prompts are in the thread that reads output and not in the one that started progress) --- .../svn/checkin/IdeaSvnkitBasedAuthenticationCallback.java | 5 ++++- .../jetbrains/idea/svn/commandLine/TerminalSshModule.java | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/IdeaSvnkitBasedAuthenticationCallback.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/IdeaSvnkitBasedAuthenticationCallback.java index 29d5068073f5..4fd546987bfb 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/IdeaSvnkitBasedAuthenticationCallback.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/IdeaSvnkitBasedAuthenticationCallback.java @@ -15,6 +15,7 @@ */ package org.jetbrains.idea.svn.checkin; +import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; @@ -169,7 +170,9 @@ public class IdeaSvnkitBasedAuthenticationCallback implements AuthenticationCall } }; - WaitForProgressToShow.runOrInvokeAndWaitAboveProgress(command); + // Use ModalityState.any() as currently ssh credentials in terminal mode are requested in the thread that reads output and not in + // the thread that started progress + WaitForProgressToShow.runOrInvokeAndWaitAboveProgress(command, ModalityState.any()); return new Pair(answer.get(), !save.isNull() && save.get()); } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalSshModule.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalSshModule.java index a2ea3c7cecc6..5a624e9d3414 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalSshModule.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalSshModule.java @@ -15,6 +15,7 @@ */ package org.jetbrains.idea.svn.commandLine; +import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; @@ -111,7 +112,9 @@ public class TerminalSshModule extends LineCommandAdapter implements CommandRunt } }; - WaitForProgressToShow.runOrInvokeAndWaitAboveProgress(command); + // Use ModalityState.any() as currently ssh credentials in terminal mode are requested in the thread that reads output and not in + // the thread that started progress + WaitForProgressToShow.runOrInvokeAndWaitAboveProgress(command, ModalityState.any()); unknownHost = null; fingerprintAlgorithm = null; From 1a7aff60fc479edb13272f450856d7cb6600d4a8 Mon Sep 17 00:00:00 2001 From: Konstantin Kolosovsky Date: Wed, 2 Apr 2014 16:57:29 +0400 Subject: [PATCH 06/14] svn: Do not resolve repository url (with "svn info" command) only for local "svn info" commands --- plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java | 2 ++ .../jetbrains/idea/svn/api/InfoCommandRepositoryProvider.java | 3 ++- .../svn/commandLine/CommandParametersResolutionModule.java | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java index 6c636d228c7e..a1acf5de4e8d 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java @@ -952,6 +952,8 @@ public class SvnVcs extends AbstractVcs { return getInfo(new File(path)); } + // TODO: Most likely make getInfo(File) correspond to getInfo(File, SVNRevision.UNDEFINED) + // TODO: And create special method that tries also to resolve HEAD revision - to be used only in special code paths @Nullable public SVNInfo getInfo(@NotNull File ioFile) { SVNInfo result = null; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/api/InfoCommandRepositoryProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/InfoCommandRepositoryProvider.java index 9c119223aa9e..f58dfb5b72f7 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/api/InfoCommandRepositoryProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/api/InfoCommandRepositoryProvider.java @@ -19,6 +19,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.SvnVcs; import org.tmatesoft.svn.core.wc.SVNInfo; +import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; /** @@ -41,7 +42,7 @@ public class InfoCommandRepositoryProvider extends BaseRepositoryProvider { result = new Repository(myTarget.getURL()); } else { - SVNInfo info = myVcs.getInfo(myTarget.getFile()); + SVNInfo info = myVcs.getInfo(myTarget.getFile(), SVNRevision.UNDEFINED); result = info != null ? new Repository(info.getRepositoryRootURL()) : null; } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandParametersResolutionModule.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandParametersResolutionModule.java index 2e9e42097495..e4883549b0aa 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandParametersResolutionModule.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandParametersResolutionModule.java @@ -53,7 +53,7 @@ public class CommandParametersResolutionModule extends BaseCommandRuntimeModule InfoCommandRepositoryProvider infoCommandProvider = new InfoCommandRepositoryProvider(myVcs, command.getTarget()); Repository repository = urlMappingProvider.get(); - if (repository == null && !SvnCommandName.info.equals(command.getName())) { + if (repository == null && !command.isLocalInfo()) { repository = infoCommandProvider.get(); } From 3ff58dc56f1ce1a4d44ef5c3dbd7f36313644649 Mon Sep 17 00:00:00 2001 From: Konstantin Kolosovsky Date: Wed, 2 Apr 2014 18:09:37 +0400 Subject: [PATCH 07/14] svn: Refactored SvnDiffProvider - log errors while resolving current revision for file as info (not as debug) --- .../svn4idea/src/org/jetbrains/idea/svn/SvnDiffProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnDiffProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnDiffProvider.java index 978a52b7990e..9ec170986494 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnDiffProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnDiffProvider.java @@ -152,7 +152,7 @@ public class SvnDiffProvider extends DiffProviderEx implements DiffProvider, Dif svnInfo.getAuthor(), message); } catch (VcsException e) { - LOG.debug(e); // most likely the file is unversioned + LOG.info(e); // most likely the file is unversioned return null; } } From 0c348254492c8306f50c2fc2449a30821f19a9c5 Mon Sep 17 00:00:00 2001 From: Konstantin Kolosovsky Date: Thu, 3 Apr 2014 20:01:44 +0400 Subject: [PATCH 08/14] IDEA-122391 Trim xml data before parsing - as unnecessary whitespaces (i.e. line breaks at the beginning) break parsing logic --- .../src/org/jetbrains/idea/svn/commandLine/CommandUtil.java | 2 +- .../idea/svn/commandLine/SvnCommandLineInfoClient.java | 4 ++-- .../idea/svn/commandLine/SvnCommandLineStatusClient.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandUtil.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandUtil.java index b38d1c914bdf..0b529a954415 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandUtil.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandUtil.java @@ -167,7 +167,7 @@ public class CommandUtil { JAXBContext context = JAXBContext.newInstance(type); Unmarshaller unmarshaller = context.createUnmarshaller(); - return (T) unmarshaller.unmarshal(new StringReader(data)); + return (T) unmarshaller.unmarshal(new StringReader(data.trim())); } /** diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineInfoClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineInfoClient.java index 8bbb58242e05..8f4cae8f313c 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineInfoClient.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineInfoClient.java @@ -156,7 +156,7 @@ public class SvnCommandLineInfoClient extends SvnkitSvnWcClient { } private static void parseResult(@NotNull final ISVNInfoHandler handler, @Nullable File base, @Nullable String result) throws SVNException { - if (StringUtil.isEmpty(result)) { + if (StringUtil.isEmptyOrSpaces(result)) { return; } @@ -176,7 +176,7 @@ public class SvnCommandLineInfoClient extends SvnkitSvnWcClient { try { SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); - parser.parse(new ByteArrayInputStream(result.getBytes(CharsetToolkit.UTF8_CHARSET)), infoHandler[0]); + parser.parse(new ByteArrayInputStream(result.trim().getBytes(CharsetToolkit.UTF8_CHARSET)), infoHandler[0]); } catch (SvnExceptionWrapper e) { LOG.info("info output " + result); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineStatusClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineStatusClient.java index 860e858c7a8e..236aeadc80dc 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineStatusClient.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineStatusClient.java @@ -136,7 +136,7 @@ public class SvnCommandLineStatusClient implements SvnStatusClientI { final SvnStatusHandler[] svnHandl = new SvnStatusHandler[1]; svnHandl[0] = createStatusHandler(revision, handler, base, infoBase, svnHandl); SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); - parser.parse(new ByteArrayInputStream(result.getBytes(CharsetToolkit.UTF8_CHARSET)), svnHandl[0]); + parser.parse(new ByteArrayInputStream(result.trim().getBytes(CharsetToolkit.UTF8_CHARSET)), svnHandl[0]); if (!svnHandl[0].isAnythingReported()) { if (!SvnUtil.isSvnVersioned(myVcs, path)) { throw new SVNException( From 788fe65780d103490d88da8ebfaf67901517cd9e Mon Sep 17 00:00:00 2001 From: Konstantin Kolosovsky Date: Thu, 3 Apr 2014 20:25:50 +0400 Subject: [PATCH 09/14] IDEA-122391 Apply correct arguments escaping for pty4j (depending on current OS) --- .../svn/commandLine/TerminalExecutor.java | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalExecutor.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalExecutor.java index 203b25cecf34..7705b3a8556a 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalExecutor.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalExecutor.java @@ -19,6 +19,7 @@ import com.intellij.execution.CommandLineUtil; import com.intellij.execution.ExecutionException; import com.intellij.execution.process.OSProcessHandler; import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; @@ -92,13 +93,34 @@ public class TerminalExecutor extends CommandExecutor { LOG.info("Terminal output " + ((TerminalProcessHandler) myHandler).getTerminalOutput()); } - private static List escapeArguments(List collection) { - // TODO: Add additional checks like in java.lang.ProcessImpl constructor - return ContainerUtil.map(collection, new Function() { + @NotNull + private static List escapeArguments(@NotNull List arguments) { + return SystemInfo.isWindows ? escapeForWin(arguments) : arguments; + } + + /** + * TODO: Identify pty4j quoting requirements for Windows and implement accordingly + */ + @NotNull + private static List escapeForWin(@NotNull List arguments) { + return ContainerUtil.map(arguments, new Function() { @Override - public String fun(String s) { - return s.contains(" ") ? "\"" + s + "\"" : s; + public String fun(String argument) { + return needQuote(argument) && !isQuoted(argument) ? quote(argument) : argument; } }); } + + @NotNull + private static String quote(@NotNull String argument) { + return StringUtil.wrapWithDoubleQuote(argument); + } + + private static boolean needQuote(@NotNull String argument) { + return argument.contains(" "); + } + + private static boolean isQuoted(@NotNull String argument) { + return StringUtil.startsWithChar(argument, '\"') && StringUtil.endsWithChar(argument, '\"'); + } } From 2b2ba0151f7eaa2740caccccc75366ce11aa7fad Mon Sep 17 00:00:00 2001 From: Konstantin Kolosovsky Date: Thu, 3 Apr 2014 20:38:14 +0400 Subject: [PATCH 10/14] svn: Refactored CommandExecutor - extracted utility methods to work with temp files --- .../idea/svn/commandLine/CommandExecutor.java | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandExecutor.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandExecutor.java index 5d909a5e186e..8f3471d38f41 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandExecutor.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandExecutor.java @@ -157,26 +157,45 @@ public class CommandExecutor { private void ensureMessageFile() throws SvnBindException { if (myMessage != null) { + myMessageFile = createTempFile("commit-message", ".txt"); try { - File vcsFolder = new File(PathManager.getSystemPath(), "vcs"); - myMessageFile = FileUtil.createTempFile(new File(vcsFolder, "svn"), "commit-message", ".txt"); FileUtil.writeToFile(myMessageFile, myMessage); - - myCommandLine.addParameters("-F", myMessageFile.getAbsolutePath()); - myCommandLine.addParameters("--config-option", "config:miscellany:log-encoding=" + CharsetToolkit.UTF8); } catch (IOException e) { throw new SvnBindException(e); } + myCommandLine.addParameters("-F", myMessageFile.getAbsolutePath()); + myCommandLine.addParameters("--config-option", "config:miscellany:log-encoding=" + CharsetToolkit.UTF8); } } private void cleanupMessageFile() { - if (myMessageFile != null) { - boolean wasDeleted = FileUtil.delete(myMessageFile); + deleteTempFile(myMessageFile); + } + + @NotNull + protected static File getSvnFolder() { + File vcsFolder = new File(PathManager.getSystemPath(), "vcs"); + + return new File(vcsFolder, "svn"); + } + + @NotNull + protected static File createTempFile(@NotNull String prefix, @NotNull String extension) throws SvnBindException { + try { + return FileUtil.createTempFile(getSvnFolder(), prefix, extension); + } + catch (IOException e) { + throw new SvnBindException(e); + } + } + + protected static void deleteTempFile(@Nullable File file) { + if (file != null) { + boolean wasDeleted = FileUtil.delete(file); if (!wasDeleted) { - LOG.info("Failed to delete temp commit message file " + myMessageFile.getAbsolutePath()); + LOG.info("Failed to delete temp file " + file.getAbsolutePath()); } } } From dd713091e41f72bc6480ce7bb62426ff0d218f4f Mon Sep 17 00:00:00 2001 From: Konstantin Kolosovsky Date: Thu, 3 Apr 2014 21:09:39 +0400 Subject: [PATCH 11/14] svn: terminal: Terminal support for Windows extracted to separate classes --- .../idea/svn/commandLine/CommandRuntime.java | 8 +- .../svn/commandLine/TerminalExecutor.java | 41 +--------- .../commandLine/TerminalProcessHandler.java | 39 +-------- .../svn/commandLine/WinTerminalExecutor.java | 81 +++++++++++++++++++ .../WinTerminalProcessHandler.java | 70 ++++++++++++++++ 5 files changed, 164 insertions(+), 75 deletions(-) create mode 100644 plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalExecutor.java create mode 100644 plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalProcessHandler.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandRuntime.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandRuntime.java index 649b204ac790..a93665f280ac 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandRuntime.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandRuntime.java @@ -17,6 +17,7 @@ package org.jetbrains.idea.svn.commandLine; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.containers.ContainerUtil; @@ -208,13 +209,18 @@ public class CommandRuntime { else { // do not explicitly specify "--force-interactive" as it is not supported in svn 1.7 - commands will be interactive by default as // running under terminal - executor = new TerminalExecutor(exePath, command); + executor = newTerminalExecutor(command); ((TerminalExecutor)executor).addInteractiveListener(new TerminalSshModule(this, executor)); } return executor; } + @NotNull + private TerminalExecutor newTerminalExecutor(@NotNull Command command) { + return SystemInfo.isWindows ? new WinTerminalExecutor(exePath, command) : new TerminalExecutor(exePath, command); + } + private static boolean isLocal(@NotNull Command command) { return SvnCommandName.version.equals(command.getName()) || SvnCommandName.cleanup.equals(command.getName()) || diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalExecutor.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalExecutor.java index 7705b3a8556a..259388ba6074 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalExecutor.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalExecutor.java @@ -35,17 +35,6 @@ import java.util.List; */ public class TerminalExecutor extends CommandExecutor { - // max available value is 480 - // if greater value is provided than the default value of 80 will be assumed - // this could provide unnecessary line breaks and thus could break parsing logic - private static final int TERMINAL_WINDOW_MAX_COLUMNS = 480; - - static { - if (SystemInfo.isWindows) { - System.setProperty("win.pty.cols", String.valueOf(TERMINAL_WINDOW_MAX_COLUMNS)); - } - } - private final List myInteractiveListeners = ContainerUtil.createLockFreeCopyOnWriteList(); public TerminalExecutor(@NotNull @NonNls String exePath, @NotNull Command command) { @@ -94,33 +83,7 @@ public class TerminalExecutor extends CommandExecutor { } @NotNull - private static List escapeArguments(@NotNull List arguments) { - return SystemInfo.isWindows ? escapeForWin(arguments) : arguments; - } - - /** - * TODO: Identify pty4j quoting requirements for Windows and implement accordingly - */ - @NotNull - private static List escapeForWin(@NotNull List arguments) { - return ContainerUtil.map(arguments, new Function() { - @Override - public String fun(String argument) { - return needQuote(argument) && !isQuoted(argument) ? quote(argument) : argument; - } - }); - } - - @NotNull - private static String quote(@NotNull String argument) { - return StringUtil.wrapWithDoubleQuote(argument); - } - - private static boolean needQuote(@NotNull String argument) { - return argument.contains(" "); - } - - private static boolean isQuoted(@NotNull String argument) { - return StringUtil.startsWithChar(argument, '\"') && StringUtil.endsWithChar(argument, '\"'); + protected List escapeArguments(@NotNull List arguments) { + return arguments; } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalProcessHandler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalProcessHandler.java index 96d6543c74e2..ee9c0ebab43d 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalProcessHandler.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalProcessHandler.java @@ -20,7 +20,6 @@ import com.intellij.execution.process.OSProcessHandler; import com.intellij.execution.process.ProcessEvent; import com.intellij.execution.process.ProcessOutputTypes; import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; @@ -34,10 +33,6 @@ import java.util.regex.Matcher; */ public class TerminalProcessHandler extends OSProcessHandler { - // see http://en.wikipedia.org/wiki/ANSI_escape_code - private static final String NON_CSI_ESCAPE_CODE = "\u001B.[@-_]"; - private static final String CSI_ESCAPE_CODE = "\u001B\\[(.*?)[@-~]"; - private final List myInteractiveListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private final CapturingProcessAdapter terminalOutputCapturer = new CapturingProcessAdapter(); @@ -88,24 +83,13 @@ public class TerminalProcessHandler extends OSProcessHandler { } } - private static String filterCombinedText(@NotNull String currentLine) { - // for windows platform output is assumed in format suitable for terminal emulator - // for instance, same text could be returned twice with '\r' symbol in between (so in emulator output we'll still see correct - // text without duplication) - // because of this we manually process '\r' occurrences to get correct output - if (SystemInfo.isWindows) { - currentLine = removeAllBeforeCaretReturn(currentLine); - } + @NotNull + protected String filterCombinedText(@NotNull String currentLine) { return currentLine; } - private static String filterText(@NotNull String text) { - if (SystemInfo.isWindows) { - // filter terminal escape codes - they are presented in the output for windows platform - text = text.replaceAll(CSI_ESCAPE_CODE, "").replaceAll(NON_CSI_ESCAPE_CODE, ""); - // trim leading '\r' symbols - as they break xml parsing logic - text = StringUtil.trimLeading(text, '\r'); - } + @NotNull + protected String filterText(@NotNull String text) { return text; } @@ -121,21 +105,6 @@ public class TerminalProcessHandler extends OSProcessHandler { } } - private static String removeAllBeforeCaretReturn(@NotNull String line) { - int caretReturn = line.lastIndexOf("\r"); - - while (caretReturn >= 0) { - if (caretReturn + 1 < line.length() && line.charAt(caretReturn + 1) != '\n') { - // next symbol is not '\n' - we should not treat text before found caret return symbol - line = line.substring(caretReturn + 1); - break; - } - caretReturn = line.lastIndexOf("\r", caretReturn - 1); - } - - return line; - } - private static Key resolveOutputType(@NotNull String line, @NotNull Key outputType) { Key result = outputType; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalExecutor.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalExecutor.java new file mode 100644 index 000000000000..817d1bc20809 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalExecutor.java @@ -0,0 +1,81 @@ +/* + * Copyright 2000-2014 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.idea.svn.commandLine; + +import com.intellij.execution.process.OSProcessHandler; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * @author Konstantin Kolosovsky. + */ +public class WinTerminalExecutor extends TerminalExecutor { + + // max available value is 480 + // if greater value is provided than the default value of 80 will be assumed + // this could provide unnecessary line breaks and thus could break parsing logic + private static final int TERMINAL_WINDOW_MAX_COLUMNS = 480; + + static { + // still use isWindows check here not to initialize corresponding property on non-Windows environments + if (SystemInfo.isWindows) { + System.setProperty("win.pty.cols", String.valueOf(TERMINAL_WINDOW_MAX_COLUMNS)); + } + } + + public WinTerminalExecutor(@NotNull @NonNls String exePath, @NotNull Command command) { + super(exePath, command); + } + + @NotNull + @Override + protected OSProcessHandler createProcessHandler() { + return new WinTerminalProcessHandler(myProcess); + } + + /** + * TODO: Identify pty4j quoting requirements for Windows and implement accordingly + */ + @NotNull + @Override + protected List escapeArguments(@NotNull List arguments) { + return ContainerUtil.map(arguments, new Function() { + @Override + public String fun(String argument) { + return needQuote(argument) && !isQuoted(argument) ? quote(argument) : argument; + } + }); + } + + @NotNull + private static String quote(@NotNull String argument) { + return StringUtil.wrapWithDoubleQuote(argument); + } + + private static boolean needQuote(@NotNull String argument) { + return argument.contains(" "); + } + + private static boolean isQuoted(@NotNull String argument) { + return StringUtil.startsWithChar(argument, '\"') && StringUtil.endsWithChar(argument, '\"'); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalProcessHandler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalProcessHandler.java new file mode 100644 index 000000000000..f9911a7a55b5 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalProcessHandler.java @@ -0,0 +1,70 @@ +/* + * Copyright 2000-2014 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.idea.svn.commandLine; + +import com.intellij.openapi.util.text.StringUtil; +import org.jetbrains.annotations.NotNull; + +/** + * @author Konstantin Kolosovsky. + */ +public class WinTerminalProcessHandler extends TerminalProcessHandler { + + // see http://en.wikipedia.org/wiki/ANSI_escape_code + private static final String NON_CSI_ESCAPE_CODE = "\u001B.[@-_]"; + private static final String CSI_ESCAPE_CODE = "\u001B\\[(.*?)[@-~]"; + + public WinTerminalProcessHandler(@NotNull Process process) { + super(process); + } + + @NotNull + @Override + protected String filterCombinedText(@NotNull String currentLine) { + // for windows platform output is assumed in format suitable for terminal emulator + // for instance, same text could be returned twice with '\r' symbol in between (so in emulator output we'll still see correct + // text without duplication) + // because of this we manually process '\r' occurrences to get correct output + return removeAllBeforeCaretReturn(currentLine); + } + + @NotNull + @Override + protected String filterText(@NotNull String text) { + // filter terminal escape codes - they are presented in the output for windows platform + text = text.replaceAll(CSI_ESCAPE_CODE, "").replaceAll(NON_CSI_ESCAPE_CODE, ""); + // trim leading '\r' symbols - as they break xml parsing logic + text = StringUtil.trimLeading(text, '\r'); + + return text; + } + + @NotNull + private static String removeAllBeforeCaretReturn(@NotNull String line) { + int caretReturn = line.lastIndexOf("\r"); + + while (caretReturn >= 0) { + if (caretReturn + 1 < line.length() && line.charAt(caretReturn + 1) != '\n') { + // next symbol is not '\n' - we should not treat text before found caret return symbol + line = line.substring(caretReturn + 1); + break; + } + caretReturn = line.lastIndexOf("\r", caretReturn - 1); + } + + return line; + } +} From cd55e1afc49084aa312d2c58902bd301aa8453df Mon Sep 17 00:00:00 2001 From: Konstantin Kolosovsky Date: Thu, 3 Apr 2014 22:38:11 +0400 Subject: [PATCH 12/14] Refactored RunnerWinProcess - delegating method calls to original process extracted to separate class --- .../execution/process/ProcessWrapper.java | 68 +++++++++++++++++++ .../execution/process/RunnerWinProcess.java | 43 +----------- 2 files changed, 70 insertions(+), 41 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/execution/process/ProcessWrapper.java diff --git a/platform/platform-impl/src/com/intellij/execution/process/ProcessWrapper.java b/platform/platform-impl/src/com/intellij/execution/process/ProcessWrapper.java new file mode 100644 index 000000000000..76c1a13e52b8 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/execution/process/ProcessWrapper.java @@ -0,0 +1,68 @@ +/* + * Copyright 2000-2014 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.process; + +import org.jetbrains.annotations.NotNull; + +import java.io.InputStream; +import java.io.OutputStream; + +/** + * @author Konstantin Kolosovsky. + */ +public class ProcessWrapper extends Process { + + @NotNull private final Process myOriginalProcess; + + public ProcessWrapper(@NotNull Process originalProcess) { + myOriginalProcess = originalProcess; + } + + @Override + public OutputStream getOutputStream() { + return myOriginalProcess.getOutputStream(); + } + + @Override + public InputStream getInputStream() { + return myOriginalProcess.getInputStream(); + } + + @Override + public InputStream getErrorStream() { + return myOriginalProcess.getErrorStream(); + } + + @Override + public int waitFor() throws InterruptedException { + return myOriginalProcess.waitFor(); + } + + @Override + public int exitValue() { + return myOriginalProcess.exitValue(); + } + + @Override + public void destroy() { + myOriginalProcess.destroy(); + } + + @NotNull + public Process getOriginalProcess() { + return myOriginalProcess; + } +} diff --git a/platform/platform-impl/src/com/intellij/execution/process/RunnerWinProcess.java b/platform/platform-impl/src/com/intellij/execution/process/RunnerWinProcess.java index e2dd827b5018..8e142c45c1a4 100644 --- a/platform/platform-impl/src/com/intellij/execution/process/RunnerWinProcess.java +++ b/platform/platform-impl/src/com/intellij/execution/process/RunnerWinProcess.java @@ -20,49 +20,10 @@ import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.openapi.util.SystemInfo; import org.jetbrains.annotations.NotNull; -import java.io.InputStream; -import java.io.OutputStream; - -public class RunnerWinProcess extends Process { - - private final Process myOriginalProcess; +public class RunnerWinProcess extends ProcessWrapper { private RunnerWinProcess(@NotNull Process originalProcess) { - myOriginalProcess = originalProcess; - } - - @Override - public OutputStream getOutputStream() { - return myOriginalProcess.getOutputStream(); - } - - @Override - public InputStream getInputStream() { - return myOriginalProcess.getInputStream(); - } - - @Override - public InputStream getErrorStream() { - return myOriginalProcess.getErrorStream(); - } - - @Override - public int waitFor() throws InterruptedException { - return myOriginalProcess.waitFor(); - } - - @Override - public int exitValue() { - return myOriginalProcess.exitValue(); - } - - @Override - public void destroy() { - myOriginalProcess.destroy(); - } - - public Process getOriginalProcess() { - return myOriginalProcess; + super(originalProcess); } /** From 084e9d4f5f012180cf9ed3a3f197fd7cc4d0c884 Mon Sep 17 00:00:00 2001 From: Konstantin Kolosovsky Date: Thu, 3 Apr 2014 22:49:01 +0400 Subject: [PATCH 13/14] svn: terminal: Implemented running svn commands using "cmd /c" and redirecting output to separate file for Windows (so we do not need separately process terminal escape sequences in process output) --- .../svn/commandLine/TerminalExecutor.java | 22 +++-- .../svn/commandLine/WinTerminalExecutor.java | 82 +++++++++++++++++++ .../WinTerminalProcessHandler.java | 5 ++ 3 files changed, 101 insertions(+), 8 deletions(-) diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalExecutor.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalExecutor.java index 259388ba6074..f23a9e092ff1 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalExecutor.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalExecutor.java @@ -18,10 +18,7 @@ package org.jetbrains.idea.svn.commandLine; import com.intellij.execution.CommandLineUtil; import com.intellij.execution.ExecutionException; import com.intellij.execution.process.OSProcessHandler; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ArrayUtil; -import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import com.pty4j.PtyProcess; import org.jetbrains.annotations.NonNls; @@ -63,12 +60,21 @@ public class TerminalExecutor extends CommandExecutor { @NotNull @Override protected Process createProcess() throws ExecutionException { - List parameters = - escapeArguments(CommandLineUtil.toCommandLine(myCommandLine.getExePath(), myCommandLine.getParametersList().getList())); + List parameters = escapeArguments(buildParameters()); + return createProcess(parameters); + } + + @NotNull + protected List buildParameters() { + return CommandLineUtil.toCommandLine(myCommandLine.getExePath(), myCommandLine.getParametersList().getList()); + } + + @NotNull + protected Process createProcess(@NotNull List parameters) throws ExecutionException { try { - return PtyProcess - .exec(ArrayUtil.toStringArray(parameters), myCommandLine.getEnvironment(), myCommandLine.getWorkDirectory().getAbsolutePath()); + return PtyProcess.exec(ArrayUtil.toStringArray(parameters), myCommandLine.getEnvironment(), + myCommandLine.getWorkDirectory().getAbsolutePath()); } catch (IOException e) { throw new ExecutionException(e); @@ -79,7 +85,7 @@ public class TerminalExecutor extends CommandExecutor { public void logCommand() { super.logCommand(); - LOG.info("Terminal output " + ((TerminalProcessHandler) myHandler).getTerminalOutput()); + LOG.info("Terminal output " + ((TerminalProcessHandler)myHandler).getTerminalOutput()); } @NotNull diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalExecutor.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalExecutor.java index 817d1bc20809..c5002a2dd852 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalExecutor.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalExecutor.java @@ -15,14 +15,19 @@ */ package org.jetbrains.idea.svn.commandLine; +import com.intellij.execution.ExecutionException; import com.intellij.execution.process.OSProcessHandler; +import com.intellij.execution.process.ProcessWrapper; +import com.intellij.execution.util.ExecUtil; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.io.*; import java.util.List; /** @@ -42,6 +47,9 @@ public class WinTerminalExecutor extends TerminalExecutor { } } + @Nullable private File myRedirectFile; + @Nullable private FileInputStream myRedirectStream; + public WinTerminalExecutor(@NotNull @NonNls String exePath, @NotNull Command command) { super(exePath, command); } @@ -52,6 +60,80 @@ public class WinTerminalExecutor extends TerminalExecutor { return new WinTerminalProcessHandler(myProcess); } + @Override + protected void beforeCreateProcess() throws SvnBindException { + super.beforeCreateProcess(); + + createRedirectFile(); + } + + private void createRedirectFile() throws SvnBindException { + myRedirectFile = createTempFile("terminal-output", ""); + + try { + myRedirectStream = new FileInputStream(myRedirectFile); + } + catch (FileNotFoundException e) { + throw new SvnBindException(e); + } + } + + @Override + protected void cleanup() { + super.cleanup(); + + deleteRedirectFile(); + } + + private void deleteRedirectFile() { + if (myRedirectStream != null) { + try { + myRedirectStream.close(); + } + catch (IOException e) { + LOG.info(e); + } + } + + deleteTempFile(myRedirectFile); + } + + @NotNull + @Override + protected Process createProcess() throws ExecutionException { + checkRedirectFile(); + + List parameters = escapeArguments(buildParameters()); + parameters.add(0, ExecUtil.getWindowsShellName()); + parameters.add(1, "/c"); + parameters.add(">>"); + //noinspection ConstantConditions + parameters.add(quote(myRedirectFile.getAbsolutePath())); + + Process process = createProcess(parameters); + + return new ProcessWrapper(process) { + @Override + public InputStream getInputStream() { + return myRedirectStream; + } + + @Override + public InputStream getErrorStream() { + return getOriginalProcess().getInputStream(); + } + }; + } + + private void checkRedirectFile() { + if (myRedirectFile == null) { + throw new IllegalStateException("No redirect file found"); + } + if (myRedirectStream == null) { + throw new IllegalStateException("No redirect stream found"); + } + } + /** * TODO: Identify pty4j quoting requirements for Windows and implement accordingly */ diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalProcessHandler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalProcessHandler.java index f9911a7a55b5..f7657023ff41 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalProcessHandler.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalProcessHandler.java @@ -31,6 +31,11 @@ public class WinTerminalProcessHandler extends TerminalProcessHandler { super(process); } + @Override + protected boolean processHasSeparateErrorStream() { + return true; + } + @NotNull @Override protected String filterCombinedText(@NotNull String currentLine) { From 0ce4ecdd92a3395efc0f29105e18a05c0a0dec59 Mon Sep 17 00:00:00 2001 From: Konstantin Kolosovsky Date: Fri, 4 Apr 2014 13:54:37 +0400 Subject: [PATCH 14/14] svn: Implemented one common process handler for svn commands (both for terminal and regular mode) --- .../idea/svn/commandLine/CommandExecutor.java | 75 +++------------ .../svn/commandLine/SvnProcessHandler.java | 91 +++++++++++++++++++ .../svn/commandLine/TerminalExecutor.java | 5 +- .../commandLine/TerminalProcessHandler.java | 7 +- .../svn/commandLine/WinTerminalExecutor.java | 5 +- .../WinTerminalProcessHandler.java | 4 +- .../idea/svn/content/CmdContentClient.java | 10 +- 7 files changed, 118 insertions(+), 79 deletions(-) create mode 100644 plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnProcessHandler.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandExecutor.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandExecutor.java index 8f3471d38f41..982eb44ce725 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandExecutor.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/CommandExecutor.java @@ -26,16 +26,16 @@ import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.util.EventDispatcher; -import com.intellij.util.io.BaseDataReader; -import com.intellij.util.io.BinaryOutputReader; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.tmatesoft.svn.core.SVNCancelException; -import java.io.*; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStreamWriter; import java.util.List; -import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -57,7 +57,7 @@ public class CommandExecutor { private volatile boolean myWasCancelled; protected final GeneralCommandLine myCommandLine; protected Process myProcess; - protected OSProcessHandler myHandler; + protected SvnProcessHandler myHandler; private OutputStreamWriter myProcessWriter; // TODO: Try to implement commands in a way that they manually indicate if they need full output - to prevent situations // TODO: when large amount of data needs to be stored instead of just sequential processing. @@ -201,16 +201,18 @@ public class CommandExecutor { } @NotNull - protected OSProcessHandler createProcessHandler() { - return needsBinaryOutput() - ? new BinaryOSProcessHandler(myProcess, myCommandLine.getCommandLineString()) - : new MyOSProcessHandler(myProcess, myCommandLine.getCommandLineString()); + protected SvnProcessHandler createProcessHandler() { + return new SvnProcessHandler(myProcess, myCommandLine.getCommandLineString(), needsUtf8Output(), needsBinaryOutput()); } - private boolean needsBinaryOutput() { + protected boolean needsBinaryOutput() { return SvnCommandName.cat.equals(myCommand.getName()); } + protected boolean needsUtf8Output() { + return myCommand.getParameters().contains("--xml"); + } + @NotNull protected Process createProcess() throws ExecutionException { return myCommandLine.createProcess(); @@ -237,9 +239,9 @@ public class CommandExecutor { return outputAdapter.getOutput(); } - @Nullable + @NotNull public ByteArrayOutputStream getBinaryOutput() { - return myHandler instanceof BinaryOSProcessHandler ? ((BinaryOSProcessHandler)myHandler).myBinaryOutput : null; + return myHandler.getBinaryOutput(); } // TODO: Carefully here - do not modify command from threads other than the one started command execution @@ -463,53 +465,4 @@ public class CommandExecutor { } } } - - private class MyOSProcessHandler extends OSProcessHandler { - - public MyOSProcessHandler(@NotNull Process process, @Nullable String commandLine) { - super(process, commandLine); - } - - @Override - protected Reader createProcessOutReader() { - if (myCommand.getParameters().contains("--xml")) { - return new InputStreamReader(myProcess.getInputStream(), CharsetToolkit.UTF8_CHARSET); - } - return super.createProcessOutReader(); - } - } - - private static class BinaryOSProcessHandler extends OSProcessHandler { - - @NotNull private final ByteArrayOutputStream myBinaryOutput; - - public BinaryOSProcessHandler(@NotNull final Process process, @Nullable final String commandLine) { - super(process, commandLine); - myBinaryOutput = new ByteArrayOutputStream(); - } - - @NotNull - @Override - protected BaseDataReader createOutputDataReader(BaseDataReader.SleepingPolicy sleepingPolicy) { - return new SimpleBinaryOutputReader(myProcess.getInputStream(), sleepingPolicy); - } - - private class SimpleBinaryOutputReader extends BinaryOutputReader { - - public SimpleBinaryOutputReader(@NotNull InputStream stream, SleepingPolicy sleepingPolicy) { - super(stream, sleepingPolicy); - start(); - } - - @Override - protected void onBinaryAvailable(@NotNull byte[] data, int size) { - myBinaryOutput.write(data, 0, size); - } - - @Override - protected Future executeOnPooledThread(Runnable runnable) { - return BinaryOSProcessHandler.this.executeOnPooledThread(runnable); - } - } - } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnProcessHandler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnProcessHandler.java new file mode 100644 index 000000000000..8bed772d70f9 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnProcessHandler.java @@ -0,0 +1,91 @@ +/* + * Copyright 2000-2014 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.idea.svn.commandLine; + +import com.intellij.execution.process.OSProcessHandler; +import com.intellij.openapi.vfs.CharsetToolkit; +import com.intellij.util.io.BaseDataReader; +import com.intellij.util.io.BinaryOutputReader; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.concurrent.Future; + +/** + * @author Konstantin Kolosovsky. + */ +public class SvnProcessHandler extends OSProcessHandler { + + private final boolean myForceUtf8; + private final boolean myForceBinary; + @NotNull private final ByteArrayOutputStream myBinaryOutput; + + public SvnProcessHandler(@NotNull Process process, boolean forceUtf8, boolean forceBinary) { + this(process, null, forceUtf8, forceBinary); + } + + public SvnProcessHandler(@NotNull Process process, @Nullable String commandLine, boolean forceUtf8, boolean forceBinary) { + super(process, commandLine); + + myForceUtf8 = forceUtf8; + myForceBinary = forceBinary; + myBinaryOutput = new ByteArrayOutputStream(); + } + + @NotNull + public ByteArrayOutputStream getBinaryOutput() { + return myBinaryOutput; + } + + @Override + protected Reader createProcessOutReader() { + if (myForceUtf8) { + return new InputStreamReader(myProcess.getInputStream(), CharsetToolkit.UTF8_CHARSET); + } + return super.createProcessOutReader(); + } + + @NotNull + @Override + protected BaseDataReader createOutputDataReader(BaseDataReader.SleepingPolicy sleepingPolicy) { + if (myForceBinary) { + return new SimpleBinaryOutputReader(myProcess.getInputStream(), sleepingPolicy); + } + return super.createOutputDataReader(sleepingPolicy); + } + + private class SimpleBinaryOutputReader extends BinaryOutputReader { + + public SimpleBinaryOutputReader(@NotNull InputStream stream, SleepingPolicy sleepingPolicy) { + super(stream, sleepingPolicy); + start(); + } + + @Override + protected void onBinaryAvailable(@NotNull byte[] data, int size) { + myBinaryOutput.write(data, 0, size); + } + + @Override + protected Future executeOnPooledThread(Runnable runnable) { + return SvnProcessHandler.this.executeOnPooledThread(runnable); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalExecutor.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalExecutor.java index f23a9e092ff1..742cbd2584bc 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalExecutor.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalExecutor.java @@ -17,7 +17,6 @@ package org.jetbrains.idea.svn.commandLine; import com.intellij.execution.CommandLineUtil; import com.intellij.execution.ExecutionException; -import com.intellij.execution.process.OSProcessHandler; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import com.pty4j.PtyProcess; @@ -53,8 +52,8 @@ public class TerminalExecutor extends CommandExecutor { @NotNull @Override - protected OSProcessHandler createProcessHandler() { - return new TerminalProcessHandler(myProcess); + protected SvnProcessHandler createProcessHandler() { + return new TerminalProcessHandler(myProcess, needsUtf8Output(), needsBinaryOutput()); } @NotNull diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalProcessHandler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalProcessHandler.java index ee9c0ebab43d..c6f8dd7cc081 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalProcessHandler.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/TerminalProcessHandler.java @@ -16,7 +16,6 @@ package org.jetbrains.idea.svn.commandLine; import com.intellij.execution.process.CapturingProcessAdapter; -import com.intellij.execution.process.OSProcessHandler; import com.intellij.execution.process.ProcessEvent; import com.intellij.execution.process.ProcessOutputTypes; import com.intellij.openapi.util.Key; @@ -31,7 +30,7 @@ import java.util.regex.Matcher; /** * @author Konstantin Kolosovsky. */ -public class TerminalProcessHandler extends OSProcessHandler { +public class TerminalProcessHandler extends SvnProcessHandler { private final List myInteractiveListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private final CapturingProcessAdapter terminalOutputCapturer = new CapturingProcessAdapter(); @@ -39,8 +38,8 @@ public class TerminalProcessHandler extends OSProcessHandler { private final StringBuilder outputLine = new StringBuilder(); private final StringBuilder errorLine = new StringBuilder(); - public TerminalProcessHandler(@NotNull Process process) { - super(process); + public TerminalProcessHandler(@NotNull Process process, boolean forceUtf8, boolean forceBinary) { + super(process, forceUtf8, forceBinary); } public void addInteractiveListener(@NotNull InteractiveCommandListener listener) { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalExecutor.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalExecutor.java index c5002a2dd852..e08594d33c28 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalExecutor.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalExecutor.java @@ -16,7 +16,6 @@ package org.jetbrains.idea.svn.commandLine; import com.intellij.execution.ExecutionException; -import com.intellij.execution.process.OSProcessHandler; import com.intellij.execution.process.ProcessWrapper; import com.intellij.execution.util.ExecUtil; import com.intellij.openapi.util.SystemInfo; @@ -56,8 +55,8 @@ public class WinTerminalExecutor extends TerminalExecutor { @NotNull @Override - protected OSProcessHandler createProcessHandler() { - return new WinTerminalProcessHandler(myProcess); + protected SvnProcessHandler createProcessHandler() { + return new WinTerminalProcessHandler(myProcess, needsUtf8Output(), needsBinaryOutput()); } @Override diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalProcessHandler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalProcessHandler.java index f7657023ff41..3856b336d07f 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalProcessHandler.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/WinTerminalProcessHandler.java @@ -27,8 +27,8 @@ public class WinTerminalProcessHandler extends TerminalProcessHandler { private static final String NON_CSI_ESCAPE_CODE = "\u001B.[@-_]"; private static final String CSI_ESCAPE_CODE = "\u001B\\[(.*?)[@-~]"; - public WinTerminalProcessHandler(@NotNull Process process) { - super(process); + public WinTerminalProcessHandler(@NotNull Process process, boolean forceUtf8, boolean forceBinary) { + super(process, forceUtf8, forceBinary); } @Override diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/content/CmdContentClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/content/CmdContentClient.java index d654253bf426..17b195eb927e 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/content/CmdContentClient.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/content/CmdContentClient.java @@ -2,15 +2,15 @@ package org.jetbrains.idea.svn.content; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.impl.ContentRevisionCache; -import com.intellij.openapi.vfs.CharsetToolkit; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.api.BaseSvnClient; -import org.jetbrains.idea.svn.commandLine.*; +import org.jetbrains.idea.svn.commandLine.CommandExecutor; +import org.jetbrains.idea.svn.commandLine.CommandUtil; +import org.jetbrains.idea.svn.commandLine.SvnCommandName; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc2.SvnTarget; -import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.List; @@ -31,9 +31,7 @@ public class CmdContentClient extends BaseSvnClient implements ContentClient { CommandUtil.put(parameters, revision); CommandExecutor command = CommandUtil.execute(myVcs, target, SvnCommandName.cat, parameters, null); - // TODO: currently binary output will be null for terminal mode - use text output in this case - ByteArrayOutputStream output = command.getBinaryOutput(); - byte[] bytes = output != null ? output.toByteArray() : CharsetToolkit.getUtf8Bytes(command.getOutput()); + byte[] bytes = command.getBinaryOutput().toByteArray(); ContentRevisionCache.checkContentsSize(target.getPathOrUrlString(), bytes.length);