diff options
Diffstat (limited to 'plugins/svn4idea')
88 files changed, 6792 insertions, 714 deletions
diff --git a/plugins/svn4idea/bindSvn/bindSvn.iml b/plugins/svn4idea/bindSvn/bindSvn.iml new file mode 100644 index 000000000000..846587cef36f --- /dev/null +++ b/plugins/svn4idea/bindSvn/bindSvn.iml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="module" module-name="platform-api" /> + <orderEntry type="module" module-name="vcs-api" /> + <orderEntry type="module-library"> + <library> + <CLASSES> + <root url="jar://$MODULE_DIR$/lib/javahl.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MODULE_DIR$/lib/javahlsrc.zip!/src" /> + </SOURCES> + </library> + </orderEntry> + </component> +</module> + diff --git a/plugins/svn4idea/bindSvn/lib/javahl.jar b/plugins/svn4idea/bindSvn/lib/javahl.jar Binary files differnew file mode 100644 index 000000000000..a0c5370065af --- /dev/null +++ b/plugins/svn4idea/bindSvn/lib/javahl.jar diff --git a/plugins/svn4idea/bindSvn/lib/javahlsrc.zip b/plugins/svn4idea/bindSvn/lib/javahlsrc.zip Binary files differnew file mode 100644 index 000000000000..9c50118203b7 --- /dev/null +++ b/plugins/svn4idea/bindSvn/lib/javahlsrc.zip diff --git a/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/AuthenticationCallback.java b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/AuthenticationCallback.java new file mode 100644 index 000000000000..0d3314f1ece5 --- /dev/null +++ b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/AuthenticationCallback.java @@ -0,0 +1,109 @@ +/* + * 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; + +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; + +/** + * Passed for authentication purpose to SvnLineCommand + * Called when svn command indicates that it needs credentials. It also means that credential was not found in standard place + * (Subversion config directory) + * + * Implementations should 1) ask credential from user or take it from any other storage (memory, for instance) + * 2) write credential in Subversion standard form into + * a) standard config directory if user allowed to save *all* credentials + * b) TMP directory and return path to the directory from getSpecialConfigDir() - if user rejected at least one credential storing + * + * Please note that several credentials could be asked during the command and therefore implementation class is used as + * keeping its state, and that TMP directory should be reused for all written credentials + * + * User: Irina.Chernushina + * Date: 2/26/13 + * Time: 1:05 PM + */ +public interface AuthenticationCallback { + /** + * Authenticate for realm and base file belonging to corresponding working copy + * + * @param realm - realm that should be used for credential retrieval/storage. + * @param base - file target of the operation + * @param previousFailed - whether previous credentials were correct + * @param passwordRequest - if true, password should be asked. Otherwise that may be a certificate (determined by the protocol) + * @return false if authentication canceled or was unsuccessful + */ + boolean authenticateFor(@Nullable String realm, File base, boolean previousFailed, boolean passwordRequest); + + /** + * @return config directory if TMP was created + */ + @Nullable + File getSpecialConfigDir(); + + /** + * Ask user or read from memory storage whether server certificate should be accepted + * + * @param url - that we used for request + * @param realm - realm that should be used for credential retrieval/storage. + * @return true is certificate was accepted + */ + boolean acceptSSLServerCertificate(String url, final String realm); + + /** + * Ask user or read from memory storage whether server certificate should be accepted + * + * @param file - that we used for request + * @param realm - realm that should be used for credential retrieval/storage. + * @return true is certificate was accepted + */ + boolean acceptSSLServerCertificate(File file, final String realm); + + /** + * Clear credentials stored anywhere - in case they were not full, wrong or anything else + * + * @param realm - required that credential + * @param base - file used in command + * @param password - whether password credential should be deleted or certificate, if protocol might demand certificate + */ + void clearPassiveCredentials(String realm, File base, boolean password); + + /** + * @return true if there's something from IDEA config that should be persisted into Subversion tmp config directory + * for successful call + * (now it's IDEA proxy settings) + */ + boolean haveDataForTmpConfig(); + + /** + * writes IDEA config settings (that should be written) into tmp config directory + * (now it's IDEA proxy settings) + * @return true if have written data, false if wasn't able to determine parameters etc + * @throws IOException + * @throws URISyntaxException + */ + boolean persistDataToTmpConfig(File baseFile) throws IOException, URISyntaxException; + + /** + * Ask for IDEA-defined proxy credentials, using standard authenticator + * Store data into tmp config + * + * @return false if authentication was canceled or related calculations were unsuccessful + */ + boolean askProxyCredentials(File base); +} diff --git a/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/CommitEventHandler.java b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/CommitEventHandler.java new file mode 100644 index 000000000000..7f3bcf3bf4a5 --- /dev/null +++ b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/CommitEventHandler.java @@ -0,0 +1,30 @@ +/* + * 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; + +import java.io.File; + +/** + * Used to listen to commit events to display progress to user + * + * User: Irina.Chernushina + * Date: 2/26/13 + * Time: 10:12 AM + */ +public interface CommitEventHandler { + void commitEvent(final CommitEventType type, final File target); + void committedRevision(final long revNum); +} diff --git a/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/CommitEventType.java b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/CommitEventType.java new file mode 100644 index 000000000000..46609e40532c --- /dev/null +++ b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/CommitEventType.java @@ -0,0 +1,49 @@ +/* + * 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; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/25/13 + * Time: 6:51 PM + */ +public enum CommitEventType { + adding("Adding"), + deleting("Deleting"), + sending("Sending"), + replacing("Replacing"), + transmittingDeltas("Transmitting file data"), + committedRevision("Committed revision"); + + private final String myText; + + CommitEventType(String text) { + myText = text; + } + + public String getText() { + return myText; + } + + public static CommitEventType create(String text) { + text = text.trim(); + for (CommitEventType value : CommitEventType.values()) { + if (value.getText().equals(text)) return value; + } + return null; + } +} diff --git a/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/SvnBindClient.java b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/SvnBindClient.java new file mode 100644 index 000000000000..c155b78ddc53 --- /dev/null +++ b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/SvnBindClient.java @@ -0,0 +1,925 @@ +/* + * 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; + +import org.tigris.subversion.javahl.*; + +import java.io.OutputStream; +import java.util.Map; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/5/13 + * Time: 3:08 PM + */ +public class SvnBindClient implements SVNClientInterface { + private final String myExecutablePath; + private CommitEventHandler myHandler; + private AuthenticationCallback myAuthenticationCallback; + + public SvnBindClient(String path) { + myExecutablePath = path; + } + + @Override + public void dispose() { + } + + @Override + public Version getVersion() { + // todo real version + throw new UnsupportedOperationException(); + } + + @Override + public String getAdminDirectoryName() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isAdminDirectory(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public String getLastPath() { + throw new UnsupportedOperationException(); + } + + @Override + public Status singleStatus(String path, boolean onServer) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public Status[] status(String path, boolean descend, boolean onServer, boolean getAll) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public Status[] status(String path, boolean descend, boolean onServer, boolean getAll, boolean noIgnore) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public Status[] status(String path, boolean descend, boolean onServer, boolean getAll, boolean noIgnore, boolean ignoreExternals) + throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void status(String path, + int depth, + boolean onServer, + boolean getAll, + boolean noIgnore, + boolean ignoreExternals, + String[] changelists, + StatusCallback callback) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public DirEntry[] list(String url, Revision revision, boolean recurse) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public DirEntry[] list(String url, Revision revision, Revision pegRevision, boolean recurse) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void list(String url, + Revision revision, + Revision pegRevision, + int depth, + int direntFields, + boolean fetchLocks, + ListCallback callback) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void username(String username) { + throw new UnsupportedOperationException(); + } + + @Override + public void password(String password) { + throw new UnsupportedOperationException(); + } + + @Override + public void setPrompt(PromptUserPassword prompt) { + throw new UnsupportedOperationException(); + } + + @Override + public LogMessage[] logMessages(String path, Revision revisionStart, Revision revisionEnd) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public LogMessage[] logMessages(String path, Revision revisionStart, Revision revisionEnd, boolean stopOnCopy) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public LogMessage[] logMessages(String path, Revision revisionStart, Revision revisionEnd, boolean stopOnCopy, boolean discoverPath) + throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public LogMessage[] logMessages(String path, + Revision revisionStart, + Revision revisionEnd, + boolean stopOnCopy, + boolean discoverPath, + long limit) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void logMessages(String path, + Revision pegRevision, + Revision revisionStart, + Revision revisionEnd, + boolean stopOnCopy, + boolean discoverPath, + boolean includeMergedRevisions, + String[] revProps, + long limit, + LogMessageCallback callback) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void logMessages(String path, + Revision pegRevision, + RevisionRange[] ranges, + boolean stopOnCopy, + boolean discoverPath, + boolean includeMergedRevisions, + String[] revProps, + long limit, + LogMessageCallback callback) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public long checkout(String moduleName, String destPath, Revision revision, boolean recurse) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public long checkout(String moduleName, + String destPath, + Revision revision, + Revision pegRevision, + boolean recurse, + boolean ignoreExternals) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public long checkout(String moduleName, + String destPath, + Revision revision, + Revision pegRevision, + int depth, + boolean ignoreExternals, + boolean allowUnverObstructions) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void notification(Notify notify) { + throw new UnsupportedOperationException(); + } + + @Override + public void notification2(Notify2 notify) { + throw new UnsupportedOperationException(); + } + + @Override + public void setConflictResolver(ConflictResolverCallback listener) { + throw new UnsupportedOperationException(); + } + + @Override + public void setProgressListener(ProgressListener listener) { + throw new UnsupportedOperationException(); + } + + @Override + public void commitMessageHandler(CommitMessage messageHandler) { + throw new UnsupportedOperationException(); + } + + @Override + public void remove(String[] path, String message, boolean force) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void remove(String[] path, String message, boolean force, boolean keepLocal, Map revpropTable) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void revert(String path, boolean recurse) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void revert(String path, int depth, String[] changelists) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void add(String path, boolean recurse) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void add(String path, boolean recurse, boolean force) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void add(String path, int depth, boolean force, boolean noIgnores, boolean addParents) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public long update(String path, Revision revision, boolean recurse) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public long[] update(String[] path, Revision revision, boolean recurse, boolean ignoreExternals) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public long update(String path, + Revision revision, + int depth, + boolean depthIsSticky, + boolean ignoreExternals, + boolean allowUnverObstructions) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public long[] update(String[] path, + Revision revision, + int depth, + boolean depthIsSticky, + boolean ignoreExternals, + boolean allowUnverObstructions) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public long commit(String[] path, String message, boolean recurse) throws ClientException { + return commit(path, message, recurse? 3 : 0, false, false, null, null); + } + + @Override + public long commit(String[] path, String message, boolean recurse, boolean noUnlock) throws ClientException { + return commit(path, message, recurse? 3 : 0, noUnlock, false, null, null); + } + + @Override + public long commit(String[] path, + String message, + int depth, + boolean noUnlock, + boolean keepChangelist, + String[] changelists, + Map revpropTable) throws ClientException { + final long commit = new SvnCommitRunner(myExecutablePath, myHandler, myAuthenticationCallback). + commit(path, message, depth, noUnlock, keepChangelist, changelists, revpropTable); + if (commit < 0) { + throw new BindClientException("Wrong committed revision number: " + commit, null, -1); + } + return commit; + } + + @Override + public void copy(CopySource[] sources, + String destPath, + String message, + boolean copyAsChild, + boolean makeParents, + boolean ignoreExternals, + Map revpropTable) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void copy(CopySource[] sources, String destPath, String message, boolean copyAsChild, boolean makeParents, Map revpropTable) + throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void copy(String srcPath, String destPath, String message, Revision revision) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void move(String[] srcPaths, + String destPath, + String message, + boolean force, + boolean moveAsChild, + boolean makeParents, + Map revpropTable) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void move(String srcPath, String destPath, String message, Revision ignored, boolean force) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void move(String srcPath, String destPath, String message, boolean force) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void mkdir(String[] path, String message, boolean makeParents, Map revpropTable) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void mkdir(String[] path, String message) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void cleanup(String path) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void resolve(String path, int depth, int conflictResult) throws SubversionException { + throw new UnsupportedOperationException(); + } + + @Override + public void resolved(String path, boolean recurse) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public long doExport(String srcPath, String destPath, Revision revision, boolean force) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public long doExport(String srcPath, + String destPath, + Revision revision, + Revision pegRevision, + boolean force, + boolean ignoreExternals, + boolean recurse, + String nativeEOL) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public long doExport(String srcPath, + String destPath, + Revision revision, + Revision pegRevision, + boolean force, + boolean ignoreExternals, + int depth, + String nativeEOL) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public long doSwitch(String path, String url, Revision revision, boolean recurse) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public long doSwitch(String path, + String url, + Revision revision, + Revision pegRevision, + int depth, + boolean depthIsSticky, + boolean ignoreExternals, + boolean allowUnverObstructions) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void doImport(String path, String url, String message, boolean recurse) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void doImport(String path, + String url, + String message, + int depth, + boolean noIgnore, + boolean ignoreUnknownNodeTypes, + Map revpropTable) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public String[] suggestMergeSources(String path, Revision pegRevision) throws SubversionException { + throw new UnsupportedOperationException(); + } + + @Override + public void merge(String path1, Revision revision1, String path2, Revision revision2, String localPath, boolean force, boolean recurse) + throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void merge(String path1, + Revision revision1, + String path2, + Revision revision2, + String localPath, + boolean force, + boolean recurse, + boolean ignoreAncestry, + boolean dryRun) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void merge(String path1, + Revision revision1, + String path2, + Revision revision2, + String localPath, + boolean force, + int depth, + boolean ignoreAncestry, + boolean dryRun, + boolean recordOnly) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void merge(String path, + Revision pegRevision, + Revision revision1, + Revision revision2, + String localPath, + boolean force, + boolean recurse, + boolean ignoreAncestry, + boolean dryRun) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void merge(String path, + Revision pegRevision, + RevisionRange[] revisions, + String localPath, + boolean force, + int depth, + boolean ignoreAncestry, + boolean dryRun, + boolean recordOnly) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void mergeReintegrate(String path, Revision pegRevision, String localPath, boolean dryRun) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public Mergeinfo getMergeinfo(String path, Revision pegRevision) throws SubversionException { + throw new UnsupportedOperationException(); + } + + @Override + public void getMergeinfoLog(int kind, + String pathOrUrl, + Revision pegRevision, + String mergeSourceUrl, + Revision srcPegRevision, + boolean discoverChangedPaths, + int depth, + String[] revProps, + LogMessageCallback callback) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void getMergeinfoLog(int kind, + String pathOrUrl, + Revision pegRevision, + String mergeSourceUrl, + Revision srcPegRevision, + boolean discoverChangedPaths, + String[] revProps, + LogMessageCallback callback) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void diff(String target1, Revision revision1, String target2, Revision revision2, String outFileName, boolean recurse) + throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void diff(String target1, + Revision revision1, + String target2, + Revision revision2, + String outFileName, + boolean recurse, + boolean ignoreAncestry, + boolean noDiffDeleted, + boolean force) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void diff(String target1, + Revision revision1, + String target2, + Revision revision2, + String relativeToDir, + String outFileName, + int depth, + String[] changelists, + boolean ignoreAncestry, + boolean noDiffDeleted, + boolean force, + boolean copiesAsAdds) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void diff(String target1, + Revision revision1, + String target2, + Revision revision2, + String relativeToDir, + String outFileName, + int depth, + String[] changelists, + boolean ignoreAncestry, + boolean noDiffDeleted, + boolean force) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void diff(String target, + Revision pegRevision, + Revision startRevision, + Revision endRevision, + String outFileName, + boolean recurse, + boolean ignoreAncestry, + boolean noDiffDeleted, + boolean force) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void diff(String target, + Revision pegRevision, + Revision startRevision, + Revision endRevision, + String relativeToDir, + String outFileName, + int depth, + String[] changelists, + boolean ignoreAncestry, + boolean noDiffDeleted, + boolean force, + boolean copiesAsAdds) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void diff(String target, + Revision pegRevision, + Revision startRevision, + Revision endRevision, + String relativeToDir, + String outFileName, + int depth, + String[] changelists, + boolean ignoreAncestry, + boolean noDiffDeleted, + boolean force) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void diffSummarize(String target1, + Revision revision1, + String target2, + Revision revision2, + int depth, + String[] changelists, + boolean ignoreAncestry, + DiffSummaryReceiver receiver) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void diffSummarize(String target, + Revision pegRevision, + Revision startRevision, + Revision endRevision, + int depth, + String[] changelists, + boolean ignoreAncestry, + DiffSummaryReceiver receiver) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public PropertyData[] properties(String path) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public PropertyData[] properties(String path, Revision revision) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public PropertyData[] properties(String path, Revision revision, Revision pegRevision) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void properties(String path, Revision revision, Revision pegRevision, int depth, String[] changelists, ProplistCallback callback) + throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void propertySet(String path, String name, String value, boolean recurse) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void propertySet(String path, String name, String value, boolean recurse, boolean force) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void propertySet(String path, String name, byte[] value, boolean recurse) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void propertySet(String path, String name, byte[] value, boolean recurse, boolean force) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void propertySet(String path, String name, String value, int depth, String[] changelists, boolean force, Map revpropTable) + throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void propertyRemove(String path, String name, boolean recurse) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void propertyRemove(String path, String name, int depth, String[] changelists) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void propertyCreate(String path, String name, String value, boolean recurse) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void propertyCreate(String path, String name, String value, boolean recurse, boolean force) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void propertyCreate(String path, String name, byte[] value, boolean recurse) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void propertyCreate(String path, String name, byte[] value, boolean recurse, boolean force) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void propertyCreate(String path, String name, String value, int depth, String[] changelists, boolean force) + throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public PropertyData revProperty(String path, String name, Revision rev) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public PropertyData[] revProperties(String path, Revision rev) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void setRevProperty(String path, String name, Revision rev, String value, boolean force) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void setRevProperty(String path, String name, Revision rev, String value, String originalValue, boolean force) + throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public PropertyData propertyGet(String path, String name) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public PropertyData propertyGet(String path, String name, Revision revision) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public PropertyData propertyGet(String path, String name, Revision revision, Revision pegRevision) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public byte[] fileContent(String path, Revision revision) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public byte[] fileContent(String path, Revision revision, Revision pegRevision) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void streamFileContent(String path, Revision revision, Revision pegRevision, int bufferSize, OutputStream stream) + throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void relocate(String from, String to, String path, boolean recurse) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public byte[] blame(String path, Revision revisionStart, Revision revisionEnd) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void blame(String path, Revision revisionStart, Revision revisionEnd, BlameCallback callback) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void blame(String path, Revision pegRevision, Revision revisionStart, Revision revisionEnd, BlameCallback callback) + throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void blame(String path, + Revision pegRevision, + Revision revisionStart, + Revision revisionEnd, + boolean ignoreMimeType, + boolean includeMergedRevisions, + BlameCallback2 callback) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void blame(String path, + Revision pegRevision, + Revision revisionStart, + Revision revisionEnd, + boolean ignoreMimeType, + boolean includeMergedRevisions, + BlameCallback3 callback) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void setConfigDirectory(String configDir) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public String getConfigDirectory() throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void cancelOperation() throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public Info info(String path) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void addToChangelist(String[] paths, String changelist, int depth, String[] changelists) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void removeFromChangelists(String[] paths, int depth, String[] changelists) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void getChangelists(String rootPath, String[] changelists, int depth, ChangelistCallback callback) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void lock(String[] path, String comment, boolean force) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void unlock(String[] path, boolean force) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public Info2[] info2(String pathOrUrl, Revision revision, Revision pegRevision, boolean recurse) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void info2(String pathOrUrl, Revision revision, Revision pegRevision, int depth, String[] changelists, InfoCallback callback) + throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public String getVersionInfo(String path, String trailUrl, boolean lastChanged) throws ClientException { + throw new UnsupportedOperationException(); + } + + @Override + public void upgrade(String path) throws ClientException { + throw new UnsupportedOperationException(); + } + + public void setHandler(CommitEventHandler handler) { + myHandler = handler; + } + + public void setAuthenticationCallback(AuthenticationCallback authenticationCallback) { + myAuthenticationCallback = authenticationCallback; + } +} diff --git a/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/SvnBindUtil.java b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/SvnBindUtil.java new file mode 100644 index 000000000000..1fec6b3bf865 --- /dev/null +++ b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/SvnBindUtil.java @@ -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; + +import java.io.File; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/5/13 + * Time: 4:56 PM + */ +public class SvnBindUtil { + /** + * SVN_ASP_DOT_NET_HACK allows use of an alternate name for Subversion working copy + * administrative directories on Windows (which were formerly always + * named ".svn"), by setting the SVN_ASP_DOT_NET_HACK environment variable. + * When the variable is set (to any value), the administrative directory + * will be "_svn" instead of ".svn". + * + * http://svn.apache.org/repos/asf/subversion/trunk/notes/asp-dot-net-hack.txt + */ + public static final String ADM_NAME = System.getenv("SVN_ASP_DOT_NET_HACK") != null ? "_svn" : ".svn"; + private final static List<DateFormat> ourFormats = new ArrayList<DateFormat>(); + + static { + ourFormats.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS")); + ourFormats.add(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'000Z'")); + ourFormats.add(new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z", Locale.US)); + ourFormats.add(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z (EE, d MMM yyyy)", Locale.getDefault())); + ourFormats.add(new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss' 'ZZZZ' ('E', 'dd' 'MMM' 'yyyy')'")); + ourFormats.add(new SimpleDateFormat("yyyy-MM-dd' 'HH:mm:ss'Z'")); + ourFormats.add(new SimpleDateFormat("EEE' 'MMM' 'dd' 'HH:mm:ss' 'yyyy")); + ourFormats.add(new SimpleDateFormat("MM' 'dd' 'yyyy")); + ourFormats.add(new SimpleDateFormat("MM' 'dd' 'HH:mm")); + ourFormats.add(new SimpleDateFormat("MM' 'dd' 'HH:mm:ss")); + } + + public static Date parseDate(final String date) { + for (DateFormat format : ourFormats) { + try { + return format.parse(date); + } + catch (ParseException e) { + continue; + } + } + return new Date(0); + } + + public static void changelistsToCommand(String[] changeLists, final List<String> list) { + if (changeLists != null) { + for (String name : changeLists) { + list.add("--cl"); + list.add(name); + } + } + } + + public static String getDepthName(final int i) { + return org.tigris.subversion.javahl.Depth.toADepth(i).name(); + } + + public static File correctUpToExistingParent(File base) { + while (base != null) { + if (base.exists() && base.isDirectory()) return base; + base = base.getParentFile(); + } + return null; + } + + public static File getWcRoot(File base) { + File current = base; + while (current != null) { + if (getWcDbUnder(current).exists()) return current; + current = current.getParentFile(); + } + return null; + } + + public static File getWcDbUnder(final File file) { + return new File(file, ADM_NAME + File.separator + "wc.db"); + } +} diff --git a/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/SvnCommitRunner.java b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/SvnCommitRunner.java new file mode 100644 index 000000000000..525d675303fc --- /dev/null +++ b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/SvnCommitRunner.java @@ -0,0 +1,217 @@ +/* + * 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; + +import com.intellij.execution.process.ProcessOutputTypes; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.ArrayUtil; +import org.apache.subversion.javahl.types.Revision; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.commandLine.LineCommandListener; +import org.jetbrains.idea.svn.commandLine.SvnCommandName; +import org.jetbrains.idea.svn.commandLine.SvnLineCommand; +import org.jetbrains.idea.svn.config.SvnBindException; +import org.tigris.subversion.javahl.BindClientException; +import org.tigris.subversion.javahl.ClientException; + +import java.io.File; +import java.util.*; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/25/13 + * Time: 4:56 PM + */ +public class SvnCommitRunner { + private final String myExePath; + @Nullable private final AuthenticationCallback myAuthenticationCallback; + private static final Logger LOG = Logger.getInstance("org.jetbrains.idea.svn.SvnCommitRunner"); + private SvnCommitRunner.CommandListener myCommandListener; + + public SvnCommitRunner(@NotNull String path, @Nullable CommitEventHandler handler, @Nullable AuthenticationCallback authenticationCallback) { + myExePath = path; + myCommandListener = new CommandListener(handler); + myAuthenticationCallback = authenticationCallback; + } + + public long commit(String[] paths, + String message, + int depth, + boolean noUnlock, + boolean keepChangelist, + String[] changelists, + Map revpropTable) throws ClientException { + if (paths.length == 0) return Revision.SVN_INVALID_REVNUM; + + final List<String> parameters = new ArrayList<String>(); + parameters.add("--depth"); + parameters.add(SvnBindUtil.getDepthName(depth)); + if (noUnlock) { + parameters.add("--no-unlock"); + } + if (keepChangelist) { + parameters.add("--keep-changelists"); + } + if (changelists != null && changelists.length > 0) { + SvnBindUtil.changelistsToCommand(changelists, parameters); + } + if (revpropTable != null && ! revpropTable.isEmpty()) { + final Set<Map.Entry<Object, Object>> set = revpropTable.entrySet(); + for (Map.Entry<Object, Object> entry : set) { + parameters.add("--with-revprop"); + parameters.add(entry.getKey() + "=" + entry.getValue()); + } + } + parameters.add("-m"); + parameters.add(message); + Arrays.sort(paths); + parameters.addAll(Arrays.asList(paths)); + + try { + SvnLineCommand.runWithAuthenticationAttempt(myExePath, new File(paths[0]), SvnCommandName.ci, + myCommandListener, myAuthenticationCallback, ArrayUtil.toStringArray(parameters)); + } + catch (SvnBindException e) { + throw BindClientException.create(e, Revision.SVN_INVALID_REVNUM); + } + myCommandListener.throwExceptionIfOccurred(); + + return myCommandListener.getCommittedRevision(); + } + + private static class CommandListener extends LineCommandListener { + @Nullable private final CommitEventHandler myHandler; + private SvnBindException myException; + private long myCommittedRevision = Revision.SVN_INVALID_REVNUM; + private File myBase; + + public CommandListener(@Nullable CommitEventHandler handler) { + myHandler = handler; + } + + public void throwExceptionIfOccurred() throws BindClientException { + if (myException != null) { + throw BindClientException.create(myException, Revision.SVN_INVALID_REVNUM); + } + } + + private long getCommittedRevision() { + return myCommittedRevision; + } + + @Override + public void baseDirectory(File file) { + myBase = file; + } + + @Override + public void onLineAvailable(String line, Key outputType) { + final String trim = line.trim(); + if (ProcessOutputTypes.STDOUT.equals(outputType)) { + try { + parseLine(trim); + } + catch (SvnBindException e) { + myException = e; + } + } + } + + private void parseLine(String line) throws SvnBindException { + if (StringUtil.isEmptyOrSpaces(line)) return; + if (line.startsWith(CommitEventType.transmittingDeltas.getText())) { + if (myHandler != null) { + myHandler.commitEvent(CommitEventType.transmittingDeltas, myBase); + } + return; + } + if (line.startsWith(CommitEventType.committedRevision.getText())) { + final String substring = line.substring(CommitEventType.committedRevision.getText().length()); + int cnt = 0; + while (StringUtil.isWhiteSpace(substring.charAt(cnt))) { + ++ cnt; + } + final StringBuilder num = new StringBuilder(); + while (Character.isDigit(substring.charAt(cnt))) { + num.append(substring.charAt(cnt)); + ++ cnt; + } + if (num.length() > 0) { + try { + myCommittedRevision = Long.parseLong(num.toString()); + if (myHandler != null) { + myHandler.committedRevision(myCommittedRevision); + } + } catch (NumberFormatException e) { + final String message = "Wrong committed revision number: " + num.toString() + ", string: " + line; + LOG.info(message, e); + throw new SvnBindException(message); + } + } else { + final String message = "Missing committed revision number: " + num.toString() + ", string: " + line; + LOG.info(message); + throw new SvnBindException(message); + } + } else { + if (myHandler == null) return; + final int idxSpace = line.indexOf(' '); + if (idxSpace == -1) { + LOG.info("Can not parse event type: " + line); + return; + } + final CommitEventType type = CommitEventType.create(line.substring(0, idxSpace)); + if (type == null) { + LOG.info("Can not parse event type: " + line); + return; + } + final File target = new File(myBase, new String(line.substring(idxSpace + 1).trim())); + myHandler.commitEvent(type, target); + } + } + } + +/*C:\TestProjects\sortedProjects\Subversion\local2\preRelease\mod2\src\com\test>sv + n st + D gggG + D gggG\Rrr.java + D gggG\and555.txt + D gggG\test.txt + A + gggGA + D + gggGA\Rrr.java + A + gggGA\RrrAA.java + D + gggGA\and.txt + M + gggGA\and555.txt + A gggGA\someNewFile.txt + + --- Changelist 'New changelistrwerwe': + A ddd.jpg + + C:\TestProjects\sortedProjects\Subversion\local2\preRelease\mod2\src\com\test>sv + n ci -m 123 + Adding ddd.jpg + Deleting gggG + Adding gggGA + Deleting gggGA\Rrr.java + Adding gggGA\RrrAA.java + Sending gggGA\and555.txt + Adding gggGA\someNewFile.txt + Transmitting file data .... + Committed revision 165.*/ +} diff --git a/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/commandLine/LineCommandListener.java b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/commandLine/LineCommandListener.java new file mode 100644 index 000000000000..ccbc5c37db61 --- /dev/null +++ b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/commandLine/LineCommandListener.java @@ -0,0 +1,40 @@ +/* + * 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.vcs.LineProcessEventListenerAdapter; + +import java.io.File; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/26/13 + * Time: 10:38 AM + */ +public abstract class LineCommandListener extends LineProcessEventListenerAdapter { + private boolean myCanceled; + + public abstract void baseDirectory(final File file); + + public void cancel() { + myCanceled = true; + } + + protected boolean isCanceled() { + return myCanceled; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommand.java b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/commandLine/SvnCommand.java index 5ef336a98428..a1e358b6f4a0 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommand.java +++ b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/commandLine/SvnCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * 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. @@ -20,14 +20,12 @@ import com.intellij.execution.process.OSProcessHandler; import com.intellij.execution.process.ProcessEvent; import com.intellij.execution.process.ProcessListener; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.vcs.ProcessEventListener; import com.intellij.util.EventDispatcher; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import org.jetbrains.idea.svn.SvnApplicationSettings; -import org.jetbrains.idea.svn.SvnVcs; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.util.List; @@ -39,9 +37,8 @@ import java.util.List; * Time: 12:58 PM */ public abstract class SvnCommand { - private static final Logger LOG = Logger.getInstance(SvnCommand.class.getName()); + static final Logger LOG = Logger.getInstance(SvnCommand.class.getName()); - protected final Project myProject; private boolean myIsDestroyed; private int myExitCode; protected final GeneralCommandLine myCommandLine; @@ -56,14 +53,20 @@ public abstract class SvnCommand { /*c:\Program Files (x86)\CollabNet\Subversion Client17>svn --version --quiet 1.7.2*/ - public SvnCommand(Project project, File workingDirectory, @NotNull SvnCommandName commandName) { + public SvnCommand(File workingDirectory, @NotNull SvnCommandName commandName, @NotNull @NonNls String exePath) { + this(workingDirectory, commandName, exePath, null); + } + + public SvnCommand(File workingDirectory, @NotNull SvnCommandName commandName, @NotNull @NonNls String exePath, + @Nullable File configDir) { myLock = new Object(); - myProject = project; myCommandLine = new GeneralCommandLine(); myWorkingDirectory = workingDirectory; - final SvnApplicationSettings applicationSettings17 = SvnApplicationSettings.getInstance(); - myCommandLine.setExePath(applicationSettings17.getCommandLinePath()); + myCommandLine.setExePath(exePath); myCommandLine.setWorkDirectory(workingDirectory); + if (configDir != null) { + myCommandLine.addParameters("--config-dir", configDir.getPath()); + } myCommandLine.addParameter(commandName.getName()); } @@ -73,10 +76,12 @@ public abstract class SvnCommand { try { myProcess = myCommandLine.createProcess(); + if (LOG.isDebugEnabled()) { + LOG.debug(myCommandLine.toString()); + } myHandler = new OSProcessHandler(myProcess, myCommandLine.getCommandLineString()); startHandlingStreams(); } catch (Throwable t) { - SvnVcs.getInstance(myProject).checkCommandLineVersion(); myListeners.getMulticaster().startFailed(t); } } @@ -92,7 +97,6 @@ public abstract class SvnCommand { final int exitCode = event.getExitCode(); try { setExitCode(exitCode); - //cleanupEnv(); todo SvnCommand.this.processTerminated(exitCode); } finally { listeners().processTerminated(exitCode); @@ -210,4 +214,8 @@ public abstract class SvnCommand { return myExitCode; } } + + protected File getWorkingDirectory() { + return myWorkingDirectory; + } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandName.java b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/commandLine/SvnCommandName.java index 950ec7f9eb1b..58c2ff2ab308 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandName.java +++ b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/commandLine/SvnCommandName.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * 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. @@ -22,18 +22,26 @@ package org.jetbrains.idea.svn.commandLine; * Time: 1:49 PM */ public enum SvnCommandName { - version("--version"), - info("info"), - st("st"), - up("up"); + version("--version", false), + info("info", false), + st("st", false), + up("up", true), + ci("commit", true), + cleanup("cleanup", true); private final String myName; + private final boolean myWriteable; - private SvnCommandName(String name) { + private SvnCommandName(String name, boolean writeable) { myName = name; + myWriteable = writeable; } public String getName() { return myName; } + + public boolean isWriteable() { + return myWriteable; + } } diff --git a/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/commandLine/SvnLineCommand.java b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/commandLine/SvnLineCommand.java new file mode 100644 index 000000000000..3554b1cd4d81 --- /dev/null +++ b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/commandLine/SvnLineCommand.java @@ -0,0 +1,415 @@ +/* + * 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.ProcessOutputTypes; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ApplicationNamesInfo; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.LineHandlerHelper; +import com.intellij.openapi.vcs.LineProcessEventListener; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.util.EventDispatcher; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.AuthenticationCallback; +import org.jetbrains.idea.svn.SvnBindUtil; +import org.jetbrains.idea.svn.config.SvnBindException; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 1/25/12 + * Time: 4:05 PM + * + * honestly stolen from GitLineHandler + */ +public class SvnLineCommand extends SvnCommand { + public static final String AUTHENTICATION_REALM = "Authentication realm:"; + public static final String CERTIFICATE_ERROR = "Error validating server certificate for"; + public static final String PASSPHRASE_FOR = "Passphrase for"; + public static final String UNABLE_TO_CONNECT = "svn: E170001:"; + public static final String CANNOT_AUTHENTICATE_TO_PROXY = "Could not authenticate to proxy server"; + + // kept for exact text + //public static final String CLIENT_CERTIFICATE_FILENAME = "Client certificate filename:"; + /** + * the partial line from stdout stream + */ + private final StringBuilder myStdoutLine = new StringBuilder(); + /** + * the partial line from stderr stream + */ + private final StringBuilder myStderrLine = new StringBuilder(); + private final EventDispatcher<LineProcessEventListener> myLineListeners; + private final AtomicReference<Integer> myExitCode; + private final StringBuffer myErr; + + public SvnLineCommand(File workingDirectory, @NotNull SvnCommandName commandName, @NotNull @NonNls String exePath) { + this(workingDirectory, commandName, exePath, null); + } + + public SvnLineCommand(File workingDirectory, @NotNull SvnCommandName commandName, @NotNull @NonNls String exePath, File configDir) { + super(workingDirectory, commandName, exePath, configDir); + myLineListeners = EventDispatcher.create(LineProcessEventListener.class); + myExitCode = new AtomicReference<Integer>(); + myErr = new StringBuffer(); + } + + @Override + protected void processTerminated(int exitCode) { + // force newline + if (myStdoutLine.length() != 0) { + onTextAvailable("\n\r", ProcessOutputTypes.STDOUT); + } + else if (myStderrLine.length() != 0) { + onTextAvailable("\n\r", ProcessOutputTypes.STDERR); + } + } + + public static void runWithAuthenticationAttempt(final String exePath, + final File firstFile, + SvnCommandName commandName, + final LineCommandListener listener, + @Nullable AuthenticationCallback authenticationCallback, + final String... parameters) throws SvnBindException { + File base = firstFile.isDirectory() ? firstFile : firstFile.getParentFile(); + base = SvnBindUtil.correctUpToExistingParent(base); + + listener.baseDirectory(base); + + File configDir = null; + + // for IDEA proxy case + if (authenticationCallback != null && authenticationCallback.haveDataForTmpConfig()) { + try { + if (! authenticationCallback.persistDataToTmpConfig(base)) { + throw new SvnBindException("Can not persist " + ApplicationNamesInfo.getInstance().getProductName() + + " HTTP proxy information into tmp config directory"); + } + } + catch (IOException e) { + throw new SvnBindException(e); + } + catch (URISyntaxException e) { + throw new SvnBindException(e); + } + assert authenticationCallback.getSpecialConfigDir() != null; + configDir = authenticationCallback.getSpecialConfigDir(); + } + + while (true) { + final SvnLineCommand command = runCommand(exePath, commandName, listener, base, configDir, parameters); + if (command.myErr.length() > 0) { + final String errText = command.myErr.toString().trim(); + if (authenticationCallback != null) { + final AuthCallbackCase callback = createCallback(errText, authenticationCallback, base); + if (callback != null) { + cleanup(exePath, commandName, base); + if (callback.getCredentials(errText)) { + if (authenticationCallback.getSpecialConfigDir() != null) { + configDir = authenticationCallback.getSpecialConfigDir(); + } + continue; + } + } + } + throw new SvnBindException(errText); + } + final Integer exitCode = command.myExitCode.get(); + if (exitCode != 0) { + throw new SvnBindException("Svn process exited with error code: " + exitCode); + } + return; + } + //ok + } + + private static AuthCallbackCase createCallback(final String errText, final AuthenticationCallback callback, final File base) { + if (errText.startsWith(CERTIFICATE_ERROR)) { + return new CertificateCallbackCase(callback, base); + } + if (errText.startsWith(AUTHENTICATION_REALM)) { + return new CredentialsCallback(callback, base); + } + if (errText.startsWith(PASSPHRASE_FOR)) { + return new PassphraseCallback(callback, base); + } + if (errText.startsWith(UNABLE_TO_CONNECT) && errText.contains(CANNOT_AUTHENTICATE_TO_PROXY)) { + return new ProxyCallback(callback, base); + } + return null; + } + + private static class ProxyCallback extends AuthCallbackCase { + protected ProxyCallback(AuthenticationCallback callback, File base) { + super(callback, base); + } + + @Override + boolean getCredentials(String errText) throws SvnBindException { + return myAuthenticationCallback.askProxyCredentials(myBase); + } + } + + private static class CredentialsCallback extends AuthCallbackCase { + protected CredentialsCallback(AuthenticationCallback callback, File base) { + super(callback, base); + } + + @Override + boolean getCredentials(String errText) throws SvnBindException { + final String realm = cutFirstLine(errText).substring(AUTHENTICATION_REALM.length()).trim(); + final boolean isPassword = StringUtil.containsIgnoreCase(errText, "password"); + if (myTried) { + myAuthenticationCallback.clearPassiveCredentials(realm, myBase, isPassword); + } + myTried = true; + if (myAuthenticationCallback.authenticateFor(realm, myBase, myAuthenticationCallback.getSpecialConfigDir() != null, isPassword)) { + return true; + } + throw new SvnBindException("Authentication canceled for realm: " + realm); + } + } + + private static String cutFirstLine(final String text) { + final int idx = text.indexOf('\n'); + if (idx == -1) return text; + return text.substring(0, idx); + } + + private static class CertificateCallbackCase extends AuthCallbackCase { + private CertificateCallbackCase(AuthenticationCallback callback, File base) { + super(callback, base); + } + + @Override + public boolean getCredentials(final String errText) throws SvnBindException { + String realm = cutFirstLine(errText).substring(CERTIFICATE_ERROR.length()); + final int idx1 = realm.indexOf('\''); + if (idx1 == -1) { + throw new SvnBindException("Can not detect authentication realm name: " + errText); + } + final int idx2 = realm.indexOf('\'', idx1 + 1); + if (idx2== -1) { + throw new SvnBindException("Can not detect authentication realm name: " + errText); + } + realm = realm.substring(idx1 + 1, idx2); + if (! myTried && myAuthenticationCallback.acceptSSLServerCertificate(myBase, realm)) { + myTried = true; + return true; + } + throw new SvnBindException("Server SSL certificate rejected"); + } + } + + private static abstract class AuthCallbackCase { + protected boolean myTried = false; + protected final AuthenticationCallback myAuthenticationCallback; + protected final File myBase; + + protected AuthCallbackCase(AuthenticationCallback callback, final File base) { + myAuthenticationCallback = callback; + myBase = base; + } + + abstract boolean getCredentials(final String errText) throws SvnBindException; + } + + private static void cleanup(String exePath, SvnCommandName commandName, File base) throws SvnBindException { + File wcRoot = SvnBindUtil.getWcRoot(base); + if (wcRoot == null) throw new SvnBindException("Can not find working copy root for: " + base.getPath()); + + //cleanup -> check command type + if (commandName.isWriteable()) { + final SvnSimpleCommand command = new SvnSimpleCommand(wcRoot, SvnCommandName.cleanup, exePath); + try { + command.run(); + } + catch (VcsException e) { + throw new SvnBindException(e); + } + } + } + + /*svn: E170001: Commit failed (details follow): + svn: E170001: Unable to connect to a repository at URL 'htt../svn/secondRepo/local2/trunk/mod2/src/com/test/gggGA' + svn: E170001: OPTIONS of 'htt.../svn/secondRepo/local2/trunk/mod2/src/com/test/gggGA': authorization failed: Could not authenticate to server: rejected Basic challenge (ht)*/ + private final static String ourAuthFailed = "authorization failed"; + private final static String ourAuthFailed2 = "Could not authenticate to server"; + + private static boolean isAuthenticationFailed(String s) { + return s.trim().startsWith(AUTHENTICATION_REALM); + //return s.contains(ourAuthFailed) && s.contains(ourAuthFailed2); + } + + private static SvnLineCommand runCommand(String exePath, + SvnCommandName commandName, + final LineCommandListener listener, + File base, File configDir, + String... parameters) throws SvnBindException { + final SvnLineCommand command = new SvnLineCommand(base, commandName, exePath, configDir) { + int myErrCnt = 0; + + @Override + protected void onTextAvailable(String text, Key outputType) { + + // we won't stop right now if got "authentication realm" -> since we want to get "password" string (that would mean password is expected + // or certificate maybe is expected + // but for certificate passphrase we get just "Passphrase for ..." - one line without line feed + + // for client certificate (when no path in servers file) we get + // Authentication realm: <text> + // Client certificate filename: + if (ProcessOutputTypes.STDERR.equals(outputType)) { + ++ myErrCnt; + final String trim = text.trim(); + if (trim.startsWith(UNABLE_TO_CONNECT)) { + // wait for 3 lines of text then + if (myErrCnt >= 3) { + destroyProcess(); + } + } else if (trim.startsWith(PASSPHRASE_FOR) || myErrCnt >= 2) { + destroyProcess(); + } + } + super.onTextAvailable(text, outputType); + } + }; + + //command.addParameters("--non-interactive"); + command.addParameters(parameters); + final AtomicReference<Throwable> exceptionRef = new AtomicReference<Throwable>(); + // several threads + command.addLineListener(new LineProcessEventListener() { + @Override + public void onLineAvailable(String line, Key outputType) { + if (SvnCommand.LOG.isDebugEnabled()) { + SvnCommand.LOG.debug("==> " + line); + } + if (ApplicationManager.getApplication().isUnitTestMode()) { + System.out.println("==> " + line); + } + listener.onLineAvailable(line, outputType); + if (listener.isCanceled()) { + command.destroyProcess(); + return; + } + if (ProcessOutputTypes.STDERR.equals(outputType)) { + if (command.myErr.length() > 0) { + command.myErr.append('\n'); + } + command.myErr.append(line); + } + } + + @Override + public void processTerminated(int exitCode) { + listener.processTerminated(exitCode); + command.myExitCode.set(exitCode); + } + + @Override + public void startFailed(Throwable exception) { + listener.startFailed(exception); + exceptionRef.set(exception); + } + }); + command.start(); + command.waitFor(); + if (exceptionRef.get() != null) { + throw new SvnBindException(exceptionRef.get()); + } + return command; + } + + @Override + protected void onTextAvailable(String text, Key outputType) { + Iterator<String> lines = LineHandlerHelper.splitText(text).iterator(); + if (ProcessOutputTypes.STDOUT == outputType) { + notifyLines(outputType, lines, myStdoutLine); + } + else if (ProcessOutputTypes.STDERR == outputType) { + notifyLines(outputType, lines, myStderrLine); + } + } + + private void notifyLines(final Key outputType, final Iterator<String> lines, final StringBuilder lineBuilder) { + if (!lines.hasNext()) return; + if (lineBuilder.length() > 0) { + lineBuilder.append(lines.next()); + if (lines.hasNext()) { + // line is complete + final String line = lineBuilder.toString(); + notifyLine(line, outputType); + lineBuilder.setLength(0); + } + } + while (true) { + String line = null; + if (lines.hasNext()) { + line = lines.next(); + } + + if (lines.hasNext()) { + notifyLine(line, outputType); + } + else { + if (line != null && line.length() > 0) { + lineBuilder.append(line); + } + break; + } + } + } + + private void notifyLine(final String line, final Key outputType) { + String trimmed = LineHandlerHelper.trimLineSeparator(line); + myLineListeners.getMulticaster().onLineAvailable(trimmed, outputType); + } + + public void addLineListener(LineProcessEventListener listener) { + myLineListeners.addListener(listener); + super.addListener(listener); + } + + private static class PassphraseCallback extends AuthCallbackCase { + public PassphraseCallback(AuthenticationCallback callback, File base) { + super(callback, base); + } + + @Override + boolean getCredentials(String errText) throws SvnBindException { + // try to get from file + /*if (myTried) { + myAuthenticationCallback.clearPassiveCredentials(null, myBase); + }*/ + myTried = true; + if (myAuthenticationCallback.authenticateFor(null, myBase, myAuthenticationCallback.getSpecialConfigDir() != null, false)) { + return true; + } + throw new SvnBindException("Authentication canceled for : " + errText.substring(PASSPHRASE_FOR.length())); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnSimpleCommand.java b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/commandLine/SvnSimpleCommand.java index 6f68d390798e..da5df9e6d299 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnSimpleCommand.java +++ b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/commandLine/SvnSimpleCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * 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. @@ -16,10 +16,10 @@ package org.jetbrains.idea.svn.commandLine; import com.intellij.execution.process.ProcessOutputTypes; -import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.vcs.ProcessEventListener; import com.intellij.openapi.vcs.VcsException; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import java.io.File; @@ -36,8 +36,8 @@ public class SvnSimpleCommand extends SvnCommand { private VcsException myException; private final Object myDataLock; - public SvnSimpleCommand(Project project, File workingDirectory, @NotNull SvnCommandName commandName) { - super(project, workingDirectory, commandName); + public SvnSimpleCommand(File workingDirectory, @NotNull SvnCommandName commandName, @NotNull @NonNls String exePath) { + super(workingDirectory, commandName, exePath); myDataLock = new Object(); myStderr = new StringBuilder(); @@ -96,13 +96,9 @@ public class SvnSimpleCommand extends SvnCommand { if (code == 0) { return myStdout.toString(); } else { - String msg = myStderr.toString(); - if (msg.length() == 0) { - msg = getStdout().toString(); - } - if (msg.length() == 0) { - msg = "Svn process exited with error code: " + code; - } + final String msg = new StringBuilder("Svn process exited with error code: ").append(code).append("\n") + .append("stderr: ").append(myStderr.toString()).append("\nstdout: ").append(getStdout().toString()) + .append("\nCommand was: ").append(myCommandLine.getCommandLineString()).toString(); throw new VcsException(msg); } } diff --git a/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/config/SvnBindException.java b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/config/SvnBindException.java new file mode 100644 index 000000000000..8957843e911c --- /dev/null +++ b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/config/SvnBindException.java @@ -0,0 +1,54 @@ +/* + * 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.config; + +import com.intellij.openapi.vcs.VcsException; + +import java.util.Collection; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/25/13 + * Time: 5:57 PM + * + * Marker exception + */ +public class SvnBindException extends VcsException { + public SvnBindException(String message) { + super(message); + } + + public SvnBindException(Throwable throwable, boolean isWarning) { + super(throwable, isWarning); + } + + public SvnBindException(Throwable throwable) { + super(throwable); + } + + public SvnBindException(String message, Throwable cause) { + super(message, cause); + } + + public SvnBindException(String message, boolean isWarning) { + super(message, isWarning); + } + + public SvnBindException(Collection<String> messages) { + super(messages); + } +} diff --git a/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/status/LockWrapper.java b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/status/LockWrapper.java new file mode 100644 index 000000000000..f4e0f4cf2269 --- /dev/null +++ b/plugins/svn4idea/bindSvn/src/org/jetbrains/idea/svn/status/LockWrapper.java @@ -0,0 +1,121 @@ +/* + * 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.status; + +import org.apache.subversion.javahl.types.Lock; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Date; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/21/12 + * Time: 2:33 PM + */ +public class LockWrapper { + private String myPath; + private String myID; + private String myOwner; + private String myComment; + private Date myCreationDate; + private Date myExpirationDate; + + public LockWrapper(String path, String ID, String owner, String comment, Date creationDate, Date expirationDate) { + myPath = path; + myID = ID; + myOwner = owner; + myComment = comment; + myCreationDate = creationDate; + myExpirationDate = expirationDate; + } + + public LockWrapper() { + } + + public String getPath() { + return myPath; + } + + public void setPath(String path) { + myPath = path; + } + + public String getID() { + return myID; + } + + public void setID(String ID) { + myID = ID; + } + + public String getOwner() { + return myOwner; + } + + public void setOwner(String owner) { + myOwner = owner; + } + + public String getComment() { + return myComment; + } + + public void setComment(String comment) { + myComment = comment; + } + + public Date getCreationDate() { + return myCreationDate; + } + + public void setCreationDate(Date creationDate) { + myCreationDate = creationDate; + } + + public Date getExpirationDate() { + return myExpirationDate; + } + + public void setExpirationDate(Date expirationDate) { + myExpirationDate = expirationDate; + } + + public org.tigris.subversion.javahl.Lock create() { + final Date creation = getCreationDate(); + final Date expiration = getExpirationDate(); + final Lock newLock = new Lock(getOwner(), getPath(), getID(), getComment(), creation == null ? 0 : creation.getTime(), + expiration == null ? 0 : expiration.getTime()); + try { + final Constructor<org.tigris.subversion.javahl.Lock> constructor = org.tigris.subversion.javahl.Lock.class.getConstructor(Lock.class); + constructor.setAccessible(true); + return constructor.newInstance(newLock); + } + catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + catch (InstantiationException e) { + throw new RuntimeException(e); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} diff --git a/plugins/svn4idea/bindSvn/src/org/tigris/subversion/javahl/BindClientException.java b/plugins/svn4idea/bindSvn/src/org/tigris/subversion/javahl/BindClientException.java new file mode 100644 index 000000000000..f8acc17d0558 --- /dev/null +++ b/plugins/svn4idea/bindSvn/src/org/tigris/subversion/javahl/BindClientException.java @@ -0,0 +1,47 @@ +/* + * 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.tigris.subversion.javahl; + +import org.jetbrains.annotations.NotNull; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/25/13 + * Time: 6:29 PM + */ +public class BindClientException extends ClientException { + private Throwable myCause; + + public BindClientException(String message, String source, int aprError) { + super(message, source, aprError); + } + + public BindClientException(org.apache.subversion.javahl.ClientException ex) { + super(ex); + } + + public static BindClientException create(@NotNull final Throwable t, final int code) { + final BindClientException exception = new BindClientException(t.getMessage(), null, code); + exception.myCause = t; + return exception; + } + + @Override + public Throwable getCause() { + return myCause; + } +} diff --git a/plugins/svn4idea/lib/sqljet.jar b/plugins/svn4idea/lib/sqljet.jar Binary files differindex d9aa77b29c3f..d6e0232c1001 100644 --- a/plugins/svn4idea/lib/sqljet.jar +++ b/plugins/svn4idea/lib/sqljet.jar diff --git a/plugins/svn4idea/lib/svnkit-javahl.jar b/plugins/svn4idea/lib/svnkit-javahl.jar Binary files differindex 8fbe09f66c57..265fda0bb963 100644 --- a/plugins/svn4idea/lib/svnkit-javahl.jar +++ b/plugins/svn4idea/lib/svnkit-javahl.jar diff --git a/plugins/svn4idea/lib/svnkit-javahl16.zip b/plugins/svn4idea/lib/svnkit-javahl16.zip Binary files differindex 7661a0365e9d..da9b8269cbb5 100644 --- a/plugins/svn4idea/lib/svnkit-javahl16.zip +++ b/plugins/svn4idea/lib/svnkit-javahl16.zip diff --git a/plugins/svn4idea/lib/svnkit.jar b/plugins/svn4idea/lib/svnkit.jar Binary files differindex 203ef4a61af2..46a3ab556976 100644 --- a/plugins/svn4idea/lib/svnkit.jar +++ b/plugins/svn4idea/lib/svnkit.jar diff --git a/plugins/svn4idea/lib/svnkitsrc.zip b/plugins/svn4idea/lib/svnkitsrc.zip Binary files differindex d31e1a5a49f6..ab1a19f594c0 100644 --- a/plugins/svn4idea/lib/svnkitsrc.zip +++ b/plugins/svn4idea/lib/svnkitsrc.zip diff --git a/plugins/svn4idea/lib/trilead.jar b/plugins/svn4idea/lib/trilead.jar Binary files differindex 1351e821540a..7fca04a83626 100644 --- a/plugins/svn4idea/lib/trilead.jar +++ b/plugins/svn4idea/lib/trilead.jar diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/AuthManagerType.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/AuthManagerType.java new file mode 100644 index 000000000000..b46b7a875b51 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/AuthManagerType.java @@ -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; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/28/13 + * Time: 10:14 AM + */ +public enum AuthManagerType { + active, passive, usual; +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationManager.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationManager.java index 0202a3c28604..8efa61780535 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationManager.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationManager.java @@ -27,17 +27,22 @@ import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.*; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.CalledInAwt; import com.intellij.openapi.vcs.changes.committed.AbstractCalledLater; import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; import com.intellij.util.EventDispatcher; +import com.intellij.util.messages.Topic; import com.intellij.util.net.HttpConfigurable; import com.intellij.util.proxy.CommonProxy; import com.intellij.util.ui.UIUtil; +import org.intellij.lang.annotations.MagicConstant; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.auth.ProviderType; import org.jetbrains.idea.svn.auth.SvnAuthenticationInteraction; import org.jetbrains.idea.svn.auth.SvnAuthenticationListener; +import org.jetbrains.idea.svn.config.ProxyGroup; +import org.jetbrains.idea.svn.config.SvnServerFileKeys; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNProperties; @@ -60,6 +65,10 @@ public class SvnAuthenticationManager extends DefaultSVNAuthenticationManager im public static final String SVN_SSH = "svn+ssh"; public static final String HTTP = "http"; public static final String HTTPS = "https"; + public static final String HTTP_PROXY_HOST = "http-proxy-host"; + public static final String HTTP_PROXY_PORT = "http-proxy-port"; + public static final String HTTP_PROXY_USERNAME = "http-proxy-username"; + public static final String HTTP_PROXY_PASSWORD = "http-proxy-password"; private Project myProject; private File myConfigDirectory; private PersistentAuthenticationProviderProxy myPersistentAuthenticationProviderProxy; @@ -71,6 +80,10 @@ public class SvnAuthenticationManager extends DefaultSVNAuthenticationManager im private final ThreadLocalSavePermissions mySavePermissions; private final Map<Thread, String> myKeyAlgorithm; private boolean myArtificialSaving; + private ISVNAuthenticationProvider myProvider; + public static final Topic<ISVNAuthenticationProviderListener> AUTHENTICATION_PROVIDER_LISTENER = + new Topic<ISVNAuthenticationProviderListener>("AUTHENTICATION_PROVIDER_LISTENER", ISVNAuthenticationProviderListener.class); + private final static ThreadLocal<ISVNAuthenticationProvider> ourThreadLocalProvider = new ThreadLocal<ISVNAuthenticationProvider>(); public SvnAuthenticationManager(final Project project, final File configDirectory) { super(configDirectory, true, null, null); @@ -106,6 +119,69 @@ public class SvnAuthenticationManager extends DefaultSVNAuthenticationManager im }); } + private class AuthenticationProviderProxy implements ISVNAuthenticationProvider { + private final ISVNAuthenticationProvider myDelegate; + + private AuthenticationProviderProxy(ISVNAuthenticationProvider delegate) { + myDelegate = delegate; + } + + @Override + public SVNAuthentication requestClientAuthentication(String kind, + SVNURL url, + String realm, + SVNErrorMessage errorMessage, + SVNAuthentication previousAuth, boolean authMayBeStored) { + final SVNAuthentication authentication = + myDelegate.requestClientAuthentication(kind, url, realm, errorMessage, previousAuth, authMayBeStored); + if (myProject != null && ! myProject.isDisposed()) { + myProject.getMessageBus().syncPublisher(AUTHENTICATION_PROVIDER_LISTENER) + .requestClientAuthentication(kind, url, realm, errorMessage, previousAuth, authMayBeStored, authentication); + } + return authentication; + } + + @Override + public int acceptServerAuthentication(SVNURL url, + String realm, + Object certificate, + boolean resultMayBeStored) { + final int result = myDelegate.acceptServerAuthentication(url, realm, certificate, resultMayBeStored); + if (myProject != null && ! myProject.isDisposed()) { + myProject.getMessageBus().syncPublisher(AUTHENTICATION_PROVIDER_LISTENER) + .acceptServerAuthentication(url, realm, certificate, resultMayBeStored, result); + } + return result; + } + } + + public interface ISVNAuthenticationProviderListener { + void requestClientAuthentication(String kind, SVNURL url, String realm, SVNErrorMessage errorMessage, + SVNAuthentication previousAuth, boolean authMayBeStored, SVNAuthentication authentication); + void acceptServerAuthentication(SVNURL url, String realm, Object certificate, boolean resultMayBeStored, @MagicConstant int acceptResult); + } + + @Override + public void setAuthenticationProvider(ISVNAuthenticationProvider provider) { + ISVNAuthenticationProvider useProvider = provider; + if (! (provider instanceof AuthenticationProviderProxy)) { + useProvider = new AuthenticationProviderProxy(provider); + } + myProvider = useProvider; + super.setAuthenticationProvider(myProvider); + } + + public ISVNAuthenticationProvider getProvider() { + final ISVNAuthenticationProvider threadProvider = ourThreadLocalProvider.get(); + if (threadProvider != null) return threadProvider; + return myProvider; + } + + @Override + public ISVNAuthenticationStorage getRuntimeAuthStorage() { + return super.getRuntimeAuthStorage(); + } + // since set to null during dispose and we have background processes private SvnConfiguration getConfig() { if (myConfig == null) throw new ProcessCanceledException(); @@ -361,6 +437,16 @@ public class SvnAuthenticationManager extends DefaultSVNAuthenticationManager im public void acknowledgeConnectionSuccessful(SVNURL url) { CommonProxy.getInstance().removeNoProxy(url.getProtocol(), url.getHost(), url.getPort()); SSLExceptionsHelper.removeInfo(); + ourThreadLocalProvider.remove(); + } + + @Override + public void acknowledgeAuthentication(boolean accepted, + String kind, + String realm, + SVNErrorMessage errorMessage, + SVNAuthentication authentication) throws SVNException { + acknowledgeAuthentication(accepted, kind, realm, errorMessage, authentication, null); } @Override @@ -371,7 +457,10 @@ public class SvnAuthenticationManager extends DefaultSVNAuthenticationManager im SVNAuthentication authentication, SVNURL url) throws SVNException { SSLExceptionsHelper.removeInfo(); - CommonProxy.getInstance().removeNoProxy(url.getProtocol(), url.getHost(), url.getPort()); + ourThreadLocalProvider.remove(); + if (url != null) { + CommonProxy.getInstance().removeNoProxy(url.getProtocol(), url.getHost(), url.getPort()); + } boolean successSaving = false; myListener.getMulticaster().acknowledge(accepted, kind, realm, errorMessage, authentication); try { @@ -382,18 +471,40 @@ public class SvnAuthenticationManager extends DefaultSVNAuthenticationManager im } finally { mySavePermissions.remove(); if (myArtificialSaving) { + myArtificialSaving = false; throw new CredentialsSavedException(successSaving); } } } + public void acknowledgeForSSL(boolean accepted, String kind, String realm, SVNErrorMessage message, SVNAuthentication proxy) { + if (accepted && proxy instanceof SVNSSLAuthentication && (((SVNSSLAuthentication) proxy).getCertificateFile() != null)) { + final SVNSSLAuthentication svnsslAuthentication = (SVNSSLAuthentication)proxy; + final SVNURL url = svnsslAuthentication.getURL(); + + final IdeaSVNHostOptionsProvider provider = getHostOptionsProvider(); + final SVNCompositeConfigFile serversFile = provider.getServersFile(); + String groupName = getGroupName(serversFile.getProperties("groups"), url.getHost()); + + if (StringUtil.isEmptyOrSpaces(groupName)) { + serversFile.setPropertyValue("global", SvnServerFileKeys.SSL_CLIENT_CERT_FILE, svnsslAuthentication.getCertificateFile().getPath(), true); + //serversFile.setPropertyValue("global", SvnServerFileKeys.SSL_CLIENT_CERT_PASSWORD, null, true); + } else { + serversFile.setPropertyValue(groupName, SvnServerFileKeys.SSL_CLIENT_CERT_FILE, svnsslAuthentication.getCertificateFile().getPath(), true); + //serversFile.setPropertyValue(groupName, SvnServerFileKeys.SSL_CLIENT_CERT_PASSWORD, null, true); + } + serversFile.save(); + } + } + public ISVNProxyManager getProxyManager(SVNURL url) throws SVNException { SSLExceptionsHelper.addInfo("Accessing URL: " + url.toString()); CommonProxy.getInstance().noProxy(url.getProtocol(), url.getHost(), url.getPort()); + ourThreadLocalProvider.set(myProvider); // this code taken from default manager (changed for system properties reading) String host = url.getHost(); - String proxyHost = getServersPropertyIdea(host, "http-proxy-host"); + String proxyHost = getServersPropertyIdea(host, HTTP_PROXY_HOST); if ((proxyHost == null) || "".equals(proxyHost.trim())) { if (getConfig().isIsUseDefaultProxy()) { // ! use common proxy if it is set @@ -431,9 +542,9 @@ public class SvnAuthenticationManager extends DefaultSVNAuthenticationManager im } } } - String proxyPort = getServersPropertyIdea(host, "http-proxy-port"); - String proxyUser = getServersPropertyIdea(host, "http-proxy-username"); - String proxyPassword = getServersPropertyIdea(host, "http-proxy-password"); + String proxyPort = getServersPropertyIdea(host, HTTP_PROXY_PORT); + String proxyUser = getServersPropertyIdea(host, HTTP_PROXY_USERNAME); + String proxyPassword = getServersPropertyIdea(host, HTTP_PROXY_PASSWORD); return new MySimpleProxyManager(proxyHost, proxyPort, proxyUser, proxyPassword); } @@ -601,21 +712,35 @@ public class SvnAuthenticationManager extends DefaultSVNAuthenticationManager im return false; } + @Nullable + public static String getGroupForHost(final String host, final IdeaSVNConfigFile serversFile) { + final Map<String,ProxyGroup> groups = serversFile.getAllGroups(); + for (Map.Entry<String, ProxyGroup> entry : groups.entrySet()) { + if (matchesGroupPattern(host, entry.getValue().getPatterns())) return entry.getKey(); + } + return null; + } + // taken from default manager as is private static String getGroupName(Map groups, String host) { - for (Iterator names = groups.keySet().iterator(); names.hasNext();) { - String name = (String) names.next(); - String pattern = (String) groups.get(name); - for(StringTokenizer tokens = new StringTokenizer(pattern, ","); tokens.hasMoreTokens();) { - String token = tokens.nextToken(); - if (DefaultSVNOptions.matches(token, host)) { - return name; - } - } - } + for (Object o : groups.keySet()) { + final String name = (String) o; + final String pattern = (String) groups.get(name); + if (matchesGroupPattern(host, pattern)) return name; + } return null; } + private static boolean matchesGroupPattern(String host, String pattern) { + for(StringTokenizer tokens = new StringTokenizer(pattern, ","); tokens.hasMoreTokens();) { + String token = tokens.nextToken(); + if (DefaultSVNOptions.matches(token, host)) { + return true; + } + } + return false; + } + // default = yes private static boolean isTurned(final String value) { return value == null || "yes".equalsIgnoreCase(value) || "on".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties index d2e430e91b2a..0cf1b0a5eb25 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties @@ -435,7 +435,7 @@ checkout.options.recursive.label=Checkout directories recursively checkout.options.externals.label=Include externals locations configure.branches.title=Configure Subversion Branches configure.branches.error.no.connection.title=Repository root was not found -configure.branches.error.wrong.url=Trunk location must be under repository root +configure.branches.error.wrong.url=Trunk location must be under repository root\n''{0}'' configure.branches.trunk.location=Trunk location: configure.branches.branch.locations=Branch locations: configure.branches.item=Configure Branches... @@ -648,4 +648,10 @@ svn.create.external.below.action=Create External... svn.create.external.below.description=Select URL, add svn\:external property, and optionally checkout it svn.edit.commit.message.title=Edit Revision #{0} Comment svn.edit.commit.message.attention=Attention! Previous message will be lost! -svn.edit.commit.message.prompt=New revision comment:
\ No newline at end of file +svn.edit.commit.message.prompt=New revision comment: +quick.merge.variants.merge.all.explanation=All not merged revisions will be merged.\n\ +Subversion will find not merged revisions using svn\:mergeinfo property recorded in local copy. +quick.merge.variants.quick.select.explanation=Shows all revisions from target branch, merged and not merged.\n\ +For manual selection. Very quick. +quick.merge.variants.pre.select.explanation=Finds where one of involved branches was copied from another.\n\ +Loads only not yet merged revisions for selection. Can take long time for execution.
\ No newline at end of file diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProvider.java index ebdf274bb4d7..3d837368ed66 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProvider.java @@ -113,7 +113,7 @@ public class SvnChangeProvider implements ChangeProvider { throw new VcsException(e.getCause()); } catch (SVNException e) { if (e.getCause() != null) { - throw new VcsException(e.getMessage() + e.getCause().getMessage(), e); + throw new VcsException(e.getMessage() + " " + e.getCause().getMessage(), e); } throw new VcsException(e); } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfiguration.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfiguration.java index 14c005427be6..66132f516be6 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfiguration.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfiguration.java @@ -17,6 +17,8 @@ package org.jetbrains.idea.svn; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; @@ -24,11 +26,14 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.*; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.changes.VcsAnnotationRefresher; import org.jdom.Attribute; import org.jdom.DataConversionException; import org.jdom.Element; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.config.ProxyGroup; import org.jetbrains.idea.svn.config.SvnServerFileKeys; import org.jetbrains.idea.svn.dialogs.SvnAuthenticationProvider; import org.jetbrains.idea.svn.dialogs.SvnInteractiveAuthenticationProvider; @@ -48,6 +53,9 @@ import org.tmatesoft.svn.core.wc.SVNWCUtil; import java.io.File; import java.io.FilenameFilter; +import java.net.InetSocketAddress; +import java.net.PasswordAuthentication; +import java.net.Proxy; import java.util.*; @State( @@ -166,6 +174,47 @@ public class SvnConfiguration implements PersistentStateComponent<Element> { myConfigFile.save(); } + public static void putProxyIntoServersFile(final File configDir, final String host, final Proxy proxyInfo) { + final IdeaSVNConfigFile configFile = new IdeaSVNConfigFile(new File(configDir, SERVERS_FILE_NAME)); + configFile.updateGroups(); + + String groupName = SvnAuthenticationManager.getGroupForHost(host, configFile); + + if (StringUtil.isEmptyOrSpaces(groupName)) { + groupName = StringUtil.replace(host, " ", "_"); + final Map<String,ProxyGroup> groups = configFile.getAllGroups(); + while (true) { + if (! groups.containsKey(groupName)) break; + groupName += "1"; + } + } + + final HashMap<String, String> map = new HashMap<String, String>(); + final InetSocketAddress address = ((InetSocketAddress) proxyInfo.address()); + map.put(SvnAuthenticationManager.HTTP_PROXY_HOST, address.getHostName()); + map.put(SvnAuthenticationManager.HTTP_PROXY_PORT, String.valueOf(address.getPort())); + configFile.addGroup(groupName, host + "*", map); + configFile.save(); + } + + public static boolean putProxyCredentialsIntoServerFile(@NotNull final File configDir, @NotNull final String host, + @NotNull final PasswordAuthentication authentication) { + final IdeaSVNConfigFile configFile = new IdeaSVNConfigFile(new File(configDir, SERVERS_FILE_NAME)); + configFile.updateGroups(); + + String groupName = SvnAuthenticationManager.getGroupForHost(host, configFile); + // no proxy defined in group -> no sense in password + if (StringUtil.isEmptyOrSpaces(groupName)) return false; + final Map<String, String> properties = configFile.getAllGroups().get(groupName).getProperties(); + if (StringUtil.isEmptyOrSpaces(properties.get(SvnAuthenticationManager.HTTP_PROXY_HOST))) return false; + if (StringUtil.isEmptyOrSpaces(properties.get(SvnAuthenticationManager.HTTP_PROXY_PORT))) return false; + + configFile.setValue(groupName, SvnAuthenticationManager.HTTP_PROXY_USERNAME, authentication.getUserName()); + configFile.setValue(groupName, SvnAuthenticationManager.HTTP_PROXY_PASSWORD, String.valueOf(authentication.getPassword())); + configFile.save(); + return true; + } + public static SvnConfiguration getInstance(final Project project) { return ServiceManager.getService(project, SvnConfiguration.class); } @@ -281,19 +330,33 @@ public class SvnConfiguration implements PersistentStateComponent<Element> { } public static SvnAuthenticationManager createForTmpDir(final Project project, final File dir) { + return createForTmpDir(project, dir, null); + } + + public static SvnAuthenticationManager createForTmpDir(final Project project, final File dir, + @Nullable final SvnInteractiveAuthenticationProvider provider) { final SvnVcs vcs = SvnVcs.getInstance(project); - //final SvnAuthenticationManager manager = new SvnAuthenticationManager(project, dir); final SvnAuthenticationManager interactive = new SvnAuthenticationManager(project, dir); interactive.setRuntimeStorage(RUNTIME_AUTH_CACHE); - final SvnInteractiveAuthenticationProvider interactiveProvider = new SvnInteractiveAuthenticationProvider(vcs, interactive); + final SvnInteractiveAuthenticationProvider interactiveProvider = provider == null ? + new SvnInteractiveAuthenticationProvider(vcs, interactive) : provider; interactive.setAuthenticationProvider(interactiveProvider); - //manager.setAuthenticationProvider(new SvnAuthenticationProvider(vcs, interactiveProvider, RUNTIME_AUTH_CACHE)); - //manager.setRuntimeStorage(RUNTIME_AUTH_CACHE); return interactive; } + public SvnAuthenticationManager getManager(final AuthManagerType type, final SvnVcs vcs) { + if (AuthManagerType.active.equals(type)) { + return getInteractiveManager(vcs); + } else if (AuthManagerType.passive.equals(type)) { + return getPassiveAuthenticationManager(vcs.getProject()); + } else if (AuthManagerType.usual.equals(type)) { + return getAuthenticationManager(vcs); + } + throw new IllegalArgumentException(); + } + public SvnAuthenticationManager getAuthenticationManager(final SvnVcs svnVcs) { if (myAuthManager == null) { // reloaded when configuration directory changes @@ -565,7 +628,7 @@ public class SvnConfiguration implements PersistentStateComponent<Element> { public void clearAuthenticationDirectory(@Nullable Project project) { final File authDir = new File(getConfigurationDirectory(), "auth"); if (authDir.exists()) { - ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + final Runnable process = new Runnable() { public void run() { final ProgressIndicator ind = ProgressManager.getInstance().getProgressIndicator(); if (ind != null) { @@ -585,7 +648,13 @@ public class SvnConfiguration implements PersistentStateComponent<Element> { FileUtil.delete(dir); } } - }, "button.text.clear.authentication.cache", false, project); + }; + final Application application = ApplicationManager.getApplication(); + if (application.isUnitTestMode() || ! application.isDispatchThread()) { + process.run(); + } else { + ProgressManager.getInstance().runProcessWithProgressSynchronously(process, "button.text.clear.authentication.cache", false, project); + } } } @@ -601,6 +670,11 @@ public class SvnConfiguration implements PersistentStateComponent<Element> { RUNTIME_AUTH_CACHE.putData(kind, realm, null); } + public void clearRuntimeStorage() { + RUNTIME_AUTH_CACHE.clear(); + } + + public int getMaxAnnotateRevisions() { return myMaxAnnotateRevisions; } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnContentRevision.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnContentRevision.java index 105b156a914e..38c099ba4136 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnContentRevision.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnContentRevision.java @@ -29,12 +29,9 @@ import com.intellij.openapi.vcs.impl.CurrentRevisionProvider; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc.SVNStatus; -import org.tmatesoft.svn.core.wc.SVNWCClient; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; @@ -116,29 +113,7 @@ public class SvnContentRevision implements ContentRevision, MarkerVcsContentRevi if (lock.exists()) { throw new VcsException("Can not access file base revision contents: administrative area is locked"); } - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - SVNWCClient wcClient = myVcs.createWCClient(); - try { - wcClient.doGetFileContents(file, SVNRevision.UNDEFINED, myUseBaseRevision ? SVNRevision.BASE : myRevision, true, buffer); - buffer.close(); - } - catch (SVNException e) { - /*try { - final SVNInfo info = wcClient.doInfo(file, SVNRevision.UNDEFINED); - //todo - } - catch (SVNException e1) { - throw new VcsException(e); - }*/ - throw new VcsException(e); - } - catch (IOException e) { - throw new VcsException(e); - } - final byte[] bytes = buffer.toByteArray(); - /*final Charset charset = myFile.getCharset(); - return charset == null ? bytes : SvnUtil.decode(charset, bytes);*/ - return bytes; + return SvnUtil.getFileContents(myVcs, file.getPath(), false, myUseBaseRevision ? SVNRevision.BASE : myRevision, SVNRevision.UNDEFINED); } @NotNull diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileSystemListener.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileSystemListener.java index 30cb2a92a6af..062c412f2336 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileSystemListener.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileSystemListener.java @@ -228,6 +228,8 @@ public class SvnFileSystemListener extends CommandAdapter implements LocalFileOp } public boolean rename(VirtualFile file, String newName) throws IOException { + FileDocumentManager.getInstance().saveAllDocuments(); + File srcFile = getIOFile(file); File dstFile = new File(srcFile.getParentFile(), newName); SvnVcs vcs = getVCS(file); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFormatSelector.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFormatSelector.java index 22f8b05ca71f..94d21dbbbd9d 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFormatSelector.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFormatSelector.java @@ -173,7 +173,7 @@ public class SvnFormatSelector implements ISVNAdminAreaFactorySelector { private static boolean displayUpgradeDialog(Project project, File path, final boolean dispay13format, String[] newMode) { UpgradeFormatDialog dialog = new UpgradeFormatDialog(project, path, false); - dialog.setData(dispay13format, newMode[0]); + dialog.setData(newMode[0]); dialog.show(); if (dialog.isOK()) { newMode[0] = dialog.getUpgradeMode(); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnRecursiveStatusWalker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnRecursiveStatusWalker.java index f4fdfac1114d..4a49dc16a153 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnRecursiveStatusWalker.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnRecursiveStatusWalker.java @@ -16,6 +16,7 @@ package org.jetbrains.idea.svn; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; @@ -28,6 +29,7 @@ import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Processor; import com.intellij.vcsUtil.VcsUtil; +import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.commandLine.SvnCommandLineStatusClient; import org.jetbrains.idea.svn.portable.JavaHLSvnStatusClient; import org.jetbrains.idea.svn.portable.SvnStatusClientI; @@ -43,6 +45,7 @@ import java.io.File; import java.util.LinkedList; public class SvnRecursiveStatusWalker { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnRecursiveStatusWalker"); private final StatusWalkerPartner myPartner; private final Project myProject; private final StatusReceiver myReceiver; @@ -71,8 +74,9 @@ public class SvnRecursiveStatusWalker { if (path.isDirectory()) { myHandler.setCurrentItem(item); try { - item.getClient(ioFile).doStatus(ioFile, SVNRevision.WORKING, item.getDepth(), false, false, true, true, myHandler, null); - myHandler.checkIfCopyRootWasReported(); + final SvnStatusClientI client = item.getClient(ioFile); + client.doStatus(ioFile, SVNRevision.WORKING, item.getDepth(), false, false, true, true, myHandler, null); + myHandler.checkIfCopyRootWasReported(null, ioFile); } catch (SVNException e) { handleStatusException(item, path, e); @@ -223,10 +227,18 @@ public class SvnRecursiveStatusWalker { myMetCurrentItem = false; } - public void checkIfCopyRootWasReported() { - if (! myMetCurrentItem) { + public void checkIfCopyRootWasReported(@Nullable final SVNStatus ioFileStatus, final File ioFile) { + if (! myMetCurrentItem && FileUtil.filesEqual(ioFile, myCurrentItem.getPath().getIOFile())) { myMetCurrentItem = true; - final SVNStatus statusInner = SvnUtil.getStatus(SvnVcs.getInstance(myProject), myCurrentItem.getPath().getIOFile()); + SVNStatus statusInner; + try { + statusInner = ioFileStatus != null ? ioFileStatus : + myCurrentItem.getClient().doStatus(myCurrentItem.getPath().getIOFile(), false); + } + catch (SVNException e) { + LOG.info(e); + statusInner = null; + } if (statusInner == null) return; final SVNStatusType status = statusInner.getNodeStatus(); @@ -261,7 +273,7 @@ public class SvnRecursiveStatusWalker { public void handleStatus(final SVNStatus status) throws SVNException { myPartner.checkCanceled(); final File ioFile = status.getFile(); - checkIfCopyRootWasReported(); + checkIfCopyRootWasReported(status, ioFile); final VirtualFile vFile = getVirtualFile(ioFile); if (vFile != null) { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnUtil.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnUtil.java index ee25402d8ff0..0c049ac4671d 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnUtil.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnUtil.java @@ -30,6 +30,7 @@ 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.impl.ContentRevisionCache; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; @@ -38,6 +39,7 @@ import com.intellij.openapi.wm.impl.status.StatusBarUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.Convertor; import com.intellij.util.containers.MultiMap; +import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -52,7 +54,10 @@ import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.wc.*; import org.tmatesoft.svn.core.wc2.SvnOperationFactory; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; +import java.io.OutputStream; import java.util.*; public class SvnUtil { @@ -298,7 +303,7 @@ public class SvnUtil { } public static Collection<List<Change>> splitChangesIntoWc(final SvnVcs vcs, final List<Change> changes) { - return splitIntoWc(vcs, changes, new Convertor<Change, File>() { + return splitIntoRepositories(vcs, changes, new Convertor<Change, File>() { @Override public File convert(Change o) { return ChangesUtil.getFilePath(o).getIOFile(); @@ -306,16 +311,30 @@ public class SvnUtil { }); } - public static Collection<List<File>> splitFilesIntoWc(final SvnVcs vcs, final List<File> committables) { - return splitIntoWc(vcs, committables, Convertor.SELF); + public static Collection<List<File>> splitFilesIntoRepositories(final SvnVcs vcs, final List<File> committables) { + return splitIntoRepositories(vcs, committables, Convertor.SELF); } - public static <T> Collection<List<T>> splitIntoWc(final SvnVcs vcs, final List<T> committables, - Convertor<T, File> convertor) { + public static <T> Collection<List<T>> splitIntoRepositories(final SvnVcs vcs, final List<T> committables, + Convertor<T, File> convertor) { if (committables.size() == 1) { return Collections.singletonList(committables); } + final MultiMap<Pair<SVNURL, WorkingCopyFormat>, T> result = splitIntoRepositoriesMap(vcs, committables, convertor); + + if (result.size() == 1) { + return Collections.singletonList(committables); + } + final Collection<List<T>> result2 = new ArrayList<List<T>>(); + for (Map.Entry<Pair<SVNURL, WorkingCopyFormat>, Collection<T>> entry : result.entrySet()) { + result2.add((List<T>)entry.getValue()); + } + return result2; + } + + public static <T> MultiMap<Pair<SVNURL, WorkingCopyFormat>, T> splitIntoRepositoriesMap(SvnVcs vcs, + List<T> committables, Convertor<T, File> convertor) { final MultiMap<Pair<SVNURL, WorkingCopyFormat>, T> result = new MultiMap<Pair<SVNURL, WorkingCopyFormat>, T>() { @Override protected Collection<T> createCollection() { @@ -330,15 +349,7 @@ public class SvnUtil { result.putValue(new Pair<SVNURL, WorkingCopyFormat>(path.getRepositoryUrlUrl(), path.getFormat()), committable); } } - - if (result.size() == 1) { - return Collections.singletonList(committables); - } - final Collection<List<T>> result2 = new ArrayList<List<T>>(); - for (Map.Entry<Pair<SVNURL, WorkingCopyFormat>, Collection<T>> entry : result.entrySet()) { - result2.add((List<T>)entry.getValue()); - } - return result2; + return result; } private static class LocationsCrawler implements SvnWCRootCrawler { @@ -578,6 +589,12 @@ public class SvnUtil { return false; } + public static SVNURL getCommittedURL(final SvnVcs vcs, final File file) { + final File root = getWorkingCopyRoot(file); + if (root == null) return null; + return getUrl(vcs, root); + } + @Nullable public static SVNURL getUrl(final SvnVcs vcs, final File file) { try { @@ -626,12 +643,22 @@ public class SvnUtil { } public static File getWcDb(final File file) { - return new File(file, ".svn/wc.db"); + return new File(file, SVN_ADMIN_DIR_NAME + "/wc.db"); } @Nullable public static File getWcCopyRootIf17(final File file, @Nullable final File upperBound) { File current = file; + boolean wcDbFound = false; + while (current != null) { + File wcDb; + if ((wcDb = getWcDb(current)).exists() && ! wcDb.isDirectory()) { + wcDbFound = true; + break; + } + current = current.getParentFile(); + } + if (! wcDbFound) return null; while (current != null) { try { final SvnWcGeneration svnWcGeneration = SvnOperationFactory.detectWcGeneration(current, false); @@ -675,4 +702,45 @@ public class SvnUtil { } return result; } + + public static byte[] getFileContents(final SvnVcs vcs, final String path, final boolean isUrl, final SVNRevision revision, + final SVNRevision pegRevision) + throws VcsException { + final int maxSize = VcsUtil.getMaxVcsLoadedFileSize(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream() { + @Override + public synchronized void write(int b) { + if (size() > maxSize) throw new FileTooBigRuntimeException(); + super.write(b); + } + + @Override + public synchronized void write(byte[] b, int off, int len) { + if (size() > maxSize) throw new FileTooBigRuntimeException(); + super.write(b, off, len); + } + + @Override + public synchronized void writeTo(OutputStream out) throws IOException { + if (size() > maxSize) throw new FileTooBigRuntimeException(); + super.writeTo(out); + } + }; + SVNWCClient wcClient = vcs.createWCClient(); + try { + if (isUrl) { + wcClient.doGetFileContents(SVNURL.parseURIEncoded(path), pegRevision, revision, true, buffer); + } else { + wcClient.doGetFileContents(new File(path), pegRevision, revision, true, buffer); + } + ContentRevisionCache.checkContentsSize(path, buffer.size()); + } catch (FileTooBigRuntimeException e) { + ContentRevisionCache.checkContentsSize(path, buffer.size()); + } catch (SVNException e) { + throw new VcsException(e); + } + return buffer.toByteArray(); + } + + private static class FileTooBigRuntimeException extends RuntimeException {} } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java index 838295cb20a7..dbc78b2799d8 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java @@ -77,7 +77,8 @@ import org.jetbrains.idea.svn.history.LoadedRevisionsCache; import org.jetbrains.idea.svn.history.SvnChangeList; import org.jetbrains.idea.svn.history.SvnCommittedChangesProvider; import org.jetbrains.idea.svn.history.SvnHistoryProvider; -import org.jetbrains.idea.svn.lowLevel.SvnIdeaRepositoryPoolManager; +import org.jetbrains.idea.svn.lowLevel.PrimitivePool; +import org.jetbrains.idea.svn.networking.SSLProtocolExceptionParser; import org.jetbrains.idea.svn.rollback.SvnRollbackEnvironment; import org.jetbrains.idea.svn.update.SvnIntegrateEnvironment; import org.jetbrains.idea.svn.update.SvnUpdateEnvironment; @@ -88,6 +89,7 @@ import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory; import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory; import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl; +import org.tmatesoft.svn.core.internal.util.SVNSSLUtil; import org.tmatesoft.svn.core.internal.util.jna.SVNJNAUtil; import org.tmatesoft.svn.core.internal.wc.SVNAdminUtil; import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea14; @@ -100,10 +102,12 @@ import org.tmatesoft.svn.util.SVNDebugLogAdapter; import org.tmatesoft.svn.util.SVNLogType; import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLProtocolException; import java.io.File; import java.io.UnsupportedEncodingException; import java.security.cert.CertificateException; import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; @SuppressWarnings({"IOResourceOpenedButNotSafelyClosed"}) @@ -127,7 +131,6 @@ public class SvnVcs extends AbstractVcs<CommittedChangeList> { private final Map<String, Map<String, Pair<SVNPropertyValue, Trinity<Long, Long, Long>>>> myPropertyCache = new SoftHashMap<String, Map<String, Pair<SVNPropertyValue, Trinity<Long, Long, Long>>>>(); - private SvnIdeaRepositoryPoolManager myPool; private final SvnConfiguration myConfiguration; private final SvnEntriesFileListener myEntriesFileListener; @@ -436,7 +439,6 @@ public class SvnVcs extends AbstractVcs<CommittedChangeList> { @Override public void activate() { - createPool(); final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject); if (!myProject.isDefault()) { ChangeListManager.getInstance(myProject).addChangeListListener(myChangeListListener); @@ -593,8 +595,6 @@ public class SvnVcs extends AbstractVcs<CommittedChangeList> { mySvnBranchPointsCalculator = null; myWorkingCopiesContent.deactivate(); myLoadedBranchesStorage.deactivate(); - myPool.dispose(); - myPool = null; } public VcsShowConfirmationOption getAddConfirmation() { @@ -640,29 +640,17 @@ public class SvnVcs extends AbstractVcs<CommittedChangeList> { return repos; } - private void createPool() { - if (myPool != null) return; - final String property = System.getProperty(KEEP_CONNECTIONS_KEY); - final boolean keep; - boolean unitTestMode = ApplicationManager.getApplication().isUnitTestMode(); - // pool variant by default - if (StringUtil.isEmptyOrSpaces(property) || unitTestMode) { - keep = ! unitTestMode; // default - } else { - keep = Boolean.getBoolean(KEEP_CONNECTIONS_KEY); - } - myPool = new SvnIdeaRepositoryPoolManager(false, myConfiguration.getAuthenticationManager(this), myConfiguration.getOptions(myProject)); + @NotNull + private ISVNRepositoryPool getPool() { + return getPool(myConfiguration.getAuthenticationManager(this)); } @NotNull - private ISVNRepositoryPool getPool() { + private ISVNRepositoryPool getPool(ISVNAuthenticationManager manager) { if (myProject.isDisposed()) { throw new ProcessCanceledException(); } - if (myPool == null) { - createPool(); - } - return myPool; + return new PrimitivePool(manager, myConfiguration.getOptions(myProject)); } public SVNUpdateClient createUpdateClient() { @@ -672,7 +660,7 @@ public class SvnVcs extends AbstractVcs<CommittedChangeList> { } public SVNUpdateClient createUpdateClient(@NotNull ISVNAuthenticationManager manager) { - final SVNUpdateClient client = new SVNUpdateClient(getPool(), myConfiguration.getOptions(myProject)); + final SVNUpdateClient client = new SVNUpdateClient(getPool(manager), myConfiguration.getOptions(myProject)); client.getOperationsFactory().setAuthenticationManager(manager); return client; } @@ -691,7 +679,7 @@ public class SvnVcs extends AbstractVcs<CommittedChangeList> { } public SVNWCClient createWCClient(@NotNull ISVNAuthenticationManager manager) { - final SVNWCClient client = new SVNWCClient(getPool(), myConfiguration.getOptions(myProject)); + final SVNWCClient client = new SVNWCClient(getPool(manager), myConfiguration.getOptions(myProject)); client.getOperationsFactory().setAuthenticationManager(manager); return client; } @@ -715,7 +703,7 @@ public class SvnVcs extends AbstractVcs<CommittedChangeList> { } public SVNLogClient createLogClient(@NotNull ISVNAuthenticationManager manager) { - final SVNLogClient client = new SVNLogClient(getPool(), myConfiguration.getOptions(myProject)); + final SVNLogClient client = new SVNLogClient(getPool(manager), myConfiguration.getOptions(myProject)); client.getOperationsFactory().setAuthenticationManager(manager); return client; } @@ -957,7 +945,7 @@ public class SvnVcs extends AbstractVcs<CommittedChangeList> { private final boolean myLoggingEnabled; private final boolean myLogNative; private final Logger myLog; - private final static long ourErrorNotificationInterval = 10000; + private final static long ourErrorNotificationInterval = TimeUnit.MINUTES.toMillis(2); private long myPreviousTime = 0; public JavaSVNDebugLogger(boolean loggingEnabled, boolean logNative, Logger log) { @@ -972,23 +960,46 @@ public class SvnVcs extends AbstractVcs<CommittedChangeList> { @Override public void log(final SVNLogType logType, final Throwable th, final Level logLevel) { + handleSpecificSSLExceptions(th); + if (shouldLog(logType)) { + myLog.info(th); + } + } + + private void handleSpecificSSLExceptions(Throwable th) { + final long time = System.currentTimeMillis(); + if ((time - myPreviousTime) <= ourErrorNotificationInterval) { + return; + } if (th instanceof SSLHandshakeException) { - final long time = System.currentTimeMillis(); - if ((time - myPreviousTime) > ourErrorNotificationInterval) { + // not trusted certificate exception is not the problem, just part of normal behaviour + if (th.getCause() instanceof SVNSSLUtil.CertificateNotTrustedException) { + LOG.info(th); + return; + } + + myPreviousTime = time; + String info = SSLExceptionsHelper.getAddInfo(); + info = info == null ? "" : " (" + info + ") "; + if (th.getCause() instanceof CertificateException) { + PopupUtil.showBalloonForActiveFrame("Subversion: " + info + th.getCause().getMessage(), MessageType.ERROR); + } else { + final String postMessage = "\nPlease check Subversion SSL settings (Settings | Version Control | Subversion | Network)\n" + + "Maybe you should specify SSL protocol manually - SSLv3 or TLSv1"; + PopupUtil.showBalloonForActiveFrame("Subversion: " + info + th.getMessage() + postMessage, MessageType.ERROR); + } + } else if (th instanceof SSLProtocolException) { + final String message = th.getMessage(); + if (! StringUtil.isEmptyOrSpaces(message)) { myPreviousTime = time; String info = SSLExceptionsHelper.getAddInfo(); info = info == null ? "" : " (" + info + ") "; - if (th.getCause() instanceof CertificateException) { - PopupUtil.showBalloonForActiveComponent("Subversion: " + info + th.getCause().getMessage(), MessageType.ERROR); - } else { - final String postMessage = "\nPlease check Subversion SSL settings (Settings | Version Control | Subversion | Network)"; - PopupUtil.showBalloonForActiveComponent("Subversion: " + info + th.getMessage() + postMessage, MessageType.ERROR); - } + final SSLProtocolExceptionParser parser = new SSLProtocolExceptionParser(message); + parser.parse(); + final String errMessage = "Subversion: " + info + parser.getParsedMessage(); + PopupUtil.showBalloonForActiveFrame(errMessage, MessageType.ERROR); } } - if (shouldLog(logType)) { - myLog.info(th); - } } @Override diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/IdeaCommitHandler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/IdeaCommitHandler.java new file mode 100644 index 000000000000..3bd8256e7b02 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/IdeaCommitHandler.java @@ -0,0 +1,62 @@ +/* + * 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.checkin; + +import com.intellij.openapi.progress.ProgressIndicator; +import org.jetbrains.idea.svn.CommitEventHandler; +import org.jetbrains.idea.svn.CommitEventType; +import org.jetbrains.idea.svn.SvnBundle; + +import java.io.File; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/26/13 + * Time: 11:13 AM + */ +public class IdeaCommitHandler implements CommitEventHandler { + private final ProgressIndicator myPi; + + public IdeaCommitHandler(ProgressIndicator pi) { + myPi = pi; + } + + @Override + public void commitEvent(CommitEventType type, File target) { + if (myPi == null) return; + myPi.checkCanceled(); + + if (CommitEventType.adding.equals(type)) { + myPi.setText2(SvnBundle.message("progress.text2.adding", target)); + } else if (CommitEventType.deleting.equals(type)) { + myPi.setText2(SvnBundle.message("progress.text2.deleting", target)); + } else if (CommitEventType.sending.equals(type)) { + myPi.setText2(SvnBundle.message("progress.text2.sending", target)); + } else if (CommitEventType.replacing.equals(type)) { + myPi.setText2(SvnBundle.message("progress.text2.replacing", target)); + } else if (CommitEventType.transmittingDeltas.equals(type)) { + myPi.setText2(SvnBundle.message("progress.text2.transmitting.delta", target)); + } + } + + @Override + public void committedRevision(long revNum) { + if (myPi == null) return; + myPi.checkCanceled(); + myPi.setText2(SvnBundle.message("status.text.comitted.revision", revNum)); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/IdeaSvnkitBasedAuthenticationCallback.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/IdeaSvnkitBasedAuthenticationCallback.java new file mode 100644 index 000000000000..618507117bee --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/IdeaSvnkitBasedAuthenticationCallback.java @@ -0,0 +1,591 @@ +/* + * 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.checkin; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.ui.popup.util.PopupUtil; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; +import com.intellij.util.ThrowableRunnable; +import com.intellij.util.messages.MessageBusConnection; +import com.intellij.util.net.HttpConfigurable; +import com.intellij.util.proxy.CommonProxy; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn.*; +import org.tmatesoft.svn.core.*; +import org.tmatesoft.svn.core.auth.*; +import org.tmatesoft.svn.core.internal.util.SVNBase64; +import org.tmatesoft.svn.core.internal.util.SVNHashMap; +import org.tmatesoft.svn.core.internal.util.SVNSSLUtil; +import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; +import org.tmatesoft.svn.core.internal.wc.SVNWCProperties; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.net.*; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/26/13 + * Time: 1:27 PM + */ +public class IdeaSvnkitBasedAuthenticationCallback implements AuthenticationCallback { + private final SvnVcs myVcs; + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.checkin.IdeaSvnkitBasedAuthenticationCallback"); + private File myTempDirectory; + private boolean myProxyCredentialsWereReturned; + + public IdeaSvnkitBasedAuthenticationCallback(SvnVcs vcs) { + myVcs = vcs; + } + + @Override + public boolean authenticateFor(String realm, File file, boolean previousFailed, boolean passwordRequest) { + final File base = getExistingParent(file); + if (base == null) return false; + final SVNURL url = SvnUtil.getCommittedURL(myVcs, base); + if (url == null) return false; + + return new CredentialsAuthenticator(myVcs).tryAuthenticate(realm, url, file, previousFailed, passwordRequest); + } + + @Override + public boolean acceptSSLServerCertificate(final File file, final String realm) { + final File base = getExistingParent(file); + if (base == null) return false; + final SVNURL url = SvnUtil.getCommittedURL(myVcs, base); + if (url == null) return false; + + return new SSLServerCertificateAuthenticator(myVcs).tryAuthenticate(url, realm); + } + + @Override + public void clearPassiveCredentials(String realm, File file, boolean password) { + final File base = getExistingParent(file); + if (base == null) return; + final SVNURL url = SvnUtil.getCommittedURL(myVcs, base); + if (url == null) return; + final SvnConfiguration configuration = SvnConfiguration.getInstance(myVcs.getProject()); + final List<String> kinds = getKinds(url, password); + + for (String kind : kinds) { + configuration.clearCredentials(kind, realm); + } + } + + @Override + public boolean haveDataForTmpConfig() { + final HttpConfigurable instance = HttpConfigurable.getInstance(); + return SvnConfiguration.getInstance(myVcs.getProject()).isIsUseDefaultProxy() && + (instance.USE_HTTP_PROXY || instance.USE_PROXY_PAC); + } + + @Override + public boolean persistDataToTmpConfig(final File baseFile) throws IOException, URISyntaxException { + final File base = getExistingParent(baseFile); + if (base == null) return false; + final SVNURL url = SvnUtil.getCommittedURL(myVcs, base); + if (url == null) return false; + + final SvnConfiguration configuration = SvnConfiguration.getInstance(myVcs.getProject()); + initTmpDir(configuration); + + final Proxy proxy = getIdeaDefinedProxy(url); + if (proxy != null){ + SvnConfiguration.putProxyIntoServersFile(myTempDirectory, url.getHost(), proxy); + } + return true; + } + + @Nullable + private static Proxy getIdeaDefinedProxy(final SVNURL url) throws URISyntaxException { + final List<Proxy> proxies = CommonProxy.getInstance().select(new URI(url.toString())); + if (proxies != null && ! proxies.isEmpty()) { + for (Proxy proxy : proxies) { + if (HttpConfigurable.isRealProxy(proxy) && Proxy.Type.HTTP.equals(proxy.type())) { + return proxy; + } + } + } + return null; + } + + @Override + public boolean askProxyCredentials(File baseFile) { + final File base = getExistingParent(baseFile); + if (base == null) return false; + final SVNURL url = SvnUtil.getCommittedURL(myVcs, base); + if (url == null) return false; + + final Proxy proxy; + try { + proxy = getIdeaDefinedProxy(url); + } + catch (URISyntaxException e) { + LOG.info(e); + return false; + } + if (proxy == null) return false; + if (myProxyCredentialsWereReturned){ + // ask loud + final HttpConfigurable instance = HttpConfigurable.getInstance(); + if (instance.USE_HTTP_PROXY || instance.USE_PROXY_PAC) { + PopupUtil.showBalloonForActiveComponent("Failed to authenticate to proxy. You can change proxy credentials in HTTP proxy settings.", MessageType.ERROR); + } else { + PopupUtil.showBalloonForActiveComponent("Failed to authenticate to proxy.", MessageType.ERROR); + } + return false; + } + final InetSocketAddress address = (InetSocketAddress)proxy.address(); + final PasswordAuthentication authentication; + try { + authentication = Authenticator.requestPasswordAuthentication(url.getHost(), address.getAddress(), + url.getPort(), url.getProtocol(), url.getHost(), url.getProtocol(), + new URL(url.toString()), Authenticator.RequestorType.PROXY); + } catch (MalformedURLException e) { + LOG.info(e); + return false; + } + if (authentication != null) { + myProxyCredentialsWereReturned = true; + return SvnConfiguration.putProxyCredentialsIntoServerFile(myTempDirectory, url.getHost(), authentication); + } + return false; + } + + @Override + public boolean acceptSSLServerCertificate(final String url, final String realm) { + try { + return new SSLServerCertificateAuthenticator(myVcs).tryAuthenticate(SVNURL.parseURIEncoded(url), realm); + } + catch (SVNException e) { + return false; + } + } + + public void reset() { + if (myTempDirectory != null) { + FileUtil.delete(myTempDirectory); + } + } + + private abstract class AbstractAuthenticator<T> { + protected final SvnVcs myVcs; + protected boolean myStoreInUsual; + protected SvnAuthenticationManager myTmpDirManager; + + protected AbstractAuthenticator(SvnVcs vcs) { + myVcs = vcs; + } + + protected boolean tryAuthenticate() { + final SvnConfiguration configuration = SvnConfiguration.getInstance(myVcs.getProject()); + final SvnAuthenticationManager passive = configuration.getPassiveAuthenticationManager(myVcs.getProject()); + final SvnAuthenticationManager manager = configuration.getAuthenticationManager(myVcs); + + try { + T svnAuthentication = getWithPassive(passive); + if (svnAuthentication == null) { + svnAuthentication = getWithActive(manager); + } + if (svnAuthentication == null) return false; + + if (myStoreInUsual) { + manager.setArtificialSaving(true); + return acknowledge(manager, svnAuthentication); + } else { + if (myTmpDirManager == null) { + initTmpDir(configuration); + myTmpDirManager = createTmpManager(); + } + myTmpDirManager.setArtificialSaving(true); + return acknowledge(myTmpDirManager, svnAuthentication); + } + } + catch (IOException e) { + LOG.info(e); + VcsBalloonProblemNotifier.showOverChangesView(myVcs.getProject(), e.getMessage(), MessageType.ERROR); + return false; + } + catch (SVNException e) { + LOG.info(e); + VcsBalloonProblemNotifier.showOverChangesView(myVcs.getProject(), e.getMessage(), MessageType.ERROR); + return false; + } + } + + protected SvnAuthenticationManager createTmpManager() { + return SvnConfiguration.createForTmpDir(myVcs.getProject(), myTempDirectory); + } + + protected abstract T getWithPassive(SvnAuthenticationManager passive) throws SVNException; + protected abstract T getWithActive(SvnAuthenticationManager active) throws SVNException; + protected abstract boolean acknowledge(SvnAuthenticationManager manager, T svnAuthentication) throws SVNException; + } + + private void initTmpDir(SvnConfiguration configuration) throws IOException { + if (myTempDirectory == null) { + myTempDirectory = FileUtil.createTempDirectory("tmp", "Subversion"); + FileUtil.copyDir(new File(configuration.getConfigurationDirectory()), myTempDirectory); + } + } + + private void doWithSubscribeToAuthProvider(SvnAuthenticationManager.ISVNAuthenticationProviderListener listener, + final ThrowableRunnable<SVNException> runnable) throws SVNException { + MessageBusConnection connection = null; + try { + final Project project = myVcs.getProject(); + connection = project.getMessageBus().connect(project); + connection.subscribe(SvnAuthenticationManager.AUTHENTICATION_PROVIDER_LISTENER, listener); + runnable.run(); + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + + // plus seems that we also should ask for credentials; but we didn't receive realm name yet + private class SSLServerCertificateAuthenticator extends AbstractAuthenticator<Boolean> { + private SVNURL myUrl; + private String myRealm; + private String myCertificateRealm; + private String myCredentialsRealm; + private Object myCertificate; + private int myResult; + private SVNAuthentication myAuthentication; + + protected SSLServerCertificateAuthenticator(SvnVcs vcs) { + super(vcs); + } + + public boolean tryAuthenticate(final SVNURL url, final String realm) { + myUrl = url; + myRealm = realm; + myResult = ISVNAuthenticationProvider.ACCEPTED_TEMPORARY; + myStoreInUsual = false; + return tryAuthenticate(); + } + + @Override + protected Boolean getWithPassive(SvnAuthenticationManager passive) throws SVNException { + String stored = (String) passive.getRuntimeAuthStorage().getData("svn.ssl.server", myRealm); + if (stored == null) return null; + CertificateFactory cf; + try { + cf = CertificateFactory.getInstance("X509"); + final byte[] buffer = new byte[stored.length()]; + SVNBase64.base64ToByteArray(new StringBuffer(stored), buffer); + myCertificate = cf.generateCertificate(new ByteArrayInputStream(buffer)); + } + catch (CertificateException e) { + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.AUTHN_CREDS_UNAVAILABLE, e)); + } + myCertificateRealm = myRealm; + return myCertificate != null ? true : null; + } + + @Override + protected Boolean getWithActive(final SvnAuthenticationManager active) throws SVNException { + doWithSubscribeToAuthProvider(new SvnAuthenticationManager.ISVNAuthenticationProviderListener() { + @Override + public void requestClientAuthentication(String kind, + SVNURL url, + String realm, + SVNErrorMessage errorMessage, + SVNAuthentication previousAuth, + boolean authMayBeStored, + SVNAuthentication authentication) { + if (!myUrl.equals(url)) return; + myCredentialsRealm = realm; + myAuthentication = authentication; + if (myAuthentication != null) { + myStoreInUsual &= myAuthentication.isStorageAllowed(); + } + } + + @Override + public void acceptServerAuthentication(SVNURL url, + String realm, + Object certificate, + boolean resultMayBeStored, + int accepted) { + if (!myUrl.equals(url)) return; + myCertificateRealm = realm; + myCertificate = certificate; + myResult = accepted; + } + }, new ThrowableRunnable<SVNException>() { + @Override + public void run() throws SVNException { + myVcs.createWCClient(active).doInfo(myUrl, SVNRevision.UNDEFINED, SVNRevision.HEAD); + } + } + ); + + myStoreInUsual &= myCertificate != null && ISVNAuthenticationProvider.ACCEPTED == myResult; + return ISVNAuthenticationProvider.REJECTED != myResult && myCertificate != null; + } + + @Override + protected boolean acknowledge(SvnAuthenticationManager manager, Boolean svnAuthentication) throws SVNException { + // we should store certificate, if it wasn't accepted (if temporally tmp) + if (myCertificate == null) { // this is if certificate was stored only in passive area + String stored = (String) manager.getRuntimeAuthStorage().getData("svn.ssl.server", myRealm); + if (StringUtil.isEmptyOrSpaces(stored)) { + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.AUTHN_CREDS_UNAVAILABLE, "No stored server certificate was found in runtime")); + } + CertificateFactory cf; + try { + cf = CertificateFactory.getInstance("X509"); + final byte[] buffer = new byte[stored.length()]; + SVNBase64.base64ToByteArray(new StringBuffer(stored), buffer); + myCertificate = cf.generateCertificate(new ByteArrayInputStream(buffer)); + } + catch (CertificateException e) { + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.AUTHN_CREDS_UNAVAILABLE, e)); + } + myCertificateRealm = myRealm; + } + if (myTempDirectory != null && myCertificate != null) { + if (! (myCertificate instanceof X509Certificate)) { + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "Can not store server certificate: " + myCertificate)); + } + X509Certificate x509Certificate = (X509Certificate) myCertificate; + String stored; + try { + stored = SVNBase64.byteArrayToBase64(x509Certificate.getEncoded()); + } + catch (CertificateEncodingException e) { + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e)); + } + int failures = SVNSSLUtil.getServerCertificateFailures(x509Certificate, myUrl.getHost()); + storeServerCertificate(myTempDirectory, myCertificateRealm, stored, failures); + if (myAuthentication != null) { + final String realm = myCredentialsRealm == null ? myCertificateRealm : myCredentialsRealm; + return storeCredentials(manager, myAuthentication, realm); + } + } + return true; + } + + private void storeServerCertificate(final File configDir, String realm, String data, int failures) throws SVNException { + //noinspection ResultOfMethodCallIgnored + configDir.mkdirs(); + + File file = new File(configDir, "auth/svn.ssl.server/" + SVNFileUtil.computeChecksum(realm)); + SVNHashMap map = new SVNHashMap(); + map.put("ascii_cert", data); + map.put("svn:realmstring", realm); + map.put("failures", Integer.toString(failures)); + + SVNFileUtil.deleteFile(file); + + File tmpFile = SVNFileUtil.createUniqueFile(configDir, "auth", ".tmp", true); + try { + SVNWCProperties.setProperties(SVNProperties.wrap(map), file, tmpFile, SVNWCProperties.SVN_HASH_TERMINATOR); + } finally { + SVNFileUtil.deleteFile(tmpFile); + } + } + } + + private boolean storeCredentials(SvnAuthenticationManager manager, final SVNAuthentication authentication, final String realm) throws SVNException { + try { + final String kind = getFromType(authentication); + if (! acknowledgeSSL(manager, authentication)) { + manager.acknowledgeAuthentication(true, kind, realm, null, authentication, authentication.getURL()); + } + } catch (SvnAuthenticationManager.CredentialsSavedException e) { + return e.isSuccess(); + } + return true; + } + + private class CredentialsAuthenticator extends AbstractAuthenticator<SVNAuthentication> { + private String myKind; + private String myRealm; + // sometimes realm string is different (with <>), so store credentials for both strings.. + private String myRealm2; + private SVNURL myUrl; + private SVNAuthentication myAuthentication; + private File myFile; + + protected CredentialsAuthenticator(SvnVcs vcs) { + super(vcs); + } + + public boolean tryAuthenticate(String realm, SVNURL url, File file, boolean previousFailed, boolean passwordRequest) { + myFile = file; + realm = realm == null ? url.getHost() : realm; + myRealm = realm; + myUrl = url; + final List<String> kinds = getKinds(url, passwordRequest); + for (String kind : kinds) { + myKind = kind; + if (! tryAuthenticate()) { + return false; + } + } + return true; + } + + @Override + protected SVNAuthentication getWithPassive(SvnAuthenticationManager passive) throws SVNException { + final SVNAuthentication impl = getWithPassiveImpl(passive); + if (impl != null && ! checkAuthOk(impl)) { + clearPassiveCredentials(myRealm, myFile, impl instanceof SVNPasswordAuthentication); //clear passive also take into acconut ssl filepath + return null; + } + return impl; + } + + private SVNAuthentication getWithPassiveImpl(SvnAuthenticationManager passive) throws SVNException { + try { + return passive.getFirstAuthentication(myKind, myRealm, myUrl); + } catch (SVNCancelException e) { + return null; + } + } + + private boolean checkAuthOk(SVNAuthentication authentication) { + if (authentication instanceof SVNPasswordAuthentication && StringUtil.isEmptyOrSpaces(authentication.getUserName())) return false; + if (authentication instanceof SVNSSLAuthentication) { + if (StringUtil.isEmptyOrSpaces(((SVNSSLAuthentication)authentication).getPassword())) return false; + } + return true; + } + + @Override + protected SVNAuthentication getWithActive(final SvnAuthenticationManager active) throws SVNException { + if (ISVNAuthenticationManager.SSL.equals(myKind)) { + doWithSubscribeToAuthProvider(new SvnAuthenticationManager.ISVNAuthenticationProviderListener() { + @Override + public void requestClientAuthentication(String kind, + SVNURL url, + String realm, + SVNErrorMessage errorMessage, + SVNAuthentication previousAuth, + boolean authMayBeStored, + SVNAuthentication authentication) { + if (!myUrl.equals(url)) return; + myAuthentication = authentication; + myRealm2 = realm; + myStoreInUsual = myAuthentication != null && myAuthentication.isStorageAllowed(); + } + + @Override + public void acceptServerAuthentication(SVNURL url, + String realm, + Object certificate, + boolean resultMayBeStored, + int accepted) { + } + }, new ThrowableRunnable<SVNException>() { + @Override + public void run() throws SVNException { + myVcs.createWCClient(active).doInfo(myUrl, SVNRevision.UNDEFINED, SVNRevision.HEAD); + } + } + ); + if (myAuthentication != null) return myAuthentication; + } + myAuthentication = active.getProvider().requestClientAuthentication(myKind, myUrl, myRealm, null, null, true); + myStoreInUsual = myTempDirectory == null && myAuthentication != null && myAuthentication.isStorageAllowed(); + return myAuthentication; + } + + @Override + protected boolean acknowledge(SvnAuthenticationManager manager, SVNAuthentication svnAuthentication) throws SVNException { + if (! StringUtil.isEmptyOrSpaces(myRealm2) && ! myRealm2.equals(myRealm)) { + storeCredentials(manager, svnAuthentication, myRealm2); + } + return storeCredentials(manager, svnAuthentication, myRealm); + } + } + + private boolean acknowledgeSSL(SvnAuthenticationManager manager, SVNAuthentication svnAuthentication) throws SVNException { + if (svnAuthentication instanceof SVNSSLAuthentication && (((SVNSSLAuthentication) svnAuthentication).getCertificateFile() != null)) { + manager.acknowledgeForSSL(true, getFromType(svnAuthentication), + ((SVNSSLAuthentication) svnAuthentication).getCertificateFile().getPath(), + null, svnAuthentication); + manager.acknowledgeAuthentication(true, getFromType(svnAuthentication), + ((SVNSSLAuthentication) svnAuthentication).getCertificateFile().getPath(), + null, svnAuthentication, svnAuthentication.getURL()); + return true; + } + return false; + } + + private File getExistingParent(final File file) { + File current = file; + while (current != null) { + if (current.exists()) return current; + current = current.getParentFile(); + } + return null; + } + + private static List<String> getKinds(final SVNURL url, boolean passwordRequest) { + if (passwordRequest || "http".equals(url.getProtocol())) { + return Collections.singletonList(ISVNAuthenticationManager.PASSWORD); + } else if ("https".equals(url.getProtocol())) { + return Collections.singletonList(ISVNAuthenticationManager.SSL); + } else if ("svn".equals(url.getProtocol())) { + return Collections.singletonList(ISVNAuthenticationManager.PASSWORD); + } else if (url.getProtocol().contains("svn+")) { // todo +- + return Arrays.asList(ISVNAuthenticationManager.SSH, ISVNAuthenticationManager.USERNAME); + } else if ("file".equals(url.getProtocol())) { + return Collections.singletonList(ISVNAuthenticationManager.USERNAME); + } + return Collections.singletonList(ISVNAuthenticationManager.USERNAME); + } + + @Nullable + @Override + public File getSpecialConfigDir() { + return myTempDirectory; + } + + private String getFromType(SVNAuthentication authentication) { + if (authentication instanceof SVNPasswordAuthentication) { + return ISVNAuthenticationManager.PASSWORD; + } + if (authentication instanceof SVNSSHAuthentication) { + return ISVNAuthenticationManager.SSH; + } + if (authentication instanceof SVNSSLAuthentication) { + return ISVNAuthenticationManager.SSL; + } + if (authentication instanceof SVNUserNameAuthentication) { + return ISVNAuthenticationManager.USERNAME; + } + throw new IllegalArgumentException(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/SvnCheckinEnvironment.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/SvnCheckinEnvironment.java index ea1a7ee6937e..433e4063e02c 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/SvnCheckinEnvironment.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/SvnCheckinEnvironment.java @@ -25,10 +25,9 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vcs.CheckinProjectPanel; -import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.ChangeList; import com.intellij.openapi.vcs.changes.ChangesUtil; @@ -39,20 +38,19 @@ import com.intellij.openapi.vcs.ui.RefreshableOnComponent; import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.util.ArrayUtil; import com.intellij.util.FunctionUtil; import com.intellij.util.NullableFunction; import com.intellij.util.PairConsumer; +import com.intellij.util.containers.Convertor; +import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.idea.svn.SvnBundle; -import org.jetbrains.idea.svn.SvnConfiguration; -import org.jetbrains.idea.svn.SvnUtil; -import org.jetbrains.idea.svn.SvnVcs; -import org.tmatesoft.svn.core.SVNCancelException; -import org.tmatesoft.svn.core.SVNCommitInfo; -import org.tmatesoft.svn.core.SVNDepth; -import org.tmatesoft.svn.core.SVNException; +import org.jetbrains.idea.svn.*; +import org.jetbrains.idea.svn.commandLine.SvnCommandLineStatusClient; +import org.tigris.subversion.javahl.ClientException; +import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.wc.*; import javax.swing.*; @@ -140,18 +138,18 @@ public class SvnCheckinEnvironment implements CheckinEnvironment { } if (progress != null) { - doCommit(committables, progress, committer, comment, force, recursive, exception, feedback); + doCommit(committables, committer, comment, force, exception, feedback); } else if (ApplicationManager.getApplication().isDispatchThread()) { ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { public void run() { ProgressIndicator p = ProgressManager.getInstance().getProgressIndicator(); - doCommit(committables, p, committer, comment, force, recursive, exception, feedback); + doCommit(committables, committer, comment, force, exception, feedback); } }, SvnBundle.message("progress.title.commit"), false, mySvnVcs.getProject()); } else { - doCommit(committables, progress, committer, comment, force, recursive, exception, feedback); + doCommit(committables, committer, comment, force, exception, feedback); } for(VirtualFile f : deletedFiles) { @@ -161,28 +159,39 @@ public class SvnCheckinEnvironment implements CheckinEnvironment { } private void doCommit(List<File> committables, - ProgressIndicator progress, SVNCommitClient committer, String comment, boolean force, - boolean recursive, List<VcsException> exception, final Set<String> feedback) { - final Collection<List<File>> collections = SvnUtil.splitFilesIntoWc(mySvnVcs, committables); - for (List<File> collection : collections) { - doCommitOneWc(collection, progress, committer, comment, force, recursive, exception, feedback); + final MultiMap<Pair<SVNURL,WorkingCopyFormat>,File> map = SvnUtil.splitIntoRepositoriesMap(mySvnVcs, committables, Convertor.SELF); + for (Map.Entry<Pair<SVNURL, WorkingCopyFormat>, Collection<File>> entry : map.entrySet()) { + doCommitOneRepo(entry.getValue(), committer, comment, force, exception, feedback, entry.getKey().getSecond(), entry.getKey().getFirst()); } } - private void doCommitOneWc(Collection<File> committables, - ProgressIndicator progress, - SVNCommitClient committer, - String comment, - boolean force, - boolean recursive, - List<VcsException> exception, final Set<String> feedback) { + private void doCommitOneRepo(Collection<File> committables, + SVNCommitClient committer, + String comment, + boolean force, + List<VcsException> exception, final Set<String> feedback, final WorkingCopyFormat format, SVNURL url) { if (committables.isEmpty()) { return; } + if (WorkingCopyFormat.ONE_DOT_SEVEN.equals(format) && + SvnConfiguration.UseAcceleration.commandLine.equals(SvnConfiguration.getInstance(mySvnVcs.getProject()).myUseAcceleration) && + (SvnAuthenticationManager.HTTP.equals(url.getProtocol()) || SvnAuthenticationManager.HTTPS.equals(url.getProtocol()))) { + doWithCommandLine(committables, comment, exception, feedback); + return; + } + + doWithSvnkit(committables, committer, comment, force, exception, feedback); + } + + private void doWithSvnkit(Collection<File> committables, + SVNCommitClient committer, + String comment, + boolean force, + List<VcsException> exception, Set<String> feedback) { File[] pathsToCommit = committables.toArray(new File[committables.size()]); boolean keepLocks = SvnConfiguration.getInstance(mySvnVcs.getProject()).isKeepLocks(); SVNCommitPacket[] commitPackets = null; @@ -224,22 +233,98 @@ public class SvnCheckinEnvironment implements CheckinEnvironment { } } if (committedRevisions.length() > 0) { - final Project project = mySvnVcs.getProject(); - final String message = SvnBundle.message("status.text.comitted.revision", committedRevisions); - if (feedback == null) { - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - new VcsBalloonProblemNotifier(project, message, MessageType.INFO).run(); - } - }, new Condition<Object>() { - @Override - public boolean value(Object o) { - return (! project.isOpen()) || project.isDisposed(); + reportCommittedRevisions(feedback, committedRevisions.toString()); + } + } + + private void doWithCommandLine(Collection<File> committables, String comment, List<VcsException> exception, Set<String> feedback) { + // if directory renames were used, IDEA reports all files under them as moved, but for svn we can not pass some of them + // to commit command - since not all paths are registered as changes -> so we need to filter these cases, but only if + // there at least some child-parent relationships in passed paths + try { + committables = filterCommittables(committables); + } catch (SVNException e) { + exception.add(new VcsException(e)); + return; + } + + final List<String> paths = ObjectsConvertor.convert(committables, new Convertor<File, String>() { + @Override + public String convert(File o) { + return o.getPath(); + } + }); + final IdeaSvnkitBasedAuthenticationCallback authenticationCallback = new IdeaSvnkitBasedAuthenticationCallback(mySvnVcs); + try { + final SvnBindClient client = new SvnBindClient(SvnApplicationSettings.getInstance().getCommandLinePath()); + client.setAuthenticationCallback(authenticationCallback); + client.setHandler(new IdeaCommitHandler(ProgressManager.getInstance().getProgressIndicator())); + final long revision = client.commit(ArrayUtil.toStringArray(paths), comment, false, false); + reportCommittedRevisions(feedback, String.valueOf(revision)); + } + catch (ClientException e) { + exception.add(new VcsException(e)); + } finally { + authenticationCallback.reset(); + } + } + + private Collection<File> filterCommittables(Collection<File> committables) throws SVNException { + final Set<File> childrenOfSomebody = new HashSet<File>(); + new AbstractFilterChildren<File>() { + @Override + protected void sortAscending(List<File> list) { + Collections.sort(list); + } + + @Override + protected boolean isAncestor(File parent, File child) { + final boolean isAncestor = FileUtil.isAncestor(parent, child, false); + if (isAncestor) { + childrenOfSomebody.add(child); + } + return isAncestor; + } + }.doFilter(new ArrayList<File>(committables)); + if (! childrenOfSomebody.isEmpty()) { + final HashSet<File> result = new HashSet<File>(committables); + result.removeAll(childrenOfSomebody); + final SvnCommandLineStatusClient statusClient = new SvnCommandLineStatusClient(mySvnVcs.getProject()); + for (File file : childrenOfSomebody) { + try { + final SVNStatus status = statusClient.doStatus(file, false); + if (status != null && ! SVNStatusType.STATUS_NONE.equals(status.getContentsStatus()) && + ! SVNStatusType.STATUS_UNVERSIONED.equals(status.getContentsStatus())) { + result.add(file); } - }); - } else { - feedback.add("Subversion: " + message); + } + catch (SVNException e) { + // not versioned + LOG.info(e); + throw e; + } } + return result; + } + return committables; + } + + private void reportCommittedRevisions(Set<String> feedback, String committedRevisions) { + final Project project = mySvnVcs.getProject(); + final String message = SvnBundle.message("status.text.comitted.revision", committedRevisions); + if (feedback == null) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + new VcsBalloonProblemNotifier(project, message, MessageType.INFO).run(); + } + }, new Condition<Object>() { + @Override + public boolean value(Object o) { + return (! project.isOpen()) || project.isDisposed(); + } + }); + } else { + feedback.add("Subversion: " + message); } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/AddLineConverter.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/AddLineConverter.java new file mode 100644 index 000000000000..46fec54d0a79 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/AddLineConverter.java @@ -0,0 +1,38 @@ +/* + * 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 org.tmatesoft.svn.core.wc.SVNEvent; + +import java.io.File; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/4/13 + * Time: 3:49 PM + */ +public class AddLineConverter { + private final File myBase; + + public AddLineConverter(File base) { + myBase = base; + } + + public SVNEvent convert(final String line) { + return null; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandFactory.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandFactory.java new file mode 100644 index 000000000000..085c8e54dc0f --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandFactory.java @@ -0,0 +1,59 @@ +/* + * 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.project.Project; +import com.intellij.openapi.vcs.ProcessEventListener; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.SvnApplicationSettings; +import org.jetbrains.idea.svn.SvnVcs; + +import java.io.File; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/4/13 + * Time: 4:58 PM + */ +public class SvnCommandFactory { + public static SvnSimpleCommand createSimpleCommand(final Project project, File workingDirectory, @NotNull SvnCommandName commandName) { + final SvnSimpleCommand command = + new SvnSimpleCommand(workingDirectory, commandName, SvnApplicationSettings.getInstance().getCommandLinePath()); + addStartFailedListener(project, command); + return command; + } + + private static void addStartFailedListener(final Project project, SvnCommand command) { + command.addListener(new ProcessEventListener() { + @Override + public void processTerminated(int exitCode) { + } + + @Override + public void startFailed(Throwable exception) { + SvnVcs.getInstance(project).checkCommandLineVersion(); + } + }); + } + + public static SvnLineCommand createLineCommand(Project project, File workingDirectory, @NotNull SvnCommandName commandName) { + final SvnLineCommand command = + new SvnLineCommand(workingDirectory, commandName, SvnApplicationSettings.getInstance().getCommandLinePath(), null); + addStartFailedListener(project, command); + return command; + } +} 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 c2a0be1e2d6d..0d2f4d00bd39 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineInfoClient.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineInfoClient.java @@ -20,6 +20,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.util.Consumer; +import org.jetbrains.idea.svn.SvnBindUtil; import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.portable.SvnExceptionWrapper; import org.jetbrains.idea.svn.portable.SvnkitSvnWcClient; @@ -70,12 +71,12 @@ public class SvnCommandLineInfoClient extends SvnkitSvnWcClient { Collection changeLists, final ISVNInfoHandler handler) throws SVNException { File base = path.isDirectory() ? path : path.getParentFile(); - base = correctUpToExistingParent(base); + base = SvnBindUtil.correctUpToExistingParent(base); if (base == null) { // very unrealistic throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), new RuntimeException("Can not find existing parent file")); } - final SvnSimpleCommand command = new SvnSimpleCommand(myProject, base, SvnCommandName.info); + final SvnSimpleCommand command = SvnCommandFactory.createSimpleCommand(myProject, base, SvnCommandName.info); if (depth != null) { command.addParameters("--depth", depth.getName()); @@ -137,14 +138,6 @@ public class SvnCommandLineInfoClient extends SvnkitSvnWcClient { } } - private File correctUpToExistingParent(File base) { - while (base != null) { - if (base.exists() && base.isDirectory()) return base; - base = base.getParentFile(); - } - return null; - } - @Override public void doInfo(SVNURL url, SVNRevision pegRevision, SVNRevision revision, boolean recursive, ISVNInfoHandler handler) throws SVNException { 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 f45b5c7039d0..a1a31536c8a9 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineStatusClient.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineStatusClient.java @@ -17,15 +17,15 @@ package org.jetbrains.idea.svn.commandLine; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.util.containers.Convertor; +import org.jetbrains.idea.svn.SvnBindUtil; import org.jetbrains.idea.svn.SvnUtil; import org.jetbrains.idea.svn.portable.PortableStatus; import org.jetbrains.idea.svn.portable.SvnExceptionWrapper; import org.jetbrains.idea.svn.portable.SvnStatusClientI; -import org.tmatesoft.sqljet.core.SqlJetErrorCode; -import org.tmatesoft.sqljet.core.SqlJetException; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.wc.*; @@ -96,12 +96,12 @@ public class SvnCommandLineStatusClient implements SvnStatusClientI { boolean collectParentExternals, final ISVNStatusHandler handler, final Collection changeLists) throws SVNException { - final File base = path.isDirectory() ? path : path.getParentFile(); + File base = path.isDirectory() ? path : path.getParentFile(); + base = SvnBindUtil.correctUpToExistingParent(base); final SVNInfo infoBase = myInfoClient.doInfo(base, revision); - // todo can not understand why revision can be used here - final SvnSimpleCommand command = new SvnSimpleCommand(myProject, base, SvnCommandName.st); + final SvnSimpleCommand command = SvnCommandFactory.createSimpleCommand(myProject, base, SvnCommandName.st); putParameters(depth, remote, reportAll, includeIgnored, changeLists, command); final SvnStatusHandler[] svnHandl = new SvnStatusHandler[1]; @@ -109,6 +109,9 @@ public class SvnCommandLineStatusClient implements SvnStatusClientI { try { final String result = command.run(); + if (StringUtil.isEmptyOrSpaces(result)) { + throw new VcsException("Status request returned nothing for command: " + command.myCommandLine.getCommandLineString()); + } SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); parser.parse(new ByteArrayInputStream(result.getBytes(CharsetToolkit.UTF8_CHARSET)), svnHandl[0]); if (! svnHandl[0].isAnythingReported()) { @@ -192,10 +195,6 @@ public class SvnCommandLineStatusClient implements SvnStatusClientI { @Override public void switchPath() { final PortableStatus pending = svnHandl[0].getPending(); - if (pending.isLocked()) { - throw new SvnExceptionWrapper(new SVNException(SVNErrorMessage.create(SVNErrorCode.SQLITE_ERROR), - new SqlJetException(SqlJetErrorCode.BUSY))); - } pending.setChangelistName(changelistName[0]); try { //if (infoBase != null) { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineUpdateClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineUpdateClient.java index 4870183b9823..c0e9c8e79fa2 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineUpdateClient.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnCommandLineUpdateClient.java @@ -18,17 +18,21 @@ package org.jetbrains.idea.svn.commandLine; import com.intellij.execution.process.ProcessOutputTypes; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; -import com.intellij.openapi.vcs.LineProcessEventListener; +import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.concurrency.Semaphore; -import org.jetbrains.idea.svn.SvnAuthenticationManager; +import com.intellij.util.ArrayUtil; +import org.jetbrains.idea.svn.SvnApplicationSettings; import org.jetbrains.idea.svn.SvnVcs; -import org.jetbrains.idea.svn.portable.SvnExceptionWrapper; +import org.jetbrains.idea.svn.checkin.IdeaSvnkitBasedAuthenticationCallback; +import org.jetbrains.idea.svn.config.SvnBindException; import org.jetbrains.idea.svn.portable.SvnSvnkitUpdateClient; import org.tmatesoft.svn.core.*; import org.tmatesoft.svn.core.wc.*; import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -78,127 +82,105 @@ public class SvnCommandLineUpdateClient extends SvnSvnkitUpdateClient { if (info == null || info.getURL() == null) { throw new SVNException(SVNErrorMessage.create(SVNErrorCode.WC_NOT_WORKING_COPY, paths[0].getPath())); } - final long[] result = new long[paths.length]; + final AtomicReference<long[]> updatedToRevision = new AtomicReference<long[]>(); + updatedToRevision.set(new long[0]); - new CommandLineAuthenticator(myProject, new CommandLineAuthenticator.AuthenticationRequiringCommand() { - @Override - public void run(File configDir) throws SVNException { - File base = myCommonAncestor == null ? paths[0] : new File(myCommonAncestor.getPath()); - base = base.isDirectory() ? base : base.getParentFile(); - final SvnLineCommand command = new SvnLineCommand(myProject, base, SvnCommandName.up); - if (revision != null && ! SVNRevision.UNDEFINED.equals(revision) && ! SVNRevision.WORKING.equals(revision)) { - command.addParameters("-r", revision.toString()); - } - // unknown depth is not used any more for 1.7 -> why? - if (depth != null && ! SVNDepth.UNKNOWN.equals(depth)) { - command.addParameters("--depth", depth.toString()); - } - if (allowUnversionedObstructions) { - command.addParameters("--force"); - } - if (depthIsSticky && depth != null) {// !!! not sure, but not used - command.addParameters("--set-depth", depth.toString()); - } - if (makeParents) { - command.addParameters("--parents"); - } - if (myIgnoreExternals) { - command.addParameters("--ignore-externals"); - } - command.addParameters("--accept", "postpone"); - command.addParameters("--config-dir", configDir.getPath()); + File base = myCommonAncestor == null ? paths[0] : new File(myCommonAncestor.getPath()); + base = base.isDirectory() ? base : base.getParentFile(); + + final List<String> parameters = new ArrayList<String>(); + if (revision != null && ! SVNRevision.UNDEFINED.equals(revision) && ! SVNRevision.WORKING.equals(revision)) { + parameters.add("-r"); + parameters.add(revision.toString()); + } + // unknown depth is not used any more for 1.7 -> why? + if (depth != null && ! SVNDepth.UNKNOWN.equals(depth)) { + parameters.add("--depth"); + parameters.add(depth.toString()); + } + if (allowUnversionedObstructions) { + parameters.add("--force"); + } + if (depthIsSticky && depth != null) {// !!! not sure, but not used + parameters.add("--set-depth"); + parameters.add(depth.toString()); + } + if (makeParents) { + parameters.add("--parents"); + } + if (myIgnoreExternals) { + parameters.add("--ignore-externals"); + } + parameters.add("--accept"); + parameters.add("postpone"); - for (File path : paths) { - command.addParameters(path.getPath()); + for (File path : paths) { + parameters.add(path.getPath()); + } + + final AtomicReference<SVNException> excRef = new AtomicReference<SVNException>(); + final ISVNEventHandler handler = getEventHandler(); + final UpdateOutputLineConverter converter = new UpdateOutputLineConverter(base); + try { + final LineCommandListener listener = new LineCommandListener() { + final long[] myRevisions = new long[paths.length]; + + @Override + public void baseDirectory(File file) { } - final StringBuffer sbError = new StringBuffer(); - final Semaphore semaphore = new Semaphore(); - semaphore.down(); - final ISVNEventHandler handler = getEventHandler(); - final UpdateOutputLineConverter converter = new UpdateOutputLineConverter(base); - final SVNException[] innerException = new SVNException[1]; - command.addListener(new LineProcessEventListener() { - @Override - public void onLineAvailable(String line, Key outputType) { - if (ProcessOutputTypes.STDOUT.equals(outputType)) { - final SVNEvent event = converter.convert(line); - if (event != null) { - checkForUpdateCompleted(event); - try { - handler.handleEvent(event, 0.5); - } - catch (SVNException e) { - command.cancel(); - semaphore.up(); - innerException[0] = e; - } + @Override + public void onLineAvailable(String line, Key outputType) { + if (ProcessOutputTypes.STDOUT.equals(outputType)) { + final SVNEvent event = converter.convert(line); + if (event != null) { + checkForUpdateCompleted(event); + try { + handler.handleEvent(event, 0.5); } - } else if (ProcessOutputTypes.STDERR.equals(outputType)) { - sbError.append(line); - if (line.contains(ourAuthenticationRealm)) { - command.cancel(); - semaphore.up(); + catch (SVNException e) { + cancel(); + excRef.set(e); } } } - - @Override - public void processTerminated(int exitCode) { - semaphore.up(); - } - - @Override - public void startFailed(Throwable exception) { - semaphore.up(); - } - }); - try { - command.start(); - semaphore.waitFor(); - - checkForException(sbError); - } catch (SvnExceptionWrapper e){ - throw (SVNException) e.getCause(); } - } - @Override - public void runWithSvnkitClient(File configDir, SvnAuthenticationManager manager) throws SVNException { - final SVNUpdateClient client = SvnVcs.getInstance(myProject).createUpdateClient(manager); - client.doUpdate(paths, revision, depth, allowUnversionedObstructions, depthIsSticky, makeParents); - } - - private void checkForUpdateCompleted(SVNEvent event) { - if (SVNEventAction.UPDATE_COMPLETED.equals(event.getAction())) { - final long eventRevision = event.getRevision(); - for (int i = 0; i < paths.length; i++) { - final File path = paths[i]; - if (path.equals(event.getFile())) { - result[i] = eventRevision; - break; + private void checkForUpdateCompleted(SVNEvent event) { + if (SVNEventAction.UPDATE_COMPLETED.equals(event.getAction())) { + final long eventRevision = event.getRevision(); + for (int i = 0; i < paths.length; i++) { + final File path = paths[i]; + if (FileUtil.filesEqual(path, event.getFile())) { + myRevisions[i] = eventRevision; + break; + } } } } - } - @Override - public SVNURL sampleUrl() { - return info.getURL(); - } - - @Override - public void cleanup() throws SVNException { - final SvnVcs vcs17 = SvnVcs.getInstance(myProject); - final SVNWCClient client = vcs17.createWCClient(); - for (File path : paths) { - client.doCleanup(path); + @Override + public void processTerminated(int exitCode) { + super.processTerminated(exitCode); + updatedToRevision.set(myRevisions); } - } - }).doWithAuthentication(); - return result; + }; + SvnLineCommand.runWithAuthenticationAttempt(SvnApplicationSettings.getInstance().getCommandLinePath(), + base, SvnCommandName.up, listener, + new IdeaSvnkitBasedAuthenticationCallback(SvnVcs.getInstance(myProject)), + ArrayUtil.toStringArray(parameters)); + } + catch (SvnBindException e) { + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e)); + } + if (excRef.get() != null) { + throw excRef.get(); + } + + return updatedToRevision.get(); } + private void checkForException(final StringBuffer sbError) throws SVNException { if (sbError.length() == 0) return; final String message = sbError.toString(); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnLineCommand.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnLineCommand.java deleted file mode 100644 index cf31abde7f1a..000000000000 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnLineCommand.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2000-2012 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.ProcessOutputTypes; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.vcs.LineHandlerHelper; -import com.intellij.openapi.vcs.LineProcessEventListener; -import com.intellij.util.EventDispatcher; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.util.Iterator; - -/** - * Created with IntelliJ IDEA. - * User: Irina.Chernushina - * Date: 1/25/12 - * Time: 4:05 PM - * - * honestly stolen from GitLineHandler - */ -public class SvnLineCommand extends SvnCommand { - /** - * the partial line from stdout stream - */ - private final StringBuilder myStdoutLine = new StringBuilder(); - /** - * the partial line from stderr stream - */ - private final StringBuilder myStderrLine = new StringBuilder(); - private final EventDispatcher<LineProcessEventListener> myLineListeners; - - public SvnLineCommand(Project project, File workingDirectory, @NotNull SvnCommandName commandName) { - super(project, workingDirectory, commandName); - myLineListeners = EventDispatcher.create(LineProcessEventListener.class); - } - - @Override - protected void processTerminated(int exitCode) { - // force newline - if (myStdoutLine.length() != 0) { - onTextAvailable("\n\r", ProcessOutputTypes.STDOUT); - } - else if (myStderrLine.length() != 0) { - onTextAvailable("\n\r", ProcessOutputTypes.STDERR); - } - } - - @Override - protected void onTextAvailable(String text, Key outputType) { - Iterator<String> lines = LineHandlerHelper.splitText(text).iterator(); - if (ProcessOutputTypes.STDOUT == outputType) { - notifyLines(outputType, lines, myStdoutLine); - } - else if (ProcessOutputTypes.STDERR == outputType) { - notifyLines(outputType, lines, myStderrLine); - } - } - - private void notifyLines(final Key outputType, final Iterator<String> lines, final StringBuilder lineBuilder) { - if (!lines.hasNext()) return; - if (lineBuilder.length() > 0) { - lineBuilder.append(lines.next()); - if (lines.hasNext()) { - // line is complete - final String line = lineBuilder.toString(); - notifyLine(line, outputType); - lineBuilder.setLength(0); - } - } - while (true) { - String line = null; - if (lines.hasNext()) { - line = lines.next(); - } - - if (lines.hasNext()) { - notifyLine(line, outputType); - } - else { - if (line != null && line.length() > 0) { - lineBuilder.append(line); - } - break; - } - } - } - - private void notifyLine(final String line, final Key outputType) { - String trimmed = LineHandlerHelper.trimLineSeparator(line); - myLineListeners.getMulticaster().onLineAvailable(trimmed, outputType); - } - - public void addListener(LineProcessEventListener listener) { - myLineListeners.addListener(listener); - super.addListener(listener); - } -} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnStatusHandler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnStatusHandler.java index 84d25f59f4b3..7b789f92e7ed 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnStatusHandler.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/SvnStatusHandler.java @@ -284,6 +284,7 @@ public class SvnStatusHandler extends DefaultHandler { if (createNewChild) { assertSAX(myElementsMap.containsKey(qName)); final ElementHandlerBase newChild = myElementsMap.get(qName).get(); + newChild.preAttributesEffect(myDataCallback); newChild.updateStatus(attributes, myPending, myLockWrapper); newChild.preEffect(myDataCallback); myParseStack.add(newChild); @@ -547,12 +548,11 @@ public class SvnStatusHandler extends DefaultHandler { private static class Lock extends ElementHandlerBase { private Lock() { - super(new String[]{"token","owner","comment","created"}, new String[]{}); + super(new String[]{"token", "owner", "comment", "created"}, new String[]{}); } @Override protected void updateStatus(Attributes attributes, PortableStatus status, SVNLockWrapper lock) throws SAXException { - // todo check inside-repository path lock.setPath(status.getPath()); } @@ -562,11 +562,15 @@ public class SvnStatusHandler extends DefaultHandler { } @Override - public void preEffect(DataCallback callback) { + public void preAttributesEffect(DataCallback callback) { callback.startLock(); } @Override + public void preEffect(DataCallback callback) { + } + + @Override public void characters(String s, PortableStatus pending, SVNLockWrapper lock) { } } @@ -587,8 +591,12 @@ public class SvnStatusHandler extends DefaultHandler { } @Override + public void preAttributesEffect(DataCallback callback) { + callback.startLock(); + } + + @Override public void preEffect(DataCallback callback) { - callback.startRemoteStatus(); } @Override @@ -851,6 +859,8 @@ and no "mod4" under } public abstract void characters(String s, PortableStatus pending, SVNLockWrapper lock); + + public void preAttributesEffect(DataCallback callback) {} } public interface ExternalDataCallback { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.form b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.form index 170c92072c51..a416aa145505 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.form +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.form @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.idea.svn.dialogs.BranchConfigurationDialog"> - <grid id="27dc6" binding="myTopPanel" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <grid id="27dc6" binding="myTopPanel" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> <margin top="0" left="0" bottom="0" right="0"/> <constraints> <xy x="20" y="20" width="500" height="400"/> @@ -50,6 +50,14 @@ </component> </children> </grid> + <component id="f6743" class="javax.swing.JLabel" binding="myErrorPrompt"> + <constraints> + <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text value="Label"/> + </properties> + </component> </children> </grid> </form> diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.java index aa6506917005..221ba2abbce2 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.java @@ -20,15 +20,14 @@ import com.intellij.openapi.actionSystem.ActionToolbarPosition; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.ui.MultiLineLabelUI; import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.AnActionButton; -import com.intellij.ui.AnActionButtonRunnable; -import com.intellij.ui.DocumentAdapter; -import com.intellij.ui.ToolbarDecorator; +import com.intellij.ui.*; import com.intellij.ui.components.JBList; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.*; import org.jetbrains.idea.svn.branchConfig.InfoReliability; @@ -54,11 +53,11 @@ public class BranchConfigurationDialog extends DialogWrapper { private TextFieldWithBrowseButton myTrunkLocationTextField; private JList myLocationList; private JPanel myListPanel; + private JLabel myErrorPrompt; private final SvnBranchConfigManager mySvnBranchConfigManager; private final VirtualFile myRoot; - public BranchConfigurationDialog(final Project project, final SvnBranchConfigurationNew configuration, final String rootUrl, - final VirtualFile root) { + public BranchConfigurationDialog(@NotNull final Project project, @NotNull final SvnBranchConfigurationNew configuration, final @NotNull String rootUrl, @NotNull final VirtualFile root, @NotNull String url) { super(project, true); myRoot = root; init(); @@ -66,7 +65,7 @@ public class BranchConfigurationDialog extends DialogWrapper { final String trunkUrl = configuration.getTrunkUrl(); if (trunkUrl == null || trunkUrl.trim().length() == 0) { - configuration.setTrunkUrl(rootUrl); + configuration.setTrunkUrl(url); } mySvnBranchConfigManager = SvnBranchConfigurationManager.getInstance(project).getSvnBranchConfigManager(); @@ -85,6 +84,9 @@ public class BranchConfigurationDialog extends DialogWrapper { myTrunkLocationTextField.getTextField().getDocument().addDocumentListener(trunkUrlValidator); trunkUrlValidator.textChanged(null); + myErrorPrompt.setUI(new MultiLineLabelUI()); + myErrorPrompt.setForeground(SimpleTextAttributes.ERROR_ATTRIBUTES.getFgColor()); + final MyListModel listModel = new MyListModel(configuration); myLocationList = new JBList(listModel); @@ -142,11 +144,10 @@ public class BranchConfigurationDialog extends DialogWrapper { (currentValue.length() > myRootUrlPrefix.length()); myTrunkLocationTextField.getButton().setEnabled(valueOk); - setOKActionEnabled(prefixOk); if (prefixOk) { myConfiguration.setTrunkUrl(currentValue.endsWith("/") ? currentValue.substring(0, currentValue.length() - 1) : currentValue); } - setErrorText(prefixOk ? null : SvnBundle.message("configure.branches.error.wrong.url")); + myErrorPrompt.setText(prefixOk ? "" : SvnBundle.message("configure.branches.error.wrong.url", myRootUrl)); } } @@ -197,7 +198,7 @@ public class BranchConfigurationDialog extends DialogWrapper { } final SvnBranchConfigurationNew clonedConfiguration = configuration.copy(); - BranchConfigurationDialog dlg = new BranchConfigurationDialog(project, clonedConfiguration, rootUrl, vcsRoot); + BranchConfigurationDialog dlg = new BranchConfigurationDialog(project, clonedConfiguration, rootUrl, vcsRoot, wcRoot.getUrl()); dlg.show(); if (dlg.isOK()) { SvnBranchConfigurationManager.getInstance(project).setConfiguration(vcsRoot, clonedConfiguration); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CopiesPanel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CopiesPanel.java index d66cb6f0ae54..bfb5c645a46c 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CopiesPanel.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CopiesPanel.java @@ -28,6 +28,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.ui.ColorUtil; import com.intellij.ui.DottedBorder; +import com.intellij.ui.JBColor; import com.intellij.ui.ScrollPaneFactory; import com.intellij.ui.components.labels.LinkLabel; import com.intellij.ui.components.labels.LinkListener; @@ -36,6 +37,8 @@ import com.intellij.util.containers.Convertor; import com.intellij.util.io.EqualityPolicy; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.NestedCopyType; import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.WorkingCopyFormat; @@ -43,6 +46,7 @@ import org.jetbrains.idea.svn.actions.CleanupWorker; import org.jetbrains.idea.svn.actions.SelectBranchPopup; import org.jetbrains.idea.svn.branchConfig.SvnBranchConfigurationNew; import org.jetbrains.idea.svn.checkout.SvnCheckoutProvider; +import org.jetbrains.idea.svn.integrate.QuickMergeInteractionImpl; import org.tmatesoft.svn.core.SVNDepth; import org.tmatesoft.svn.core.internal.util.SVNPathUtil; import org.tmatesoft.svn.core.wc.SVNRevision; @@ -205,7 +209,7 @@ public class CopiesPanel { final int result = Messages.showOkCancelDialog(myVcs.getProject(), "You are going to checkout into '" + wcInfo.getPath() + "' with 'infinity' depth.\n" + "This will update your working copy to HEAD revision as well.", - "Set working copy infinity depth", + "Set Working Copy Infinity Depth", Messages.getWarningIcon()); if (result == 0) { // update of view will be triggered by roots changed event @@ -258,6 +262,7 @@ public class CopiesPanel { myPanel.repaint(); } + @SuppressWarnings("MethodMayBeStatic") private String formatWc(WCInfo info) { final StringBuilder sb = new StringBuilder().append("<html><head>").append(UIUtil.getCssFontDeclaration(UIUtil.getLabelFont())) .append("</head><body><table bgColor=\"").append(ColorUtil.toHex(UIUtil.getPanelBackground())).append("\">"); @@ -297,17 +302,18 @@ public class CopiesPanel { return sb.toString(); } - private void mergeFrom(final WCInfo wcInfo, final VirtualFile root, final Component mergeLabel) { + private void mergeFrom(@NotNull final WCInfo wcInfo, @NotNull final VirtualFile root, @Nullable final Component mergeLabel) { SelectBranchPopup.showForBranchRoot(myProject, root, new SelectBranchPopup.BranchSelectedCallback() { @Override public void branchSelected(Project project, SvnBranchConfigurationNew configuration, String url, long revision) { - new QuickMerge(project, url, wcInfo, SVNPathUtil.tail(url), root).execute(); + new QuickMerge(project, url, wcInfo, SVNPathUtil.tail(url), root).execute(new QuickMergeInteractionImpl(myProject)); } }, "Select branch", mergeLabel); } + @SuppressWarnings("MethodMayBeStatic") private void setFocusableForLinks(final LinkLabel label) { - final Border border = new DottedBorder(new Insets(1,2,1,1), Color.black); + final Border border = new DottedBorder(new Insets(1,2,1,1), JBColor.BLACK); label.setFocusable(true); label.addFocusListener(new FocusAdapter() { @Override @@ -334,7 +340,7 @@ public class CopiesPanel { private void changeFormat(final WCInfo wcInfo) { ChangeFormatDialog dialog = new ChangeFormatDialog(myProject, new File(wcInfo.getPath()), false, ! wcInfo.isIsWcRoot()); - dialog.setData(true, wcInfo.getFormat().getOption()); + dialog.setData(wcInfo.getFormat().getOption()); dialog.show(); if (! dialog.isOK()) { return; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/LoadRecentBranchRevisions.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/LoadRecentBranchRevisions.java new file mode 100644 index 000000000000..7187613a0f86 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/LoadRecentBranchRevisions.java @@ -0,0 +1,145 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.util.PairConsumer; +import com.intellij.util.continuation.ContinuationContext; +import com.intellij.util.continuation.TaskDescriptor; +import com.intellij.util.continuation.Where; +import org.jetbrains.idea.svn.SvnBundle; +import org.jetbrains.idea.svn.SvnVcs; +import org.jetbrains.idea.svn.history.SvnChangeList; +import org.jetbrains.idea.svn.history.SvnCommittedChangesProvider; +import org.jetbrains.idea.svn.history.SvnRepositoryLocation; +import org.jetbrains.idea.svn.history.TreeStructureNode; +import org.jetbrains.idea.svn.mergeinfo.OneShotMergeInfoHelper; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNLogEntry; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; + +import java.util.ArrayList; +import java.util.List; + +/** +* Created with IntelliJ IDEA. +* User: Irina.Chernushina +* Date: 3/30/13 +* Time: 7:40 PM +*/ +class LoadRecentBranchRevisions extends TaskDescriptor { + public static final String PROP_BUNCH_SIZE = "idea.svn.quick.merge.bunch.size"; + private final static int BUNCH_SIZE = 100; + private int myBunchSize; + private long myFirst; + private boolean myLastLoaded; + private OneShotMergeInfoHelper myHelper; + private List<CommittedChangeList> myCommittedChangeLists; + private final WCInfo myWcInfo; + private final SvnVcs myVcs; + private final String mySourceUrl; + private final Integer myTestBunchSize; + + LoadRecentBranchRevisions(String branchName, long first, WCInfo info, SvnVcs vcs, String url) { + super("Loading recent " + branchName + " revisions", Where.POOLED); + myFirst = first; + myWcInfo = info; + myVcs = vcs; + mySourceUrl = url; + // for test purposes!!! + myTestBunchSize = Integer.getInteger(PROP_BUNCH_SIZE); + if (myTestBunchSize != null) { + myBunchSize = myTestBunchSize.intValue(); + } else { + myBunchSize = BUNCH_SIZE; + } + } + + void setBunchSize(int bunchSize) { + if (myTestBunchSize != null) return; + myBunchSize = bunchSize; + } + + public boolean isLastLoaded() { + return myLastLoaded; + } + + @Override + public void run(ContinuationContext context) { + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + final SvnCommittedChangesProvider committedChangesProvider = (SvnCommittedChangesProvider)myVcs.getCommittedChangesProvider(); + final ChangeBrowserSettings settings = new ChangeBrowserSettings(); + if (myFirst > 0){ + settings.CHANGE_BEFORE = String.valueOf(myFirst); + settings.USE_CHANGE_BEFORE_FILTER = true; + } + + String local = SVNPathUtil.getRelativePath(myWcInfo.getRepositoryRoot(), myWcInfo.getRootUrl()); + final String relativeLocal = (local.startsWith("/") ? local : "/" + local); + String relativeBranch = SVNPathUtil.getRelativePath(myWcInfo.getRepositoryRoot(), mySourceUrl); + relativeBranch = (relativeBranch.startsWith("/") ? relativeBranch : "/" + relativeBranch); + + ProgressManager.progress2(SvnBundle.message("progress.text2.collecting.history", mySourceUrl + (myFirst > 0 ? ("@" + myFirst) : ""))); + final List<Pair<SvnChangeList, TreeStructureNode<SVNLogEntry>>> list = new ArrayList<Pair<SvnChangeList, TreeStructureNode<SVNLogEntry>>>(); + try { + committedChangesProvider.getCommittedChangesWithMergedRevisons(settings, new SvnRepositoryLocation(mySourceUrl), + myBunchSize + (myFirst > 0 ? 2 : 1), + new PairConsumer<SvnChangeList, TreeStructureNode<SVNLogEntry>>() { + public void consume(SvnChangeList svnList, TreeStructureNode<SVNLogEntry> tree) { + indicator.setText2(SvnBundle.message("progress.text2.processing.revision", svnList.getNumber())); + list.add(new Pair<SvnChangeList, TreeStructureNode<SVNLogEntry>>(svnList, tree)); + } + }); + } catch (VcsException e) { + context.handleException(e, true); + return; + } + myCommittedChangeLists = new ArrayList<CommittedChangeList>(); + for (Pair<SvnChangeList, TreeStructureNode<SVNLogEntry>> pair : list) { + // do not take first since it's equal + if (myFirst > 0 && myFirst == pair.getFirst().getNumber()) continue; + if (! QuickMerge.checkListForPaths(relativeLocal, relativeBranch, pair)) { + myCommittedChangeLists.add(pair.getFirst()); + } + } + + try { + myHelper = new OneShotMergeInfoHelper(myVcs.getProject(), myWcInfo, mySourceUrl); + ProgressManager.progress2("Calculating not merged revisions"); + myHelper.prepare(); + } + catch (SVNException e) { + context.handleException(new VcsException(e), true); + } + myLastLoaded = myCommittedChangeLists.size() < myBunchSize + 1; + if (myCommittedChangeLists.size() > myBunchSize){ + myCommittedChangeLists = myCommittedChangeLists.subList(0, myBunchSize); + } + } + + public OneShotMergeInfoHelper getHelper() { + return myHelper; + } + + public List<CommittedChangeList> getCommittedChangeLists() { + return myCommittedChangeLists; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/LocalChangesAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/LocalChangesAction.java new file mode 100644 index 000000000000..2520d7af2b6e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/LocalChangesAction.java @@ -0,0 +1,29 @@ +/* + * 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.dialogs; + +/** +* Created with IntelliJ IDEA. +* User: Irina.Chernushina +* Date: 3/27/13 +* Time: 12:37 PM +*/ +public enum LocalChangesAction { + cancel, + continueMerge, + shelve, + inspect +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/MergeDialogI.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/MergeDialogI.java new file mode 100644 index 000000000000..83e514aabc80 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/MergeDialogI.java @@ -0,0 +1,34 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; + +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 3/30/13 + * Time: 8:11 PM + */ +public interface MergeDialogI { + void setEverythingLoaded(boolean everythingLoaded); + + long getLastNumber(); + + void addMoreLists(List<CommittedChangeList> list); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/QuickMerge.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/QuickMerge.java index fa3428d2afd2..01e5a1d0342a 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/QuickMerge.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/QuickMerge.java @@ -21,18 +21,15 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.RunBackgroundable; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.MessageType; -import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.ui.popup.util.PopupUtil; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.changes.*; import com.intellij.openapi.vcs.changes.shelf.ShelveChangesManager; -import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings; import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; import com.intellij.openapi.vfs.VirtualFile; @@ -78,6 +75,7 @@ public class QuickMerge { private SvnVcs myVcs; private final String myTitle; private final Continuation myContinuation; + private QuickMergeInteraction myInteraction; private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.dialogs.QuickMerge"); public QuickMerge(Project project, String sourceUrl, WCInfo wcInfo, final String branchName, final VirtualFile root) { @@ -110,10 +108,6 @@ public class QuickMerge { } } - private boolean prompt(final String question) { - return Messages.showOkCancelDialog(myProject, question, myTitle, Messages.getQuestionIcon()) == 0; - } - private class MyInitChecks extends TaskDescriptor { private MyInitChecks() { super("initial checks", Where.AWT); @@ -157,17 +151,104 @@ public class QuickMerge { } @CalledInAwt - public void execute() { + public void execute(@NotNull final QuickMergeInteraction interaction, @NotNull final TaskDescriptor... finalTasks) { + myInteraction = interaction; + myInteraction.setTitle(myTitle); + FileDocumentManager.getInstance().saveAllDocuments(); final List<TaskDescriptor> tasks = new LinkedList<TaskDescriptor>(); tasks.add(new MyInitChecks()); tasks.add(new SourceUrlCorrection()); tasks.add(new CheckRepositorySupportsMergeinfo()); + if (finalTasks.length > 0) { + tasks.addAll(Arrays.asList(finalTasks)); + } + myContinuation.addExceptionHandler(VcsException.class, new Consumer<VcsException>() { + @Override + public void consume(VcsException e) { + myInteraction.showErrors(myTitle, Collections.singletonList(e)); + } + }); + myContinuation.addExceptionHandler(SVNException.class, new Consumer<SVNException>() { + @Override + public void consume(SVNException e) { + myInteraction.showErrors(myTitle, Collections.singletonList(new VcsException(e))); + } + }); + myContinuation.addExceptionHandler(RuntimeException.class, new Consumer<RuntimeException>() { + @Override + public void consume(RuntimeException e) { + myInteraction.showError(e); + } + }); myContinuation.run(tasks); } + private class ShowRecentInDialog extends TaskDescriptor { + private final LoadRecentBranchRevisions myLoader; + + private ShowRecentInDialog(LoadRecentBranchRevisions loader) { + super("", Where.AWT); + myLoader = loader; + } + + @Override + public void run(ContinuationContext context) { + final PairConsumer<Long, MergeDialogI> loader = new PairConsumer<Long, MergeDialogI>() { + @Override + public void consume(Long bunchSize, final MergeDialogI dialog) { + final LoadRecentBranchRevisions loader = + new LoadRecentBranchRevisions(myBranchName, dialog.getLastNumber(), myWcInfo, myVcs, mySourceUrl); + loader.setBunchSize(bunchSize.intValue()); + final TaskDescriptor updater = new TaskDescriptor("", Where.AWT) { + @Override + public void run(ContinuationContext context) { + dialog.addMoreLists(loader.getCommittedChangeLists()); + if (loader.isLastLoaded()) { + dialog.setEverythingLoaded(true); + } + } + + @Override + public void canceled() { + dialog.addMoreLists(Collections.<CommittedChangeList>emptyList()); + dialog.setEverythingLoaded(true); + } + }; + final Continuation fragmented = Continuation.createFragmented(myProject, true); + fragmented.addExceptionHandler(VcsException.class, new Consumer<VcsException>() { + @Override + public void consume(VcsException e) { + PopupUtil.showBalloonForActiveComponent(e.getMessage() == null ? e.getClass().getName() : e.getMessage(), MessageType.ERROR); + } + }); + fragmented.run(loader, updater); + } + }; + final List<CommittedChangeList> lists = myInteraction.showRecentListsForSelection(myLoader.getCommittedChangeLists(), + myTitle, myLoader.getHelper(), loader, myLoader.isLastLoaded()); + + if (lists != null && ! lists.isEmpty()){ + final MergerFactory factory = new ChangeListsMergerFactory(lists) { + @Override + public IMerger createMerger(SvnVcs vcs, File target, UpdateEventHandler handler, SVNURL currentBranchUrl, String branchName) { + return new GroupMerger(vcs, lists, target, handler, currentBranchUrl, branchName, false, false, false); + } + }; + // fictive branch point, just for + final SvnBranchPointsCalculator.BranchCopyData copyData = + new SvnBranchPointsCalculator.BranchCopyData(myWcInfo.getUrl().toString(), -1, mySourceUrl, -1); + context.next(new LocalChangesPrompt(false, lists, + new SvnBranchPointsCalculator.WrapperInvertor<SvnBranchPointsCalculator.BranchCopyData>(false, copyData)), + new MergeTask(factory, myTitle)); + } else { + context.cancelEverything(); + } + } + } + private class MergeAllOrSelectedChooser extends TaskDescriptor { private MergeAllOrSelectedChooser() { super("merge source selector", Where.AWT); @@ -175,13 +256,18 @@ public class QuickMerge { @Override public void run(final ContinuationContext context) { - final int result = Messages.showYesNoCancelDialog(myProject, "Merge all?", myTitle, - "Merge &all", "&Select revisions to merge", "Cancel", Messages.getQuestionIcon()); - if (result == 2) return; - if (result == 0) { + final QuickMergeContentsVariants variant = myInteraction.selectMergeVariant(); + if (QuickMergeContentsVariants.cancel == variant) return; + if (QuickMergeContentsVariants.all == variant) { insertMergeAll(context); return; } + if (QuickMergeContentsVariants.showLatest == variant) { + final LoadRecentBranchRevisions loader = new LoadRecentBranchRevisions(myBranchName, -1, myWcInfo, myVcs, mySourceUrl); + final ShowRecentInDialog dialog = new ShowRecentInDialog(loader); + context.next(loader, dialog); + return; + } final MergeCalculator calculator; try { @@ -197,7 +283,7 @@ public class QuickMerge { } private void insertMergeAll(final ContinuationContext context) { - final List<TaskDescriptor> queue = new LinkedList<TaskDescriptor>(); + final List<TaskDescriptor> queue = new ArrayList<TaskDescriptor>(); insertMergeAll(queue); context.next(queue); } @@ -213,7 +299,7 @@ public class QuickMerge { } } if (switchedFound) { - return prompt("There are some switched paths in the working copy. Do you want to continue?"); + return myInteraction.shouldContinueSwitchedRootFound(); } return true; } @@ -229,20 +315,19 @@ public class QuickMerge { context.next(new TaskDescriptor(message, Where.AWT) { @Override public void run(ContinuationContext context) { - AbstractVcsHelper.getInstance(myProject).showErrors(exceptions, message); + myInteraction.showErrors(message, exceptions); } }); } - // todo can be a very base class! @CalledInAny private void finishWithError(final ContinuationContext context, final String message, final boolean isError) { LOG.info((isError ? "Error: " : "Info: ") + message); - context.cancelEverything(); context.next(new TaskDescriptor(message, Where.AWT) { @Override public void run(ContinuationContext context) { - VcsBalloonProblemNotifier.showOverChangesView(myProject, message, isError ? MessageType.ERROR : MessageType.WARNING); + myInteraction.showErrors(message, isError); + context.cancelEverything(); } }); } @@ -288,10 +373,7 @@ public class QuickMerge { return; } final boolean reintegrate = invertor.isInvertedSense(); - if (reintegrate && (! prompt("<html><body>You are going to reintegrate changes.<br><br>This will make branch '" + mySourceUrl + - "' <b>no longer usable for further work</b>." + - "<br>It will not be able to correctly absorb new trunk (" + invertor.inverted().getTarget() + - ") changes,<br>nor can this branch be properly reintegrated to trunk again.<br><br>Are you sure?</body></html>"))) { + if (reintegrate && (! myInteraction.shouldReintegrate(mySourceUrl, invertor.inverted().getTarget()))) { context.cancelEverything(); return; } @@ -338,14 +420,12 @@ public class QuickMerge { return; } - context.next(new TaskDescriptor(getName(), Where.POOLED) { - @Override - public void run(ContinuationContext context) { - final SvnIntegrateChangesTask task = new SvnIntegrateChangesTask(SvnVcs.getInstance(myProject), - new WorkingCopyInfo(myWcInfo.getPath(), true), myFactory, sourceUrlUrl, getName(), false, myBranchName); - RunBackgroundable.run(task); - } - }); + final SvnIntegrateChangesTask task = new SvnIntegrateChangesTask(SvnVcs.getInstance(myProject), + new WorkingCopyInfo(myWcInfo.getPath(), true), myFactory, sourceUrlUrl, getName(), false, myBranchName); + final TaskDescriptor taskDescriptor = TaskDescriptor.createForBackgroundableTask(task); + // merge task will be the next after... + context.next(taskDescriptor); + // ... after we create changelist createChangelist(context); } @@ -383,6 +463,56 @@ public class QuickMerge { } } + // true if errors found + static boolean checkListForPaths(String relativeLocal, + String relativeBranch, Pair<SvnChangeList, TreeStructureNode<SVNLogEntry>> pair) { + final List<TreeStructureNode<SVNLogEntry>> children = pair.getSecond().getChildren(); + boolean localChange = false; + for (TreeStructureNode<SVNLogEntry> child : children) { + if (checkForSubtree(child, relativeLocal, relativeBranch)) { + localChange = true; + break; + } + } + if (! localChange) { + // check self + return checkForEntry(pair.getSecond().getMe(), relativeLocal, relativeBranch); + } + return localChange; + } + + // true if errors found + private static boolean checkForSubtree(final TreeStructureNode<SVNLogEntry> tree, + String relativeBranch, final String localURL) { + final LinkedList<TreeStructureNode<SVNLogEntry>> queue = new LinkedList<TreeStructureNode<SVNLogEntry>>(); + queue.addLast(tree); + + while (! queue.isEmpty()) { + final TreeStructureNode<SVNLogEntry> element = queue.removeFirst(); + ProgressManager.checkCanceled(); + + if (checkForEntry(element.getMe(), localURL, relativeBranch)) return true; + queue.addAll(element.getChildren()); + } + return false; + } + + // true if errors found + private static boolean checkForEntry(final SVNLogEntry entry, final String localURL, String relativeBranch) { + boolean atLeastOneUnderBranch = false; + final Map map = entry.getChangedPaths(); + for (Object o : map.values()) { + final SVNLogEntryPath path = (SVNLogEntryPath) o; + if (SVNPathUtil.isAncestor(localURL, path.getPath())) { + return true; + } + if (! atLeastOneUnderBranch && SVNPathUtil.isAncestor(relativeBranch, path.getPath())) { + atLeastOneUnderBranch = true; + } + } + return ! atLeastOneUnderBranch; + } + private class MergeCalculator extends TaskDescriptor implements Consumer<TransparentlyFailedValueI<SvnBranchPointsCalculator.WrapperInvertor<SvnBranchPointsCalculator.BranchCopyData>, SVNException>> { private final static String ourOneShotStrategy = "svn.quickmerge.oneShotStrategy"; @@ -450,6 +580,8 @@ public class QuickMerge { String local = SVNPathUtil.getRelativePath(myWcInfo.getRepositoryRoot(), myWcInfo.getRootUrl()); final String relativeLocal = (local.startsWith("/") ? local : "/" + local); + String relativeBranch = SVNPathUtil.getRelativePath(myWcInfo.getRepositoryRoot(), mySourceUrl); + relativeBranch = (relativeBranch.startsWith("/") ? relativeBranch : "/" + relativeBranch); final LinkedList<Pair<SvnChangeList, TreeStructureNode<SVNLogEntry>>> list = new LinkedList<Pair<SvnChangeList, TreeStructureNode<SVNLogEntry>>>(); @@ -476,14 +608,7 @@ public class QuickMerge { if (SvnMergeInfoCache.MergeCheckResult.NOT_MERGED.equals(checkResult)) { // additionally check for being 'local' - final List<TreeStructureNode<SVNLogEntry>> children = pair.getSecond().getChildren(); - boolean localChange = false; - for (TreeStructureNode<SVNLogEntry> child : children) { - if (isLocalRevisionMergeIteration(child, relativeLocal, indicator)) { - localChange = true; - break; - } - } + boolean localChange = checkListForPaths(relativeLocal, relativeBranch, pair); if (! localChange) { myNotMerged.add(svnList); @@ -498,29 +623,6 @@ public class QuickMerge { context.next(new ShowRevisionSelector(copyDataValue)); } - private boolean isLocalRevisionMergeIteration(final TreeStructureNode<SVNLogEntry> tree, - final String localURL, - ProgressIndicator indicator) { - final LinkedList<TreeStructureNode<SVNLogEntry>> queue = new LinkedList<TreeStructureNode<SVNLogEntry>>(); - queue.addLast(tree); - - while (! queue.isEmpty()) { - final TreeStructureNode<SVNLogEntry> element = queue.removeFirst(); - indicator.checkCanceled(); - - final Map map = element.getMe().getChangedPaths(); - for (Object o : map.values()) { - final SVNLogEntryPath path = (SVNLogEntryPath) o; - if (SVNPathUtil.isAncestor(localURL, path.getPath())) { - return true; - } - break; // do not check all. first should match or fail - } - queue.addAll(element.getChildren()); - } - return false; - } - private class ShowRevisionSelector extends TaskDescriptor { private final SvnBranchPointsCalculator.WrapperInvertor<SvnBranchPointsCalculator.BranchCopyData> myCopyPoint; @@ -531,16 +633,15 @@ public class QuickMerge { @Override public void run(ContinuationContext context) { - final ToBeMergedDialog dialog = new ToBeMergedDialog(myProject, myNotMerged, myMergeTitle, myMergeChecker); - dialog.show(); - if (dialog.getExitCode() == DialogWrapper.CANCEL_EXIT_CODE) { + final QuickMergeInteraction.SelectMergeItemsResult result = myInteraction.selectMergeItems(myNotMerged, myMergeTitle, myMergeChecker); + if (QuickMergeContentsVariants.cancel == result.getResultCode()) { context.cancelEverything(); return; } - if (dialog.getExitCode() == ToBeMergedDialog.MERGE_ALL_CODE) { + if (QuickMergeContentsVariants.all == result.getResultCode()) { insertMergeAll(context); } else { - final List<CommittedChangeList> lists = dialog.getSelected(); + final List<CommittedChangeList> lists = result.getSelectedLists(); if (lists.isEmpty()) return; final MergerFactory factory = new ChangeListsMergerFactory(lists) { @Override @@ -582,35 +683,18 @@ public class QuickMerge { @Override public void run(ContinuationContext context) { - final String message; final Intersection intersection; final ChangeListManager listManager = ChangeListManager.getInstance(myProject); final List<LocalChangeList> localChangeLists = listManager.getChangeListsCopy(); if (myMergeAll) { intersection = getMergeAllIntersection(localChangeLists); - message = "There are local changes that can potentially intersect with merge changes.\nDo you want to continue?"; } else { intersection = checkIntersection(myLists, localChangeLists); - message = "There are local changes that will intersect with merge changes.\nDo you want to continue?"; } if (intersection == null || intersection.getChangesSubset().isEmpty()) return; - final LocalChangesAction action; - if (! myMergeAll) { - final LocalChangesAction[] possibleResults = {LocalChangesAction.shelve, LocalChangesAction.inspect, - LocalChangesAction.continueMerge, LocalChangesAction.cancel}; - final int result = Messages.showDialog(message, myTitle, - new String[]{"Shelve local changes", "Inspect changes", "Continue merge", "Cancel"}, - 0, Messages.getQuestionIcon()); - action = possibleResults[result]; - } else { - final LocalChangesAction[] possibleResults = {LocalChangesAction.shelve, LocalChangesAction.continueMerge, LocalChangesAction.cancel}; - final int result = Messages.showDialog(message, myTitle, - new String[]{"Shelve local changes", "Continue merge", "Cancel"}, - 0, Messages.getQuestionIcon()); - action = possibleResults[result]; - } + final LocalChangesAction action = myInteraction.selectLocalChangesAction(myMergeAll); switch (action) { // shelve case shelve: @@ -625,12 +709,11 @@ public class QuickMerge { return; // inspect case inspect: - final Collection<Change> changes = (Collection<Change>) intersection.getChangesSubset().values(); + // here's cast is due to generic's bug + @SuppressWarnings("unchecked") final Collection<Change> changes = (Collection<Change>) intersection.getChangesSubset().values(); final List<FilePath> paths = ChangesUtil.getPaths(changes); Collections.sort(paths, FilePathByPathComparator.getInstance()); - // todo rework message - IntersectingLocalChangesPanel.showInVersionControlToolWindow(myProject, myTitle + ", local changes intersection", - paths, "The following file(s) have local changes that will intersect with merge changes:"); + myInteraction.showIntersectedLocalPaths(paths); context.cancelEverything(); return; default: @@ -673,13 +756,6 @@ public class QuickMerge { } } - private enum LocalChangesAction { - cancel, - continueMerge, - shelve, - inspect - } - private class ShelveLocalChanges extends TaskDescriptor { private final Intersection myIntersection; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/QuickMergeContentsVariants.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/QuickMergeContentsVariants.java new file mode 100644 index 000000000000..234126806081 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/QuickMergeContentsVariants.java @@ -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.dialogs; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 3/27/13 + * Time: 7:53 PM + */ +public enum QuickMergeContentsVariants { + all, select, cancel, showLatest +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SSLCredentialsDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SSLCredentialsDialog.java index 44e503fe08d2..ac8dc9760cc7 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SSLCredentialsDialog.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SSLCredentialsDialog.java @@ -19,6 +19,7 @@ import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.svn.SvnBundle; @@ -103,7 +104,7 @@ public class SSLCredentialsDialog extends DialogWrapper { @Override public JComponent getPreferredFocusedComponent() { - return myCertificatePath; + return StringUtil.isEmptyOrSpaces(myCertificatePath.getText()) ? myCertificatePath : myCertificatePassword; } public String getCertificatePath() { @@ -126,4 +127,8 @@ public class SSLCredentialsDialog extends DialogWrapper { protected JComponent createCenterPanel() { return null; } + + public void setFile(@NotNull String file) { + myCertificatePath.setText(file); + } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SelectLocationDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SelectLocationDialog.java index 8e0e4218d748..66ada39bd7b4 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SelectLocationDialog.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SelectLocationDialog.java @@ -61,6 +61,11 @@ public class SelectLocationDialog extends DialogWrapper { try { SVNURL.parseURIEncoded(url); final SVNURL svnurl = initRoot(project, url); + if (svnurl == null) { + Messages.showErrorDialog(project, "Can not detect repository root for URL: " + url, + SvnBundle.message("dialog.title.select.repository.location")); + return null; + } SelectLocationDialog dialog = new SelectLocationDialog(project, svnurl, null, null, true); dialog.show(); if (!dialog.isOK()) return null; @@ -116,6 +121,7 @@ public class SelectLocationDialog extends DialogWrapper { return "svn.repositoryBrowser"; } + @Nullable private static SVNURL initRoot(final Project project, final String urlString) throws SVNException { final Ref<SVNURL> result = new Ref<SVNURL>(); final Ref<SVNException> excRef = new Ref<SVNException>(); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnInteractiveAuthenticationProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnInteractiveAuthenticationProvider.java index 9caa86c08a2e..37b4f9d0448a 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnInteractiveAuthenticationProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnInteractiveAuthenticationProvider.java @@ -21,6 +21,7 @@ import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; import com.intellij.util.SystemProperties; import com.intellij.util.WaitForProgressToShow; @@ -32,6 +33,7 @@ import org.jetbrains.idea.svn.auth.ProviderType; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.auth.*; +import org.tmatesoft.svn.core.internal.wc.ISVNHostOptions; import javax.swing.*; import java.io.File; @@ -64,7 +66,7 @@ public class SvnInteractiveAuthenticationProvider implements ISVNAuthenticationP public SVNAuthentication requestClientAuthentication(final String kind, final SVNURL url, final String realm, - SVNErrorMessage errorMessage, + final SVNErrorMessage errorMessage, final SVNAuthentication previousAuth, final boolean authMayBeStored) { final MyCallState callState = new MyCallState(true, false); @@ -85,7 +87,7 @@ public class SvnInteractiveAuthenticationProvider implements ISVNAuthenticationP public void run() { SimpleCredentialsDialog dialog = new SimpleCredentialsDialog(myProject); dialog.setup(realm, userName, authCredsOn); - if (previousAuth == null) { + if (errorMessage == null) { dialog.setTitle(SvnBundle.message("dialog.title.authentication.required")); } else { @@ -106,7 +108,7 @@ public class SvnInteractiveAuthenticationProvider implements ISVNAuthenticationP public void run() { UserNameCredentialsDialog dialog = new UserNameCredentialsDialog(myProject); dialog.setup(realm, userName, authCredsOn); - if (previousAuth == null) { + if (errorMessage == null) { dialog.setTitle(SvnBundle.message("dialog.title.authentication.required")); } else { @@ -123,7 +125,7 @@ public class SvnInteractiveAuthenticationProvider implements ISVNAuthenticationP command = new Runnable() { public void run() { SSHCredentialsDialog dialog = new SSHCredentialsDialog(myProject, realm, userName, authCredsOn, url.getPort()); - if (previousAuth == null) { + if (errorMessage == null) { dialog.setTitle(SvnBundle.message("dialog.title.authentication.required")); } else { @@ -150,8 +152,13 @@ public class SvnInteractiveAuthenticationProvider implements ISVNAuthenticationP } else if (ISVNAuthenticationManager.SSL.equals(kind)) { command = new Runnable() { public void run() { + final ISVNHostOptions options = myManager.getHostOptionsProvider().getHostOptions(url); + final String file = options.getSSLClientCertFile(); final SSLCredentialsDialog dialog = new SSLCredentialsDialog(myProject, realm, authCredsOn); - if (previousAuth == null) { + if (!StringUtil.isEmptyOrSpaces(file)) { + dialog.setFile(file); + } + if (errorMessage == null) { dialog.setTitle(SvnBundle.message("dialog.title.authentication.required")); } else { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/ToBeMergedDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/ToBeMergedDialog.java index 91d8bdb4a422..97420a765ce0 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/ToBeMergedDialog.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/ToBeMergedDialog.java @@ -18,6 +18,7 @@ package org.jetbrains.idea.svn.dialogs; import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonShortcuts; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; @@ -37,13 +38,19 @@ import com.intellij.openapi.vcs.changes.ui.ChangesBrowserNodeRenderer; import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; import com.intellij.ui.*; import com.intellij.ui.table.TableView; +import com.intellij.util.Alarm; +import com.intellij.util.PairConsumer; import com.intellij.util.containers.Convertor; import com.intellij.util.ui.ColumnInfo; import com.intellij.util.ui.ListTableModel; import com.intellij.util.ui.UIUtil; +import com.intellij.vcsUtil.MoreAction; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.history.SvnChangeList; +import org.jetbrains.idea.svn.mergeinfo.ListMergeStatus; import org.jetbrains.idea.svn.mergeinfo.MergeChecker; +import org.jetbrains.idea.svn.mergeinfo.SvnMergeInfoCache; import javax.swing.*; import javax.swing.event.ListSelectionEvent; @@ -57,11 +64,12 @@ import java.awt.event.MouseEvent; import java.util.*; import java.util.List; -public class ToBeMergedDialog extends DialogWrapper { +public class ToBeMergedDialog extends DialogWrapper implements MergeDialogI { public static final int MERGE_ALL_CODE = 222; private final JPanel myPanel; private final Project myProject; private final PageEngine<List<CommittedChangeList>> myListsEngine; + private final Alarm myAlarm; private TableView<CommittedChangeList> myRevisionsList; private RepositoryChangesBrowser myRepositoryChangesBrowser; private Splitter mySplitter; @@ -69,11 +77,28 @@ public class ToBeMergedDialog extends DialogWrapper { private final QuantitySelection<Long> myWiseSelection; private final Set<Change> myAlreadyMerged; + private final List<CommittedChangeList> myLists; + private final PairConsumer<Long, MergeDialogI> myMoreLoader; private final MergeChecker myMergeChecker; - - public ToBeMergedDialog(final Project project, final List<CommittedChangeList> lists, final String title, final MergeChecker mergeChecker) { + private final boolean myAlreadyCalculatedState; + private volatile boolean myEverythingLoaded; + + private final Map<Long, ListMergeStatus> myStatusMap; + private ToBeMergedDialog.MoreXAction myMore100Action; + private ToBeMergedDialog.MoreXAction myMore500Action; + + public ToBeMergedDialog(final Project project, + final List<CommittedChangeList> lists, + final String title, + final MergeChecker mergeChecker, + final PairConsumer<Long, MergeDialogI> moreLoader) { super(project, true); + myLists = lists; + myMoreLoader = moreLoader; + myEverythingLoaded = moreLoader == null; + myStatusMap = Collections.synchronizedMap(new HashMap<Long, ListMergeStatus>()); myMergeChecker = mergeChecker; + myAlreadyCalculatedState = moreLoader == null; setTitle(title); myProject = project; @@ -82,22 +107,99 @@ public class ToBeMergedDialog extends DialogWrapper { myListsEngine = new BasePageEngine<CommittedChangeList>(lists, lists.size()); myPanel = new JPanel(new BorderLayout()); - myWiseSelection = new QuantitySelection<Long>(true); + myWiseSelection = new QuantitySelection<Long>(myEverythingLoaded); myAlreadyMerged = new HashSet<Change>(); setOKButtonText("Merge Selected"); initUI(); init(); + enableMore(); + + myAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, getDisposable()); + if (! myAlreadyCalculatedState) { + refreshListStatus(); + } + } + + private void enableMore() { + myMore100Action.setVisible(!myEverythingLoaded); + myMore500Action.setVisible(!myEverythingLoaded); + myMore100Action.setEnabled(!myEverythingLoaded); + myMore500Action.setEnabled(! myEverythingLoaded); } - @NotNull @Override - protected Action[] createActions() { - return new Action[]{getOKAction(), new DialogWrapperAction("Merge All") { + public void setEverythingLoaded(boolean everythingLoaded) { + myEverythingLoaded = everythingLoaded; + myMore100Action.setVisible(false); + myMore500Action.setVisible(false); + } + + @Override + public long getLastNumber() { + return myLists.get(myLists.size() - 1).getNumber(); + } + + @Override + public void addMoreLists(final List<CommittedChangeList> list) { + myListsEngine.getCurrent().addAll(list); + myRevisionsList.revalidate(); + myRevisionsList.repaint(); + myMore100Action.setEnabled(true); + myMore500Action.setEnabled(true); + myMore500Action.setVisible(true); + refreshListStatus(); + } + + public void refreshListStatus() { + if (myAlarm.isDisposed()) return; + myAlarm.addRequest(new Runnable() { @Override - protected void doAction(ActionEvent e) { - close(MERGE_ALL_CODE); + public void run() { + int cnt = 10; + for (CommittedChangeList list : myLists) { + final SvnMergeInfoCache.MergeCheckResult result = myMergeChecker.checkList((SvnChangeList)list); + // at the moment we calculate only "merged" since we don;t have branch copy point + if (SvnMergeInfoCache.MergeCheckResult.MERGED.equals(result)) { + myStatusMap.put(list.getNumber(), ListMergeStatus.MERGED); + } else if (SvnMergeInfoCache.MergeCheckResult.NOT_EXISTS.equals(result)) { + myStatusMap.put(list.getNumber(), ListMergeStatus.ALIEN); + } else if (SvnMergeInfoCache.MergeCheckResult.NOT_EXISTS_PARTLY_MERGED.equals(result)) { + myStatusMap.put(list.getNumber(), ListMergeStatus.NOT_MERGED); + } else { + myStatusMap.put(list.getNumber(), ListMergeStatus.REFRESHING); + } + + -- cnt; + if (cnt <= 0) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + myRevisionsList.revalidate(); + myRevisionsList.repaint(); + } + }); + cnt = 10; + } + } + myRevisionsList.revalidate(); + myRevisionsList.repaint(); } - }, getCancelAction()}; + }, 0); + } + + @NotNull + @Override + protected Action[] createActions() { + if (myAlreadyCalculatedState) { + return new Action[]{getOKAction(), new DialogWrapperAction("Merge All") { + @Override + protected void doAction(ActionEvent e) { + close(MERGE_ALL_CODE); + } + }, getCancelAction()}; + } else { + return super.createActions(); + } } public List<CommittedChangeList> getSelected() { @@ -229,8 +331,11 @@ public class ToBeMergedDialog extends DialogWrapper { myRevisionsList.repaint(); } }; + myMore100Action = new MoreXAction(100); + myMore500Action = new MoreXAction(500); final PagedListWithActions<CommittedChangeList> byRevisions = - new PagedListWithActions<CommittedChangeList>(myListsEngine, listsManager, new MySelectAll(), new MyUnselectAll()); + new PagedListWithActions<CommittedChangeList>(myListsEngine, listsManager, new MySelectAll(), new MyUnselectAll(), + myMore100Action, myMore500Action); mySplitter = new Splitter(false, 0.7f); mySplitter.setFirstComponent(byRevisions.getComponent()); @@ -317,6 +422,23 @@ public class ToBeMergedDialog extends DialogWrapper { return myPanel; } + private class MoreXAction extends MoreAction { + private final int myQuantity; + + private MoreXAction(final int quantity) { + super("Load +" + quantity); + myQuantity = quantity; + } + + @Override + public void actionPerformed(AnActionEvent e) { + myMore500Action.setVisible(false); + myMore100Action.setEnabled(false); + myMore500Action.setEnabled(false); + myMoreLoader.consume(Long.valueOf(myQuantity), ToBeMergedDialog.this); + } + } + private class MySelectAll extends DumbAwareAction { private MySelectAll() { super("Select All", "Select All", AllIcons.Actions.Selectall); @@ -351,7 +473,18 @@ public class ToBeMergedDialog extends DialogWrapper { myCheckBox = new JCheckBox(); myCheckBox.setEnabled(true); myCheckBox.setSelected(true); - myRenderer = new CommittedChangeListRenderer(myProject, Collections.<CommittedChangeListDecorator>emptyList()); + myRenderer = new CommittedChangeListRenderer(myProject, Collections.<CommittedChangeListDecorator>singletonList(new CommittedChangeListDecorator() { + @Nullable + @Override + public Icon decorate(CommittedChangeList list) { + if (myAlreadyCalculatedState) return ListMergeStatus.NOT_MERGED.getIcon(); + final ListMergeStatus status = myStatusMap.get(list.getNumber()); + if (status != null) { + return status.getIcon(); + } + return ListMergeStatus.REFRESHING.getIcon(); + } + })); } protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/UpgradeFormatDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/UpgradeFormatDialog.java index a174c59f60d1..f9bf6bda943d 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/UpgradeFormatDialog.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/UpgradeFormatDialog.java @@ -31,9 +31,6 @@ import java.awt.*; import java.io.File; public class UpgradeFormatDialog extends DialogWrapper { - private JRadioButton myUpgradeNoneButton; - private JRadioButton myUpgradeAutoButton; - private JRadioButton myUpgradeAuto15Button; private JRadioButton myUpgradeAuto16Button; private JRadioButton myUpgradeAuto17Button; @@ -64,21 +61,11 @@ public class UpgradeFormatDialog extends DialogWrapper { return "svn.upgradeDialog"; } - public void setData(final boolean display13format, final String selectedFormat) { + public void setData(final String selectedFormat) { if (SvnConfiguration.UPGRADE_AUTO_17.equals(selectedFormat)) { myUpgradeAuto17Button.setSelected(true); - } else if (SvnConfiguration.UPGRADE_AUTO_16.equals(selectedFormat)) { - myUpgradeAuto16Button.setSelected(true); - } else if (SvnConfiguration.UPGRADE_AUTO.equals(selectedFormat)) { - myUpgradeAutoButton.setSelected(true); - } else if (SvnConfiguration.UPGRADE_AUTO_15.equals(selectedFormat)) { - myUpgradeAuto15Button.setSelected(true); } else { - myUpgradeNoneButton.setSelected(true); - } - myUpgradeNoneButton.setVisible(display13format); - if (myUpgradeNoneButton.isSelected() && (! display13format)) { - myUpgradeAutoButton.setSelected(true); + myUpgradeAuto16Button.setSelected(true); } } @@ -114,31 +101,18 @@ public class UpgradeFormatDialog extends DialogWrapper { panel.add(topLabel, gb); gb.gridy += 1; - myUpgradeNoneButton = new JRadioButton(SvnBundle.message("radio.configure." + label + ".none")); - myUpgradeAutoButton = new JRadioButton(SvnBundle.message("radio.configure." + label + ".auto")); - myUpgradeAuto15Button = new JRadioButton(SvnBundle.message("radio.configure." + label + ".auto.15format")); + myUpgradeAuto16Button = new JRadioButton(SvnBundle.message("radio.configure." + label + ".auto.16format")); myUpgradeAuto17Button = new JRadioButton(SvnBundle.message("radio.configure." + label + ".auto.17format")); ButtonGroup group = new ButtonGroup(); - group.add(myUpgradeNoneButton); - group.add(myUpgradeAutoButton); - group.add(myUpgradeAuto15Button); group.add(myUpgradeAuto16Button); group.add(myUpgradeAuto17Button); - panel.add(myUpgradeNoneButton, gb); - gb.gridy += 1; - panel.add(myUpgradeAutoButton, gb); - gb.gridy += 1; - panel.add(myUpgradeAuto15Button, gb); - gb.gridy += 1; panel.add(myUpgradeAuto16Button, gb); gb.gridy += 1; panel.add(myUpgradeAuto17Button, gb); gb.gridy += 1; - myUpgradeNoneButton.setSelected(true); - final JPanel auxiliaryPanel = getBottomAuxiliaryPanel(); if (auxiliaryPanel != null) { panel.add(auxiliaryPanel, gb); @@ -165,12 +139,6 @@ public class UpgradeFormatDialog extends DialogWrapper { public String getUpgradeMode() { if (myUpgradeAuto17Button.isSelected()) { return SvnConfiguration.UPGRADE_AUTO_17; - } else if (myUpgradeNoneButton.isSelected()) { - return SvnConfiguration.UPGRADE_NONE; - } else if (myUpgradeAutoButton.isSelected()) { - return SvnConfiguration.UPGRADE_AUTO; - } else if (myUpgradeAuto15Button.isSelected()) { - return SvnConfiguration.UPGRADE_AUTO_15; } else if (myUpgradeAuto16Button.isSelected()) { return SvnConfiguration.UPGRADE_AUTO_16; } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/RootsAndBranches.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/RootsAndBranches.java index b0733e38717e..320c24248516 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/RootsAndBranches.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/RootsAndBranches.java @@ -44,6 +44,7 @@ import org.jetbrains.idea.svn.actions.RecordOnlyMergerFactory; import org.jetbrains.idea.svn.actions.ShowSvnMapAction; import org.jetbrains.idea.svn.dialogs.WCInfoWithBranches; import org.jetbrains.idea.svn.integrate.*; +import org.jetbrains.idea.svn.mergeinfo.ListMergeStatus; import org.jetbrains.idea.svn.mergeinfo.MergeInfoHolder; import org.jetbrains.idea.svn.update.UpdateEventHandler; import org.tmatesoft.svn.core.SVNURL; @@ -199,8 +200,8 @@ public class RootsAndBranches implements CommittedChangeListDecorator { } public Icon decorate(final CommittedChangeList list) { - final MergeInfoHolder.ListMergeStatus status = getStatus(list, false); - return (status == null) ? MergeInfoHolder.ListMergeStatus.ALIEN.getIcon() : status.getIcon(); + final ListMergeStatus status = getStatus(list, false); + return (status == null) ? ListMergeStatus.ALIEN.getIcon() : status.getIcon(); } private void createPanels(final RepositoryLocation location, final Runnable afterRefresh) { @@ -566,16 +567,16 @@ public class RootsAndBranches implements CommittedChangeListDecorator { } private boolean mergeEnabled(final CommittedChangeList list, final boolean forMerge) { - final MergeInfoHolder.ListMergeStatus mergeStatus = getStatus(list, true); - if ((mergeStatus == null) || (MergeInfoHolder.ListMergeStatus.ALIEN.equals(mergeStatus))) { + final ListMergeStatus mergeStatus = getStatus(list, true); + if ((mergeStatus == null) || (ListMergeStatus.ALIEN.equals(mergeStatus))) { return false; - } else if (MergeInfoHolder.ListMergeStatus.REFRESHING.equals(mergeStatus)) { + } else if (ListMergeStatus.REFRESHING.equals(mergeStatus)) { return true; } if (forMerge) { - return MergeInfoHolder.ListMergeStatus.NOT_MERGED.equals(mergeStatus); + return ListMergeStatus.NOT_MERGED.equals(mergeStatus); } - return MergeInfoHolder.ListMergeStatus.MERGED.equals(mergeStatus); + return ListMergeStatus.MERGED.equals(mergeStatus); } private class MarkAsMerged extends AbstractIntegrateChangesAction<SelectedChangeListsChecker> { @@ -782,7 +783,7 @@ public class RootsAndBranches implements CommittedChangeListDecorator { } @Nullable - public MergeInfoHolder.ListMergeStatus getStatus(final CommittedChangeList list, final boolean ignoreEnabled) { + public ListMergeStatus getStatus(final CommittedChangeList list, final boolean ignoreEnabled) { if (! (list instanceof SvnChangeList)) { return null; } @@ -867,14 +868,14 @@ public class RootsAndBranches implements CommittedChangeListDecorator { final List<CommittedChangeList> result = new ArrayList<CommittedChangeList>(); for (CommittedChangeList list : changeLists) { - final MergeInfoHolder.ListMergeStatus status = getStatus(list, true); - if (MergeInfoHolder.ListMergeStatus.REFRESHING.equals(status)) { + final ListMergeStatus status = getStatus(list, true); + if (ListMergeStatus.REFRESHING.equals(status)) { result.add(list); - } else if ((status == null) || MergeInfoHolder.ListMergeStatus.ALIEN.equals(status)) { + } else if ((status == null) || ListMergeStatus.ALIEN.equals(status)) { if (! myFilterAlien.isSelected(null)) { result.add(list); } - } else if (MergeInfoHolder.ListMergeStatus.MERGED.equals(status) || MergeInfoHolder.ListMergeStatus.COMMON.equals(status)) { + } else if (ListMergeStatus.MERGED.equals(status) || ListMergeStatus.COMMON.equals(status)) { if (! myFilterMerged.isSelected(null)) { result.add(list); } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnFileRevision.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnFileRevision.java index bbe4f8a04362..bcb40a034ac9 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnFileRevision.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnFileRevision.java @@ -29,16 +29,12 @@ import com.intellij.openapi.vcs.impl.ContentRevisionCache; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.SvnBundle; import org.jetbrains.idea.svn.SvnRevisionNumber; +import org.jetbrains.idea.svn.SvnUtil; import org.jetbrains.idea.svn.SvnVcs; -import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNLogEntry; -import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.wc.SVNRevision; -import org.tmatesoft.svn.core.wc.SVNWCClient; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.OutputStream; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Date; @@ -143,22 +139,24 @@ public class SvnFileRevision implements VcsFileRevision { } public byte[] loadContent() throws IOException, VcsException { - ByteArrayOutputStream contents = new ByteArrayOutputStream(); - ContentLoader loader = new ContentLoader(myURL, contents, myRevision, myPegRevision); + ContentLoader loader = new ContentLoader(myURL, myRevision, myPegRevision); if (ApplicationManager.getApplication().isDispatchThread() && !myRevision.isLocal()) { - ProgressManager.getInstance().runProcessWithProgressSynchronously(loader, SvnBundle.message("progress.title.loading.file.content"), false, myVCS.getProject()); + ProgressManager.getInstance().runProcessWithProgressSynchronously(loader, SvnBundle.message("progress.title.loading.file.content"), + false, myVCS.getProject()); } else { loader.run(); } if (loader.getException() == null) { - return contents.toByteArray(); + final byte[] contents = loader.getContents(); + ContentRevisionCache.checkContentsSize(myURL, contents.length); + return contents; } else { - final SVNException svnException = loader.getException(); - LOG.info("Failed to load file '" + myURL + "' content at revision: " + myRevision + "\n" + svnException.getMessage(), svnException); - throw new VcsException(svnException); + final VcsException vcsException = loader.getException(); + LOG.info("Failed to load file '" + myURL + "' content at revision: " + myRevision + "\n" + vcsException.getMessage(), vcsException); + throw vcsException; } } @@ -188,31 +186,34 @@ public class SvnFileRevision implements VcsFileRevision { private final SVNRevision myRevision; private final SVNRevision myPegRevision; private final String myURL; - private final OutputStream myDst; - private SVNException myException; + private VcsException myException; + private byte[] myContents; - public ContentLoader(String url, OutputStream dst, SVNRevision revision, SVNRevision pegRevision) { + public ContentLoader(String url, SVNRevision revision, SVNRevision pegRevision) { myURL = url; - myDst = dst; myRevision = revision; myPegRevision = pegRevision; } - public SVNException getException() { + public VcsException getException() { return myException; } + private byte[] getContents() { + return myContents; + } + public void run() { ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); if (progress != null) { progress.setText(SvnBundle.message("progress.text.loading.contents", myURL)); progress.setText2(SvnBundle.message("progress.text2.revision.information", myRevision)); } + try { - SVNWCClient client = myVCS.createWCClient(); - client.doGetFileContents(SVNURL.parseURIEncoded(myURL), myRevision, myRevision, true, myDst); + myContents = SvnUtil.getFileContents(myVCS, myURL, true, myRevision, myPegRevision); } - catch (SVNException e) { + catch (VcsException e) { myException = e; } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java index 9294fbe2ff8e..4a2c8725a898 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java @@ -21,6 +21,7 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsConfiguration; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.annotate.ShowAllAffectedGenericAction; import com.intellij.openapi.vcs.changes.Change; @@ -163,7 +164,8 @@ public class SvnHistoryProvider } public void reportAppendableHistory(FilePath path, final VcsAppendableHistorySessionPartner partner) throws VcsException { - reportAppendableHistory(path, partner, null, null, 0, null, false); + // we need + 1 rows to be reported to further detect that number of rows exceeded the limit + reportAppendableHistory(path, partner, null, null, VcsConfiguration.getInstance(myVcs.getProject()).MAXIMUM_HISTORY_ROWS + 1, null, false); } public void reportAppendableHistory(FilePath path, final VcsAppendableHistorySessionPartner partner, @@ -319,10 +321,11 @@ public class SvnHistoryProvider final SVNRevision pegRevision = myInfo.getRevision(); SVNLogClient client = myVcs.createLogClient(); try { + // a bug noticed when testing: we should pass "limit + 1" to get "limit" rows client .doLog(new File[]{new File(myFile.getIOFile().getAbsolutePath())}, myFrom == null ? SVNRevision.HEAD : myFrom, myTo == null ? SVNRevision.create(1) : myTo, myPeg, - false, true, myShowMergeSources && mySupport15, myLimit, null, + false, true, myShowMergeSources && mySupport15, myLimit + 1, null, new MyLogEntryHandler(myVcs, myUrl, pegRevision, relativeUrl, createConsumerAdapter(myConsumer), repoRootURL, myFile.getCharset())); } catch (SVNCancelException e) { @@ -393,8 +396,9 @@ public class SvnHistoryProvider relativeUrl = myUrl.substring(root.length()); } SVNLogClient client = myVcs.createLogClient(); + // a bug noticed when testing: we should pass "limit + 1" to get "limit" rows client.doLog(svnurl, new String[]{}, myPeg == null ? myFrom : myPeg, - operationalFrom, myTo == null ? SVNRevision.create(1) : myTo, false, true, myShowMergeSources && mySupport15, myLimit, null, + operationalFrom, myTo == null ? SVNRevision.create(1) : myTo, false, true, myShowMergeSources && mySupport15, myLimit + 1, null, new RepositoryLogEntryHandler(myVcs, myUrl, SVNRevision.UNDEFINED, relativeUrl, createConsumerAdapter(myConsumer), rootURL)); } catch (SVNCancelException e) { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryContentRevision.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryContentRevision.java index 459e9ab3a30e..a668a54f953a 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryContentRevision.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryContentRevision.java @@ -112,6 +112,7 @@ public class SvnRepositoryContentRevision implements ContentRevision, MarkerVcsC if (exception != null) { throw new VcsException(exception); } + ContentRevisionCache.checkContentsSize(myPath, buffer.size()); return buffer; } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryLocation.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryLocation.java index 0366c21ea202..3d5bcaf0d92b 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryLocation.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryLocation.java @@ -16,18 +16,12 @@ package org.jetbrains.idea.svn.history; import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.ProjectLevelVcsManager; import com.intellij.openapi.vcs.RepositoryLocation; import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.NotNullFunction; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.RootUrlInfo; -import org.jetbrains.idea.svn.SvnUtil; import org.jetbrains.idea.svn.SvnVcs; -import org.tmatesoft.svn.core.SVNException; -import org.tmatesoft.svn.core.SVNURL; -import org.tmatesoft.svn.core.internal.util.SVNURLUtil; import java.io.File; @@ -68,26 +62,9 @@ public class SvnRepositoryLocation implements RepositoryLocation { @Nullable public static FilePath getLocalPath(final String fullPath, final NotNullFunction<File, Boolean> detector, final SvnVcs vcs) { if (vcs.getProject().isDefault()) return null; - final SVNURL fullPathURL; - try { - fullPathURL = SVNURL.parseURIEncoded(fullPath); - } - catch (SVNException e) { - return null; - } final RootUrlInfo rootForUrl = vcs.getSvnFileUrlMapping().getWcRootForUrl(fullPath); if (rootForUrl != null) { return LocationDetector.filePathByUrlAndPath(fullPath, rootForUrl.getUrl().toString(), rootForUrl.getIoFile().getAbsolutePath(), detector); - } else { - final VirtualFile[] underVcs = ProjectLevelVcsManager.getInstance(vcs.getProject()).getRootsUnderVcs(vcs); - if (underVcs.length == 0) return null; - for (VirtualFile vf : underVcs) { - final File ioFile = new File(vf.getPath()); - final SVNURL url = SvnUtil.getUrl(vcs, ioFile); - if (url != null && SVNURLUtil.isAncestor(url, fullPathURL)) { - return LocationDetector.filePathByUrlAndPath(fullPath, url.toString(), ioFile.getPath(), detector); - } - } } return null; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/QuickMergeInteraction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/QuickMergeInteraction.java new file mode 100644 index 000000000000..def31a2c3e74 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/QuickMergeInteraction.java @@ -0,0 +1,68 @@ +/* + * 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.integrate; + +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.util.PairConsumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.dialogs.LocalChangesAction; +import org.jetbrains.idea.svn.dialogs.MergeDialogI; +import org.jetbrains.idea.svn.dialogs.QuickMergeContentsVariants; +import org.jetbrains.idea.svn.mergeinfo.MergeChecker; + +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 3/26/13 + * Time: 8:29 PM + */ +public interface QuickMergeInteraction { + void setTitle(@NotNull final String title); + /** + * @return {@link com.intellij.openapi.ui.Messages.CANCEL} - cancel, + * {@link com.intellij.openapi.ui.Messages.OK} - merge all, {@link com.intellij.openapi.ui.Messages.NO} - select revisions to merge + */ + QuickMergeContentsVariants selectMergeVariant(); + boolean shouldContinueSwitchedRootFound(); + + boolean shouldReintegrate(@NotNull final String sourceUrl, @NotNull final String targetUrl); + + @NotNull + SelectMergeItemsResult selectMergeItems(final List<CommittedChangeList> lists, final String mergeTitle, final MergeChecker mergeChecker); + + @NotNull + LocalChangesAction selectLocalChangesAction(boolean mergeAll); + + void showIntersectedLocalPaths(final List<FilePath> paths); + + void showError(@NotNull Exception exception); + void showErrors(final String message, final List<VcsException> exceptions); + void showErrors(final String message, final boolean isError); + + List<CommittedChangeList> showRecentListsForSelection(@NotNull List<CommittedChangeList> list, + @NotNull String mergeTitle, + @NotNull MergeChecker mergeChecker, + @NotNull PairConsumer<Long, MergeDialogI> loader, boolean everyThingLoaded); + + interface SelectMergeItemsResult { + QuickMergeContentsVariants getResultCode(); + List<CommittedChangeList> getSelectedLists(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/QuickMergeInteractionImpl.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/QuickMergeInteractionImpl.java new file mode 100644 index 000000000000..93790e91ba66 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/QuickMergeInteractionImpl.java @@ -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.integrate; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogBuilder; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.vcs.AbstractVcsHelper; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.util.PairConsumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.dialogs.*; +import org.jetbrains.idea.svn.mergeinfo.MergeChecker; + +import java.util.Collections; +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 3/27/13 + * Time: 11:40 AM + */ +public class QuickMergeInteractionImpl implements QuickMergeInteraction { + private final Project myProject; + private String myTitle; + + public QuickMergeInteractionImpl(Project project) { + myProject = project; + } + + @Override + public void setTitle(@NotNull String title) { + myTitle = title; + } + + @Override + public QuickMergeContentsVariants selectMergeVariant() { + final QuickMergeWayOptionsPanel panel = new QuickMergeWayOptionsPanel(); + final DialogBuilder builder = new DialogBuilder(myProject); + builder.removeAllActions(); + builder.setTitle("Select Merge Variant"); + builder.setCenterPanel(panel.getMainPanel()); + panel.setWrapper(builder.getDialogWrapper()); + builder.show(); + + return panel.getVariant(); + } + + @Override + public boolean shouldContinueSwitchedRootFound() { + return prompt("There are some switched paths in the working copy. Do you want to continue?"); + } + + @Override + public boolean shouldReintegrate(@NotNull final String sourceUrl, @NotNull final String targetUrl) { + return prompt("<html><body>You are going to reintegrate changes.<br><br>This will make branch '" + sourceUrl + + "' <b>no longer usable for further work</b>." + + "<br>It will not be able to correctly absorb new trunk (" + targetUrl + + ") changes,<br>nor can this branch be properly reintegrated to trunk again.<br><br>Are you sure?</body></html>"); + } + + @NotNull + @Override + public SelectMergeItemsResult selectMergeItems(List<CommittedChangeList> lists, String mergeTitle, MergeChecker mergeChecker) { + final ToBeMergedDialog dialog = new ToBeMergedDialog(myProject, lists, mergeTitle, mergeChecker, null); + dialog.show(); + return new SelectMergeItemsResult() { + @Override + public QuickMergeContentsVariants getResultCode() { + final int code = dialog.getExitCode(); + if (ToBeMergedDialog.MERGE_ALL_CODE == code) { + return QuickMergeContentsVariants.all; + } + return DialogWrapper.OK_EXIT_CODE == code ? QuickMergeContentsVariants.select : QuickMergeContentsVariants.cancel; + } + + @Override + public List<CommittedChangeList> getSelectedLists() { + return dialog.getSelected(); + } + }; + } + + @NotNull + @Override + public LocalChangesAction selectLocalChangesAction(final boolean mergeAll) { + if (! mergeAll) { + final LocalChangesAction[] possibleResults = {LocalChangesAction.shelve, LocalChangesAction.inspect, + LocalChangesAction.continueMerge, LocalChangesAction.cancel}; + final int result = Messages.showDialog("There are local changes that will intersect with merge changes.\nDo you want to continue?", myTitle, + new String[]{"Shelve local changes", "Inspect changes", "Continue merge", "Cancel"}, + 0, Messages.getQuestionIcon()); + return possibleResults[result]; + } else { + final LocalChangesAction[] possibleResults = {LocalChangesAction.shelve, LocalChangesAction.continueMerge, LocalChangesAction.cancel}; + final int result = Messages.showDialog("There are local changes that can potentially intersect with merge changes.\nDo you want to continue?", myTitle, + new String[]{"Shelve local changes", "Continue merge", "Cancel"}, + 0, Messages.getQuestionIcon()); + return possibleResults[result]; + } + } + + @Override + public void showIntersectedLocalPaths(final List<FilePath> paths) { + IntersectingLocalChangesPanel.showInVersionControlToolWindow(myProject, myTitle + ", local changes intersection", + paths, "The following file(s) have local changes that will intersect with merge changes:"); + } + + @Override + public void showError(@NotNull Exception exception) { + AbstractVcsHelper.getInstance(myProject).showErrors(Collections.singletonList(new VcsException(exception)), + exception.getMessage() == null ? exception.getClass().getName() : exception.getMessage()); + } + + @Override + public void showErrors(String message, List<VcsException> exceptions) { + AbstractVcsHelper.getInstance(myProject).showErrors(exceptions, message); + } + + @Override + public void showErrors(String message, boolean isError) { + VcsBalloonProblemNotifier.showOverChangesView(myProject, message, isError ? MessageType.ERROR : MessageType.WARNING); + } + + @Override + public List<CommittedChangeList> showRecentListsForSelection(@NotNull List<CommittedChangeList> list, + @NotNull String mergeTitle, + @NotNull MergeChecker mergeChecker, + @NotNull PairConsumer<Long, MergeDialogI> loader, + boolean everyThingLoaded) { + final ToBeMergedDialog dialog = new ToBeMergedDialog(myProject, list, mergeTitle, mergeChecker, loader); + if (everyThingLoaded) { + dialog.setEverythingLoaded(true); + } + dialog.show(); + if (DialogWrapper.OK_EXIT_CODE == dialog.getExitCode()) { + return dialog.getSelected(); + } + return null; + } + + private boolean prompt(final String question) { + return Messages.showOkCancelDialog(myProject, question, myTitle, Messages.getQuestionIcon()) == 0; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/QuickMergeWayOptionsPanel.form b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/QuickMergeWayOptionsPanel.form new file mode 100644 index 000000000000..a48870b200f0 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/QuickMergeWayOptionsPanel.form @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?> +<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.idea.svn.integrate.QuickMergeWayOptionsPanel"> + <grid id="27dc6" binding="myMainPanel" layout-manager="GridLayoutManager" row-count="8" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <xy x="20" y="20" width="754" height="400"/> + </constraints> + <properties/> + <border type="none"/> + <children> + <component id="3c8e5" class="javax.swing.JButton" binding="myMergeAllButton" default-binding="true"> + <constraints> + <grid row="0" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text value="Merge All"/> + </properties> + </component> + <vspacer id="66429"> + <constraints> + <grid row="7" column="0" row-span="1" col-span="3" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/> + </constraints> + </vspacer> + <hspacer id="54e1"> + <constraints> + <grid row="0" column="3" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/> + </constraints> + </hspacer> + <component id="2abc7" class="javax.swing.JButton" binding="myQuickManualSelectButton" default-binding="true"> + <constraints> + <grid row="2" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text value="Quick Manual Select"/> + </properties> + </component> + <component id="76374" class="javax.swing.JButton" binding="mySelectWithPreFilterButton" default-binding="true"> + <constraints> + <grid row="4" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text value="Select With Pre-Filter"/> + </properties> + </component> + <component id="e43e9" class="javax.swing.JButton" binding="myCancelButton" default-binding="true"> + <constraints> + <grid row="6" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text value="Cancel"/> + </properties> + </component> + <component id="54a5b" class="javax.swing.JLabel" binding="myAllNotMergedRevisionsLabel"> + <constraints> + <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="org/jetbrains/idea/svn/SvnBundle" key="quick.merge.variants.merge.all.explanation"/> + </properties> + </component> + <component id="18321" class="javax.swing.JLabel" binding="myShowsAllRevisionsFromLabel"> + <constraints> + <grid row="3" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="org/jetbrains/idea/svn/SvnBundle" key="quick.merge.variants.quick.select.explanation"/> + </properties> + </component> + <component id="f8a4d" class="javax.swing.JLabel" binding="myFindsWhereOneOfLabel"> + <constraints> + <grid row="5" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="org/jetbrains/idea/svn/SvnBundle" key="quick.merge.variants.pre.select.explanation"/> + </properties> + </component> + <hspacer id="3de54"> + <constraints> + <grid row="1" column="1" row-span="1" col-span="2" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/> + </constraints> + </hspacer> + <hspacer id="4ab9a"> + <constraints> + <grid row="3" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/> + </constraints> + </hspacer> + <hspacer id="a4a34"> + <constraints> + <grid row="5" column="3" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/> + </constraints> + </hspacer> + </children> + </grid> +</form> diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/QuickMergeWayOptionsPanel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/QuickMergeWayOptionsPanel.java new file mode 100644 index 000000000000..430da5256080 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/QuickMergeWayOptionsPanel.java @@ -0,0 +1,85 @@ +/* + * 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.integrate; + +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.MultiLineLabelUI; +import org.jetbrains.idea.svn.dialogs.QuickMergeContentsVariants; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 3/29/13 + * Time: 7:27 PM + */ +public class QuickMergeWayOptionsPanel { + private JButton myMergeAllButton; + private JButton myQuickManualSelectButton; + private JButton mySelectWithPreFilterButton; + private JButton myCancelButton; + private JPanel myMainPanel; + private JLabel myAllNotMergedRevisionsLabel; + private JLabel myShowsAllRevisionsFromLabel; + private JLabel myFindsWhereOneOfLabel; + private DialogWrapper myWrapper; + + private QuickMergeContentsVariants myVariant = QuickMergeContentsVariants.cancel; + + public QuickMergeWayOptionsPanel() { + myMergeAllButton.addActionListener(setCodeAndClose(QuickMergeContentsVariants.all)); + myQuickManualSelectButton.addActionListener(setCodeAndClose(QuickMergeContentsVariants.showLatest)); + mySelectWithPreFilterButton.addActionListener(setCodeAndClose(QuickMergeContentsVariants.select)); + myCancelButton.addActionListener(setCodeAndClose(QuickMergeContentsVariants.cancel)); + + myAllNotMergedRevisionsLabel.setUI(new MultiLineLabelUI()); + myShowsAllRevisionsFromLabel.setUI(new MultiLineLabelUI()); + myFindsWhereOneOfLabel.setUI(new MultiLineLabelUI()); + + myAllNotMergedRevisionsLabel.setBorder(BorderFactory.createEmptyBorder(0,0,10,0)); + myShowsAllRevisionsFromLabel.setBorder(BorderFactory.createEmptyBorder(0,0,10,0)); + myFindsWhereOneOfLabel.setBorder(BorderFactory.createEmptyBorder(0,0,10,0)); + } + + private ActionListener setCodeAndClose(final QuickMergeContentsVariants variant) { + return new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + myVariant = variant; + close(); + } + }; + } + + private void close() { + myWrapper.close(DialogWrapper.OK_EXIT_CODE); + } + + public void setWrapper(DialogWrapper wrapper) { + myWrapper = wrapper; + } + + public QuickMergeContentsVariants getVariant() { + return myVariant; + } + + public JPanel getMainPanel() { + return myMainPanel; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SvnIntegrateChangesTask.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SvnIntegrateChangesTask.java index 1133e8b32ef7..00f839776120 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SvnIntegrateChangesTask.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SvnIntegrateChangesTask.java @@ -215,7 +215,8 @@ public class SvnIntegrateChangesTask extends Task.Backgroundable { private void finishActions(final boolean wasCanceled) { if (! wasCanceled) { - if ((! myDryRun) && (myExceptions.isEmpty()) && (! myAccomulatedFiles.containErrors()) && + if (! ApplicationManager.getApplication().isUnitTestMode() && + (! myDryRun) && (myExceptions.isEmpty()) && (! myAccomulatedFiles.containErrors()) && ((! myAccomulatedFiles.isEmpty()) || (myMergeTarget != null))) { if (myInfo.isUnderProjectRoot()) { showLocalCommit(); diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/lowLevel/PrimitivePool.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/lowLevel/PrimitivePool.java new file mode 100644 index 000000000000..0b7e7ae67f05 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/lowLevel/PrimitivePool.java @@ -0,0 +1,113 @@ +/* + * 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.lowLevel; + +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import org.tmatesoft.svn.core.ISVNCanceller; +import org.tmatesoft.svn.core.SVNCancelException; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; +import org.tmatesoft.svn.core.io.ISVNSession; +import org.tmatesoft.svn.core.io.ISVNTunnelProvider; +import org.tmatesoft.svn.core.io.SVNRepository; +import org.tmatesoft.svn.core.io.SVNRepositoryFactory; +import org.tmatesoft.svn.core.wc.ISVNRepositoryPool; +import org.tmatesoft.svn.util.ISVNDebugLog; +import org.tmatesoft.svn.util.SVNDebugLog; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/27/13 + * Time: 10:41 PM + */ +public class PrimitivePool implements ISVNRepositoryPool, ISVNSession { + private final ISVNAuthenticationManager myManager; + private final ISVNTunnelProvider myTunnelProvider; + + public PrimitivePool(ISVNAuthenticationManager manager, ISVNTunnelProvider tunnelProvider) { + myManager = manager; + myTunnelProvider = tunnelProvider; + } + + @Override + public SVNRepository createRepository(SVNURL url, boolean mayReuse) throws SVNException { + final SVNRepository repos = SVNRepositoryFactory.create(url, this); + repos.setAuthenticationManager(myManager); + repos.setTunnelProvider(myTunnelProvider); + repos.setDebugLog(new ProxySvnLog(SVNDebugLog.getDefaultLog())); + repos.setCanceller(new MyCanceller()); + return repos; + } + + @Override + public void setAuthenticationManager(ISVNAuthenticationManager authManager) { + } + + @Override + public void setCanceller(ISVNCanceller canceller) { + throw new UnsupportedOperationException(); + } + + @Override + public void setDebugLog(ISVNDebugLog log) { + throw new UnsupportedOperationException(); + } + + @Override + public void shutdownConnections(boolean shutdownAll) { + } + + @Override + public void dispose() { + } + + @Override + public boolean keepConnection(SVNRepository repository) { + return false; + } + + @Override + public void saveCommitMessage(SVNRepository repository, long revision, String message) { + } + + @Override + public String getCommitMessage(SVNRepository repository, long revision) { + return null; + } + + @Override + public boolean hasCommitMessage(SVNRepository repository, long revision) { + return false; + } + + private static class MyCanceller implements ISVNCanceller { + @Override + public void checkCancelled() throws SVNCancelException { + final ProgressManager pm = ProgressManager.getInstance(); + final ProgressIndicator pi = pm.getProgressIndicator(); + if (pi != null) { + if (pi.isCanceled()) throw new SVNCancelException(); + } + ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (indicator != null && indicator.isCanceled()) { + throw new SVNCancelException(); + } + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/ListMergeStatus.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/ListMergeStatus.java new file mode 100644 index 000000000000..750a1fc46ff9 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/ListMergeStatus.java @@ -0,0 +1,47 @@ +/* + * 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.mergeinfo; + +import icons.SvnIcons; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +/** +* Created with IntelliJ IDEA. +* User: Irina.Chernushina +* Date: 3/30/13 +* Time: 2:41 PM +*/ +public enum ListMergeStatus { + COMMON(SvnIcons.Common), + MERGED(SvnIcons.Integrated), + NOT_MERGED(SvnIcons.Notintegrated), + ALIEN(null), + REFRESHING(SvnIcons.IntegrationStatusUnknown); + + @Nullable + private final Icon myIcon; + + ListMergeStatus(@Nullable final Icon icon) { + myIcon = icon; + } + + @Nullable + public Icon getIcon() { + return myIcon; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/MergeInfoHolder.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/MergeInfoHolder.java index a08bc68ea618..fb0f52362cdf 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/MergeInfoHolder.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/MergeInfoHolder.java @@ -24,14 +24,12 @@ import com.intellij.openapi.vcs.changes.committed.DecoratorManager; import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; import com.intellij.ui.SimpleTextAttributes; import com.intellij.util.Consumer; -import icons.SvnIcons; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.svn.SvnBundle; import org.jetbrains.idea.svn.dialogs.WCInfoWithBranches; import org.jetbrains.idea.svn.dialogs.WCPaths; import org.jetbrains.idea.svn.history.SvnChangeList; -import javax.swing.*; import java.awt.*; import java.io.File; import java.util.HashMap; @@ -165,25 +163,6 @@ public class MergeInfoHolder { } } - public static enum ListMergeStatus { - COMMON(SvnIcons.Common), - MERGED(SvnIcons.Integrated), - NOT_MERGED(SvnIcons.Notintegrated), - //ALIEN(IconLoader.getIcon("/icons/OnDefault.png")), - ALIEN(null), - REFRESHING(SvnIcons.IntegrationStatusUnknown); - - private final Icon myIcon; - - private ListMergeStatus(final Icon icon) { - myIcon = icon; - } - - public Icon getIcon() { - return myIcon; - } - } - public static interface ListChecker { ListMergeStatus check(final CommittedChangeList list, final boolean ignoreEnabled); } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/OneRecursiveShotMergeInfoWorker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/OneRecursiveShotMergeInfoWorker.java index 5eda9a30ff54..e2962ed58222 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/OneRecursiveShotMergeInfoWorker.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/OneRecursiveShotMergeInfoWorker.java @@ -39,10 +39,12 @@ public class OneRecursiveShotMergeInfoWorker implements MergeInfoWorker { private final WCInfo myWCInfo; // subpath [file] (local) to (subpathURL - merged FROM - to ranges list) private final AreaMap<String, Map<String, SVNMergeRangeList>> myDataMap; - private String myFromUrlRelative; + private final Object myLock; + private final String myFromUrlRelative; public OneRecursiveShotMergeInfoWorker(final Project project, final WCInfo WCInfo, final String fromUrl) { myProject = project; + myLock = new Object(); myWCInfo = WCInfo; myDataMap = AreaMap.create(new PairProcessor<String, String>() { @@ -68,7 +70,9 @@ public class OneRecursiveShotMergeInfoWorker implements MergeInfoWorker { depth, new ISVNPropertyHandler() { public void handleProperty(File path, SVNPropertyData property) throws SVNException { final String key = keyFromFile(path); - myDataMap.put(key, SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(replaceSeparators(property.getValue().getString())), null)); + synchronized (myLock) { + myDataMap.put(key, SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(replaceSeparators(property.getValue().getString())), null)); + } } public void handleProperty(SVNURL url, SVNPropertyData property) throws SVNException { @@ -84,7 +88,9 @@ public class OneRecursiveShotMergeInfoWorker implements MergeInfoWorker { if (relativeToWc == null) return SvnMergeInfoCache.MergeCheckResult.NOT_EXISTS; final InfoProcessor processor = new InfoProcessor(relativeToWc, myFromUrlRelative, revisionNumber); - myDataMap.getSimiliar(keyFromPath(relativeToWc), processor); + synchronized (myLock) { + myDataMap.getSimiliar(keyFromPath(relativeToWc), processor); + } return SvnMergeInfoCache.MergeCheckResult.getInstance(processor.isMerged()); } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/networking/SSLProtocolExceptionParser.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/networking/SSLProtocolExceptionParser.java new file mode 100644 index 000000000000..51813274257b --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/networking/SSLProtocolExceptionParser.java @@ -0,0 +1,87 @@ +/* + * 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.networking; + +import com.intellij.openapi.util.text.StringUtil; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Field; +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 3/25/13 + * Time: 5:44 PM + */ +public class SSLProtocolExceptionParser { + private final String myMessage; + private String myParsedMessage; + private byte myFieldValue; + private String myFieldName; + + public SSLProtocolExceptionParser(@NotNull String message) { + myMessage = message; + } + + public void parse() { + myParsedMessage = myMessage; + myFieldValue = 0; + myFieldName = null; + + final List<String> words = StringUtil.split(myMessage.trim(), " "); + if (words.isEmpty()) return; + // we'll try to parse by last word - it's just an attempt so ok if failed - just will show the real message + + final String[] possiblePlaces = {"com.sun.net.ssl.internal.ssl.Alerts", "sun.security.ssl.Alerts"}; + for (String place : possiblePlaces) { + try { + final Class<?> clazz = Class.forName(place); + if (tryByStaticField(clazz, words.get(words.size() - 1))) { + return; + } + } + catch (ClassNotFoundException e) { + // + } + } + } + + public String getParsedMessage() { + return myParsedMessage; + } + + private boolean tryByStaticField(Class<?> clazz, String word) { + try { + final Field field = clazz.getDeclaredField("alert_" + word); + field.setAccessible(true); + myFieldValue = field.getByte(clazz); + myFieldName = field.getName(); + myParsedMessage = "SSLProtocolException: alert code: " + Byte.toString(myFieldValue) + " alert name: " + myFieldName + + ", original message: " + myMessage; + if ("alert_unrecognized_name".equals(myFieldName)) { + myParsedMessage += "\nThis may be JDK bug 7127374 : JSSE creates SSLProtocolException on (common) warning: unrecognized_name for SNI"; + } + } + catch (NoSuchFieldException e) { + return false; + } + catch (IllegalAccessException e) { + return false; + } + return true; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/status/DiffContentRevision.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/status/DiffContentRevision.java index 2d280b21d3ec..bec2f3e963e9 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/status/DiffContentRevision.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/status/DiffContentRevision.java @@ -21,6 +21,7 @@ import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.ContentRevision; import com.intellij.openapi.vcs.history.VcsRevisionNumber; import com.intellij.openapi.vfs.CharsetToolkit; +import com.intellij.openapi.vfs.encoding.EncodingRegistry; import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -59,7 +60,7 @@ public class DiffContentRevision implements ContentRevision { } final byte[] bytes = bos.toByteArray(); final Charset charset = myFilePath.getCharset(); - myContents = charset == null ? CharsetToolkit.bytesToString(bytes) : CharsetToolkit.bytesToString(bytes, charset); + myContents = charset == null ? CharsetToolkit.bytesToString(bytes, EncodingRegistry.getInstance().getDefaultCharset()) : CharsetToolkit.bytesToString(bytes, charset); } return myContents; } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnRevisionPanel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnRevisionPanel.java index 45cdaf080ca4..897f5b76f72b 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnRevisionPanel.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnRevisionPanel.java @@ -20,6 +20,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.DocumentAdapter; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.svn.SvnBundle; import org.jetbrains.idea.svn.history.SvnChangeList; @@ -33,7 +34,7 @@ import javax.swing.event.DocumentEvent; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.util.ArrayList; +import java.util.List; public class SvnRevisionPanel extends JPanel { private JRadioButton mySpecified; @@ -42,7 +43,7 @@ public class SvnRevisionPanel extends JPanel { private TextFieldWithBrowseButton myRevisionField; private Project myProject; private UrlProvider myUrlProvider; - private final ArrayList<ChangeListener> myChangeListeners = new ArrayList<ChangeListener>(); + private final List<ChangeListener> myChangeListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private VirtualFile myRoot; public SvnRevisionPanel() { diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateEnvironment.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateEnvironment.java index 559c8b72e2d9..80e43aaf7828 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateEnvironment.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateEnvironment.java @@ -106,7 +106,10 @@ public class SvnUpdateEnvironment extends AbstractSvnUpdateIntegrateEnvironment final SvnUpdateClientI updateClient; // do not do from command line for switch now if (! isSwitch && SvnConfiguration.UseAcceleration.commandLine.equals(configuration.myUseAcceleration) && - Svn17Detector.is17(myVcs.getProject(), root) && SvnAuthenticationManager.HTTP.equals(sourceUrl.getProtocol())) { + Svn17Detector.is17(myVcs.getProject(), root) && ( + SvnAuthenticationManager.HTTP.equals(sourceUrl.getProtocol()) || + SvnAuthenticationManager.HTTPS.equals(sourceUrl.getProtocol()) + )) { updateClient = new SvnCommandLineUpdateClient(myVcs.getProject(), null); } else { updateClient = new SvnSvnkitUpdateClient(myVcs.createUpdateClient()); diff --git a/plugins/svn4idea/svn4idea-tests.iml b/plugins/svn4idea/svn4idea-tests.iml index 0b826ed87489..9a537c253abf 100644 --- a/plugins/svn4idea/svn4idea-tests.iml +++ b/plugins/svn4idea/svn4idea-tests.iml @@ -38,6 +38,7 @@ </SOURCES> </library> </orderEntry> + <orderEntry type="module" module-name="bindSvn" /> </component> </module> diff --git a/plugins/svn4idea/svn4idea.iml b/plugins/svn4idea/svn4idea.iml index e27f7e52a099..a2785e1261f5 100644 --- a/plugins/svn4idea/svn4idea.iml +++ b/plugins/svn4idea/svn4idea.iml @@ -94,23 +94,22 @@ <orderEntry type="module-library"> <library> <CLASSES> - <root url="jar://$MODULE_DIR$/lib/javahl.jar!/" /> + <root url="jar://$MODULE_DIR$/lib/svnkit-javahl.jar!/" /> </CLASSES> <JAVADOC /> <SOURCES> - <root url="jar://$MODULE_DIR$/lib/javahlsrc.zip!/src" /> + <root url="jar://$MODULE_DIR$/lib/svnkit-javahl16.zip!/" /> </SOURCES> </library> </orderEntry> + <orderEntry type="module" module-name="bindSvn" /> <orderEntry type="module-library"> <library> <CLASSES> - <root url="jar://$MODULE_DIR$/lib/svnkit-javahl.jar!/" /> + <root url="jar://$MODULE_DIR$/bindSvn/lib/javahl.jar!/" /> </CLASSES> <JAVADOC /> - <SOURCES> - <root url="jar://$MODULE_DIR$/lib/svnkit-javahl16.zip!/" /> - </SOURCES> + <SOURCES /> </library> </orderEntry> </component> diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/SvnTestCase.java b/plugins/svn4idea/testSource/org/jetbrains/idea/SvnTestCase.java index 25e5f71f7826..a1b7360206e7 100644 --- a/plugins/svn4idea/testSource/org/jetbrains/idea/SvnTestCase.java +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/SvnTestCase.java @@ -83,11 +83,12 @@ public abstract class SvnTestCase extends AbstractJunitVcsTestCase { protected String myWcRootName; protected boolean myUseNativeAcceleration = new GregorianCalendar().get(Calendar.HOUR_OF_DAY) % 2 == 0; - private final String myTestDataDir; + protected final String myTestDataDir; private File myRepoRoot; private File myWcRoot; private ChangeListManagerGate myGate; protected String myAnotherRepoUrl; + protected File myPluginRoot; protected SvnTestCase(@NotNull String testDataDir) { PlatformTestCase.initPlatformLangPrefix(); @@ -128,15 +129,15 @@ public abstract class SvnTestCase extends AbstractJunitVcsTestCase { myRepoRoot = new File(myTempDirFixture.getTempDirPath(), "svnroot"); assert myRepoRoot.mkdir() || myRepoRoot.isDirectory() : myRepoRoot; - File pluginRoot = new File(PluginPathManager.getPluginHomePath("svn4idea")); - if (!pluginRoot.isDirectory()) { + myPluginRoot = new File(PluginPathManager.getPluginHomePath("svn4idea")); + if (!myPluginRoot.isDirectory()) { // try standalone mode Class aClass = SvnTestCase.class; String rootPath = PathManager.getResourceRoot(aClass, "/" + aClass.getName().replace('.', '/') + ".class"); - pluginRoot = new File(rootPath).getParentFile().getParentFile().getParentFile(); + myPluginRoot = new File(rootPath).getParentFile().getParentFile().getParentFile(); } - File svnBinDir = new File(pluginRoot, myTestDataDir + "/svn/bin"); + File svnBinDir = new File(myPluginRoot, myTestDataDir + "/svn/bin"); File svnExecutable = null; if (SystemInfo.isWindows) { svnExecutable = new File(svnBinDir, "windows/svn.exe"); @@ -154,7 +155,7 @@ public abstract class SvnTestCase extends AbstractJunitVcsTestCase { ? createClientRunner(Collections.singletonMap("DYLD_LIBRARY_PATH", myClientBinaryPath.getPath())) : createClientRunner(); - ZipUtil.extract(new File(pluginRoot, myTestDataDir + "/svn/newrepo.zip"), myRepoRoot, null); + ZipUtil.extract(new File(myPluginRoot, myTestDataDir + "/svn/newrepo.zip"), myRepoRoot, null); myWcRoot = new File(myTempDirFixture.getTempDirPath(), myWcRootName); assert myWcRoot.mkdir() || myWcRoot.isDirectory() : myWcRoot; @@ -280,6 +281,43 @@ public abstract class SvnTestCase extends AbstractJunitVcsTestCase { }); } + protected void prepareInnerCopy(final boolean anotherRepository) throws Exception { + final String mainUrl = myRepoUrl + "/root/source"; + final String externalURL; + if (anotherRepository) { + createAnotherRepo(); + externalURL = myAnotherRepoUrl + "/root/target"; + } else { + externalURL = myRepoUrl + "/root/target"; + } + + final ChangeListManagerImpl clManager = (ChangeListManagerImpl)ChangeListManager.getInstance(myProject); + final SubTree subTree = new SubTree(myWorkingCopyDir); + checkin(); + clManager.stopEveryThingIfInTestMode(); + sleep(100); + final File rootFile = new File(subTree.myRootDir.getPath()); + FileUtil.delete(rootFile); + FileUtil.delete(new File(myWorkingCopyDir.getPath() + File.separator + ".svn")); + Assert.assertTrue(!rootFile.exists()); + sleep(200); + myWorkingCopyDir.refresh(false, true); + + runInAndVerifyIgnoreOutput("co", mainUrl); + final File sourceDir = new File(myWorkingCopyDir.getPath(), "source"); + final File innerDir = new File(sourceDir, "inner1/inner2/inner"); + runInAndVerifyIgnoreOutput("co", externalURL, innerDir.getPath()); + sleep(100); + myWorkingCopyDir.refresh(false, true); + // above is preparation + + // start change list manager again + clManager.forceGoInTestMode(); + refreshSvnMappingsSynchronously(); + //clManager.ensureUpToDate(false); + //clManager.ensureUpToDate(false); + } + protected class SubTree { public VirtualFile myRootDir; public VirtualFile mySourceDir; @@ -412,7 +450,7 @@ public abstract class SvnTestCase extends AbstractJunitVcsTestCase { //clManager.ensureUpToDate(false); } - private void createAnotherRepo() throws Exception { + protected void createAnotherRepo() throws Exception { final File repo = FileUtil.createTempDirectory("anotherRepo", ""); FileUtil.delete(repo); FileUtil.copyDir(myRepoRoot, repo); @@ -547,4 +585,11 @@ public abstract class SvnTestCase extends AbstractJunitVcsTestCase { final ProcessOutput output = runner.runClient("svn", null, workingDir, input); primitiveVerifier.process(output); } + + protected void setNativeAcceleration(final boolean value) { + System.out.println("Set native acceleration to " + value); + SvnConfiguration.getInstance(myProject).myUseAcceleration = + value ? SvnConfiguration.UseAcceleration.commandLine : SvnConfiguration.UseAcceleration.nothing; + SvnApplicationSettings.getInstance().setCommandLinePath(myClientBinaryPath + File.separator + "svn"); + } } diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/QuickMergeTestInteraction.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/QuickMergeTestInteraction.java new file mode 100644 index 000000000000..6336da4b0606 --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/QuickMergeTestInteraction.java @@ -0,0 +1,149 @@ +/* + * 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; + +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.util.PairConsumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn.dialogs.LocalChangesAction; +import org.jetbrains.idea.svn.dialogs.MergeDialogI; +import org.jetbrains.idea.svn.dialogs.QuickMergeContentsVariants; +import org.jetbrains.idea.svn.integrate.QuickMergeInteraction; +import org.jetbrains.idea.svn.mergeinfo.MergeChecker; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 3/27/13 + * Time: 6:56 PM + */ +public class QuickMergeTestInteraction implements QuickMergeInteraction { + private QuickMergeContentsVariants myMergeVariant = QuickMergeContentsVariants.all; + private boolean myReintegrateAnswer = false; + private LocalChangesAction myLocalChangesAction = LocalChangesAction.continueMerge; + private QuickMergeContentsVariants mySelectMergeAction2ndStep = QuickMergeContentsVariants.all; + + private final List<Exception> myExceptions; + + public QuickMergeTestInteraction() { + myExceptions = new ArrayList<Exception>(); + } + + @Override + public void setTitle(@NotNull String title) { + } + + @Override + public QuickMergeContentsVariants selectMergeVariant() { + return myMergeVariant; + } + + public void setMergeVariant(QuickMergeContentsVariants mergeVariant) { + myMergeVariant = mergeVariant; + } + + @Override + public boolean shouldContinueSwitchedRootFound() { + // not gonna test this at the moment + return false; + } + + @Override + public boolean shouldReintegrate(@NotNull String sourceUrl, @NotNull String targetUrl) { + return myReintegrateAnswer; + } + + public void setReintegrateAnswer(boolean reintegrateAnswer) { + myReintegrateAnswer = reintegrateAnswer; + } + + public void setSelectMergeAction2ndStep(QuickMergeContentsVariants selectMergeAction2ndStep) { + mySelectMergeAction2ndStep = selectMergeAction2ndStep; + } + + @NotNull + @Override + public SelectMergeItemsResult selectMergeItems(List<CommittedChangeList> lists, String mergeTitle, MergeChecker mergeChecker) { + return new SelectMergeItemsResult() { + @Override + public QuickMergeContentsVariants getResultCode() { + return mySelectMergeAction2ndStep; + } + + @Override + public List<CommittedChangeList> getSelectedLists() { + return null; + } + }; + } + + @Override + public List<CommittedChangeList> showRecentListsForSelection(@NotNull List<CommittedChangeList> list, + @NotNull String mergeTitle, + @NotNull MergeChecker mergeChecker, + @NotNull PairConsumer<Long, MergeDialogI> loader, + boolean everyThingLoaded) { + return null; + } + + @NotNull + @Override + public LocalChangesAction selectLocalChangesAction(boolean mergeAll) { + return myLocalChangesAction; + } + + public void setLocalChangesAction(LocalChangesAction localChangesAction) { + myLocalChangesAction = localChangesAction; + } + + @Override + public void showIntersectedLocalPaths(List<FilePath> paths) { + } + + @Override + public void showError(@NotNull Exception exception) { + myExceptions.add(exception); + } + + @Override + public void showErrors(String message, List<VcsException> exceptions) { + if (exceptions != null && ! exceptions.isEmpty()) { + myExceptions.addAll(exceptions); + return; + } + myExceptions.add(new RuntimeException(message)); + } + + @Override + public void showErrors(String message, boolean isError) { + if (isError) { + myExceptions.add(new RuntimeException(message)); + } else { + System.out.println("merge warning: " + message); + } + } + + public void throwIfExceptions() throws Exception { + for (Exception exception : myExceptions) { + throw exception; + } + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SSLExceptionParserTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SSLExceptionParserTest.java new file mode 100644 index 000000000000..3e83843c7d7b --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SSLExceptionParserTest.java @@ -0,0 +1,41 @@ +/* + * 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; + +import org.jetbrains.idea.svn.networking.SSLProtocolExceptionParser; +import org.junit.Assert; +import org.junit.Test; + +import javax.net.ssl.SSLProtocolException; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 3/25/13 + * Time: 6:09 PM + */ +public class SSLExceptionParserTest { + @Test + public void testRealLifeCase() throws Exception { + final String original = "handshake alert: unrecognized_name"; + final SSLProtocolException exception = new SSLProtocolException(original); + final SSLProtocolExceptionParser parser = new SSLProtocolExceptionParser(exception.getMessage()); + parser.parse(); + final String message = parser.getParsedMessage(); + System.out.println(message); + Assert.assertNotSame(original, message); + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnCommandLineStabilityTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnCommandLineStabilityTest.java index e699a1a8069b..b6ffb7e7e7fb 100644 --- a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnCommandLineStabilityTest.java +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnCommandLineStabilityTest.java @@ -2,6 +2,7 @@ package org.jetbrains.idea.svn; import com.intellij.openapi.vcs.VcsException; import junit.framework.Assert; +import org.jetbrains.idea.svn.commandLine.SvnCommandFactory; import org.jetbrains.idea.svn.commandLine.SvnCommandName; import org.jetbrains.idea.svn.commandLine.SvnSimpleCommand; import org.junit.Test; @@ -24,7 +25,7 @@ public class SvnCommandLineStabilityTest extends Svn17TestCase { } private void call() throws VcsException { - final SvnSimpleCommand command = new SvnSimpleCommand(myProject, new File(myWorkingCopyDir.getPath()), SvnCommandName.info); + final SvnSimpleCommand command = SvnCommandFactory.createSimpleCommand(myProject, new File(myWorkingCopyDir.getPath()), SvnCommandName.info); command.addParameters("--xml"); final String result = command.run(); System.out.println(result); diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnCommitTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnCommitTest.java new file mode 100644 index 000000000000..f4da04ea201a --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnCommitTest.java @@ -0,0 +1,492 @@ +/* + * 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; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.*; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.NullableFunction; +import junit.framework.Assert; +import org.jetbrains.annotations.Nullable; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 2/28/13 + * Time: 11:59 AM + */ +public class SvnCommitTest extends Svn17TestCase { + private SvnVcs myVcs; + private VcsDirtyScopeManager myDirtyScopeManager; + private ChangeListManager myChangeListManager; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + myVcs = SvnVcs.getInstance(myProject); + myDirtyScopeManager = VcsDirtyScopeManager.getInstance(myProject); + myChangeListManager = ChangeListManager.getInstance(myProject); + } + + @Test + public void testSimpleCommit() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + run2variants(new MyRunner() { + private String myName = "a.txt"; + + @Override + protected void run() throws Exception { + final VirtualFile file = createFileInCommand(myWorkingCopyDir, myName, "123"); + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + checkinFile(file, FileStatus.ADDED); + } + + @Override + protected void cleanup() throws Exception { + myName = "b.txt"; + } + }); + } + + @Test + public void testCommitRename() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + run2variants(new MyRunner() { + private String myName = "a.txt"; + private String myRenamedName = "aRenamed.txt"; + + @Override + protected void run() throws Exception { + final VirtualFile file = createFileInCommand(myWorkingCopyDir, myName, "123"); + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + checkinFile(file, FileStatus.ADDED); + + renameFileInCommand(file, myRenamedName); + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + checkinFile(file, FileStatus.MODIFIED); + } + + @Override + protected void cleanup() throws Exception { + myName = "b.txt"; + myRenamedName = "bRenamed.txt"; + } + }); + } + + @Test + public void testRenameReplace() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + run2variants(new MyRunner() { + private String myName = "a.txt"; + private String myName2 = "aRenamed.txt"; + + @Override + protected void run() throws Exception { + final VirtualFile file = createFileInCommand(myWorkingCopyDir, myName, "123"); + final VirtualFile file2 = createFileInCommand(myWorkingCopyDir, myName2, "1235"); + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + checkinFiles(file, file2); + + renameFileInCommand(file, file.getName() + "7.txt"); + renameFileInCommand(file2, myName); + + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + checkinFiles(file, file2); + } + + @Override + protected void cleanup() throws Exception { + myName = "b.txt"; + myName2 = "bRenamed.txt"; + } + }); + } + + @Test + public void testRenameFolder() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + run2variants(new MyRunner() { + private String folder = "f"; + + @Override + protected void run() throws Exception { + final VirtualFile dir = createDirInCommand(myWorkingCopyDir, folder); + final VirtualFile file = createFileInCommand(dir, "a.txt", "123"); + final VirtualFile file2 = createFileInCommand(dir, "b.txt", "1235"); + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + checkinFiles(dir, file, file2); + + renameFileInCommand(dir, dir.getName() + "dd"); + + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + checkinFiles(dir, file, file2); + } + + @Override + protected void cleanup() throws Exception { + folder = "f1"; + } + }); + } + + @Test + public void testCommitDeletion() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + run2variants(new MyRunner() { + private String folder = "f"; + + @Override + protected void run() throws Exception { + final VirtualFile dir = createDirInCommand(myWorkingCopyDir, folder); + final VirtualFile file = createFileInCommand(dir, "a.txt", "123"); + final VirtualFile file2 = createFileInCommand(dir, "b.txt", "1235"); + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + checkinFiles(dir, file, file2); + + final FilePath dirPath = new FilePathImpl(new File(dir.getPath()), true); + deleteFileInCommand(dir); + + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + checkinPaths(dirPath); + } + + @Override + protected void cleanup() throws Exception { + folder = "f1"; + } + }); + } + + @Test + public void testSameRepoPlusInnerCopyCommitNative() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + prepareInnerCopy(false); + final MyRunner runner = new MyRunner() { + @Override + protected void run() throws Exception { + final File file1 = new File(myWorkingCopyDir.getPath(), "source/s1.txt"); + final File fileInner = new File(myWorkingCopyDir.getPath(), "source/inner1/inner2/inner/t11.txt"); + + Assert.assertTrue(file1.exists()); + Assert.assertTrue(fileInner.exists()); + final VirtualFile vf1 = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file1); + final VirtualFile vf2 = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(fileInner); + Assert.assertNotNull(vf1); + Assert.assertNotNull(vf2); + + editFileInCommand(vf1, "2317468732ghdwwe7y348rf"); + editFileInCommand(vf2, "2317468732ghdwwe7y348rf csdjcjksw"); + + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + final HashSet<String> strings = checkinFiles(vf1, vf2); + System.out.println("" + StringUtil.join(strings, "\n")); + Assert.assertEquals(1, strings.size()); + } + + @Override + protected void cleanup() throws Exception { + } + }; + setNativeAcceleration(true); + runner.run(); + } + + @Test + public void testSameRepoPlusInnerCopyCommitSvnkit() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + prepareInnerCopy(false); + final MyRunner runner = new MyRunner() { + @Override + protected void run() throws Exception { + final File file1 = new File(myWorkingCopyDir.getPath(), "source/s1.txt"); + final File fileInner = new File(myWorkingCopyDir.getPath(), "source/inner1/inner2/inner/t11.txt"); + + Assert.assertTrue(file1.exists()); + Assert.assertTrue(fileInner.exists()); + final VirtualFile vf1 = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file1); + final VirtualFile vf2 = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(fileInner); + Assert.assertNotNull(vf1); + Assert.assertNotNull(vf2); + + editFileInCommand(vf1, "2317468732ghdwwe7y348rf"); + editFileInCommand(vf2, "2317468732ghdwwe7y348rf csdjcjksw"); + + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + final HashSet<String> strings = checkinFiles(vf1, vf2); + System.out.println("" + StringUtil.join(strings, "\n")); + Assert.assertEquals(1, strings.size()); + } + + @Override + protected void cleanup() throws Exception { + } + }; + setNativeAcceleration(true); + runner.run(); + } + + @Test + public void testAnotherRepoPlusInnerCopyCommitNative() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + prepareInnerCopy(true); + final MyRunner runner = new MyRunner() { + @Override + protected void run() throws Exception { + final File file1 = new File(myWorkingCopyDir.getPath(), "source/s1.txt"); + final File fileInner = new File(myWorkingCopyDir.getPath(), "source/inner1/inner2/inner/t11.txt"); + + Assert.assertTrue(file1.exists()); + Assert.assertTrue(fileInner.exists()); + final VirtualFile vf1 = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file1); + final VirtualFile vf2 = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(fileInner); + Assert.assertNotNull(vf1); + Assert.assertNotNull(vf2); + + editFileInCommand(vf1, "2317468732ghdwwe7y348rf"); + editFileInCommand(vf2, "2317468732ghdwwe7y348rf csdjcjksw"); + + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + checkinFiles(vf1, vf2); + } + + @Override + protected void cleanup() throws Exception { + } + }; + setNativeAcceleration(true); + runner.run(); + } + + @Test + public void testAnotherRepoPlusInnerCopyCommitSvnkit() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + prepareInnerCopy(true); + final MyRunner runner = new MyRunner() { + @Override + protected void run() throws Exception { + final File file1 = new File(myWorkingCopyDir.getPath(), "source/s1.txt"); + final File fileInner = new File(myWorkingCopyDir.getPath(), "source/inner1/inner2/inner/t11.txt"); + + Assert.assertTrue(file1.exists()); + Assert.assertTrue(fileInner.exists()); + final VirtualFile vf1 = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file1); + final VirtualFile vf2 = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(fileInner); + Assert.assertNotNull(vf1); + Assert.assertNotNull(vf2); + + editFileInCommand(vf1, "2317468732ghdwwe7y348rf"); + editFileInCommand(vf2, "2317468732ghdwwe7y348rf csdjcjksw"); + + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + checkinFiles(vf1, vf2); + } + + @Override + protected void cleanup() throws Exception { + } + }; + setNativeAcceleration(true); + runner.run(); + } + + @Test + public void testPlusExternalCopyCommitNative() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + prepareExternal(); + final MyRunner runner = new MyRunner() { + @Override + protected void run() throws Exception { + final File file1 = new File(myWorkingCopyDir.getPath(), "source/s1.txt"); + final File fileInner = new File(myWorkingCopyDir.getPath(), "source/external/t11.txt"); + + Assert.assertTrue(file1.exists()); + Assert.assertTrue(fileInner.exists()); + final VirtualFile vf1 = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file1); + final VirtualFile vf2 = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(fileInner); + Assert.assertNotNull(vf1); + Assert.assertNotNull(vf2); + + editFileInCommand(vf1, "2317468732ghdwwe7y348rf"); + editFileInCommand(vf2, "2317468732ghdwwe7y348rf csdjcjksw"); + + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + checkinFiles(vf1, vf2); + } + + @Override + protected void cleanup() throws Exception { + } + }; + setNativeAcceleration(true); + runner.run(); + } + + @Test + public void testPlusExternalCopyCommitSvnkit() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + prepareExternal(); + final MyRunner runner = new MyRunner() { + @Override + protected void run() throws Exception { + final File file1 = new File(myWorkingCopyDir.getPath(), "source/s1.txt"); + final File fileInner = new File(myWorkingCopyDir.getPath(), "source/external/t11.txt"); + + Assert.assertTrue(file1.exists()); + Assert.assertTrue(fileInner.exists()); + final VirtualFile vf1 = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file1); + final VirtualFile vf2 = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(fileInner); + Assert.assertNotNull(vf1); + Assert.assertNotNull(vf2); + + editFileInCommand(vf1, "2317468732ghdwwe7y348rf"); + editFileInCommand(vf2, "2317468732ghdwwe7y348rf csdjcjksw"); + + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + checkinFiles(vf1, vf2); + } + + @Override + protected void cleanup() throws Exception { + } + }; + setNativeAcceleration(false); + runner.run(); + } + + private void checkinPaths(FilePath... files) { + final List<Change> changes = new ArrayList<Change>(); + for (FilePath file : files) { + final Change change = myChangeListManager.getChange(file); + Assert.assertNotNull(change); + changes.add(change); + } + final List<VcsException> exceptions = myVcs.getCheckinEnvironment().commit(changes, "test comment list"); + Assert.assertTrue(exceptions == null || exceptions.isEmpty()); + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + for (FilePath file : files) { + final Change changeA = myChangeListManager.getChange(file); + Assert.assertNull(changeA); + } + } + + private HashSet<String> checkinFiles(VirtualFile... files) { + final List<Change> changes = new ArrayList<Change>(); + for (VirtualFile file : files) { + final Change change = myChangeListManager.getChange(file); + Assert.assertNotNull(change); + changes.add(change); + } + final HashSet<String> feedback = new HashSet<String>(); + final List<VcsException> exceptions = myVcs.getCheckinEnvironment().commit(changes, "test comment list", + new NullableFunction<Object, Object>() { + @Nullable + @Override + public Object fun(Object o) { + return null; + } + }, feedback); + if (exceptions !=null && ! exceptions.isEmpty()) { + exceptions.get(0).printStackTrace(); + } + Assert.assertTrue(exceptions == null || exceptions.isEmpty()); + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + for (VirtualFile file : files) { + final Change changeA = myChangeListManager.getChange(file); + Assert.assertNull(changeA); + } + return feedback; + } + + private void checkinFile(VirtualFile file, FileStatus status) { + final Change change = myChangeListManager.getChange(file); + Assert.assertNotNull(change); + Assert.assertEquals(status, change.getFileStatus()); + final List<VcsException> exceptions = myVcs.getCheckinEnvironment().commit(Collections.singletonList(change), "test comment"); + Assert.assertTrue(exceptions == null || exceptions.isEmpty()); + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + final Change changeA = myChangeListManager.getChange(file); + Assert.assertNull(changeA); + } + + protected void run2variants(final MyRunner runner) throws Exception { + setNativeAcceleration(false); + runner.run(); + runner.cleanup(); + setNativeAcceleration(true); + runner.run(); + } + + private static abstract class MyRunner { + protected abstract void run() throws Exception; + protected abstract void cleanup() throws Exception; + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnExternalTests.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnExternalTests.java index 177f608ea768..500acbdff4c9 100644 --- a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnExternalTests.java +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnExternalTests.java @@ -91,31 +91,8 @@ public class SvnExternalTests extends Svn17TestCase { Assert.assertTrue(expectedUrls.isEmpty()); } - private void prepareInnerCopy() throws Exception { - final SubTree subTree = new SubTree(myWorkingCopyDir); - checkin(); - clManager.stopEveryThingIfInTestMode(); - sleep(100); - final File rootFile = new File(subTree.myRootDir.getPath()); - FileUtil.delete(rootFile); - FileUtil.delete(new File(myWorkingCopyDir.getPath() + File.separator + ".svn")); - Assert.assertTrue(!rootFile.exists()); - sleep(200); - myWorkingCopyDir.refresh(false, true); - - runInAndVerifyIgnoreOutput("co", myMainUrl); - final File sourceDir = new File(myWorkingCopyDir.getPath(), "source"); - final File innerDir = new File(sourceDir, "inner1/inner2/inner"); - runInAndVerifyIgnoreOutput("co", myExternalURL, innerDir.getPath()); - sleep(100); - myWorkingCopyDir.refresh(false, true); - // above is preparation - - // start change list manager again - clManager.forceGoInTestMode(); - refreshSvnMappingsSynchronously(); - //clManager.ensureUpToDate(false); - //clManager.ensureUpToDate(false); + protected void prepareInnerCopy() throws Exception { + prepareInnerCopy(false); } @Test diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnNativeClientAuthTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnNativeClientAuthTest.java new file mode 100644 index 000000000000..61aca5670867 --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnNativeClientAuthTest.java @@ -0,0 +1,630 @@ +/* + * 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; + +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.*; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.CurrentContentRevision; +import com.intellij.openapi.vcs.update.SequentialUpdatesContext; +import com.intellij.openapi.vcs.update.UpdateSession; +import com.intellij.openapi.vcs.update.UpdatedFiles; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Processor; +import com.intellij.util.containers.Convertor; +import junit.framework.Assert; +import org.jetbrains.idea.svn.checkout.SvnCheckoutProvider; +import org.junit.Before; +import org.tmatesoft.svn.core.*; +import org.tmatesoft.svn.core.auth.*; +import org.tmatesoft.svn.core.io.SVNRepository; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 3/4/13 + * Time: 9:55 PM + */ +public class SvnNativeClientAuthTest extends Svn17TestCase { + private SvnVcs myVcs; + private int myCertificateAnswer = ISVNAuthenticationProvider.ACCEPTED_TEMPORARY; + private boolean myCredentialsCorrect = true; + private boolean mySaveCredentials = false; + private boolean myCancelAuth = false; + + private String outHttpUser = "test"; + private String outHttpPassword = "test"; + + private final static String ourHTTP_URL = "http://svnsrvtest/stuff/autoTest"; + private final static String ourHTTPS_URL = "https://svnsrvtest:443/TestSSL/autoTest"; + + private int myCertificateAskedInteractivelyCount = 0; + private int myCredentialsAskedInteractivelyCount = 0; + + private int myExpectedCreds = 0; + private int myExpectedCert = 0; + private boolean myIsSecure; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + final File certFile = new File(myPluginRoot, myTestDataDir + "/svn/____.pfx"); + setNativeAcceleration(true); + myVcs = SvnVcs.getInstance(myProject); + // replace authentication provider so that pass credentials without dialogs + final SvnConfiguration configuration = SvnConfiguration.getInstance(myProject); + final File svnconfig = FileUtil.createTempDirectory("svnconfig", ""); + configuration.setConfigurationDirParameters(false, svnconfig.getPath()); + + final SvnAuthenticationManager interactiveManager = configuration.getInteractiveManager(myVcs); + final SvnTestInteractiveAuthentication authentication = new SvnTestInteractiveAuthentication(interactiveManager) { + @Override + public int acceptServerAuthentication(SVNURL url, String realm, Object certificate, boolean resultMayBeStored) { + ++ myCertificateAskedInteractivelyCount; + return myCertificateAnswer; + } + + @Override + public SVNAuthentication requestClientAuthentication(String kind, + SVNURL url, + String realm, + SVNErrorMessage errorMessage, + SVNAuthentication previousAuth, + boolean authMayBeStored) { + if (myCancelAuth) return null; + return super.requestClientAuthentication(kind, url, realm, errorMessage, previousAuth, authMayBeStored); + } + }; + interactiveManager.setAuthenticationProvider(authentication); + + final SvnAuthenticationManager manager = configuration.getAuthenticationManager(myVcs); + // will be the same as in interactive -> authentication notifier is not used + manager.setAuthenticationProvider(authentication); + + authentication.addAuthentication(ISVNAuthenticationManager.PASSWORD, + new Convertor<SVNURL, SVNAuthentication>() { + @Override + public SVNAuthentication convert(SVNURL o) { + ++ myCredentialsAskedInteractivelyCount; + if (myCancelAuth) return null; + if (myCredentialsCorrect) { + return new SVNPasswordAuthentication(outHttpUser, outHttpPassword, mySaveCredentials, o, false); + } else { + myCredentialsCorrect = true;// only once + return new SVNPasswordAuthentication("1234214 23 4234", "324324", mySaveCredentials, o, false); + } + } + }); + authentication.addAuthentication(ISVNAuthenticationManager.SSL, + new Convertor<SVNURL, SVNAuthentication>() { + @Override + public SVNAuthentication convert(SVNURL o) { + ++ myCredentialsAskedInteractivelyCount; + if (myCancelAuth) return null; + if (myCredentialsCorrect) { + return new SVNSSLAuthentication(certFile, "12345", mySaveCredentials, o, false); + } else { + myCredentialsCorrect = true;// only once + return new SVNSSLAuthentication(new File("1232432423"), "3245321532534235445", mySaveCredentials, o, false); + } + } + }); + myCertificateAskedInteractivelyCount = 0; + myCredentialsAskedInteractivelyCount = 0; + } + + @Test + public void testTmpHttpUpdate() throws Exception { + final File wc1 = testCheckoutImpl(ourHTTP_URL); + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + + final SvnConfiguration instance = SvnConfiguration.getInstance(myProject); + instance.clearAuthenticationDirectory(myProject); + instance.clearRuntimeStorage(); + + Assert.assertEquals(SvnConfiguration.UseAcceleration.commandLine, instance.myUseAcceleration); + mySaveCredentials = false; + + updateSimple(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + + // credentials are cached now only + updateSimple(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + } + + @Test + public void testTmpSSLUpdate() throws Exception { + final File wc1 = testCheckoutImpl(ourHTTPS_URL); + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + + final SvnConfiguration instance = SvnConfiguration.getInstance(myProject); + instance.clearAuthenticationDirectory(myProject); + instance.clearRuntimeStorage(); + + Assert.assertEquals(SvnConfiguration.UseAcceleration.commandLine, instance.myUseAcceleration); + mySaveCredentials = false; + + updateSimple(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + + // credentials are cached now only + updateSimple(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + } + + @Test + public void testPermanentSSLUpdate() throws Exception { + final File wc1 = testCheckoutImpl(ourHTTPS_URL); + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + + final SvnConfiguration instance = SvnConfiguration.getInstance(myProject); + instance.clearAuthenticationDirectory(myProject); + instance.clearRuntimeStorage(); + + Assert.assertEquals(SvnConfiguration.UseAcceleration.commandLine, instance.myUseAcceleration); + mySaveCredentials = true; + + updateSimple(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + + // credentials are cached now only + updateSimple(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + } + + @Test + public void testPermanentHttpUpdate() throws Exception { + final File wc1 = testCheckoutImpl(ourHTTP_URL); + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + + final SvnConfiguration instance = SvnConfiguration.getInstance(myProject); + instance.clearAuthenticationDirectory(myProject); + instance.clearRuntimeStorage(); + + Assert.assertEquals(SvnConfiguration.UseAcceleration.commandLine, instance.myUseAcceleration); + mySaveCredentials = true; + + updateSimple(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + + // credentials are cached now only + updateSimple(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + } + + @Test + public void testTmpHttpCommit() throws Exception { + final File wc1 = testCheckoutImpl(ourHTTP_URL); + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + + final SvnConfiguration instance = SvnConfiguration.getInstance(myProject); + instance.clearAuthenticationDirectory(myProject); + instance.clearRuntimeStorage(); + + Assert.assertEquals(SvnConfiguration.UseAcceleration.commandLine, instance.myUseAcceleration); + mySaveCredentials = false; + + testCommitImpl(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + + // credentials are cached now only + testCommitImpl(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + } + + @Test + public void testTmpSSLCommit() throws Exception { + final File wc1 = testCheckoutImpl(ourHTTPS_URL); + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + + final SvnConfiguration instance = SvnConfiguration.getInstance(myProject); + instance.clearAuthenticationDirectory(myProject); + instance.clearRuntimeStorage(); + + Assert.assertEquals(SvnConfiguration.UseAcceleration.commandLine, instance.myUseAcceleration); + mySaveCredentials = false; + + testCommitImpl(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + + // credentials are cached now only + testCommitImpl(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + } + + @Test + public void testPermanentSSLCommit() throws Exception { + final File wc1 = testCheckoutImpl(ourHTTPS_URL); + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + + final SvnConfiguration instance = SvnConfiguration.getInstance(myProject); + instance.clearAuthenticationDirectory(myProject); + instance.clearRuntimeStorage(); + + Assert.assertEquals(SvnConfiguration.UseAcceleration.commandLine, instance.myUseAcceleration); + mySaveCredentials = true; + + testCommitImpl(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + + // credentials are cached now only + testCommitImpl(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + } + + @Test + public void test2PermanentSSLCommit() throws Exception { + final File wc1 = testCheckoutImpl(ourHTTPS_URL); + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + + final SvnConfiguration instance = SvnConfiguration.getInstance(myProject); + instance.clearAuthenticationDirectory(myProject); + instance.clearRuntimeStorage(); + + Assert.assertEquals(SvnConfiguration.UseAcceleration.commandLine, instance.myUseAcceleration); + mySaveCredentials = true; + myCertificateAnswer = ISVNAuthenticationProvider.ACCEPTED; + + testCommitImpl(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + + // credentials are cached now only + testCommitImpl(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + } + + @Test + public void testMixedSSLCommit() throws Exception { + final File wc1 = testCheckoutImpl(ourHTTPS_URL); + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + + final SvnConfiguration instance = SvnConfiguration.getInstance(myProject); + instance.clearAuthenticationDirectory(myProject); + instance.clearRuntimeStorage(); + + Assert.assertEquals(SvnConfiguration.UseAcceleration.commandLine, instance.myUseAcceleration); + mySaveCredentials = false; + myCertificateAnswer = ISVNAuthenticationProvider.ACCEPTED; + + testCommitImpl(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + + // credentials are cached now only + testCommitImpl(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + //------------ + instance.clearAuthenticationDirectory(myProject); + instance.clearRuntimeStorage(); + mySaveCredentials = true; + myCertificateAnswer = ISVNAuthenticationProvider.ACCEPTED_TEMPORARY; + + testCommitImpl(wc1); + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + testCommitImpl(wc1); + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + } + + @Test + public void testPermanentHttpCommit() throws Exception { + final File wc1 = testCheckoutImpl(ourHTTP_URL); + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + + final SvnConfiguration instance = SvnConfiguration.getInstance(myProject); + instance.clearAuthenticationDirectory(myProject); + instance.clearRuntimeStorage(); + + Assert.assertEquals(SvnConfiguration.UseAcceleration.commandLine, instance.myUseAcceleration); + mySaveCredentials = true; + + testCommitImpl(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + + // credentials are cached now only + testCommitImpl(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + } + + @Test + public void testFailedThenSuccessTmpHttpUpdate() throws Exception { + final File wc1 = testCheckoutImpl(ourHTTP_URL); + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + + final SvnConfiguration instance = SvnConfiguration.getInstance(myProject); + instance.clearAuthenticationDirectory(myProject); + instance.clearRuntimeStorage(); + + Assert.assertEquals(SvnConfiguration.UseAcceleration.commandLine, instance.myUseAcceleration); + mySaveCredentials = false; + myCredentialsCorrect = false; + + updateSimple(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + + // credentials are cached now only + updateSimple(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + } + + @Test + public void testFailedThenSuccessTmpSSLUpdate() throws Exception { + final File wc1 = testCheckoutImpl(ourHTTPS_URL); + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + + final SvnConfiguration instance = SvnConfiguration.getInstance(myProject); + instance.clearAuthenticationDirectory(myProject); + instance.clearRuntimeStorage(); + + Assert.assertEquals(SvnConfiguration.UseAcceleration.commandLine, instance.myUseAcceleration); + mySaveCredentials = false; + myCredentialsCorrect = false; + + updateSimple(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + // credentials wrong, but certificate was ok accepted + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + + // credentials are cached now only + updateSimple(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + } + + @Test + public void testCertificateRejectedThenCredentialsFailedThenSuccessTmpSSLUpdate() throws Exception { + final File wc1 = testCheckoutImpl(ourHTTPS_URL); + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + + final SvnConfiguration instance = SvnConfiguration.getInstance(myProject); + instance.clearAuthenticationDirectory(myProject); + instance.clearRuntimeStorage(); + + Assert.assertEquals(SvnConfiguration.UseAcceleration.commandLine, instance.myUseAcceleration); + mySaveCredentials = false; + myCertificateAnswer = ISVNAuthenticationProvider.REJECTED; + + updateExpectAuthCanceled(wc1, "Authentication canceled"); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + + myCertificateAnswer = ISVNAuthenticationProvider.ACCEPTED_TEMPORARY; + myCredentialsCorrect = false; + + updateSimple(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + // credentials wrong, but certificate was ok accepted + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + + // credentials are cached now only + updateSimple(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + } + + @Test + public void testCanceledThenFailedThenSuccessTmpHttpUpdate() throws Exception { + final File wc1 = testCheckoutImpl(ourHTTP_URL); + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + + final SvnConfiguration instance = SvnConfiguration.getInstance(myProject); + instance.clearAuthenticationDirectory(myProject); + instance.clearRuntimeStorage(); + + Assert.assertEquals(SvnConfiguration.UseAcceleration.commandLine, instance.myUseAcceleration); + mySaveCredentials = false; + myCredentialsCorrect = false; + myCancelAuth = true; + updateExpectAuthCanceled(wc1, "Authentication canceled"); + myCancelAuth = false; + + updateSimple(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + + // credentials are cached now only + updateSimple(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + } + + @Test + public void testCanceledThenFailedThenSuccessTmpSSLUpdate() throws Exception { + final File wc1 = testCheckoutImpl(ourHTTPS_URL); + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + + final SvnConfiguration instance = SvnConfiguration.getInstance(myProject); + instance.clearAuthenticationDirectory(myProject); + instance.clearRuntimeStorage(); + + Assert.assertEquals(SvnConfiguration.UseAcceleration.commandLine, instance.myUseAcceleration); + mySaveCredentials = false; + myCredentialsCorrect = false; + myCancelAuth = true; + updateExpectAuthCanceled(wc1, "Authentication canceled"); + myCancelAuth = false; + + updateSimple(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + + // credentials are cached now only + updateSimple(wc1); + + //Assert.assertEquals(myExpectedCreds, myCredentialsAskedInteractivelyCount); + //Assert.assertEquals(myExpectedCert, myCertificateAskedInteractivelyCount); + } + + private File testCommitImpl(File wc1) throws IOException { + Assert.assertTrue(wc1.isDirectory()); + final File file = FileUtil.createTempFile(wc1, "file", ".txt"); + final VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file); + Assert.assertNotNull(vf); + final ArrayList<VirtualFile> files = new ArrayList<VirtualFile>(); + files.add(vf); + final List<VcsException> exceptions = myVcs.getCheckinEnvironment().scheduleUnversionedFilesForAddition(files); + Assert.assertTrue(exceptions.isEmpty()); + + final Change change = new Change(null, new CurrentContentRevision(new FilePathImpl(vf))); + final List<VcsException> commit = myVcs.getCheckinEnvironment().commit(Collections.singletonList(change), "commit"); + Assert.assertTrue(commit.isEmpty()); + ++ myExpectedCreds; + ++ myExpectedCert; + return file; + } + + private File testCheckoutImpl(final String url) throws IOException { + final File root = FileUtil.createTempDirectory("checkoutRoot", ""); + root.deleteOnExit(); + Assert.assertTrue(root.exists()); + SvnCheckoutProvider + .checkout(myProject, root, url, SVNRevision.HEAD, SVNDepth.INFINITY, false, new CheckoutProvider.Listener() { + @Override + public void directoryCheckedOut(File directory, VcsKey vcs) { + } + + @Override + public void checkoutCompleted() { + } + }, WorkingCopyFormat.ONE_DOT_SEVEN); + final int[] cnt = new int[1]; + cnt[0] = 0; + FileUtil.processFilesRecursively(root, new Processor<File>() { + @Override + public boolean process(File file) { + ++ cnt[0]; + return ! (cnt[0] > 1); + } + }); + Assert.assertTrue(cnt[0] > 1); + myIsSecure = url.contains("https:"); + if (myIsSecure) { + ++ myExpectedCreds; + ++ myExpectedCert; + } + ProjectLevelVcsManager.getInstance(myProject).setDirectoryMapping(root.getPath(), SvnVcs.VCS_NAME); + refreshSvnMappingsSynchronously(); + return root; + } + + private void updateExpectAuthCanceled(File wc1, String expectedText) { + Assert.assertTrue(wc1.isDirectory()); + final VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(wc1); + final UpdatedFiles files = UpdatedFiles.create(); + final UpdateSession session = + myVcs.getUpdateEnvironment().updateDirectories(new FilePath[]{new FilePathImpl(vf)}, files, new EmptyProgressIndicator(), + new Ref<SequentialUpdatesContext>()); + Assert.assertTrue(session.getExceptions() != null && ! session.getExceptions().isEmpty()); + Assert.assertTrue(!session.isCanceled()); + Assert.assertTrue(session.getExceptions().get(0).getMessage().contains(expectedText)); + + if (myIsSecure) { + ++ myExpectedCreds; + ++ myExpectedCert; + } + } + + private void updateSimple(File wc1) { + Assert.assertTrue(wc1.isDirectory()); + final VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(wc1); + final UpdatedFiles files = UpdatedFiles.create(); + final UpdateSession session = + myVcs.getUpdateEnvironment().updateDirectories(new FilePath[]{new FilePathImpl(vf)}, files, new EmptyProgressIndicator(), + new Ref<SequentialUpdatesContext>()); + Assert.assertTrue(session.getExceptions() == null || session.getExceptions().isEmpty()); + Assert.assertTrue(!session.isCanceled()); + if (myIsSecure) { + ++ myExpectedCreds; + ++ myExpectedCert; + } + } + + private void testBrowseRepositoryImpl(final String url) throws SVNException { + final List<SVNDirEntry> list = new ArrayList<SVNDirEntry>(); + final SVNRepository repository = myVcs.createRepository(url); + repository.getDir(".", -1, null, new ISVNDirEntryHandler() { + @Override + public void handleDirEntry(SVNDirEntry dirEntry) throws SVNException { + list.add(dirEntry); + } + }); + + Assert.assertTrue(!list.isEmpty()); + } + + private static @interface Test {} +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnParseCommandLineParseTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnParseCommandLineParseTest.java index 3870bd3c6142..5bf5d7651f94 100644 --- a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnParseCommandLineParseTest.java +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnParseCommandLineParseTest.java @@ -611,7 +611,8 @@ public class SvnParseCommandLineParseTest extends TestCase { } private String changePathsIfNix(String s) { - s = StringUtil.replace(s, "\\", "/"); + if (SystemInfo.isWindows) return s; + s = FileUtil.toSystemIndependentName(s); return StringUtil.replace(s, "C:/", LINUX_ROOT); } diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnQuickMergeTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnQuickMergeTest.java new file mode 100644 index 000000000000..d7b48df7b479 --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnQuickMergeTest.java @@ -0,0 +1,510 @@ +/* + * 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; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.PairConsumer; +import com.intellij.util.SmartList; +import com.intellij.util.concurrency.Semaphore; +import com.intellij.util.continuation.ContinuationContext; +import com.intellij.util.continuation.TaskDescriptor; +import com.intellij.util.continuation.Where; +import junit.framework.Assert; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.SvnTestCase; +import org.jetbrains.idea.svn.branchConfig.InfoReliability; +import org.jetbrains.idea.svn.branchConfig.InfoStorage; +import org.jetbrains.idea.svn.branchConfig.SvnBranchConfigurationNew; +import org.jetbrains.idea.svn.dialogs.MergeDialogI; +import org.jetbrains.idea.svn.dialogs.QuickMerge; +import org.jetbrains.idea.svn.dialogs.QuickMergeContentsVariants; +import org.jetbrains.idea.svn.dialogs.WCInfo; +import org.jetbrains.idea.svn.integrate.SvnBranchItem; +import org.jetbrains.idea.svn.mergeinfo.MergeChecker; +import org.junit.Before; +import org.junit.Test; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; +import org.tmatesoft.svn.core.wc.SVNInfo; +import org.tmatesoft.svn.core.wc.SVNPropertyData; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 3/27/13 + * Time: 12:58 PM + */ +public class SvnQuickMergeTest extends Svn17TestCase { + private SvnVcs myVcs; + private String myBranchUrl; + private File myBranchRoot; + private VirtualFile myBranchVf; + private SubTree myBranchTree; + private ChangeListManager myChangeListManager; + private SvnTestCase.SubTree myTree; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + + myVcs = SvnVcs.getInstance(myProject); + myChangeListManager = ChangeListManager.getInstance(myProject); + myBranchUrl = prepareBranchesStructure(); + myBranchRoot = new File(myTempDirFixture.getTempDirPath(), "b1"); + + runInAndVerifyIgnoreOutput("co", myBranchUrl, myBranchRoot.getPath()); + Assert.assertTrue(myBranchRoot.exists()); + myBranchVf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(myBranchRoot); + Assert.assertNotNull(myBranchVf); + + myBranchTree = new SubTree(myBranchVf); + myTree = new SubTree(myWorkingCopyDir); + + final SvnBranchConfigurationManager branchConfigurationManager = SvnBranchConfigurationManager.getInstance(myProject); + final SvnBranchConfigurationNew configuration = new SvnBranchConfigurationNew(); + configuration.setTrunkUrl(myRepoUrl + "/trunk"); + configuration.addBranches(myRepoUrl + "/branches", + new InfoStorage<List<SvnBranchItem>>(new ArrayList<SvnBranchItem>(), InfoReliability.empty)); + branchConfigurationManager.setConfiguration(myWorkingCopyDir, configuration); + + //((ApplicationImpl) ApplicationManager.getApplication()).setRunPooledInTest(true); + + runInAndVerifyIgnoreOutput(new File(myWorkingCopyDir.getPath()), "up"); + Thread.sleep(10); + } + + @Test + public void testSimpleMergeAllFromB1ToTrunk() throws Exception { + editFileInCommand(myProject, myBranchTree.myS1File, "edited in branch"); + runInAndVerifyIgnoreOutput(myBranchRoot, "ci", "-m", "change in branch", myBranchTree.myS1File.getPath()); + + final WCInfo found = getWcInfo(); + final QuickMerge quickMerge = + new QuickMerge(myProject, myBranchUrl, found, SVNPathUtil.tail(myBranchUrl), myWorkingCopyDir); + // by default merges all + final QuickMergeTestInteraction testInteraction = new QuickMergeTestInteraction() { + @Override + public boolean shouldReintegrate(@NotNull String sourceUrl, @NotNull String targetUrl) { + return true; + } + }; + final WaitingTaskDescriptor descriptor = new WaitingTaskDescriptor(); + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + quickMerge.execute(testInteraction, descriptor); + } + }); + descriptor.waitForCompletion(); + testInteraction.throwIfExceptions(); + + Assert.assertTrue(descriptor.isCompleted()); + + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + // should have changed svn:mergeinfo on wc root and s1 file + final Change fileChange = myChangeListManager.getChange(myTree.myS1File); + Assert.assertNotNull(fileChange); + Assert.assertEquals(FileStatus.MODIFIED, fileChange.getFileStatus()); + + final Change dirChange = myChangeListManager.getChange(myWorkingCopyDir); + Assert.assertNotNull(dirChange); + Assert.assertEquals(FileStatus.MODIFIED, dirChange.getFileStatus()); + } + + // if we create branches like this: + // trunk -> b1, b1->b2, b2->b3, b1->b4, then we should be able to merge between b1 and b2. some time before we had bug with it + @Test + public void testMergeBetweenDifferentTimeCreatedBranches() throws Exception { + // b1 -> b2 + runInAndVerifyIgnoreOutput("copy", "-q", "-m", "copy1", myBranchUrl, myRepoUrl + "/branches/b2"); + // b2 -> b3 + runInAndVerifyIgnoreOutput("copy", "-q", "-m", "copy1", myRepoUrl + "/branches/b2", myRepoUrl + "/branches/b3"); + // b1 -> b4 + runInAndVerifyIgnoreOutput("copy", "-q", "-m", "copy1", myBranchUrl, myRepoUrl + "/branches/b4"); + + testSimpleMergeAllFromB1ToTrunk(); + } + + @Test + public void testSelectRevisionsWithQuickSelectCheckForLocalChanges() throws Exception { + // get revision # + final SVNInfo info = myVcs.createWCClient().doInfo(new File(myBranchTree.myS1File.getPath()), SVNRevision.WORKING); + Assert.assertNotNull(info); + + final long numberBefore = info.getRevision().getNumber(); + final int totalChanges = 3; + + final StringBuilder sb = new StringBuilder(FileUtil.loadFile(new File(myBranchTree.myS1File.getPath()))); + for (int i = 0; i < totalChanges; i++) { + sb.append("\nedited in branch ").append(i); + editFileInCommand(myProject, myBranchTree.myS1File, sb.toString()); + runInAndVerifyIgnoreOutput(myBranchRoot, "ci", "-m", "change in branch " + i, myBranchTree.myS1File.getPath()); + Thread.sleep(10); + } + + // we should get exactly 2 revisions for selection (copy and change in b2) + final WCInfo found = getWcInfo(); + final QuickMerge quickMerge = + new QuickMerge(myProject, myBranchUrl, found, SVNPathUtil.tail(myBranchUrl), myWorkingCopyDir); + // by default merges all + final AtomicReference<String> selectionError = new AtomicReference<String>(); + final QuickMergeTestInteraction testInteraction = new QuickMergeTestInteraction() { + @Override + public boolean shouldReintegrate(@NotNull String sourceUrl, @NotNull String targetUrl) { + return true; + } + + @Override + public List<CommittedChangeList> showRecentListsForSelection(@NotNull List<CommittedChangeList> list, + @NotNull String mergeTitle, + @NotNull MergeChecker mergeChecker, + @NotNull PairConsumer<Long, MergeDialogI> loader, + boolean everyThingLoaded) { + if (list.size() != 4) { + selectionError.set("List size: " + list.size()); + } else if (list.get(3).getNumber() != numberBefore) { + selectionError.set("wrong revision for copy statement: " + list.get(3).getNumber()); + } + return new SmartList<CommittedChangeList>(list.get(2)); // get a change + } + }; + testInteraction.setMergeVariant(QuickMergeContentsVariants.showLatest); + final WaitingTaskDescriptor descriptor = new WaitingTaskDescriptor(); + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + quickMerge.execute(testInteraction, descriptor); + } + }); + descriptor.waitForCompletion(); + testInteraction.throwIfExceptions(); + if (selectionError.get() != null){ + throw new RuntimeException(selectionError.get()); + } + + Assert.assertTrue(descriptor.isCompleted()); + + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + // should have changed svn:mergeinfo on wc root and s1 file + final Change fileChange = myChangeListManager.getChange(myTree.myS1File); + Assert.assertNotNull(fileChange); + Assert.assertEquals(FileStatus.MODIFIED, fileChange.getFileStatus()); + + final Change dirChange = myChangeListManager.getChange(myWorkingCopyDir); + Assert.assertNotNull(dirChange); + Assert.assertEquals(FileStatus.MODIFIED, dirChange.getFileStatus()); + + final SVNPropertyData data = myVcs.createWCClient() + .doGetProperty(new File(myWorkingCopyDir.getPath()), "svn:mergeinfo", SVNRevision.UNDEFINED, SVNRevision.WORKING); + System.out.println(data.getValue().getString()); + Assert.assertEquals("/branches/b1:" + (numberBefore + 1), data.getValue().getString()); + } + + // this test is mainly to check revisions selection. at the moment we are not sure whether we support + // trunk->b1->b2 merges between trunk and b2 + @Test + public void testSelectRevisionsWithQuickSelect() throws Exception { + // get revision # + final SVNInfo info = myVcs.createWCClient().doInfo(new File(myBranchTree.myS1File.getPath()), SVNRevision.WORKING); + Assert.assertNotNull(info); + + final long numberBefore = info.getRevision().getNumber(); + final int totalChanges = 3; + + final StringBuilder sb = new StringBuilder(FileUtil.loadFile(new File(myBranchTree.myS1File.getPath()))); + for (int i = 0; i < totalChanges; i++) { + sb.append("\nedited in branch ").append(i); + editFileInCommand(myProject, myBranchTree.myS1File, sb.toString()); + runInAndVerifyIgnoreOutput(myBranchRoot, "ci", "-m", "change in branch " + i, myBranchTree.myS1File.getPath()); + Thread.sleep(10); + } + + // before copy + final SVNInfo info2 = myVcs.createWCClient().doInfo(new File(myBranchTree.myS1File.getPath()), SVNRevision.WORKING); + Assert.assertNotNull(info2); + final long numberBeforeCopy = info2.getRevision().getNumber(); + + runInAndVerifyIgnoreOutput("copy", "-q", "-m", "copy1", myBranchUrl, myRepoUrl + "/branches/b2"); + + // switch b1 to b2 + runInAndVerifyIgnoreOutput(myBranchRoot, "switch", myRepoUrl + "/branches/b2", myBranchRoot.getPath()); + myBranchTree = new SubTree(myBranchVf); //reload + + // one commit in b2 in s2 file + editFileInCommand(myProject, myBranchTree.myS2File, "completely changed"); + runInAndVerifyIgnoreOutput(myBranchRoot, "ci", "-m", "change in b2", myBranchTree.myS2File.getPath()); + + // we should get exactly 2 revisions for selection (copy and change in b2) + final WCInfo found = getWcInfo(); + final QuickMerge quickMerge = + new QuickMerge(myProject, myRepoUrl + "/branches/b2", found, SVNPathUtil.tail(myRepoUrl + "/branches/b2"), myWorkingCopyDir); + // by default merges all + final AtomicReference<String> selectionError = new AtomicReference<String>(); + final QuickMergeTestInteraction testInteraction = new QuickMergeTestInteraction() { + @Override + public boolean shouldReintegrate(@NotNull String sourceUrl, @NotNull String targetUrl) { + return true; + } + + @Override + public List<CommittedChangeList> showRecentListsForSelection(@NotNull List<CommittedChangeList> list, + @NotNull String mergeTitle, + @NotNull MergeChecker mergeChecker, + @NotNull PairConsumer<Long, MergeDialogI> loader, + boolean everyThingLoaded) { + if (list.size() != 2) { + selectionError.set("List size: " + list.size()); + } else if (list.get(1).getNumber() != numberBeforeCopy + 1) { + selectionError.set("wrong revision for copy statement: " + list.get(1).getNumber()); + } + return new SmartList<CommittedChangeList>(list.get(0)); // get a change + } + }; + testInteraction.setMergeVariant(QuickMergeContentsVariants.showLatest); + final WaitingTaskDescriptor descriptor = new WaitingTaskDescriptor(); + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + quickMerge.execute(testInteraction, descriptor); + } + }); + descriptor.waitForCompletion(); + testInteraction.throwIfExceptions(); + if (selectionError.get() != null){ + throw new RuntimeException(selectionError.get()); + } + + Assert.assertTrue(descriptor.isCompleted()); + + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + // should have changed svn:mergeinfo on wc root and s1 file + final Change fileChange = myChangeListManager.getChange(myTree.myS2File); + Assert.assertNotNull(fileChange); + Assert.assertEquals(FileStatus.MODIFIED, fileChange.getFileStatus()); + + final Change dirChange = myChangeListManager.getChange(myWorkingCopyDir); + Assert.assertNotNull(dirChange); + Assert.assertEquals(FileStatus.MODIFIED, dirChange.getFileStatus()); + + final SVNPropertyData data = myVcs.createWCClient() + .doGetProperty(new File(myWorkingCopyDir.getPath()), "svn:mergeinfo", SVNRevision.UNDEFINED, SVNRevision.WORKING); + System.out.println(data.getValue().getString()); + Assert.assertEquals("/branches/b2:" + (numberBeforeCopy + 2), data.getValue().getString()); + } + + @Test + public void testSelectRevisions() throws Exception { + // get revision # + final SVNInfo info = myVcs.createWCClient().doInfo(new File(myBranchTree.myS1File.getPath()), SVNRevision.WORKING); + Assert.assertNotNull(info); + + final long numberBefore = info.getRevision().getNumber(); + final int totalChanges = 10; + + final StringBuilder sb = new StringBuilder(FileUtil.loadFile(new File(myBranchTree.myS1File.getPath()))); + for (int i = 0; i < totalChanges; i++) { + sb.append("\nedited in branch ").append(i); + editFileInCommand(myProject, myBranchTree.myS1File, sb.toString()); + runInAndVerifyIgnoreOutput(myBranchRoot, "ci", "-m", "change in branch " + i, myBranchTree.myS1File.getPath()); + Thread.sleep(10); + } + + final WCInfo found = getWcInfo(); + final QuickMerge quickMerge = + new QuickMerge(myProject, myBranchUrl, found, SVNPathUtil.tail(myBranchUrl), myWorkingCopyDir); + // by default merges all + final QuickMergeTestInteraction testInteraction = new QuickMergeTestInteraction() { + @Override + public boolean shouldReintegrate(@NotNull String sourceUrl, @NotNull String targetUrl) { + return true; + } + + @NotNull + @Override + public SelectMergeItemsResult selectMergeItems(final List<CommittedChangeList> lists, + String mergeTitle, + MergeChecker mergeChecker) { + return new SelectMergeItemsResult() { + @Override + public QuickMergeContentsVariants getResultCode() { + return QuickMergeContentsVariants.select; + } + + @Override + public List<CommittedChangeList> getSelectedLists() { + final List<CommittedChangeList> result = new ArrayList<CommittedChangeList>(); + for (CommittedChangeList list : lists) { + if (numberBefore + 1 == list.getNumber() || numberBefore + 2 == list.getNumber()) { + result.add(list); + } + } + return result; + } + }; + } + }; + testInteraction.setMergeVariant(QuickMergeContentsVariants.select); + final WaitingTaskDescriptor descriptor = new WaitingTaskDescriptor(); + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + quickMerge.execute(testInteraction, descriptor); + } + }); + descriptor.waitForCompletion(); + testInteraction.throwIfExceptions(); + + Assert.assertTrue(descriptor.isCompleted()); + + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + // should have changed svn:mergeinfo on wc root and s1 file + final Change fileChange = myChangeListManager.getChange(myTree.myS1File); + Assert.assertNotNull(fileChange); + Assert.assertEquals(FileStatus.MODIFIED, fileChange.getFileStatus()); + + final Change dirChange = myChangeListManager.getChange(myWorkingCopyDir); + Assert.assertNotNull(dirChange); + Assert.assertEquals(FileStatus.MODIFIED, dirChange.getFileStatus()); + + final SVNPropertyData data = myVcs.createWCClient() + .doGetProperty(new File(myWorkingCopyDir.getPath()), "svn:mergeinfo", SVNRevision.UNDEFINED, SVNRevision.WORKING); + System.out.println(data.getValue().getString()); + Assert.assertEquals("/branches/b1:" + (numberBefore + 1) + "-" + (numberBefore + 2), data.getValue().getString()); + } + + private WCInfo getWcInfo() { + WCInfo found = null; + final File workingIoFile = new File(myWorkingCopyDir.getPath()); + final List<WCInfo> infos = myVcs.getAllWcInfos(); + for (WCInfo info : infos) { + if (FileUtil.filesEqual(workingIoFile, new File(info.getPath()))) { + found = info; + break; + } + } + Assert.assertNotNull(found); + return found; + } + + @Test + public void testSimpleMergeFromTrunkToB1() throws Exception { + // change in trunk + editFileInCommand(myProject, myTree.myS1File, "903403240328"); + final File workingIoFile = new File(myWorkingCopyDir.getPath()); + runInAndVerifyIgnoreOutput(workingIoFile, "ci", "-m", "change in trunk", myTree.myS1File.getPath()); + + final String trunkUrl = myRepoUrl + "/trunk"; + // switch this copy to b1 + runInAndVerifyIgnoreOutput(workingIoFile, "switch", myBranchUrl, workingIoFile.getPath()); + myTree = new SubTree(myWorkingCopyDir); //reload + + refreshSvnMappingsSynchronously(); + final WCInfo found = getWcInfo(); + final QuickMerge quickMerge = + new QuickMerge(myProject, trunkUrl, found, SVNPathUtil.tail(trunkUrl), myWorkingCopyDir); + // by default merges all + final QuickMergeTestInteraction testInteraction = new QuickMergeTestInteraction(); + final WaitingTaskDescriptor descriptor = new WaitingTaskDescriptor(); + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + quickMerge.execute(testInteraction, descriptor); + } + }); + descriptor.waitForCompletion(); + testInteraction.throwIfExceptions(); + + Assert.assertTrue(descriptor.isCompleted()); + + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + // should have changed svn:mergeinfo on wc root and s1 file + final Change fileChange = myChangeListManager.getChange(myTree.myS1File); + Assert.assertNotNull(fileChange); + Assert.assertEquals(FileStatus.MODIFIED, fileChange.getFileStatus()); + + final Change dirChange = myChangeListManager.getChange(myWorkingCopyDir); + Assert.assertNotNull(dirChange); + Assert.assertEquals(FileStatus.MODIFIED, dirChange.getFileStatus()); + } + + private static class WaitingTaskDescriptor extends TaskDescriptor { + private static final long TEST_TIMEOUT = TimeUnit.MINUTES.toMillis(200); + private final Semaphore mySemaphore; + private volatile boolean myCompleted = false; + private volatile boolean myCanceled = false; + + public WaitingTaskDescriptor() { + super("waiting", Where.POOLED); + mySemaphore = new Semaphore(); + mySemaphore.down(); + } + + // will survive in Continuation if cancel occurred + @Override + public boolean isHaveMagicCure() { + return true; + } + + @Override + public void run(ContinuationContext context) { + myCompleted = true; + mySemaphore.up(); + } + + public void waitForCompletion() { + mySemaphore.waitFor(TEST_TIMEOUT); + } + + @Override + public void canceled() { + myCanceled = true; + mySemaphore.up(); + } + + private boolean isCompleted() { + return myCompleted; + } + + private boolean isCanceled() { + return myCanceled; + } + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnRenameTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnRenameTest.java index 486d97823815..c86ed0cc04aa 100644 --- a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnRenameTest.java +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnRenameTest.java @@ -284,6 +284,7 @@ public class SvnRenameTest extends Svn17TestCase { // IDEA-13824 @Test public void testRenameFileRenameDir() throws Exception { + setNativeAcceleration(true); //todo debug final VirtualFile child = prepareDirectoriesForRename(); final VirtualFile f = child.findChild("a.txt"); renameFileInCommand(f, "anew.txt"); diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn16/SvnExternalCommitNoticedTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn16/SvnExternalCommitNoticedTest.java index 079ca66955d8..b686c94df7e4 100644 --- a/plugins/svn4idea/testSource/org/jetbrains/idea/svn16/SvnExternalCommitNoticedTest.java +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn16/SvnExternalCommitNoticedTest.java @@ -121,6 +121,7 @@ public class SvnExternalCommitNoticedTest extends Svn17TestCase { runInAndVerifyIgnoreOutput("switch", branchUrl + "/root/source/s1.txt", tree.myS1File.getPath()); runInAndVerifyIgnoreOutput("switch", branchUrl + "/root/target", tree.myTargetDir.getPath()); + sleep(50); myWorkingCopyDir.refresh(false, true); imitateEvent(myWorkingCopyDir); // no dirty scope externally provided! just VFS refresh |