Merge branch 'svn_18_2'

This commit is contained in:
Konstantin Kolosovsky
2013-11-06 14:20:43 +04:00
14 changed files with 637 additions and 34 deletions
@@ -309,6 +309,7 @@ testng_sm_runner=false
show.flex.debug.design.view=false
git.new.log=true
svn.use.terminal=false
completion.enable.relevant.method.chain.suggestions=false
ide.mac.message.sheets.java.emulation=false
@@ -15,6 +15,7 @@
*/
package org.jetbrains.idea.svn;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
@@ -25,11 +26,13 @@ import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.AbstractVcsHelper;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangesUtil;
import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
@@ -57,6 +60,7 @@ import org.tmatesoft.svn.core.wc2.SvnOperationFactory;
import org.tmatesoft.svn.core.wc2.SvnTarget;
import java.io.File;
import java.nio.channels.NonWritableChannelException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -369,30 +373,34 @@ public class SvnUtil {
*/
@NotNull
public static WorkingCopyFormat getFormat(final File path) {
int format = 0;
WorkingCopyFormat result = null;
File dbFile = resolveDatabase(path);
if (dbFile != null) {
SqlJetDb db = null;
try {
db = SqlJetDb.open(dbFile, false);
format = db.getOptions().getUserVersion();
}
catch (SqlJetException e) {
LOG.error(e);
} finally {
if (db != null) {
try {
db.close();
}
catch (SqlJetException e) {
LOG.error(e);
}
}
result = FileUtilRt.doIOOperation(new WorkingCopyFormatOperation(dbFile));
if (result == null) {
notifyDatabaseError();
}
}
return WorkingCopyFormat.getInstance(format);
return result != null ? result : WorkingCopyFormat.UNKNOWN;
}
private static void close(@Nullable SqlJetDb db) {
if (db != null) {
try {
db.close();
}
catch (SqlJetException e) {
notifyDatabaseError();
}
}
}
private static void notifyDatabaseError() {
VcsBalloonProblemNotifier.NOTIFICATION_GROUP
.createNotification("Some errors occurred while accessing svn working copy database.", NotificationType.ERROR).notify(null);
}
private static File resolveDatabase(final File path) {
@@ -803,4 +811,40 @@ public class SvnUtil {
// thrown when getting info from repository for non-existent item - like HEAD revision for deleted file
SVNErrorCode.ILLEGAL_TARGET.equals(code);
}
private static class WorkingCopyFormatOperation implements FileUtilRt.RepeatableIOOperation<WorkingCopyFormat, RuntimeException> {
@NotNull private final File myDbFile;
public WorkingCopyFormatOperation(@NotNull File dbFile) {
myDbFile = dbFile;
}
@Nullable
@Override
public WorkingCopyFormat execute(boolean lastAttempt) {
// TODO: rewrite it using sqlite jdbc driver
SqlJetDb db = null;
WorkingCopyFormat result = null;
try {
// "write" access is requested here for now as workaround - see some details
// in https://code.google.com/p/sqljet/issues/detail?id=25 and http://issues.tmatesoft.com/issue/SVNKIT-418.
// BUSY error is currently handled same way as others.
db = SqlJetDb.open(myDbFile, true);
result = WorkingCopyFormat.getInstance(db.getOptions().getUserVersion());
}
catch (NonWritableChannelException e) {
// Such exceptions could be thrown when db is opened in "read" mode, but the db file is readonly (for instance, locked
// by other process). See links above for some details.
// handle this exception type separately - not to break execution flow
LOG.info(e);
}
catch (SqlJetException e) {
LOG.info(e);
}
finally {
close(db);
}
return result;
}
}
}
@@ -41,6 +41,12 @@ public class Command {
myParameters.addAll(parameters);
}
public void putIfNotPresent(@NotNull String parameter) {
if (!myParameters.contains(parameter)) {
myParameters.add(parameter);
}
}
@Nullable
public File getConfigDir() {
return myConfigDir;
@@ -15,6 +15,7 @@
*/
package org.jetbrains.idea.svn.commandLine;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.*;
import com.intellij.openapi.diagnostic.Logger;
@@ -25,6 +26,8 @@ import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@@ -40,9 +43,11 @@ public class CommandExecutor {
private boolean myIsDestroyed;
private boolean myNeedsDestroy;
private volatile String myDestroyReason;
protected final GeneralCommandLine myCommandLine;
private Process myProcess;
private OSProcessHandler myHandler;
protected Process myProcess;
protected OSProcessHandler 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.
private CapturingProcessAdapter outputAdapter;
@@ -86,16 +91,21 @@ public class CommandExecutor {
return myIsDestroyed;
}
public String getDestroyReason() {
return myDestroyReason;
}
public void start() {
synchronized (myLock) {
checkNotStarted();
try {
myProcess = myCommandLine.createProcess();
myProcess = createProcess();
if (LOG.isDebugEnabled()) {
LOG.debug(myCommandLine.toString());
}
myHandler = new OSProcessHandler(myProcess, myCommandLine.getCommandLineString());
myHandler = createProcessHandler();
myProcessWriter = new OutputStreamWriter(myHandler.getProcessInput());
startHandlingStreams();
} catch (Throwable t) {
listeners().startFailed(t);
@@ -104,7 +114,17 @@ public class CommandExecutor {
}
}
private void startHandlingStreams() {
@NotNull
protected OSProcessHandler createProcessHandler() {
return new OSProcessHandler(myProcess, myCommandLine.getCommandLineString());
}
@NotNull
protected Process createProcess() throws ExecutionException {
return myCommandLine.createProcess();
}
protected void startHandlingStreams() {
outputAdapter = new CapturingProcessAdapter();
myHandler.addProcessListener(outputAdapter);
myHandler.addProcessListener(new ProcessTracker());
@@ -121,6 +141,12 @@ public class CommandExecutor {
return outputAdapter.getOutput().getStderr();
}
// TODO: Carefully here - do not modify command from threads other than the one started command execution
@NotNull
public Command getCommand() {
return myCommand;
}
/**
* Wait for process termination
* @param timeout
@@ -183,6 +209,13 @@ public class CommandExecutor {
}
}
public void destroyProcess(@Nullable String destroyReason) {
synchronized (myLock) {
myDestroyReason = destroyReason;
myNeedsDestroy = true;
}
}
/**
* ProcessHandler.destroyProcess() implementations could acquire read lock in its implementation - like OSProcessManager.getInstance().
* Some commands are called under write lock - which is generally bad idea, but such logic is not refactored yet.
@@ -265,6 +298,23 @@ public class CommandExecutor {
}
}
public void write(String value) throws SvnBindException {
try {
synchronized (myLock) {
myProcessWriter.write(value);
myProcessWriter.flush();
}
}
catch (IOException e) {
throw new SvnBindException(e);
}
}
public void logCommand() {
LOG.info("Command text " + getCommandText());
LOG.info("Command output " + getOutput());
}
private class CommandCancelTracker extends LineCommandAdapter {
@Override
public void onLineAvailable(String line, Key outputType) {
@@ -44,7 +44,6 @@ public class CommandParametersResolutionModule extends BaseCommandRuntimeModule
command.setWorkingDirectory(resolveWorkingDirectory(command));
}
command.setConfigDir(myAuthCallback.getSpecialConfigDir());
command.put("--non-interactive");
command.saveOriginalParameters();
}
@@ -17,6 +17,8 @@ package org.jetbrains.idea.svn.commandLine;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -85,12 +87,20 @@ public class CommandRuntime {
if (exitCode == null || exitCode != 0) {
logNullExitCode(executor, exitCode);
if (executor.isManuallyDestroyed()) {
cleanup(executor, command.getWorkingDirectory());
String destroyReason = executor.getDestroyReason();
if (!StringUtil.isEmpty(destroyReason)) {
throw new SvnBindException(destroyReason);
}
}
if (executor.getErrorOutput().length() > 0) {
// handle authentication
final String errText = executor.getErrorOutput().trim();
final AuthCallbackCase callback = createCallback(errText, command.getRepositoryUrl());
final AuthCallbackCase callback = executor instanceof TerminalExecutor ? null : createCallback(errText, command.getRepositoryUrl());
if (callback != null) {
cleanup(executor, command.getWorkingDirectory());
if (callback.getCredentials(errText)) {
if (myAuthCallback.getSpecialConfigDir() != null) {
command.setConfigDir(myAuthCallback.getSpecialConfigDir());
@@ -107,6 +117,9 @@ public class CommandRuntime {
// no errors found in error stream => we treat null exitCode as successful, otherwise exception is thrown
if (exitCode != null) {
// here exitCode != null && exitCode != 0
LOG.info("Command - " + executor.getCommandText());
LOG.info("Command output - " + executor.getOutput());
throw new SvnBindException("Svn process exited with error code: " + exitCode);
}
}
@@ -148,7 +161,7 @@ public class CommandRuntime {
}
private void cleanup(@NotNull CommandExecutor executor, @NotNull File workingDirectory) throws SvnBindException {
if (executor.isManuallyDestroyed() && executor.getCommandName().isWriteable()) {
if (executor.getCommandName().isWriteable()) {
File wcRoot = SvnUtil.getWorkingCopyRootNew(workingDirectory);
// not all commands require cleanup - for instance, some commands operate only with repository - like "svn info <url>"
@@ -167,7 +180,19 @@ public class CommandRuntime {
@NotNull
private CommandExecutor newExecutor(@NotNull Command command) {
return new CommandExecutor(exePath, command);
final CommandExecutor executor;
if (!Registry.is("svn.use.terminal")) {
command.putIfNotPresent("--non-interactive");
executor = new CommandExecutor(exePath, command);
}
else {
command.put("--force-interactive");
executor = new TerminalExecutor(exePath, command);
((TerminalExecutor)executor).addInteractiveListener(new TerminalSshModule(this, executor));
}
return executor;
}
@NotNull
@@ -0,0 +1,26 @@
/*
* Copyright 2000-2013 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.Key;
/**
* @author Konstantin Kolosovsky.
*/
public interface InteractiveCommandListener {
boolean handlePrompt(String line, Key outputType);
}
@@ -171,6 +171,11 @@ public class SvnCommandLineStatusClient implements SvnStatusClientI {
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e), e);
}
catch (SAXException e) {
// status parsing errors are logged separately as sometimes there are parsing errors connected to terminal output handling.
// these errors primarily occur when status output is rather large.
// and status output could be large, for instance, when working copy is locked (seems that each file is listed in status output).
command.logCommand();
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e), e);
}
}
@@ -0,0 +1,104 @@
/*
* Copyright 2000-2013 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.CommandLineUtil;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.process.OSProcessHandler;
import com.intellij.openapi.util.SystemInfo;
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;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.List;
/**
* @author Konstantin Kolosovsky.
*/
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<InteractiveCommandListener> myInteractiveListeners = ContainerUtil.createLockFreeCopyOnWriteList();
public TerminalExecutor(@NotNull @NonNls String exePath, @NotNull Command command) {
super(exePath, command);
}
public void addInteractiveListener(@NotNull InteractiveCommandListener listener) {
myInteractiveListeners.add(listener);
}
@Override
protected void startHandlingStreams() {
for (InteractiveCommandListener listener : myInteractiveListeners) {
((TerminalProcessHandler)myHandler).addInteractiveListener(listener);
}
super.startHandlingStreams();
}
@NotNull
@Override
protected OSProcessHandler createProcessHandler() {
return new TerminalProcessHandler(myProcess);
}
@NotNull
@Override
protected Process createProcess() throws ExecutionException {
List<String> parameters =
escapeArguments(CommandLineUtil.toCommandLine(myCommandLine.getExePath(), myCommandLine.getParametersList().getList()));
try {
return PtyProcess
.exec(ArrayUtil.toStringArray(parameters), myCommandLine.getEnvironment(), myCommandLine.getWorkDirectory().getAbsolutePath());
}
catch (IOException e) {
throw new ExecutionException(e);
}
}
@Override
public void logCommand() {
super.logCommand();
LOG.info("Terminal output " + ((TerminalProcessHandler) myHandler).getTerminalOutput());
}
private static List<String> escapeArguments(List<String> collection) {
// TODO: Add additional checks like in java.lang.ProcessImpl constructor
return ContainerUtil.map(collection, new Function<String, String>() {
@Override
public String fun(String s) {
return s.contains(" ") ? "\"" + s + "\"" : s;
}
});
}
}
@@ -0,0 +1,153 @@
/*
* Copyright 2000-2013 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.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;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.svn.SvnUtil;
import java.util.List;
import java.util.regex.Matcher;
/**
* @author Konstantin Kolosovsky.
*/
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<InteractiveCommandListener> myInteractiveListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private final CapturingProcessAdapter terminalOutputCapturer = new CapturingProcessAdapter();
private final StringBuilder outputLine = new StringBuilder();
private final StringBuilder errorLine = new StringBuilder();
public TerminalProcessHandler(@NotNull Process process) {
super(process);
}
public void addInteractiveListener(@NotNull InteractiveCommandListener listener) {
myInteractiveListeners.add(listener);
}
@Override
protected boolean processHasSeparateErrorStream() {
return false;
}
@Override
protected void destroyProcessImpl() {
final Process process = getProcess();
process.destroy();
}
@Override
public void notifyTextAvailable(String text, Key outputType) {
terminalOutputCapturer.onTextAvailable(new ProcessEvent(this, text), outputType);
// filter terminal escape codes - they are presented in the output for windows platform
String filteredText = text.replaceAll(CSI_ESCAPE_CODE, "").replaceAll(NON_CSI_ESCAPE_CODE, "");
// trim leading '\r' symbols - as they break xml parsing logic
filteredText = StringUtil.trimLeading(filteredText);
if (!StringUtil.isEmpty(filteredText)) {
StringBuilder lastLine = getLastLineFor(outputType);
String currentLine = lastLine.append(filteredText).toString();
lastLine.setLength(0);
// check if current line presents some interactive output
boolean handled = false;
for (InteractiveCommandListener listener : myInteractiveListeners) {
handled |= listener.handlePrompt(currentLine, outputType);
}
if (!handled) {
notify(currentLine, outputType, lastLine);
}
}
}
private void notify(@NotNull String text, @NotNull Key outputType, @NotNull StringBuilder lastLine) {
// 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
text = removeAllBeforeCaretReturn(text);
// text is not more than one line - either one line or part of the line
if (StringUtil.endsWith(text, "\n")) {
// we have full line - notify listeners
super.notifyTextAvailable(text, resolveOutputType(text, outputType));
}
else {
// save line part to lastLine
lastLine.append(text);
}
}
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;
if (!ProcessOutputTypes.SYSTEM.equals(outputType)) {
Matcher errorMatcher = SvnUtil.ERROR_PATTERN.matcher(line);
Matcher warningMatcher = SvnUtil.WARNING_PATTERN.matcher(line);
result = errorMatcher.find() || warningMatcher.find() ? ProcessOutputTypes.STDERR : ProcessOutputTypes.STDOUT;
}
return result;
}
@NotNull
private StringBuilder getLastLineFor(Key outputType) {
if (ProcessOutputTypes.STDERR.equals(outputType)) {
return errorLine;
}
else if (ProcessOutputTypes.STDOUT.equals(outputType)) {
return outputLine;
}
else {
throw new IllegalArgumentException("Unknown process output type " + outputType);
}
}
public String getTerminalOutput() {
return terminalOutputCapturer.getOutput().getStdout();
}
}
@@ -0,0 +1,164 @@
/*
* Copyright 2000-2013 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.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Ref;
import com.intellij.util.WaitForProgressToShow;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.svn.SvnBundle;
import org.jetbrains.idea.svn.dialogs.ServerSSHDialog;
import org.jetbrains.idea.svn.dialogs.SimpleCredentialsDialog;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationProvider;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Konstantin Kolosovsky.
*/
public class TerminalSshModule extends LineCommandAdapter implements CommandRuntimeModule, InteractiveCommandListener {
private static final Logger LOG = Logger.getInstance(TerminalSshModule.class);
private static final Pattern PASSPHRASE_PROMPT = Pattern.compile("Enter passphrase for key \\'(.*)\\':\\s?");
private static final Pattern PASSWORD_PROMPT = Pattern.compile("(.*)\\'s password:\\s?");
private static final Pattern UNKNOWN_HOST_MESSAGE =
Pattern.compile("The authenticity of host \\'((.*) \\((.*)\\))\\' can\\'t be established\\.\\s?");
private static final Pattern HOST_FINGERPRINT_MESSAGE = Pattern.compile("(\\w+) key fingerprint is (.*)\\.\\s?");
private static final Pattern ACCEPT_HOST_PROMPT = Pattern.compile("Are you sure you want to continue connecting \\(yes/no\\)\\?\\s?");
@NotNull private final CommandRuntime myRuntime;
@NotNull private final CommandExecutor myExecutor;
private String unknownHost;
private String fingerprintAlgorithm;
private String hostFingerprint;
// TODO: Do not accept executor here and make it as command runtime module
public TerminalSshModule(@NotNull CommandRuntime runtime, @NotNull CommandExecutor executor) {
myExecutor = executor;
myRuntime = runtime;
}
@Override
public void onStart(@NotNull Command command) throws SvnBindException {
}
@Override
public boolean handlePrompt(String line, Key outputType) {
return checkPassphrase(line) || checkPassword(line) || checkUnknownHost(line);
}
private boolean checkPassphrase(@NotNull String line) {
Matcher matcher = PASSPHRASE_PROMPT.matcher(line);
return matcher.matches() && handleAuthPrompt(SimpleCredentialsDialog.Mode.SSH_PASSPHRASE, matcher.group(1));
}
private boolean checkPassword(@NotNull String line) {
Matcher matcher = PASSWORD_PROMPT.matcher(line);
return matcher.matches() && handleAuthPrompt(SimpleCredentialsDialog.Mode.SSH_PASSWORD, matcher.group(1));
}
private boolean checkUnknownHost(@NotNull String line) {
Matcher unknownHostMatcher = UNKNOWN_HOST_MESSAGE.matcher(line);
Matcher hostFingerPrintMatcher = HOST_FINGERPRINT_MESSAGE.matcher(line);
Matcher acceptHostMatcher = ACCEPT_HOST_PROMPT.matcher(line);
if (unknownHostMatcher.matches()) {
unknownHost = unknownHostMatcher.group(1);
}
else if (hostFingerPrintMatcher.matches()) {
fingerprintAlgorithm = hostFingerPrintMatcher.group(1);
hostFingerprint = hostFingerPrintMatcher.group(2);
}
else if (acceptHostMatcher.matches()) {
handleUnknownHost();
}
return unknownHostMatcher.matches() || hostFingerPrintMatcher.matches() || acceptHostMatcher.matches();
}
private void handleUnknownHost() {
final Project project = myRuntime.getVcs().getProject();
final Ref<Integer> answer = new Ref<Integer>();
Runnable command = new Runnable() {
@Override
public void run() {
final ServerSSHDialog dialog = new ServerSSHDialog(project, true, unknownHost, fingerprintAlgorithm, hostFingerprint);
dialog.show();
answer.set(dialog.getResult());
}
};
WaitForProgressToShow.runOrInvokeAndWaitAboveProgress(command);
unknownHost = null;
fingerprintAlgorithm = null;
hostFingerprint = null;
sendAnswer(answer.get() == ISVNAuthenticationProvider.REJECTED ? "no" : "yes");
}
private boolean handleAuthPrompt(@NotNull final SimpleCredentialsDialog.Mode mode, @NotNull final String key) {
@NotNull final SVNURL repositoryUrl = myExecutor.getCommand().getRepositoryUrl();
final Project project = myRuntime.getVcs().getProject();
final Ref<String> answer = new Ref<String>();
Runnable command = new Runnable() {
public void run() {
SimpleCredentialsDialog dialog = new SimpleCredentialsDialog(project);
dialog.setup(mode, repositoryUrl.toDecodedString(), key, true);
dialog.setTitle(SvnBundle.message("dialog.title.authentication.required"));
dialog.show();
if (dialog.isOK()) {
answer.set(dialog.getPassword());
}
// TODO: Correctly handle "cancel" - kill the process
// TODO: and perform "cleanup" on working copy
}
};
WaitForProgressToShow.runOrInvokeAndWaitAboveProgress(command);
if (!answer.isNull()) {
sendAnswer(answer.get());
} else {
myExecutor.destroyProcess("Authentication canceled for repository: " + repositoryUrl);
}
return !answer.isNull();
}
private boolean sendAnswer(@NotNull String answer) {
try {
myExecutor.write(answer + "\n");
return true;
}
catch (SvnBindException e) {
// TODO: handle this more carefully
LOG.info(e);
}
return false;
}
}
@@ -17,6 +17,7 @@ package org.jetbrains.idea.svn.dialogs;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.SvnBundle;
@@ -41,12 +42,20 @@ public class ServerSSHDialog extends DialogWrapper {
public ServerSSHDialog(Project project, boolean store, @NotNull final String host, @Nullable final String algorithm,
@NotNull final byte[] fingerprints) {
this(project, store, host, algorithm, SVNSSLUtil.getFingerprint(fingerprints, "SHA1"));
}
public ServerSSHDialog(Project project,
boolean store,
@NotNull final String host,
@Nullable final String algorithm,
@NotNull String fingerprints) {
super(project, true);
myStore = store;
myHost = host;
myAlgorithm = algorithm == null ? "" : algorithm;
myAlgorithm = StringUtil.notNullize(algorithm);
// todo ?
myFingerprints = SVNSSLUtil.getFingerprint(fingerprints, "SHA1");
myFingerprints = fingerprints;
myResult = ISVNAuthenticationProvider.REJECTED;
setOKButtonText(SvnBundle.message("button.text.ssh.accept"));
setCancelButtonText(SvnBundle.message("button.text.ssh.reject"));
@@ -34,6 +34,7 @@ import java.awt.*;
public class SimpleCredentialsDialog extends DialogWrapper implements DocumentListener {
private boolean myAllowSave;
private String myUserName;
private Mode myMode;
private String myRealm;
private JTextField myUserNameText;
@@ -42,18 +43,24 @@ public class SimpleCredentialsDialog extends DialogWrapper implements DocumentLi
@NonNls private static final String HELP_ID = "vcs.subversion.authentication";
protected SimpleCredentialsDialog(Project project) {
public SimpleCredentialsDialog(Project project) {
super(project, true);
setResizable(false);
}
public void setup(String realm, String userName, boolean allowSave) {
setup(Mode.DEFAULT, realm, userName, allowSave);
}
public void setup(Mode mode, String realm, String userName, boolean allowSave) {
myMode = mode;
myRealm = realm;
myUserName = userName;
myAllowSave = allowSave;
getHelpAction().setEnabled(true);
init();
}
protected void doHelpAction() {
HelpManager.getInstance().invokeHelp(HELP_ID);
}
@@ -89,7 +96,7 @@ public class SimpleCredentialsDialog extends DialogWrapper implements DocumentLi
gb.weightx = 0;
gb.fill = GridBagConstraints.NONE;
label = new JLabel(SvnBundle.message("label.auth.user.name"));
label = new JLabel(SvnBundle.message(myMode.equals(Mode.SSH_PASSPHRASE) ? "label.ssh.key.file" : "label.auth.user.name"));
panel.add(label, gb);
// user name field
@@ -106,6 +113,7 @@ public class SimpleCredentialsDialog extends DialogWrapper implements DocumentLi
}
myUserNameText.selectAll();
myUserNameText.getDocument().addDocumentListener(this);
myUserNameText.setEnabled(myMode.equals(Mode.DEFAULT));
gb.gridy += 1;
gb.weightx = 0;
@@ -113,7 +121,7 @@ public class SimpleCredentialsDialog extends DialogWrapper implements DocumentLi
gb.fill = GridBagConstraints.NONE;
gb.gridwidth = 1;
label = new JLabel(SvnBundle.message("label.auth.password"));
label = new JLabel(SvnBundle.message(myMode.equals(Mode.SSH_PASSPHRASE) ? "label.ssh.passphrase" : "label.auth.password"));
panel.add(label, gb);
// passworde field
@@ -152,7 +160,7 @@ public class SimpleCredentialsDialog extends DialogWrapper implements DocumentLi
}
public JComponent getPreferredFocusedComponent() {
return myUserNameText;
return myUserNameText.isEnabled() ? myUserNameText : myPasswordText;
}
public boolean shouldCloseOnCross() {
@@ -197,4 +205,10 @@ public class SimpleCredentialsDialog extends DialogWrapper implements DocumentLi
private void updateOKButton() {
getOKAction().setEnabled(isOKActionEnabled());
}
public enum Mode {
SSH_PASSPHRASE,
SSH_PASSWORD,
DEFAULT
}
}
+3
View File
@@ -76,6 +76,9 @@
</SOURCES>
</library>
</orderEntry>
<orderEntry type="library" name="jna" level="project" />
<orderEntry type="library" name="pty4j" level="project" />
<orderEntry type="library" name="purejavacomm" level="project" />
</component>
</module>