diff --git a/bin/log.xml b/bin/log.xml
index a156e2755b79..f4abeac31a16 100644
--- a/bin/log.xml
+++ b/bin/log.xml
@@ -52,6 +52,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/build/scripts/layouts.gant b/build/scripts/layouts.gant
index c1fc719fd3f2..ae9a76c37346 100644
--- a/build/scripts/layouts.gant
+++ b/build/scripts/layouts.gant
@@ -240,6 +240,9 @@ public def layoutCommunityPlugins(String home) {
}
}
+ layoutPlugin("svn4ideaOld") {
+ fileset(dir: "$home/plugins/svn4ideaOld/lib", excludes: "**/svnkitsrc.zip")
+ }
layoutPlugin("svn4idea") {
fileset(dir: "$home/plugins/svn4idea/lib", excludes: "**/svnkitsrc.zip")
}
diff --git a/plugins/svn4idea/README.txt b/plugins/svn4idea/README.txt
deleted file mode 100644
index 69d82cef44b4..000000000000
--- a/plugins/svn4idea/README.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-This Idea plugin integrates Idea with Subversion.
-
-The plugin tries to be as tightly integrated with Idea as possible.
-
-Like the CVS integration it uses Idea's project view to display the workspace.
-Changed files are color coded in the project view and the editor just as in the CVS integration.
-
-All files & directories in the project directory, that are in the subversion repository are tracked during
-move and rename operations.
-
-Files & directories created through Idea in the projects source path are automatically tracked.
-You can configure whether the new files shall be added to the
-subversion repository (Always, Ask, Never).
-
-Additional directories to be tracked for newly created files can be entered in
-the "Additional Path" list on the configuration page.
-
-Files & directories in a subversion repository which are deleted through Idea are automatically tracked.
-You can configure whether the deleted files shall also be deleted from the subversion repository (Always, Ask, Never).
-
-Subversion operations can be trieggered from the Subversion menu (tools/Subversion) or from the context menu.
-
-For exended subversion operations (administration, blaming, properties etc) you can start the SvnUp
-dialog from the Subversion menu (tools/Subversion/SvnUp).
diff --git a/plugins/svn4idea/lib/LICENSE-ANTLR.txt b/plugins/svn4idea/lib/LICENSE-ANTLR.txt
new file mode 100644
index 000000000000..67e047c05e9f
--- /dev/null
+++ b/plugins/svn4idea/lib/LICENSE-ANTLR.txt
@@ -0,0 +1,26 @@
+[The "BSD licence"]
+Copyright (c) 2003-2008 Terence Parr
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/plugins/svn4idea/lib/LICENSE-JAVAHL.txt b/plugins/svn4idea/lib/LICENSE-JAVAHL.txt
new file mode 100644
index 000000000000..9c3e114d766c
--- /dev/null
+++ b/plugins/svn4idea/lib/LICENSE-JAVAHL.txt
@@ -0,0 +1,55 @@
+This license applies to all portions of Subversion which are not
+externally-maintained libraries (e.g. apr/, apr-util/, and neon/).
+Such libraries have their own licenses; we recommend you read them, as
+their terms may differ from the terms below.
+
+This is version 1 of this license. It is also available online at
+http://subversion.tigris.org/license-1.html. If newer versions of
+this license are posted there (the same URL, but with the version
+number incremented: .../license-2.html, .../license-3.html, and so
+on), you may use a newer version instead, at your option.
+
+====================================================================
+Copyright (c) 2000-2005 CollabNet. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+3. The end-user documentation included with the redistribution, if
+any, must include the following acknowledgment: "This product includes
+software developed by CollabNet (http://www.Collab.Net/)."
+Alternately, this acknowledgment may appear in the software itself, if
+and wherever such third-party acknowledgments normally appear.
+
+4. The hosted project names must not be used to endorse or promote
+products derived from this software without prior written
+permission. For written permission, please contact info@collab.net.
+
+5. Products derived from this software may not use the "Tigris" name
+nor may "Tigris" appear in their names without prior written
+permission of CollabNet.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL COLLABNET OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+====================================================================
+
+This software consists of voluntary contributions made by many
+individuals on behalf of CollabNet.
+
diff --git a/plugins/svn4idea/lib/LICENSE-JNA.txt b/plugins/svn4idea/lib/LICENSE-JNA.txt
new file mode 100644
index 000000000000..b1a63d1686d1
--- /dev/null
+++ b/plugins/svn4idea/lib/LICENSE-JNA.txt
@@ -0,0 +1,11 @@
+Copyright (c) 2008 Timothy Wall, All Rights Reserved
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
\ No newline at end of file
diff --git a/plugins/svn4idea/lib/LICENSE-SEQUENCE.txt b/plugins/svn4idea/lib/LICENSE-SEQUENCE.txt
new file mode 100644
index 000000000000..35b7847f302d
--- /dev/null
+++ b/plugins/svn4idea/lib/LICENSE-SEQUENCE.txt
@@ -0,0 +1,48 @@
+Sequence Library License
+
+This license applies to all portions of the Sequence library, which
+are not externally-maintained libraries (e.g. junit or jsch).
+====================================================================
+Copyright (c) 2000-2008 SyntEvo GmbH, Ainring, GERMANY.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+3. The end-user documentation included with the redistribution, if
+any, must include the following acknowledgment: "This product includes
+software developed by SyntEvo GmbH, Ainring, GERMANY."
+Alternately, this acknowledgment may appear in the software itself, if
+and wherever such third-party acknowledgments normally appear.
+
+4. The hosted project names must not be used to endorse or promote
+products derived from this software without prior written
+permission. For written permission, please contact
+info@syntevo.com.
+
+5. Neither the name of SyntEvo GmbH nor the names of its contributors
+may be used to endorse or promote products derived from this
+software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL SyntEvo GmbH OR HIS CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+====================================================================
+
+
diff --git a/plugins/svn4idea/lib/LICENSE-SQLJET.txt b/plugins/svn4idea/lib/LICENSE-SQLJET.txt
new file mode 100644
index 000000000000..1f637d44bde4
--- /dev/null
+++ b/plugins/svn4idea/lib/LICENSE-SQLJET.txt
@@ -0,0 +1,19 @@
+Copyright (C) 2009-2010 TMate Software Ltd
+
+As part of SVNKit library, SQLJet library is distributed under
+the terms of TMate Open Source License (see http://svnkit.com/license.html).
+Otherwise, outside of SVNKit library, the following licensing terms
+are applicable for SQLJet library:
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; version 2 of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+For information on how to redistribute this software under
+the terms of a license other than GNU General Public License
+contact TMate Software at support@sqljet.com
\ No newline at end of file
diff --git a/plugins/svn4idea/lib/LICENSE-TRILEAD.txt b/plugins/svn4idea/lib/LICENSE-TRILEAD.txt
new file mode 100644
index 000000000000..579538537a7c
--- /dev/null
+++ b/plugins/svn4idea/lib/LICENSE-TRILEAD.txt
@@ -0,0 +1,88 @@
+Copyright (c) 2007-2008 Trilead AG (http://www.trilead.com)
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+a.) Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+b.) Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+c.) Neither the name of Trilead nor the names of its contributors may
+ be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+Trilead SSH-2 for Java includes code that was written by Dr. Christian Plattner
+during his PhD at ETH Zurich. The license states the following:
+
+Copyright (c) 2005 - 2006 Swiss Federal Institute of Technology (ETH Zurich),
+ Department of Computer Science (http://www.inf.ethz.ch),
+ Christian Plattner. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+a.) Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+b.) Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+c.) Neither the name of ETH Zurich nor the names of its contributors may
+ be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+The Java implementations of the AES, Blowfish and 3DES ciphers have been
+taken (and slightly modified) from the cryptography package released by
+"The Legion Of The Bouncy Castle".
+
+Their license states the following:
+
+Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle
+(http://www.bouncycastle.org)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/plugins/svn4idea/lib/antlr.jar b/plugins/svn4idea/lib/antlr.jar
new file mode 100644
index 000000000000..b0a9ea69f5c2
Binary files /dev/null and b/plugins/svn4idea/lib/antlr.jar differ
diff --git a/plugins/svn4idea/lib/httpclient-4.1.1.jar b/plugins/svn4idea/lib/httpclient-4.1.1.jar
new file mode 100644
index 000000000000..74121b710397
Binary files /dev/null and b/plugins/svn4idea/lib/httpclient-4.1.1.jar differ
diff --git a/plugins/svn4idea/lib/httpcore-4.1.jar b/plugins/svn4idea/lib/httpcore-4.1.jar
new file mode 100644
index 000000000000..a357c076c48f
Binary files /dev/null and b/plugins/svn4idea/lib/httpcore-4.1.jar differ
diff --git a/plugins/svn4idea/lib/sqljet.jar b/plugins/svn4idea/lib/sqljet.jar
new file mode 100644
index 000000000000..4be2213ede2a
Binary files /dev/null and b/plugins/svn4idea/lib/sqljet.jar differ
diff --git a/plugins/svn4idea/lib/svnkit.jar b/plugins/svn4idea/lib/svnkit.jar
index 53d6c9230c4c..f5e912a4b4a2 100644
Binary files a/plugins/svn4idea/lib/svnkit.jar and b/plugins/svn4idea/lib/svnkit.jar differ
diff --git a/plugins/svn4idea/lib/svnkitsrc.zip b/plugins/svn4idea/lib/svnkitsrc.zip
index d4d9454286e4..1034de64a92b 100644
Binary files a/plugins/svn4idea/lib/svnkitsrc.zip and b/plugins/svn4idea/lib/svnkitsrc.zip differ
diff --git a/plugins/svn4idea/lib/trilead.jar b/plugins/svn4idea/lib/trilead.jar
index 6de76b9ff807..5ba0ce62b6ac 100644
Binary files a/plugins/svn4idea/lib/trilead.jar and b/plugins/svn4idea/lib/trilead.jar differ
diff --git a/plugins/svn4idea/src/META-INF/plugin.xml b/plugins/svn4idea/src/META-INF/plugin.xml
index 91272191afc3..39a63696e8d7 100644
--- a/plugins/svn4idea/src/META-INF/plugin.xml
+++ b/plugins/svn4idea/src/META-INF/plugin.xml
@@ -1,139 +1,139 @@
- Subversion Integration
- Subversion
+ Subversion 1.7 Experimental Integration
+ Subversion 1.71.0VCS Integrationcom.intellij.modules.vcsJetBrains
- org.jetbrains.idea.svn.SvnBundle
+ org.jetbrains.idea.svn17.SvnBundle
- org.jetbrains.idea.svn.SvnFileUrlMappingImpl
+ org.jetbrains.idea.svn17.SvnFileUrlMappingImpl17
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
+
-
+
-
-
-
+
+
+
-
+
-
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
+
-
+
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/ConflictState.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ConflictState.java
new file mode 100644
index 000000000000..ab2d5c40cb9d
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ConflictState.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.util.IconLoader;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+public enum ConflictState {
+ none(false, false, false, null),
+ tree(true, false, false, "/icons/conflictc.png"),
+ text(false, true, false, "/icons/conflictt.png"),
+ prop(false, false, true, "/icons/conflictp.png"),
+ tree_text(true, true, false, "/icons/conflictct.png"), // ? -
+ tree_prop(true, false, true, "/icons/conflictcp.png"), // now falls but marked
+ text_prop(false, true, true, "/icons/conflicttp.png"),
+ all3(true, true, true, "/icons/conflictctp.png"); // ? -
+
+ private final boolean myTree;
+ private final boolean myText;
+ private final boolean myProperty;
+ @Nullable
+ private final Icon myIcon;
+ private final String myDescription;
+
+ private ConflictState(final boolean tree, final boolean text, final boolean property, final String iconPath) {
+ myTree = tree;
+ myText = text;
+ myProperty = property;
+
+ if (iconPath != null) {
+ myIcon = IconLoader.getIcon(iconPath);
+ } else {
+ myIcon = null;
+ }
+
+ myDescription = createDescription();
+ }
+
+ @Nullable
+ private String createDescription() {
+ int cnt = 0;
+ final StringBuilder sb = new StringBuilder();
+ cnt = checkOne(myTree, cnt, sb, "tree");
+ cnt = checkOne(myText, cnt, sb, "text");
+ cnt = checkOne(myProperty, cnt, sb, "property");
+ if (cnt == 0) {
+ return null;
+ }
+ return sb.toString();
+ }
+
+ private int checkOne(final boolean value, final int init, final StringBuilder sb, final String text) {
+ if (value) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append(text);
+ return init + 1;
+ }
+ return init;
+ }
+
+ public boolean isTree() {
+ return myTree;
+ }
+
+ public boolean isText() {
+ return myText;
+ }
+
+ public boolean isProperty() {
+ return myProperty;
+ }
+
+ public boolean isConflict() {
+ return myProperty || myText || myTree;
+ }
+
+ @Nullable
+ public Icon getIcon() {
+ return myIcon;
+ }
+
+ public String getDescription() {
+ return myDescription;
+ }
+
+ public static ConflictState mergeState(final ConflictState leftState, final ConflictState rightState) {
+ return getInstance(leftState.myTree | rightState.myTree, leftState.myText | rightState.myText,
+ leftState.myProperty | rightState.myProperty);
+ }
+
+ public static ConflictState getInstance(final boolean tree, final boolean text, final boolean property) {
+ final ConflictState[] conflictStates = values();
+ for (ConflictState state : conflictStates) {
+ if ((state.isTree() == tree) && (state.isText() == text) && (state.isProperty() == property)) {
+ return state;
+ }
+ }
+ // all combinations are defined
+ assert false;
+ return null;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/ConflictedSvnChange.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ConflictedSvnChange.java
new file mode 100644
index 000000000000..b090aadd315a
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ConflictedSvnChange.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.vcs.FilePath;
+import com.intellij.openapi.vcs.FileStatus;
+import com.intellij.openapi.vcs.changes.Change;
+import com.intellij.openapi.vcs.changes.ContentRevision;
+
+import javax.swing.*;
+
+public class ConflictedSvnChange extends Change {
+ private final ConflictState myConflictState;
+ // +-
+ private final FilePath myTreeConflictMarkHolder;
+
+ public ConflictedSvnChange(ContentRevision beforeRevision, ContentRevision afterRevision, final ConflictState conflictState,
+ final FilePath treeConflictMarkHolder) {
+ super(beforeRevision, afterRevision);
+ myConflictState = conflictState;
+ myTreeConflictMarkHolder = treeConflictMarkHolder;
+ }
+
+ public ConflictedSvnChange(ContentRevision beforeRevision, ContentRevision afterRevision, FileStatus fileStatus,
+ final ConflictState conflictState, final FilePath treeConflictMarkHolder) {
+ super(beforeRevision, afterRevision, fileStatus);
+ myConflictState = conflictState;
+ myTreeConflictMarkHolder = treeConflictMarkHolder;
+ }
+
+ public ConflictState getConflictState() {
+ return myConflictState;
+ }
+
+ @Override
+ public Icon getAdditionalIcon() {
+ return myConflictState.getIcon();
+ }
+
+ @Override
+ public String getDescription() {
+ final String description = myConflictState.getDescription();
+ if (description != null) {
+ return SvnBundle.message("svn.changeview.item.in.conflict.text", description);
+ }
+ return description;
+ }
+
+ public FilePath getTreeConflictMarkHolder() {
+ return myTreeConflictMarkHolder;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/CopiesRefresh.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/CopiesRefresh.java
new file mode 100644
index 000000000000..21617ae3284f
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/CopiesRefresh.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+public interface CopiesRefresh {
+ void ensureInit();
+ void asynchRequest();
+ void synchRequest();
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/DefendedCopiesRefreshProxy.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/DefendedCopiesRefreshProxy.java
new file mode 100644
index 000000000000..6f56f2bcbd5b
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/DefendedCopiesRefreshProxy.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+public class DefendedCopiesRefreshProxy implements CopiesRefresh {
+ private final ReentranceDefence myDefence;
+
+ private final MyEnsureInit myEnsureInit;
+ private final MyAsynchRequest myAsynchRequest;
+ private final MySynchRequest mySynchRequest;
+
+ public DefendedCopiesRefreshProxy(final CopiesRefresh refresh) {
+ myDefence = new ReentranceDefence();
+
+ myEnsureInit = new MyEnsureInit(refresh);
+ myAsynchRequest = new MyAsynchRequest(refresh);
+ mySynchRequest = new MySynchRequest(refresh);
+ }
+
+ public void ensureInit() {
+ ReentranceDefence.executeReentrant(myDefence, myEnsureInit);
+ }
+
+ public void asynchRequest() {
+ ReentranceDefence.executeReentrant(myDefence, myAsynchRequest);
+ }
+
+ public void synchRequest() {
+ ReentranceDefence.executeReentrant(myDefence, mySynchRequest);
+ }
+
+ public Runnable proxyRefresher(final Runnable runnable) {
+ return new Runnable() {
+ public void run() {
+ myDefence.executeOtherDefended(runnable);
+ }
+ };
+ }
+
+ private static class MyAsynchRequest extends MyBase {
+ private MyAsynchRequest(final CopiesRefresh delegate) {
+ super(delegate);
+ }
+
+ public Boolean executeMe() {
+ myDelegate.asynchRequest();
+ return null;
+ }
+ }
+
+ private static class MySynchRequest extends MyBase {
+ private MySynchRequest(final CopiesRefresh delegate) {
+ super(delegate);
+ }
+
+ public Boolean executeMe() {
+ myDelegate.synchRequest();
+ return null;
+ }
+ }
+
+ private static class MyEnsureInit extends MyBase {
+ private MyEnsureInit(final CopiesRefresh delegate) {
+ super(delegate);
+ }
+
+ public Boolean executeMe() {
+ myDelegate.ensureInit();
+ return null;
+ }
+ }
+
+ private abstract static class MyBase implements ReentranceDefence.MyControlled {
+ protected final CopiesRefresh myDelegate;
+
+ protected MyBase(final CopiesRefresh delegate) {
+ myDelegate = delegate;
+ }
+
+ public Boolean executeMeSimple() {
+ return null;
+ }
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/DepthCombo.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/DepthCombo.java
new file mode 100644
index 000000000000..d54bee67d7b4
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/DepthCombo.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import org.tmatesoft.svn.core.SVNDepth;
+
+import javax.swing.*;
+
+public class DepthCombo extends JComboBox {
+ public DepthCombo(final boolean forUpdate) {
+ super(forUpdate ? ourForUpdate : ourForCheckout);
+ setSelectedIndex(forUpdate ? 0 : 3);
+ setEditable(false);
+ setToolTipText(SvnBundle.message("label.depth.description"));
+ }
+
+ public SVNDepth getDepth() {
+ return ((SVNDepthWithName) super.getSelectedItem()).getDepth();
+ }
+
+ private final static SVNDepthWithName [] ourForUpdate = {new SVNDepthWithName(SVNDepth.UNKNOWN, "working copy"),
+ new SVNDepthWithName(SVNDepth.EMPTY), new SVNDepthWithName(SVNDepth.FILES), new SVNDepthWithName(SVNDepth.IMMEDIATES),
+ new SVNDepthWithName(SVNDepth.INFINITY)};
+ private final static SVNDepthWithName [] ourForCheckout = {
+ new SVNDepthWithName(SVNDepth.EMPTY), new SVNDepthWithName(SVNDepth.FILES), new SVNDepthWithName(SVNDepth.IMMEDIATES),
+ new SVNDepthWithName(SVNDepth.INFINITY)};
+
+ private static class SVNDepthWithName {
+ private final SVNDepth myDepth;
+ private final String myName;
+
+ private SVNDepthWithName(SVNDepth depth) {
+ myDepth = depth;
+ myName = myDepth.toString();
+ }
+
+ private SVNDepthWithName(SVNDepth depth, String name) {
+ myDepth = depth;
+ myName = name;
+ }
+
+ @Override
+ public String toString() {
+ return myName;
+ }
+
+ public SVNDepth getDepth() {
+ return myDepth;
+ }
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/FilePathUtil.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/FilePathUtil.java
new file mode 100644
index 000000000000..5d35951cd53b
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/FilePathUtil.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2010 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.svn17;
+
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vcs.FilePath;
+
+import java.io.File;
+import java.util.Collection;
+
+public class FilePathUtil {
+ private FilePathUtil() {
+ }
+
+ public static boolean isNested(final Collection roots, final FilePath root) {
+ return isNested(roots, root.getIOFile());
+ }
+
+ public static boolean isNested(final Collection roots, final File root) {
+ for (FilePath filePath : roots) {
+ final File ioFile = filePath.getIOFile();
+ if (ioFile.equals(root)) continue;
+ if (FileUtil.isAncestor(ioFile, root, true)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/ForNestedRootChecker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ForNestedRootChecker.java
new file mode 100644
index 000000000000..16e1811fb09c
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ForNestedRootChecker.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.util.Getter;
+import com.intellij.openapi.vcs.impl.VcsRootIterator;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.Nullable;
+import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil;
+import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
+import org.tmatesoft.svn.core.wc.SVNInfo;
+import org.tmatesoft.svn.core.wc.SVNWCClient;
+
+import java.util.*;
+
+public class ForNestedRootChecker {
+ static class DirInfo {
+ private final VirtualFile myFile;
+ private final String myUrl;
+
+ DirInfo(final VirtualFile file, final String url) {
+ myFile = file;
+ myUrl = url;
+ }
+
+ public VirtualFile getFile() {
+ return myFile;
+ }
+
+ public String getUrl() {
+ return myUrl;
+ }
+ }
+
+ private static class UrlConstructor {
+ final SvnVcs17 myVcs;
+ final SVNWCClient myClient;
+
+ private UrlConstructor(final SvnVcs17 vcs) {
+ myVcs = vcs;
+ myClient = myVcs.createWCClient();
+ }
+
+ @Nullable
+ public Real createReal(final VirtualFile file, final VirtualFile vcsRoot) {
+ final SVNInfo info = myVcs.getInfo(file);
+ if (info == null || info.getRepositoryRootURL() == null || info.getURL() == null) {
+ return null;
+ }
+ return new Real(file, info, vcsRoot);
+ }
+
+ public Node createReplaceable(final VirtualFile file) {
+ return new Node(file, null);
+ }
+
+ public Node createSupposed(final Real parent, final VirtualFile child) {
+ return new Node(child, getForChild(parent.getUrl(), child.getName()));
+ }
+
+ public boolean replaceWithReal(final Real real, final Node supposed) {
+ return supposed.getUrl() == null || ((supposed.getUrl() != null) && (! supposed.getUrl().equals(real.getUrl())));
+ }
+
+ @Nullable
+ private String getSupposedUrl(final String parentUrl, final List subpath) {
+ if (parentUrl == null) return null;
+ final StringBuilder sb = new StringBuilder();
+ for (String s : subpath) {
+ sb.append(s).append('/');
+ }
+ if (sb.length() > 0) {
+ sb.deleteCharAt(sb.length() - 1);
+ }
+ return SVNPathUtil.append(parentUrl, sb.toString());
+ }
+
+ public String getForChild(final String parentUrl, final String childName) {
+ return parentUrl == null ? null : SVNPathUtil.append(parentUrl, SVNEncodingUtil.uriEncode(childName));
+ }
+ }
+
+ public static void filterOutSuperfluousChildren(final SvnVcs17 vcs, final List list,
+ final List result) {
+ final UrlConstructor constructor = new UrlConstructor(vcs);
+
+ Collections.sort(list, new Comparator() {
+ public int compare(final RootUrlPair o1, final RootUrlPair o2) {
+ return o1.getVirtualFile().getPath().compareTo(o2.getVirtualFile().getPath());
+ }
+ });
+
+ for (int i = 0; i < list.size(); i++) {
+ final T child = list.get(i);
+ boolean add = true;
+ for (T parent : result) {
+ if (parent.getVirtualFile().getPath().equals(child.getVirtualFile().getPath())) {
+ add = false;
+ break;
+ }
+ final List subpath = subpathIfAncestor(parent.getVirtualFile(), child.getVirtualFile());
+ if (subpath != null) {
+ // get child's supposed and real urls
+ final String supposed = constructor.getSupposedUrl(parent.getUrl(), subpath);
+ if (supposed.equals(child.getUrl())) {
+ add = false;
+ break;
+ }
+ }
+ }
+ if (add) {
+ result.add(child);
+ }
+ }
+ }
+
+ @Nullable
+ private static List subpathIfAncestor(final VirtualFile parent, final VirtualFile child) {
+ if (! VfsUtil.isAncestor(parent, child, true)) return null;
+
+ final List result = new ArrayList();
+ VirtualFile tmp = child;
+ final String parentPath = parent.getPath();
+ while ((tmp != null) && (! tmp.getPath().equals(parentPath))) {
+ result.add(tmp.getName());
+ tmp = tmp.getParent();
+ }
+ Collections.reverse(result);
+ return result;
+ }
+
+ /*public static List getAllNestedWorkingCopies(final VirtualFile[] roots, final SvnVcs vcs, final boolean goIntoNested) {
+ if (goIntoNested) {
+ FilterDescendantVirtualFiles.filter(Arrays.asList(roots));
+ }
+
+ final VcsRootIterator rootIterator = new VcsRootIterator(vcs.getProject(), vcs);
+ final List result = new ArrayList();
+ for (VirtualFile root : roots) {
+ result.addAll(getForOne(root, vcs, goIntoNested, rootIterator));
+ }
+
+ if (! goIntoNested) {
+ final List filtered = new ArrayList(result.size());
+ filterOutSuperfluousChildren(vcs, result, filtered);
+ return filtered;
+ }
+
+ return result;
+ }*/
+
+ public static List getAllNestedWorkingCopies(final VirtualFile root, final SvnVcs17 vcs, final boolean goIntoNested, final Getter cancelledGetter) {
+ final VcsRootIterator rootIterator = new VcsRootIterator(vcs.getProject(), vcs);
+ return getForOne(root, vcs, goIntoNested, rootIterator, cancelledGetter);
+ }
+
+ private static List getForOne(final VirtualFile root, final SvnVcs17 vcs, final boolean goIntoNested,
+ final VcsRootIterator rootIterator, final Getter cancelledGetter) {
+ final UrlConstructor constructor = new UrlConstructor(vcs);
+ final LinkedList queue = new LinkedList();
+ final LinkedList result = new LinkedList();
+
+ queue.add(constructor.createReplaceable(root));
+ while (! queue.isEmpty()) {
+ final Node node = queue.removeFirst();
+ if (Boolean.TRUE.equals(cancelledGetter.get())) throw new ProcessCanceledException();
+
+ // check self
+ final Real real = constructor.createReal(node.getFile(), root);
+ if (real != null) {
+ if (constructor.replaceWithReal(real, node)) {
+ result.add(real);
+ if (! goIntoNested) continue;
+ }
+ }
+
+ // for next step
+ final VirtualFile file = node.getFile();
+ if (file.isDirectory() && (! SvnUtil.isAdminDirectory(file))) {
+ for (VirtualFile child : file.getChildren()) {
+ if (Boolean.TRUE.equals(cancelledGetter.get())) throw new ProcessCanceledException();
+ if (rootIterator.acceptFolderUnderVcs(root, child)) {
+ if (real == null) {
+ queue.add(constructor.createReplaceable(child));
+ } else {
+ queue.add(constructor.createSupposed(real, child));
+ }
+ }
+ }
+ }
+ }
+ return result;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/FragmentsMerger.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/FragmentsMerger.java
new file mode 100644
index 000000000000..1370bf77f34e
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/FragmentsMerger.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.util.Factory;
+import com.intellij.util.Consumer;
+
+public class FragmentsMerger> {
+ private T myData;
+ private final Object myLock;
+ private final Factory myFactory;
+
+ public FragmentsMerger(final Factory factory) {
+ myFactory = factory;
+ myLock = new Object();
+ myData = myFactory.create();
+ }
+
+ public void add(final U data) {
+ synchronized (myLock) {
+ // only T decides what to do with input, not the external code
+ myData.consume(data);
+ }
+ }
+
+ public T receive() {
+ synchronized (myLock) {
+ final T copy = myData;
+ myData = myFactory.create();
+ return copy;
+ }
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/IdeaSVNConfigFile.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/IdeaSVNConfigFile.java
new file mode 100644
index 000000000000..dc88c1f62ef5
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/IdeaSVNConfigFile.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import org.jetbrains.idea.svn17.config.DefaultProxyGroup;
+import org.jetbrains.idea.svn17.config.ProxyGroup;
+import org.tmatesoft.svn.core.internal.wc.SVNConfigFile;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public class IdeaSVNConfigFile {
+ private final Map myPatternsMap;
+ private final long myLatestUpdate;
+ private final File myFile;
+ private Map myDefaultProperties;
+
+ private final SVNConfigFile mySVNConfigFile;
+
+ private static final String GROUPS_GROUP_NAME = "groups";
+
+ public IdeaSVNConfigFile(final File file) {
+ mySVNConfigFile = new SVNConfigFile(file);
+ myFile = file;
+ myLatestUpdate = -1;
+ myPatternsMap = new HashMap();
+ }
+
+ public void updateGroups() {
+ if (myLatestUpdate != myFile.lastModified()) {
+ myPatternsMap.clear();
+ myPatternsMap.putAll(mySVNConfigFile.getProperties(GROUPS_GROUP_NAME));
+
+ myDefaultProperties = mySVNConfigFile.getProperties(DefaultProxyGroup.DEFAULT_GROUP_NAME);
+ }
+ }
+
+ public Map getAllGroups() {
+ final Map result = new HashMap(myPatternsMap.size());
+ for (Map.Entry entry : myPatternsMap.entrySet()) {
+ final String groupName = entry.getKey();
+ result.put(groupName, new ProxyGroup(groupName, entry.getValue(), mySVNConfigFile.getProperties(groupName)));
+ }
+ return result;
+ }
+
+ public DefaultProxyGroup getDefaultGroup() {
+ return new DefaultProxyGroup(myDefaultProperties);
+ }
+
+ public void deleteGroup(final String name) {
+ // remove all properties
+ final Map properties = mySVNConfigFile.getProperties(name);
+ for (String propertyName : properties.keySet()) {
+ mySVNConfigFile.setPropertyValue(name, propertyName, null, false);
+ }
+ if (DefaultProxyGroup.DEFAULT_GROUP_NAME.equals(name)) {
+ myDefaultProperties.clear();
+ }
+ // remove group from groups
+ mySVNConfigFile.setPropertyValue(GROUPS_GROUP_NAME, name, null, false);
+ mySVNConfigFile.deleteGroup(name, false);
+ }
+
+ public void addGroup(final String name, final String patterns, final Map properties) {
+ mySVNConfigFile.setPropertyValue(GROUPS_GROUP_NAME, name, patterns, false);
+ addProperties(name, properties);
+ }
+
+ private void addProperties(final String groupName, final Map properties) {
+ for (Map.Entry entry : properties.entrySet()) {
+ mySVNConfigFile.setPropertyValue(groupName, entry.getKey(), entry.getValue(), false);
+ }
+ }
+
+ public void modifyGroup(final String name, final String patterns, final Collection delete, final Map addOrModify,
+ final boolean isDefault) {
+ if (! isDefault) {
+ mySVNConfigFile.setPropertyValue(GROUPS_GROUP_NAME, name, patterns, false);
+ }
+ final Map deletedPrepared = new HashMap(delete.size());
+ for (String property : delete) {
+ deletedPrepared.put(property, null);
+ }
+ addProperties(name, deletedPrepared);
+ addProperties(name, addOrModify);
+ }
+
+ public void save() {
+ mySVNConfigFile.save();
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/IdeaSubversionConfigurationDirectory.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/IdeaSubversionConfigurationDirectory.java
new file mode 100644
index 000000000000..1ea25a2f238a
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/IdeaSubversionConfigurationDirectory.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2000-2010 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.svn17;
+
+import com.intellij.openapi.util.SystemInfo;
+import org.tmatesoft.svn.core.wc.SVNWCUtil;
+
+import java.io.File;
+
+public class IdeaSubversionConfigurationDirectory {
+ private IdeaSubversionConfigurationDirectory() {
+ }
+
+ public static String getPath() {
+ final File standard = SVNWCUtil.getDefaultConfigurationDirectory();
+ if (SystemInfo.isWindows) {
+ return standard.getAbsolutePath();
+ }
+ return standard.getParent() + File.separator + standard.getName() + "_IDEA";
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/IgnoredFileInfo.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/IgnoredFileInfo.java
new file mode 100644
index 000000000000..fdc0e6fe5971
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/IgnoredFileInfo.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.diagnostic.Logger;
+import org.tmatesoft.svn.core.SVNDepth;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions;
+import org.tmatesoft.svn.core.wc.ISVNStatusHandler;
+import org.tmatesoft.svn.core.wc.SVNRevision;
+import org.tmatesoft.svn.core.wc.SVNStatus;
+import org.tmatesoft.svn.core.wc.SVNStatusType;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class IgnoredFileInfo {
+ private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.IgnoredFileInfo");
+
+ // directory for which properties are collected
+ private final File myFile;
+ private final List myPatterns;
+ private final Set myFileNames;
+ private final Set myOldPatterns;
+
+ public IgnoredFileInfo(final File file, final Set oldPatterns) {
+ myFile = file;
+ myPatterns = new ArrayList();
+ myFileNames = new HashSet();
+ myOldPatterns = oldPatterns;
+ }
+
+ public void addFileName(final String name) {
+ myFileNames.add(name);
+ }
+
+ public void addPattern(final String value) {
+ myPatterns.add(value);
+ }
+
+ public void calculatePatterns(final SvnVcs17 vcs) {
+ final List names = new ArrayList();
+ try {
+ vcs.createStatusClient().doStatus(myFile, SVNRevision.WORKING, SVNDepth.IMMEDIATES, false, true, true, false, new ISVNStatusHandler() {
+ public void handleStatus(SVNStatus status) throws SVNException {
+ if (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_IGNORED)) {
+ final String name = status.getFile().getName();
+ if (! myFileNames.contains(name)) {
+ names.add(name);
+ }
+ }
+ }
+ }, null);
+ }
+ catch (SVNException e) {
+ LOG.info(e);
+ }
+
+ for (String pattern : myOldPatterns) {
+ boolean usedSomewhereElse = false;
+ for (String name : names) {
+ if (DefaultSVNOptions.matches(pattern, name)) {
+ usedSomewhereElse = true;
+ break;
+ }
+ }
+
+ if (! usedSomewhereElse) {
+ for (String name : myFileNames) {
+ if (DefaultSVNOptions.matches(pattern, name)) {
+ myPatterns.add(pattern);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ public File getFile() {
+ return myFile;
+ }
+
+ public List getPatterns() {
+ return myPatterns;
+ }
+
+ public Set getFileNames() {
+ return myFileNames;
+ }
+
+ public Set getOldPatterns() {
+ return myOldPatterns;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/LimitedStringsList.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/LimitedStringsList.java
new file mode 100644
index 000000000000..9ea6dc6037b2
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/LimitedStringsList.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class LimitedStringsList {
+ private static final int ourMaxTypedUrls = 20;
+ private final List myList;
+
+ public LimitedStringsList(final List list) {
+ myList = new ArrayList((list.size() > ourMaxTypedUrls ? list.subList(0, ourMaxTypedUrls) : list));
+ }
+
+ public void add(final String value) {
+ myList.remove(value);
+
+ if (myList.size() >= ourMaxTypedUrls) {
+ // leave space for 1 to be added
+ final int numToRemove = myList.size() - ourMaxTypedUrls + 1;
+ for (int i = 0; i < numToRemove; i++) {
+ myList.remove(0);
+ }
+ }
+
+ // more recent first
+ myList.add(0, value);
+ }
+
+ public List getList() {
+ return Collections.unmodifiableList(myList);
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/MoveRenameReplaceCheck.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/MoveRenameReplaceCheck.java
new file mode 100644
index 000000000000..9dc1d06896fd
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/MoveRenameReplaceCheck.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.vcs.changes.Change;
+
+public class MoveRenameReplaceCheck {
+ public static boolean check(final Change c) {
+ if (c.getAfterRevision() == null || c.getBeforeRevision() == null) return false;
+ return c.isIsReplaced() || c.isMoved() || c.isRenamed() || (! Comparing.equal(c.getBeforeRevision().getFile(), c.getAfterRevision().getFile()));
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/NestedCopiesBuilder.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/NestedCopiesBuilder.java
new file mode 100644
index 000000000000..529f63e87a9e
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/NestedCopiesBuilder.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.vcs.FilePath;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.wc.SVNStatus;
+import org.tmatesoft.svn.core.wc.SVNStatusType;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class NestedCopiesBuilder implements StatusReceiver {
+ private final Set mySet;
+
+ public NestedCopiesBuilder() {
+ mySet = new HashSet();
+ }
+
+ public void process(final FilePath path, final SVNStatus status, final boolean isInnerCopyRoot) throws SVNException {
+ if ((path.getVirtualFile() != null) && SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_EXTERNAL)) {
+ final MyPointInfo info = new MyPointInfo(path.getVirtualFile(), null, WorkingCopyFormat.UNKNOWN, NestedCopyType.external);
+ mySet.add(info);
+ return;
+ }
+ if ((path.getVirtualFile() == null) || (status.getURL() == null)) return;
+
+ final NestedCopyType type;
+ if (SvnVcs17.svnStatusIsUnversioned(status)) {
+ return;
+ } else if (status.isSwitched()) {
+ type = NestedCopyType.switched;
+ } else if (isInnerCopyRoot) {
+ // will not be changed or modified; can't be switched or external
+ type = NestedCopyType.inner;
+ } else {
+ return;
+ }
+ final MyPointInfo info = new MyPointInfo(path.getVirtualFile(), status.getURL(),
+ WorkingCopyFormat.getInstance(status.getWorkingCopyFormat()), type);
+ mySet.add(info);
+ }
+
+ public void processIgnored(final VirtualFile vFile) {
+ }
+
+ public void processUnversioned(final VirtualFile vFile) {
+ }
+
+ static class MyPointInfo {
+ private final VirtualFile myFile;
+ private SVNURL myUrl;
+ private WorkingCopyFormat myFormat;
+ private final NestedCopyType myType;
+
+ MyPointInfo(@NotNull final VirtualFile file, final SVNURL url, final WorkingCopyFormat format, final NestedCopyType type) {
+ myFile = file;
+ myUrl = url;
+ myFormat = format;
+ myType = type;
+ }
+
+ public void setUrl(SVNURL url) {
+ myUrl = url;
+ }
+
+ public void setFormat(WorkingCopyFormat format) {
+ myFormat = format;
+ }
+
+ public VirtualFile getFile() {
+ return myFile;
+ }
+
+ public SVNURL getUrl() {
+ return myUrl;
+ }
+
+ public WorkingCopyFormat getFormat() {
+ return myFormat;
+ }
+
+ public NestedCopyType getType() {
+ return myType;
+ }
+
+ private String key(final VirtualFile file) {
+ return file.getPath();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ MyPointInfo info = (MyPointInfo)o;
+
+ if (! key(myFile).equals(key(info.myFile))) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return key(myFile).hashCode();
+ }
+ }
+
+ public Set getSet() {
+ return mySet;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/NestedCopiesData.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/NestedCopiesData.java
new file mode 100644
index 000000000000..1b73d5edffbe
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/NestedCopiesData.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.util.Consumer;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class NestedCopiesData implements Consumer> {
+ // we can keep the type here also, but
+ private final Set mySet;
+
+ public NestedCopiesData() {
+ mySet = new HashSet();
+ }
+
+ public void consume(final Set nestedCopyTypeSet) {
+ mySet.addAll(nestedCopyTypeSet);
+ }
+
+ public Set getSet() {
+ return mySet;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/NestedCopiesSink.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/NestedCopiesSink.java
new file mode 100644
index 000000000000..5b8d6db20829
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/NestedCopiesSink.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.util.Factory;
+
+import java.util.Set;
+
+public class NestedCopiesSink extends FragmentsMerger, NestedCopiesData> {
+ public NestedCopiesSink() {
+ super(new Factory() {
+ public NestedCopiesData create() {
+ return new NestedCopiesData();
+ }
+ });
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/NestedCopyType.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/NestedCopyType.java
new file mode 100644
index 000000000000..0519eab01b12
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/NestedCopyType.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+public enum NestedCopyType {
+ external("External"),
+ switched("Switched"),
+ inner("Nested");
+
+ private final String myName;
+
+ NestedCopyType(String name) {
+ myName = name;
+ }
+
+ public String getName() {
+ return myName;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/Node.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/Node.java
new file mode 100644
index 000000000000..08a54eee00e6
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/Node.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.vfs.VirtualFile;
+
+class Node {
+ private final VirtualFile myFile;
+ private final String myUrl;
+
+ Node(final VirtualFile file, final String url) {
+ myFile = file;
+ myUrl = url;
+ }
+
+ public VirtualFile getFile() {
+ return myFile;
+ }
+
+ public String getUrl() {
+ return myUrl;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/ProxySvnAuthentication.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ProxySvnAuthentication.java
new file mode 100644
index 000000000000..74b0a9e3bf31
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ProxySvnAuthentication.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2000-2011 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.svn17;
+
+import org.tmatesoft.svn.core.auth.*;
+
+/**
+ * @author irengrig
+ * Date: 1/24/11
+ * Time: 6:14 PM
+ */
+public class ProxySvnAuthentication {
+ private ProxySvnAuthentication() {
+ }
+
+ public static SVNAuthentication proxy(final SVNAuthentication in, final boolean storeAuth) {
+ if (in.isStorageAllowed() == storeAuth || (! in.isStorageAllowed())) return in;
+
+ final String userName = in.getUserName();
+ if (in instanceof SVNPasswordAuthentication) {
+ return new SVNPasswordAuthentication(userName, ((SVNPasswordAuthentication)in).getPassword(),
+ storeAuth, in.getURL(), in.isPartial());
+ } else if (in instanceof SVNSSHAuthentication) {
+ final SVNSSHAuthentication svnsshAuthentication = (SVNSSHAuthentication)in;
+ if (svnsshAuthentication.hasPrivateKey()) {
+ return new SVNSSHAuthentication(userName, svnsshAuthentication.getPrivateKeyFile(), svnsshAuthentication.getPassphrase(),
+ svnsshAuthentication.getPortNumber(), storeAuth, in.getURL(), in.isPartial());
+ } else {
+ return new SVNSSHAuthentication(userName, svnsshAuthentication.getPassword(), svnsshAuthentication.getPortNumber(),
+ storeAuth, in.getURL(), in.isPartial());
+ }
+ } else if (in instanceof SVNSSLAuthentication) {
+ final SVNSSLAuthentication svnsslAuthentication = (SVNSSLAuthentication)in;
+ if (SVNSSLAuthentication.MSCAPI.equals(svnsslAuthentication.getSSLKind())) {
+ return new SVNSSLAuthentication(SVNSSLAuthentication.MSCAPI, svnsslAuthentication.getAlias(), storeAuth, in.getURL(), in.isPartial());
+ } else {
+ return new SVNSSLAuthentication(svnsslAuthentication.getCertificateFile(), svnsslAuthentication.getPassword(),
+ storeAuth, svnsslAuthentication.getURL(), svnsslAuthentication.isPartial());
+ }
+ } else if (in instanceof SVNUserNameAuthentication) {
+ return new SVNUserNameAuthentication(in.getUserName(), storeAuth, in.getURL(), in.isPartial());
+ }
+ return in;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/Real.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/Real.java
new file mode 100644
index 000000000000..90f3ae8d00b3
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/Real.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import org.tmatesoft.svn.core.wc.SVNInfo;
+
+class Real extends Node implements RootUrlPair {
+ private final SVNInfo myInfo;
+ private final VirtualFile myVcsRoot;
+
+ Real(final VirtualFile file, final SVNInfo info, VirtualFile vcsRoot) {
+ super(file, info.getURL().toString());
+ myInfo = info;
+ myVcsRoot = vcsRoot;
+ }
+
+ public SVNInfo getInfo() {
+ return myInfo;
+ }
+
+ public VirtualFile getVcsRoot() {
+ return myVcsRoot;
+ }
+
+ public VirtualFile getVirtualFile() {
+ return getFile();
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/ReentranceDefence.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ReentranceDefence.java
new file mode 100644
index 000000000000..05e69967e0c9
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ReentranceDefence.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+public class ReentranceDefence {
+ protected final ThreadLocal myThreadLocal;
+
+ protected ReentranceDefence() {
+ myThreadLocal = new ThreadLocal() {
+ @Override
+ protected Boolean initialValue() {
+ return Boolean.TRUE;
+ }
+ };
+ }
+
+ public static T executeReentrant(final ReentranceDefence defence, final MyControlled controlled) {
+ if (defence.isInside()) {
+ return controlled.executeMeSimple();
+ }
+ return controlled.executeMe();
+ }
+
+ public boolean isInside() {
+ return ! Boolean.TRUE.equals(myThreadLocal.get());
+ }
+
+ public boolean isOutside() {
+ return ! isInside();
+ }
+
+ public void executeOtherDefended(final Runnable runnable) {
+ try {
+ myThreadLocal.set(Boolean.FALSE);
+ runnable.run();
+ } finally {
+ myThreadLocal.set(Boolean.TRUE);
+ }
+ }
+
+ public interface MyControlled {
+ T executeMe();
+ T executeMeSimple();
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/RootMixedInfo.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/RootMixedInfo.java
new file mode 100644
index 000000000000..a9bf54a97fe5
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/RootMixedInfo.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import org.tmatesoft.svn.core.SVNURL;
+
+public class RootMixedInfo {
+ /**
+ * working copy root path
+ */
+ private final String myFilePath;
+ private final VirtualFile myFile;
+ /**
+ * url corresponding to working copy root
+ */
+ private final SVNURL myUrl;
+ private final VirtualFile myParentVcsRoot;
+
+ public RootMixedInfo(final String filePath, final VirtualFile file, final SVNURL url, final VirtualFile parentVcsRoot) {
+ myFilePath = filePath;
+ myFile = file;
+ myUrl = url;
+ myParentVcsRoot = parentVcsRoot;
+ }
+
+ public String getFilePath() {
+ return myFilePath;
+ }
+
+ public VirtualFile getFile() {
+ return myFile;
+ }
+
+ public SVNURL getUrl() {
+ return myUrl;
+ }
+
+ public VirtualFile getParentVcsRoot() {
+ return myParentVcsRoot;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/RootUrlInfo.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/RootUrlInfo.java
new file mode 100644
index 000000000000..1f71b1310cd3
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/RootUrlInfo.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import org.tmatesoft.svn.core.SVNURL;
+
+import java.io.File;
+
+public class RootUrlInfo implements RootUrlPair {
+ private final SVNURL myRepositoryUrlUrl;
+ private final String myRepositoryUrl;
+ private final SVNURL myAbsoluteUrlAsUrl;
+ private final WorkingCopyFormat myFormat;
+
+ private final File myIoFile;
+ private final VirtualFile myVfile;
+ // vcs root
+ private final VirtualFile myRoot;
+ private NestedCopyType myType;
+
+ public RootUrlInfo(final SVNURL repositoryUrl, final SVNURL absoluteUrlAsUrl, final WorkingCopyFormat format, final VirtualFile vfile,
+ final VirtualFile root) {
+ myRepositoryUrlUrl = repositoryUrl;
+ myFormat = format;
+ myVfile = vfile;
+ myRoot = root;
+ myIoFile = new File(myVfile.getPath());
+ final String asString = repositoryUrl.toString();
+ myRepositoryUrl = asString.endsWith("/") ? asString.substring(0, asString.length() - 1) : asString;
+ myAbsoluteUrlAsUrl = absoluteUrlAsUrl;
+ //myType = NestedCopyType.inner; // default
+ }
+
+ public String getRepositoryUrl() {
+ return myRepositoryUrl;
+ }
+
+ public SVNURL getRepositoryUrlUrl() {
+ return myRepositoryUrlUrl;
+ }
+
+ public String getAbsoluteUrl() {
+ return myAbsoluteUrlAsUrl.toString();
+ }
+
+ public SVNURL getAbsoluteUrlAsUrl() {
+ return myAbsoluteUrlAsUrl;
+ }
+
+ public WorkingCopyFormat getFormat() {
+ return myFormat;
+ }
+
+ public File getIoFile() {
+ return myIoFile;
+ }
+
+ // vcs root
+ public VirtualFile getRoot() {
+ return myRoot;
+ }
+
+ public VirtualFile getVirtualFile() {
+ return myVfile;
+ }
+
+ public String getUrl() {
+ return myAbsoluteUrlAsUrl.toString();
+ }
+
+ public NestedCopyType getType() {
+ return myType;
+ }
+
+ public void setType(NestedCopyType type) {
+ myType = type;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/RootUrlPair.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/RootUrlPair.java
new file mode 100644
index 000000000000..53a103113d8b
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/RootUrlPair.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.vfs.VirtualFile;
+
+public interface RootUrlPair {
+ VirtualFile getVirtualFile();
+ String getUrl();
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/RootsToWorkingCopies.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/RootsToWorkingCopies.java
new file mode 100644
index 000000000000..3715796302da
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/RootsToWorkingCopies.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.progress.BackgroundTaskQueue;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.Task;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.CalledInBackground;
+import com.intellij.openapi.vcs.ProjectLevelVcsManager;
+import com.intellij.openapi.vcs.VcsListener;
+import com.intellij.openapi.vcs.changes.BackgroundFromStartOption;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.internal.util.SVNURLUtil;
+import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry;
+import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess;
+import org.tmatesoft.svn.core.wc.SVNInfo;
+import org.tmatesoft.svn.core.wc.SVNRevision;
+import org.tmatesoft.svn.core.wc.SVNWCUtil;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+// 1. listen to roots changes
+// 2. - possibly - to deletion/checkouts??? what if WC roots can be
+public class RootsToWorkingCopies implements VcsListener {
+ private final Object myLock;
+ private final Map myRootMapping;
+ private final Set myUnversioned;
+ private final BackgroundTaskQueue myQueue;
+ private final Project myProject;
+
+ public RootsToWorkingCopies(final Project project) {
+ myProject = project;
+ myQueue = new BackgroundTaskQueue(project, "SVN VCS roots authorization checker");
+ myLock = new Object();
+ myRootMapping = new HashMap();
+ myUnversioned = new HashSet();
+ }
+
+ public void addRoot(final VirtualFile root) {
+ myQueue.run(new Task.Backgroundable(myProject, "Looking for '" + root.getPath() + "' working copy root", false,
+ BackgroundFromStartOption.getInstance()) {
+
+ public void run(@NotNull ProgressIndicator indicator) {
+ calculateRoot(root);
+ }
+ });
+ }
+
+ @Nullable
+ @CalledInBackground
+ public WorkingCopy getMatchingCopy(final SVNURL url) {
+ assert ! ApplicationManager.getApplication().isDispatchThread();
+
+ final VirtualFile[] roots = ProjectLevelVcsManager.getInstance(myProject).getRootsUnderVcs(SvnVcs17.getInstance(myProject));
+ synchronized (myLock) {
+ for (VirtualFile root : roots) {
+ final WorkingCopy wcRoot = getWcRoot(root);
+ if (wcRoot != null) {
+ final SVNURL common = SVNURLUtil.getCommonURLAncestor(wcRoot.getUrl(), url);
+ if (wcRoot.getUrl().equals(common) || url.equals(common)) {
+ return wcRoot;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ @CalledInBackground
+ @Nullable
+ public WorkingCopy getWcRoot(final VirtualFile root) {
+ assert ! ApplicationManager.getApplication().isDispatchThread();
+
+ synchronized (myLock) {
+ if (myUnversioned.contains(root)) return null;
+ final WorkingCopy existing = myRootMapping.get(root);
+ if (existing != null) return existing;
+ }
+ return calculateRoot(root);
+ }
+
+ @Nullable
+ private WorkingCopy calculateRoot(final VirtualFile root) {
+ WorkingCopy workingCopy = null;
+ final File ioFile = new File(root.getPath());
+ File workingCopyRoot = null;
+ try {
+ workingCopyRoot = SVNWCUtil.getWorkingCopyRoot(ioFile, true);
+ if (workingCopyRoot != null) {
+ SVNWCAccess wcAccess = SVNWCAccess.newInstance(null);
+ try {
+ wcAccess.probeOpen(workingCopyRoot, false, 0);
+ SVNEntry entry = wcAccess.getVersionedEntry(workingCopyRoot, false);
+ final SVNURL url = entry.getSVNURL();
+ if (url != null) {
+ workingCopy = new WorkingCopy(workingCopyRoot, url);
+ }
+ } finally {
+ wcAccess.close();
+ }
+ }
+ }
+ catch (SVNException e) {
+ //
+ }
+ if (workingCopyRoot == null) {
+ workingCopyRoot = SvnUtil.getWcCopyRootIf17(ioFile, null);
+ if (workingCopyRoot != null) {
+ final SVNInfo svnInfo;
+ try {
+ svnInfo = SvnVcs17.getInstance(myProject).createWCClient().doInfo(workingCopyRoot, SVNRevision.UNDEFINED);
+ workingCopy = new WorkingCopy(workingCopyRoot, svnInfo.getURL());
+ }
+ catch (SVNException e) {
+ //
+ }
+ }
+ }
+ synchronized (myLock) {
+ if (workingCopy == null) {
+ myRootMapping.remove(root);
+ myUnversioned.add(root);
+ } else {
+ myUnversioned.remove(root);
+ myRootMapping.put(root, workingCopy);
+ }
+ }
+ return workingCopy;
+ }
+
+ public void clear() {
+ synchronized (myLock) {
+ myRootMapping.clear();
+ myUnversioned.clear();
+ }
+ }
+
+ public void directoryMappingChanged() {
+ final SvnVcs17 svnVcs = SvnVcs17.getInstance(myProject);
+ // todo +- here... shouldnt be
+ svnVcs.getAuthNotifier().clear();
+
+ final VirtualFile[] roots = ProjectLevelVcsManager.getInstance(myProject).getRootsUnderVcs(svnVcs);
+ synchronized (myLock) {
+ clear();
+ for (VirtualFile root : roots) {
+ addRoot(root);
+ }
+ }
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/StatusReceiver.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/StatusReceiver.java
new file mode 100644
index 000000000000..ad99eb667a67
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/StatusReceiver.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.vcs.FilePath;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.wc.SVNStatus;
+
+import java.util.EventListener;
+
+public interface StatusReceiver extends EventListener {
+ void process(final FilePath path, final SVNStatus status, final boolean isInnerCopyRoot) throws SVNException;
+ void processIgnored(final VirtualFile vFile);
+ void processUnversioned(final VirtualFile vFile);
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/StatusWalkerPartner.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/StatusWalkerPartner.java
new file mode 100644
index 000000000000..3d120684c712
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/StatusWalkerPartner.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import org.tmatesoft.svn.core.wc.SVNStatusClient;
+
+public interface StatusWalkerPartner {
+ SVNStatusClient createStatusClient();
+ void checkCanceled();
+ boolean isExcluded(final VirtualFile vFile);
+ boolean isIgnoredIdeaLevel(final VirtualFile vFile);
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/StatusWalkerPartnerImpl.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/StatusWalkerPartnerImpl.java
new file mode 100644
index 000000000000..505a221c770a
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/StatusWalkerPartnerImpl.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.lifecycle.PeriodicalTasksCloser;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.roots.FileIndexFacade;
+import com.intellij.openapi.vcs.changes.ChangeListManager;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.tmatesoft.svn.core.SVNCancelException;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.wc.ISVNEventHandler;
+import org.tmatesoft.svn.core.wc.ISVNStatusFileProvider;
+import org.tmatesoft.svn.core.wc.SVNEvent;
+import org.tmatesoft.svn.core.wc.SVNStatusClient;
+
+public class StatusWalkerPartnerImpl implements StatusWalkerPartner {
+ private final SvnVcs17 myVcs;
+ private final ChangeListManager myClManager;
+ private final FileIndexFacade myExcludedFileIndex;
+ private final ProgressIndicator myIndicator;
+ private ISVNStatusFileProvider myFileProvider;
+
+ public StatusWalkerPartnerImpl(final SvnVcs17 vcs, final ProgressIndicator pi) {
+ myVcs = vcs;
+ myClManager = ChangeListManager.getInstance(myVcs.getProject());
+ myExcludedFileIndex = PeriodicalTasksCloser.getInstance().safeGetService(myVcs.getProject(), FileIndexFacade.class);
+ myIndicator = pi;
+ }
+
+ public void setFileProvider(final ISVNStatusFileProvider fileProvider) {
+ myFileProvider = fileProvider;
+ }
+
+ public SVNStatusClient createStatusClient() {
+ final SVNStatusClient result = myVcs.createStatusClient();
+ result.setFilesProvider(myFileProvider);
+ result.setEventHandler(new ISVNEventHandler() {
+ public void handleEvent(SVNEvent event, double progress) throws SVNException {
+ //
+ }
+
+ public void checkCancelled() throws SVNCancelException {
+ if (myIndicator != null) {
+ myIndicator.checkCanceled();
+ }
+ }
+ });
+ return result;
+ }
+
+ public void checkCanceled() {
+ if (myIndicator != null) {
+ myIndicator.checkCanceled();
+ }
+ }
+
+ public boolean isExcluded(VirtualFile vFile) {
+ return myExcludedFileIndex.isExcludedFile(vFile);
+ }
+
+ public boolean isIgnoredIdeaLevel(VirtualFile vFile) {
+ return myClManager.isIgnoredFile(vFile);
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnApplicationSettings17.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnApplicationSettings17.java
new file mode 100644
index 000000000000..4457bb168f3a
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnApplicationSettings17.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.project.Project;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNURL;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+
+@State(
+ name="SvnApplicationSettings17",
+ storages = {
+ @Storage(
+ file="$APP_CONFIG$/other.xml"
+ )}
+)
+public class SvnApplicationSettings17 implements PersistentStateComponent {
+ private SvnFileSystemListenerWrapper myVFSHandler;
+ private int mySvnProjectCount;
+ private LimitedStringsList myLimitedStringsList;
+
+ public static class ConfigurationBean {
+ public List myCheckoutURLs = new ArrayList();
+ public List myTypedURLs = new ArrayList();
+ }
+
+ private ConfigurationBean myConfigurationBean;
+
+ public static SvnApplicationSettings17 getInstance() {
+ return ServiceManager.getService(SvnApplicationSettings17.class);
+ }
+
+ public SvnApplicationSettings17() {
+ myConfigurationBean = new ConfigurationBean();
+ }
+
+ public ConfigurationBean getState() {
+ myConfigurationBean.myTypedURLs.clear();
+ myConfigurationBean.myTypedURLs.addAll(getTypedList().getList());
+ return myConfigurationBean;
+ }
+
+ public void loadState(ConfigurationBean object) {
+ myConfigurationBean = object;
+ getTypedList();
+ }
+
+ private LimitedStringsList getTypedList() {
+ if (myLimitedStringsList == null) {
+ checkFillTypedFromCheckout();
+ myLimitedStringsList = new LimitedStringsList(myConfigurationBean.myTypedURLs);
+ }
+ return myLimitedStringsList;
+ }
+
+ private void checkFillTypedFromCheckout() {
+ if (myConfigurationBean.myTypedURLs.isEmpty() && (! myConfigurationBean.myCheckoutURLs.isEmpty())) {
+ myConfigurationBean.myTypedURLs.addAll(myConfigurationBean.myCheckoutURLs);
+ }
+ }
+
+ public void svnActivated() {
+ if (myVFSHandler == null) {
+ myVFSHandler = new SvnFileSystemListenerWrapper(new SvnFileSystemListener());
+ myVFSHandler.registerSelf();
+ }
+ mySvnProjectCount++;
+ }
+
+ public void svnDeactivated() {
+ mySvnProjectCount--;
+ if (mySvnProjectCount == 0) {
+ myVFSHandler.unregisterSelf();
+ myVFSHandler = null;
+ // todo what should be done instead?
+ //SVNSSHSession.shutdown();
+ }
+ }
+
+ private static File getCommonPath() {
+ File file = new File(PathManager.getSystemPath());
+ file = new File(file, "plugins");
+ file = new File(file, "svn4idea17");
+ file.mkdirs();
+ return file;
+ }
+
+ public static File getCredentialsFile() {
+ return new File(getCommonPath(), "credentials.xml");
+ }
+
+ private static final String LOADED_REVISIONS_DIR = "loadedRevisions";
+
+ public static File getLoadedRevisionsDir(final Project project) {
+ File file = getCommonPath();
+ file = new File(file, LOADED_REVISIONS_DIR);
+ file = new File(file, project.getLocationHash());
+ file.mkdirs();
+ return file;
+ }
+
+ public Collection getCheckoutURLs() {
+ return myConfigurationBean.myCheckoutURLs;
+ }
+
+ public void addCheckoutURL(String url) {
+ if (myConfigurationBean.myCheckoutURLs.contains(url)) {
+ return;
+ }
+ myConfigurationBean.myCheckoutURLs.add(0, url);
+ }
+
+ public void removeCheckoutURL(String url) {
+ if (myConfigurationBean.myCheckoutURLs != null) {
+ // 'url' is not necessary an exact match for some of the urls in collection - it has been parsed and then converted back to string
+ for(String oldUrl: myConfigurationBean.myCheckoutURLs) {
+ try {
+ if (url.equals(oldUrl) || SVNURL.parseURIEncoded(url).equals(SVNURL.parseURIEncoded(oldUrl))) {
+ myConfigurationBean.myCheckoutURLs.remove(oldUrl);
+ break;
+ }
+ }
+ catch (SVNException e) {
+ // ignore
+ }
+ }
+ }
+ }
+
+ public List getTypedUrlsListCopy() {
+ return new ArrayList(getTypedList().getList());
+ }
+
+ public void addTypedUrl(final String url) {
+ getTypedList().add(url);
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnAuthEquals.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnAuthEquals.java
new file mode 100644
index 000000000000..d6b33bf7eaac
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnAuthEquals.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.util.Comparing;
+import org.tmatesoft.svn.core.auth.*;
+
+public class SvnAuthEquals {
+ private SvnAuthEquals() {
+ }
+
+ public static boolean equals(final SVNAuthentication a1, final SVNAuthentication a2) {
+ if (a1 == a2) return true;
+ if (a1 == null || a2 == null) return false;
+ if (! Comparing.equal(a1.getKind(), a2.getKind())) return false;
+ if (! Comparing.equal(a1.getUserName(), a2.getUserName())) return false;
+
+ final Class extends SVNAuthentication> a1Class = a1.getClass();
+ if (! a1Class.equals(a2.getClass())) return false;
+
+ if (SVNUserNameAuthentication.class.equals(a1Class)) return true;
+ if (SVNPasswordAuthentication.class.equals(a1Class)) {
+ return Comparing.equal(((SVNPasswordAuthentication) a1).getPassword(), ((SVNPasswordAuthentication) a2).getPassword());
+ }
+ if (SVNSSLAuthentication.class.equals(a1Class)) {
+ if (! Comparing.equal(((SVNSSLAuthentication) a1).getCertificateFile(), ((SVNSSLAuthentication) a2).getCertificateFile())) return false;
+ return Comparing.equal(((SVNSSLAuthentication) a1).getPassword(), ((SVNSSLAuthentication) a2).getPassword());
+ }
+ if (SVNSSHAuthentication.class.equals(a1Class)) {
+ if (! Comparing.equal(((SVNSSHAuthentication) a1).getPrivateKeyFile(), ((SVNSSHAuthentication) a2).getPrivateKeyFile())) return false;
+ if (! Comparing.equal(((SVNSSHAuthentication) a1).getPassphrase(), ((SVNSSHAuthentication) a2).getPassphrase())) return false;
+ if (! Comparing.equal(((SVNSSHAuthentication) a1).getPortNumber(), ((SVNSSHAuthentication) a2).getPortNumber())) return false;
+ return Comparing.equal(((SVNSSHAuthentication) a1).getPassword(), ((SVNSSHAuthentication) a2).getPassword());
+ }
+ return false;
+ }
+
+ public static int hashCode(final SVNAuthentication a) {
+ int result = a.getKind().hashCode();
+ if (a.getUserName() != null) {
+ result = (31 * result) + a.getUserName().hashCode();
+ }
+ return result;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnAuthenticationManager.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnAuthenticationManager.java
new file mode 100644
index 000000000000..f14dc7d2c1d0
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnAuthenticationManager.java
@@ -0,0 +1,825 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ModalityState;
+import com.intellij.openapi.diagnostic.Logger;
+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.ui.Messages;
+import com.intellij.openapi.util.Getter;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.io.FileUtil;
+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.net.HttpConfigurable;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.idea.svn17.auth.ProviderType;
+import org.jetbrains.idea.svn17.auth.SvnAuthenticationInteraction;
+import org.jetbrains.idea.svn17.auth.SvnAuthenticationListener;
+import org.tmatesoft.svn.core.SVNErrorMessage;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNProperties;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.auth.*;
+import org.tmatesoft.svn.core.internal.wc.*;
+import org.tmatesoft.svn.core.io.SVNRepository;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * @author alex
+ */
+public class SvnAuthenticationManager extends DefaultSVNAuthenticationManager implements SvnAuthenticationListener {
+ // while Mac storage not working for IDEA, we use this key to check whether to prompt abt plaintext or just store
+ private static final Logger LOG = Logger.getInstance(SvnAuthenticationManager.class.getName());
+ public static final String SVN_SSH = "svn+ssh";
+ public static final String HTTP = "http";
+ public static final String HTTPS = "https";
+ private final Project myProject;
+ private File myConfigDirectory;
+ private PersistentAuthenticationProviderProxy myPersistentAuthenticationProviderProxy;
+ private SvnConfiguration17 myConfig;
+ private static final ThreadLocal ourJustEntered = new ThreadLocal();
+ private SvnAuthenticationInteraction myInteraction;
+ private EventDispatcher myListener;
+ private IdeaSVNHostOptionsProvider myLocalHostOptionsProvider;
+ private final ThreadLocalSavePermissions mySavePermissions;
+ private final Map myKeyAlgorithm;
+
+ public SvnAuthenticationManager(final Project project, final File configDirectory) {
+ super(configDirectory, true, null, null);
+ myProject = project;
+ myConfigDirectory = configDirectory;
+ myKeyAlgorithm = new HashMap();
+ ensureListenerCreated();
+ mySavePermissions = new ThreadLocalSavePermissions();
+ myConfig = SvnConfiguration17.getInstance(myProject);
+ if (myPersistentAuthenticationProviderProxy != null) {
+ myPersistentAuthenticationProviderProxy.setProject(myProject);
+ }
+ myInteraction = new MySvnAuthenticationInteraction(myProject);
+ }
+
+ private void ensureListenerCreated() {
+ if (myListener == null) {
+ myListener = EventDispatcher.create(SvnAuthenticationListener.class);
+ }
+ }
+
+ @Override
+ public IdeaSVNHostOptionsProvider getHostOptionsProvider() {
+ if (myLocalHostOptionsProvider == null) {
+ myLocalHostOptionsProvider = new IdeaSVNHostOptionsProvider();
+ }
+ return myLocalHostOptionsProvider;
+ }
+
+ public void addListener(final SvnAuthenticationListener listener) {
+ myListener.addListener(listener);
+ }
+
+ @Override
+ public void actualSaveWillBeTried(ProviderType type, SVNURL url, String realm, String kind) {
+ myListener.getMulticaster().actualSaveWillBeTried(type, url, realm, kind);
+ }
+
+ @Override
+ public void saveAttemptStarted(ProviderType type, SVNURL url, String realm, String kind) {
+ myListener.getMulticaster().saveAttemptStarted(type, url, realm, kind);
+ }
+
+ @Override
+ public void saveAttemptFinished(ProviderType type, SVNURL url, String realm, String kind) {
+ myListener.getMulticaster().saveAttemptFinished(type, url, realm, kind);
+ }
+
+ @Override
+ public void requested(ProviderType type, SVNURL url, String realm, String kind, boolean canceled) {
+ if (ProviderType.interactive.equals(type) && (! canceled)) {
+ ourJustEntered.set(true);
+ }
+ myListener.getMulticaster().requested(type, url, realm, kind, canceled);
+ }
+
+ @Override
+ protected ISVNAuthenticationProvider createCacheAuthenticationProvider(File authDir, String userName) {
+ // this is a hack due to the fact this method is called from super() constructor
+ myConfigDirectory = new File(authDir.getParent());
+ myPersistentAuthenticationProviderProxy = new PersistentAuthenticationProviderProxy(authDir, userName);
+ return myPersistentAuthenticationProviderProxy;
+ }
+
+ @Override
+ public void acknowledgeAuthentication(boolean accepted,
+ String kind,
+ String realm,
+ SVNErrorMessage errorMessage,
+ SVNAuthentication authentication) throws SVNException {
+ try {
+ final boolean authStorageEnabled = getHostOptionsProvider().getHostOptions(authentication.getURL()).isAuthStorageEnabled();
+ final SVNAuthentication proxy = ProxySvnAuthentication.proxy(authentication, authStorageEnabled);
+ super.acknowledgeAuthentication(accepted, kind, realm, errorMessage, proxy);
+ } finally {
+ mySavePermissions.remove();
+ }
+ }
+
+ private class PersistentAuthenticationProviderProxy implements ISVNAuthenticationProvider, ISVNPersistentAuthenticationProvider {
+ private final ISVNAuthenticationProvider myDelegate;
+ private final ISVNGnomeKeyringPasswordProvider myISVNGnomeKeyringPasswordProvider;
+ private final File myAuthDir;
+ private Project myProject;
+
+ private PersistentAuthenticationProviderProxy(File authDir, String userName) {
+ myISVNGnomeKeyringPasswordProvider = new MyKeyringMasterKeyProvider(myProject);
+ ISVNAuthenticationStorageOptions delegatingOptions = new ISVNAuthenticationStorageOptions() {
+ public boolean isNonInteractive() throws SVNException {
+ return getAuthenticationStorageOptions().isNonInteractive();
+ }
+
+ public ISVNAuthStoreHandler getAuthStoreHandler() throws SVNException {
+ return getAuthenticationStorageOptions().getAuthStoreHandler();
+ }
+
+ @Override
+ public ISVNGnomeKeyringPasswordProvider getGnomeKeyringPasswordProvider() {
+ return myISVNGnomeKeyringPasswordProvider;
+ }
+
+ @Override
+ public boolean isSSLPassphrasePromptSupported() {
+ return false;
+ }
+ };
+ ensureListenerCreated();
+ myDelegate = new DefaultSVNPersistentAuthenticationProvider(authDir, userName, delegatingOptions, getDefaultOptions(), getHostOptionsProvider()) {
+ @Override
+ protected IPasswordStorage[] createPasswordStorages(DefaultSVNOptions options) {
+ final IPasswordStorage[] passwordStorages = super.createPasswordStorages(options);
+ final IPasswordStorage[] proxied = new IPasswordStorage[passwordStorages.length];
+ for (int i = 0; i < passwordStorages.length; i++) {
+ final IPasswordStorage storage = passwordStorages[i];
+ proxied[i] = new ProxyPasswordStorageForDebug(storage, myListener);
+ }
+ return proxied;
+ }
+ };
+ myAuthDir = authDir;
+ }
+
+ public void setProject(Project project) {
+ myProject = project;
+ }
+
+ public SVNAuthentication requestClientAuthentication(final String kind, final SVNURL url, final String realm, final SVNErrorMessage errorMessage,
+ final SVNAuthentication previousAuth, final boolean authMayBeStored) {
+ final SVNAuthentication svnAuthentication =
+ myDelegate.requestClientAuthentication(kind, url, realm, errorMessage, previousAuth, authMayBeStored);
+ myListener.getMulticaster().requested(ProviderType.persistent, url, realm, kind, svnAuthentication == null);
+ return svnAuthentication;
+ }
+
+ public int acceptServerAuthentication(final SVNURL url, final String realm, final Object certificate, final boolean resultMayBeStored) {
+ return ACCEPTED_TEMPORARY;
+ }
+
+ private void actualSavePermissions(String realm, SVNAuthentication auth) {
+ final String actualKind = auth.getKind();
+ File dir = new File(myAuthDir, actualKind);
+ String fileName = SVNFileUtil.computeChecksum(realm);
+ File authFile = new File(dir, fileName);
+
+ try {
+ ((ISVNPersistentAuthenticationProvider) myDelegate).saveAuthentication(auth, actualKind, realm);
+ }
+ catch (SVNException e) {
+ if (myProject != null) {
+ ApplicationManager.getApplication().invokeLater(new VcsBalloonProblemNotifier(myProject,
+ "Problem when storing Subversion credentials: " + e.getMessage(), MessageType.ERROR));
+ }
+ }
+ finally {
+ // do not make password file readonly
+ setWriteable(authFile);
+ }
+ }
+
+ public void saveAuthentication(final SVNAuthentication auth, final String kind, final String realm) throws SVNException {
+ final Boolean fromInteractive = ourJustEntered.get();
+ ourJustEntered.set(null);
+ if (! Boolean.TRUE.equals(fromInteractive)) {
+ // not what user entered
+ return;
+ }
+
+ myListener.getMulticaster().saveAttemptStarted(ProviderType.persistent, auth.getURL(), realm, auth.getKind());
+ ((ISVNPersistentAuthenticationProvider) myDelegate).saveAuthentication(auth, kind, realm);
+ myListener.getMulticaster().saveAttemptFinished(ProviderType.persistent, auth.getURL(), realm, auth.getKind());
+ }
+
+ @Override
+ public void saveFingerprints(String realm, byte[] fingerprints) {
+ ((ISVNPersistentAuthenticationProvider) myDelegate).saveFingerprints(realm, fingerprints);
+ }
+
+ @Override
+ public byte[] loadFingerprints(String realm) {
+ return ((ISVNPersistentAuthenticationProvider) myDelegate).loadFingerprints(realm);
+ }
+
+ private final static int maxAttempts = 10;
+ private void setWriteable(final File file) {
+ if (! file.exists()) return;
+ if (file.getParentFile() == null) {
+ return;
+ }
+ for (int i = 0; i < maxAttempts; i++) {
+ final File parent = file.getParentFile();
+ try {
+ final File tempFile = FileUtil.createTempFile(parent, "123", "1", true);
+ FileUtil.delete(tempFile);
+ if (! file.renameTo(tempFile)) continue;
+ if (! file.createNewFile()) continue;
+ FileUtil.copy(tempFile, file);
+ FileUtil.delete(tempFile);
+ return;
+ }
+ catch (IOException e) {
+ //
+ }
+ }
+ }
+ }
+
+ @Override
+ public void verifyHostKey(String hostName, int port, String keyAlgorithm, byte[] hostKey) throws SVNException {
+ myKeyAlgorithm.put(Thread.currentThread(), keyAlgorithm);
+ try {
+ super.verifyHostKey(hostName, port, keyAlgorithm, hostKey);
+ } finally {
+ myKeyAlgorithm.remove(Thread.currentThread());
+ }
+ }
+
+ @Nullable
+ public String getSSHKeyAlgorithm() {
+ return myKeyAlgorithm.get(Thread.currentThread());
+ }
+
+ public ISVNProxyManager getProxyManager(SVNURL url) throws SVNException {
+ // this code taken from default manager (changed for system properties reading)
+ String host = url.getHost();
+
+ String proxyHost = getServersPropertyIdea(host, "http-proxy-host");
+ if ((proxyHost == null) || "".equals(proxyHost.trim())) {
+ if (myConfig.isIsUseDefaultProxy()) {
+ // ! use common proxy if it is set
+ final HttpConfigurable httpConfigurable = HttpConfigurable.getInstance();
+ final String ideaWideProxyHost = httpConfigurable.PROXY_HOST;
+ String ideaWideProxyPort = String.valueOf(httpConfigurable.PROXY_PORT);
+
+ if (ideaWideProxyPort == null) {
+ ideaWideProxyPort = "3128";
+ }
+
+ if ((ideaWideProxyHost != null) && (! "".equals(ideaWideProxyHost.trim()))) {
+ return new MyPromptingProxyManager(ideaWideProxyHost, ideaWideProxyPort);
+ }
+ }
+ return null;
+ }
+ String proxyExceptions = getServersPropertyIdea(host, "http-proxy-exceptions");
+ String proxyExceptionsSeparator = ",";
+ if (proxyExceptions == null) {
+ proxyExceptions = System.getProperty("http.nonProxyHosts");
+ proxyExceptionsSeparator = "|";
+ }
+ if (proxyExceptions != null) {
+ for(StringTokenizer exceptions = new StringTokenizer(proxyExceptions, proxyExceptionsSeparator); exceptions.hasMoreTokens();) {
+ String exception = exceptions.nextToken().trim();
+ if (DefaultSVNOptions.matches(exception, host)) {
+ return null;
+ }
+ }
+ }
+ 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);
+ }
+
+ private static class MyPromptingProxyManager extends MySimpleProxyManager {
+ private static final String ourPrompt = "Proxy authentication";
+
+ private MyPromptingProxyManager(final String host, final String port) {
+ super(host, port, null, null);
+ }
+
+ @Override
+ public String getProxyUserName() {
+ if (myProxyUser != null) {
+ return myProxyUser;
+ }
+ final HttpConfigurable httpConfigurable = HttpConfigurable.getInstance();
+ if (httpConfigurable.PROXY_AUTHENTICATION && (! httpConfigurable.KEEP_PROXY_PASSWORD)) {
+ httpConfigurable.getPromptedAuthentication(myProxyHost, ourPrompt);
+ }
+ myProxyUser = httpConfigurable.PROXY_LOGIN;
+ return myProxyUser;
+ }
+
+ @Override
+ public String getProxyPassword() {
+ if (myProxyPassword != null) {
+ return myProxyPassword;
+ }
+ final HttpConfigurable httpConfigurable = HttpConfigurable.getInstance();
+ if (httpConfigurable.PROXY_AUTHENTICATION && (! httpConfigurable.KEEP_PROXY_PASSWORD)) {
+ httpConfigurable.getPromptedAuthentication(myProxyUser, ourPrompt);
+ }
+ myProxyPassword = httpConfigurable.getPlainProxyPassword();
+ return myProxyPassword;
+ }
+ }
+
+ private static class MySimpleProxyManager implements ISVNProxyManager {
+ protected String myProxyHost;
+ private final String myProxyPort;
+ protected String myProxyUser;
+ protected String myProxyPassword;
+
+ public MySimpleProxyManager(String host, String port, String user, String password) {
+ myProxyHost = host;
+ myProxyPort = port == null ? "3128" : port;
+ myProxyUser = user;
+ myProxyPassword = password;
+ }
+
+ public String getProxyHost() {
+ return myProxyHost;
+ }
+
+ public int getProxyPort() {
+ try {
+ return Integer.parseInt(myProxyPort);
+ } catch (NumberFormatException nfe) {
+ //
+ }
+ return 3128;
+ }
+
+ public String getProxyUserName() {
+ return myProxyUser;
+ }
+
+ public String getProxyPassword() {
+ return myProxyPassword;
+ }
+
+ public void acknowledgeProxyContext(boolean accepted, SVNErrorMessage errorMessage) {
+ }
+ }
+
+ // 30 seconds
+ private final static int DEFAULT_READ_TIMEOUT = 30 * 1000;
+
+ @Override
+ public int getReadTimeout(final SVNRepository repository) {
+ String protocol = repository.getLocation().getProtocol();
+ if (HTTP.equals(protocol) || HTTPS.equals(protocol)) {
+ String host = repository.getLocation().getHost();
+ String timeout = getServersPropertyIdea(host, "http-timeout");
+ if (timeout != null) {
+ try {
+ return Integer.parseInt(timeout)*1000;
+ } catch (NumberFormatException nfe) {
+ // use default
+ }
+ }
+ return DEFAULT_READ_TIMEOUT;
+ }
+ if (SVN_SSH.equals(protocol)) {
+ return (int) myConfig.mySSHReadTimeout;
+ }
+ return 0;
+ }
+
+ @Override
+ public int getConnectTimeout(SVNRepository repository) {
+ String protocol = repository.getLocation().getProtocol();
+ if (SVN_SSH.equals(protocol)) {
+ return (int) myConfig.mySSHConnectionTimeout;
+ }
+ final int connectTimeout = super.getConnectTimeout(repository);
+ if ((HTTP.equals(protocol) || HTTPS.equals(protocol)) && (connectTimeout <= 0)) {
+ return DEFAULT_READ_TIMEOUT;
+ }
+ return connectTimeout;
+ }
+
+ // taken from default manager as is
+ private String getServersPropertyIdea(String host, final String name) {
+ final SVNCompositeConfigFile serversFile = getHostOptionsProvider().getServersFile();
+ return getPropertyIdea(host, serversFile, name);
+ }
+
+ private String getPropertyIdea(String host, SVNCompositeConfigFile serversFile, final String name) {
+ String groupName = getGroupName(serversFile.getProperties("groups"), host);
+ if (groupName != null) {
+ Map hostProps = serversFile.getProperties(groupName);
+ final String value = (String)hostProps.get(name);
+ if (value != null) {
+ return value;
+ }
+ }
+ Map globalProps = serversFile.getProperties("global");
+ return (String) globalProps.get(name);
+ }
+
+ public static boolean checkHostGroup(final String url, final String patterns, final String exceptions) {
+ final SVNURL svnurl;
+ try {
+ svnurl = SVNURL.parseURIEncoded(url);
+ }
+ catch (SVNException e) {
+ return false;
+ }
+
+ final String host = svnurl.getHost();
+ return matches(patterns, host) && (! matches(exceptions, host));
+ }
+
+ private static boolean matches(final String pattern, final String host) {
+ final StringTokenizer tokenizer = new StringTokenizer(pattern, ",");
+ while(tokenizer.hasMoreTokens()) {
+ String token = tokenizer.nextToken();
+ if (DefaultSVNOptions.matches(token, host)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // 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;
+ }
+ }
+ }
+ return null;
+ }
+
+ // default = yes
+ private static boolean isTurned(final String value) {
+ return value == null || "yes".equalsIgnoreCase(value) || "on".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value);
+ }
+
+ private ModalityState getCurrent() {
+ if (ApplicationManager.getApplication().isDispatchThread()) {
+ return ModalityState.current();
+ }
+ final ProgressIndicator pi = ProgressManager.getInstance().getProgressIndicator();
+ if (pi == null) {
+ return ModalityState.defaultModalityState();
+ }
+ return pi.getModalityState();
+ }
+
+ /**
+ * Shows a yes/no question whether user wants to store his password in plain text and returns his answer.
+ * @param title title of the questioning dialog.
+ * @param message questioning message to be displayed.
+ * @return true if user agrees to store his password in plaintext, false if he doesn't.
+ */
+ @CalledInAwt
+ private boolean askToStoreUnencrypted(String title, String message) {
+ final int answer = Messages.showYesNoDialog(myProject, message, title, Messages.getQuestionIcon());
+ return answer == 0;
+ }
+
+ public void setInteraction(SvnAuthenticationInteraction interaction) {
+ myInteraction = interaction;
+ }
+
+ private static class MySvnAuthenticationInteraction implements SvnAuthenticationInteraction {
+ private final Project myProject;
+
+ private MySvnAuthenticationInteraction(Project project) {
+ myProject = project;
+ }
+
+ @Override
+ public void warnOnAuthStorageDisabled(SVNURL url) {
+ VcsBalloonProblemNotifier.showOverChangesView(myProject, "Cannot store credentials: forbidden by \"store-auth-creds=no\"", MessageType.ERROR);
+ }
+
+ @Override
+ public void warnOnPasswordStorageDisabled(SVNURL url) {
+ VcsBalloonProblemNotifier.showOverChangesView(myProject, "Cannot store password: forbidden by \"store-passwords=no\"", MessageType.ERROR);
+ }
+
+ @Override
+ public void warnOnSSLPassphraseStorageDisabled(SVNURL url) {
+ VcsBalloonProblemNotifier.showOverChangesView(myProject, "Cannot store passphrase: forbidden by \"store-ssl-client-cert-pp=no\"", MessageType.ERROR);
+ }
+
+ @Override
+ public boolean promptForPlaintextPasswordSaving(SVNURL url, String realm) {
+ final int answer = Messages.showYesNoDialog(myProject, String.format("Your password for authentication realm:\n" +
+ "%s\ncan only be stored to disk unencrypted. Would you like to store it in plaintext?", realm),
+ "Store the password in plaintext?", Messages.getQuestionIcon());
+ return answer == 0;
+ }
+
+ @Override
+ public boolean promptInAwt() {
+ return true;
+ }
+
+ @Override
+ public boolean promptForSSLPlaintextPassphraseSaving(SVNURL url, String realm, File certificateFile, String certificateName) {
+ final int answer = Messages.showYesNoDialog(myProject,
+ String.format("Your passphrase for " + certificateName + ":\n%s\ncan only be stored to disk unencrypted. Would you like to store it in plaintext?",
+ certificateFile.getPath()),
+ "Store the passphrase in plaintext?", Messages.getQuestionIcon());
+ return answer == 0;
+ }
+ }
+
+ private static boolean isLion() {
+ return SystemInfo.isMac && SystemInfo.isMacOSSnowLeopard && ! SystemInfo.OS_VERSION.startsWith("10.6");
+ }
+
+ public class IdeaSVNHostOptionsProvider extends DefaultSVNHostOptionsProvider {
+ public IdeaSVNHostOptionsProvider() {
+ super(myConfigDirectory);
+ }
+
+ @Override
+ public SVNCompositeConfigFile getServersFile() {
+ return super.getServersFile();
+ }
+
+ @Override
+ public ISVNHostOptions getHostOptions(SVNURL url) {
+ return new IdeaSVNHostOptions(getServersFile(), url);
+ }
+ }
+
+ private static class ThreadLocalSavePermissions {
+ private final Map myPlainTextAllowed;
+
+ private ThreadLocalSavePermissions() {
+ myPlainTextAllowed = Collections.synchronizedMap(new HashMap());
+ }
+
+ public void put(final boolean value) {
+ myPlainTextAllowed.put(Thread.currentThread(), value);
+ }
+
+ public boolean have() {
+ return myPlainTextAllowed.containsKey(Thread.currentThread());
+ }
+
+ public void remove() {
+ myPlainTextAllowed.remove(Thread.currentThread());
+ }
+
+ public boolean allowed() {
+ return Boolean.TRUE.equals(myPlainTextAllowed.get(Thread.currentThread()));
+ }
+ }
+
+ private class IdeaSVNHostOptions extends DefaultSVNHostOptions {
+ private SVNCompositeConfigFile myConfigFile;
+ private final SVNURL myUrl;
+
+ private IdeaSVNHostOptions(SVNCompositeConfigFile serversFile, SVNURL url) {
+ super(serversFile, url);
+ myUrl = url;
+ }
+
+ @Override
+ public boolean isStorePlainTextPasswords(final String realm, SVNAuthentication auth) throws SVNException {
+ if (USERNAME.equals(auth.getKind())) return true;
+
+ final boolean superValue = super.isStorePlainTextPasswords(realm, auth);
+ final boolean value = mySavePermissions.allowed() || superValue;
+ if ((! value) && (! mySavePermissions.have())) {
+ promptAndSaveWhenWeLackEncryption(realm, auth, new Getter() {
+ @Override
+ public Boolean get() {
+ return myInteraction.promptForPlaintextPasswordSaving(myUrl, realm);
+ }
+ });
+ }
+ return value;
+ }
+
+ @Override
+ public boolean isStorePlainTextPassphrases(final String realm, final SVNAuthentication auth) throws SVNException {
+ if (USERNAME.equals(auth.getKind())) return true;
+
+ final boolean value = mySavePermissions.allowed() || super.isStorePlainTextPassphrases(realm, auth);
+ if ((! value) && (! mySavePermissions.have())) {
+ promptAndSaveWhenWeLackEncryption(realm, auth, new Getter() {
+ @Override
+ public Boolean get() {
+ File file = null;
+ String certificateName = null;
+ if (auth instanceof SVNSSLAuthentication) {
+ file = ((SVNSSLAuthentication) auth).getCertificateFile();
+ certificateName = "client certificate";
+ } else if (auth instanceof SVNSSHAuthentication) {
+ file = ((SVNSSHAuthentication) auth).getPrivateKeyFile();
+ certificateName = "private key file";
+ } else {
+ assert false;
+ }
+ return myInteraction.promptForSSLPlaintextPassphraseSaving(myUrl, realm,
+ file, certificateName);
+ }
+ });
+ }
+ return value;
+ }
+
+ @Override
+ public boolean isAuthStorageEnabled() {
+ final boolean value;
+ if (hasAuthStorageEnabledOption()) {
+ value = super.isAuthStorageEnabled();
+ } else {
+ value = isTurned(getConfigFile().getPropertyValue("auth", "store-auth-creds"));
+ }
+ if (! value) {
+ myInteraction.warnOnAuthStorageDisabled(myUrl);
+ }
+ return value;
+ }
+
+ @Override
+ public boolean isStorePasswords() {
+ final String storePasswords = getStorePasswords();
+ final boolean value;
+ if (storePasswords != null) {
+ value = isTurned(storePasswords);
+ } else {
+ final String configValue = getConfigFile().getPropertyValue("auth", "store-passwords");
+ value = isTurned(configValue);
+ }
+ if (! value) {
+ myInteraction.warnOnPasswordStorageDisabled(myUrl);
+ }
+ return value;
+ }
+
+ @Override
+ public boolean isStoreSSLClientCertificatePassphrases() {
+ final boolean value = super.isStoreSSLClientCertificatePassphrases();
+ if (! value) {
+ myInteraction.warnOnSSLPassphraseStorageDisabled(myUrl);
+ }
+ return value;
+ }
+
+ public String getStorePasswords() {
+ return getServersPropertyIdea(getHost(), "store-passwords");
+ }
+
+ private SVNCompositeConfigFile getConfigFile() {
+ if (myConfigFile == null) {
+ final File config = new File(myConfigDirectory, "config");
+ SVNConfigFile.createDefaultConfiguration(myConfigDirectory);
+ SVNConfigFile userConfig = new SVNConfigFile(config);
+ SVNConfigFile systemConfig = new SVNConfigFile(new File(SVNFileUtil.getSystemConfigurationDirectory(), "config"));
+ myConfigFile = new SVNCompositeConfigFile(systemConfig, userConfig);
+ }
+ return myConfigFile;
+ }
+
+ private void promptAndSaveWhenWeLackEncryption(final String realm, final SVNAuthentication auth, final Getter prompt) {
+ final Boolean[] saveOnce = new Boolean[1];
+ final Runnable actualSave = new Runnable() {
+ @Override
+ public void run() {
+ mySavePermissions.put(Boolean.TRUE.equals(saveOnce[0]));
+ try {
+ myPersistentAuthenticationProviderProxy.actualSavePermissions(realm, auth);
+ }
+ finally {
+ mySavePermissions.remove();
+ }
+ }
+ };
+
+ if (myInteraction.promptInAwt()) {
+ new AbstractCalledLater(myProject, getCurrent()) {
+ @Override
+ public void run() {
+ saveOnce[0] = Boolean.TRUE.equals(prompt.get());
+ ApplicationManager.getApplication().executeOnPooledThread(actualSave);
+ }
+ }.callMe();
+ } else {
+ saveOnce[0] = Boolean.TRUE.equals(prompt.get());
+ actualSave.run();
+ }
+ }
+ }
+
+ private static class ProxyPasswordStorageForDebug implements DefaultSVNPersistentAuthenticationProvider.IPasswordStorage {
+ private final DefaultSVNPersistentAuthenticationProvider.IPasswordStorage myDelegate;
+ private final EventDispatcher myListener;
+
+ public ProxyPasswordStorageForDebug(final DefaultSVNPersistentAuthenticationProvider.IPasswordStorage delegate,
+ final EventDispatcher listener) {
+ myDelegate = delegate;
+ myListener = listener;
+ }
+
+ @Override
+ public String getPassType() {
+ return myDelegate.getPassType();
+ }
+
+ @Override
+ public boolean savePassword(String realm, String password, SVNAuthentication auth, SVNProperties authParameters) throws SVNException {
+ final boolean saved = myDelegate.savePassword(realm, password, auth, authParameters);
+ if (saved) {
+ myListener.getMulticaster().actualSaveWillBeTried(ProviderType.persistent, auth.getURL(), realm, auth.getKind()
+ );
+ }
+ return saved;
+ }
+
+ @Override
+ public String readPassword(String realm, String userName, SVNProperties authParameters) throws SVNException {
+ return myDelegate.readPassword(realm, userName, authParameters);
+ }
+
+ @Override
+ public boolean savePassphrase(String realm, String passphrase, SVNAuthentication auth, SVNProperties authParameters, boolean force)
+ throws SVNException {
+ final boolean saved = myDelegate.savePassphrase(realm, passphrase, auth, authParameters, force);
+ if (saved) {
+ myListener.getMulticaster().actualSaveWillBeTried(ProviderType.persistent, auth.getURL(), realm, auth.getKind()
+ );
+ }
+ return saved;
+ }
+
+ @Override
+ public String readPassphrase(String realm, SVNProperties authParameters) throws SVNException {
+ return myDelegate.readPassphrase(realm, authParameters);
+ }
+ }
+
+ private static class MyKeyringMasterKeyProvider implements ISVNGnomeKeyringPasswordProvider {
+ private final Project myProject;
+
+ public MyKeyringMasterKeyProvider(Project project) {
+ myProject = project;
+ }
+
+ @Override
+ public String getKeyringPassword(final String keyringName) throws SVNException {
+ final String[] s = new String[1];
+ UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+ @Override
+ public void run() {
+ s[0] = Messages.showInputDialog(myProject, "Please provide '" + keyringName + "' GNOME Keyring password", "Subversion", Messages.getQuestionIcon());
+ }
+ });
+ return s[0];
+ }
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnAuthenticationNotifier.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnAuthenticationNotifier.java
new file mode 100644
index 000000000000..b52507b49238
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnAuthenticationNotifier.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.notification.NotificationType;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ModalityState;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.MessageType;
+import com.intellij.openapi.ui.popup.Balloon;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vcs.impl.GenericNotifierImpl;
+import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.wm.IdeFrame;
+import com.intellij.openapi.wm.ex.WindowManagerEx;
+import com.intellij.ui.awt.RelativePoint;
+import com.intellij.util.ThreeState;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.idea.svn17.dialogs.SvnInteractiveAuthenticationProvider;
+import org.tmatesoft.svn.core.SVNAuthenticationException;
+import org.tmatesoft.svn.core.SVNCancelException;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.auth.SVNAuthentication;
+import org.tmatesoft.svn.core.internal.util.SVNURLUtil;
+import org.tmatesoft.svn.core.wc.SVNRevision;
+import org.tmatesoft.svn.core.wc.SVNWCClient;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+import java.util.Timer;
+
+public class SvnAuthenticationNotifier extends GenericNotifierImpl {
+ private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnAuthenticationNotifier");
+
+ private final SvnVcs17 myVcs;
+ private final RootsToWorkingCopies myRootsToWorkingCopies;
+ private final Map myCopiesPassiveResults;
+ private Timer myTimer;
+ private volatile boolean myVerificationInProgress;
+
+ public SvnAuthenticationNotifier(final SvnVcs17 svnVcs) {
+ super(svnVcs.getProject(), svnVcs.getDisplayName(), "Not Logged In to Subversion", NotificationType.ERROR);
+ myVcs = svnVcs;
+ myRootsToWorkingCopies = myVcs.getRootsToWorkingCopies();
+ myCopiesPassiveResults = Collections.synchronizedMap(new HashMap());
+ myVerificationInProgress = false;
+ }
+
+ public void init() {
+ if (myTimer != null) {
+ stop();
+ }
+ myTimer = new Timer("SVN authentication timer");
+ // every 10 minutes
+ myTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ myCopiesPassiveResults.clear();
+ }
+ }, 10000, 10 * 60 * 1000);
+ }
+
+ public void stop() {
+ myTimer.cancel();
+ myTimer = null;
+ }
+
+ @Override
+ protected boolean ask(final AuthenticationRequest obj, String description) {
+ if (myVerificationInProgress) {
+ return showAlreadyChecking();
+ }
+ myVerificationInProgress = true;
+
+ final Ref resultRef = new Ref();
+
+ ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ final boolean result =
+ interactiveValidation(obj.myProject, obj.getUrl(), obj.getRealm(), obj.getKind());
+ log("ask result for: " + obj.getUrl() + " is: " + result);
+ resultRef.set(result);
+ if (result) {
+ onStateChangedToSuccess(obj);
+ }
+ } finally {
+ myVerificationInProgress = false;
+ }
+ }
+ });
+ return false;
+ }
+
+ private boolean showAlreadyChecking() {
+ final IdeFrame frameFor = WindowManagerEx.getInstanceEx().findFrameFor(myProject);
+ if (frameFor != null) {
+ final JComponent component = frameFor.getComponent();
+ Point point = component.getMousePosition();
+ if (point == null) {
+ point = new Point((int)(component.getWidth() * 0.7), 0);
+ }
+ SwingUtilities.convertPointToScreen(point, component);
+ JBPopupFactory.getInstance().createHtmlTextBalloonBuilder("Already checking...", MessageType.WARNING, null).
+ createBalloon().show(new RelativePoint(point), Balloon.Position.below);
+ }
+ return false;
+ }
+
+ private void onStateChangedToSuccess(final AuthenticationRequest obj) {
+ myCopiesPassiveResults.put(getKey(obj), true);
+ myVcs.invokeRefreshSvnRoots(false);
+
+ final List outdatedRequests = new LinkedList();
+ final Collection keys = getAllCurrentKeys();
+ for (SVNURL key : keys) {
+ final SVNURL commonURLAncestor = SVNURLUtil.getCommonURLAncestor(key, obj.getUrl());
+ if ((commonURLAncestor != null) && (! StringUtil.isEmptyOrSpaces(commonURLAncestor.getHost())) &&
+ (! StringUtil.isEmptyOrSpaces(commonURLAncestor.getPath()))) {
+ //final AuthenticationRequest currObj = getObj(key);
+ //if ((currObj != null) && passiveValidation(myVcs.getProject(), key, true, currObj.getRealm(), currObj.getKind())) {
+ outdatedRequests.add(key);
+ //}
+ }
+ }
+ log("on state changed ");
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ public void run() {
+ for (SVNURL key : outdatedRequests) {
+ removeLazyNotificationByKey(key);
+ }
+ }
+ }, ModalityState.NON_MODAL);
+ }
+
+ @Override
+ public void ensureNotify(AuthenticationRequest obj) {
+ final SVNURL key = getKey(obj);
+ myCopiesPassiveResults.remove(key);
+ /*VcsBalloonProblemNotifier.showOverChangesView(myVcs.getProject(), "You are not authenticated to '" + obj.getRealm() + "'." +
+ "To login, see pending notifications.", MessageType.ERROR);*/
+ super.ensureNotify(obj);
+ }
+
+ @NotNull
+ @Override
+ public SVNURL getKey(final AuthenticationRequest obj) {
+ // !!! wc's URL
+ return obj.getWcUrl();
+ }
+
+ @Nullable
+ public SVNURL getWcUrl(final AuthenticationRequest obj) {
+ if (obj.isOutsideCopies()) return null;
+ if (obj.getWcUrl() != null) return obj.getWcUrl();
+
+ final WorkingCopy copy = myRootsToWorkingCopies.getMatchingCopy(obj.getUrl());
+ if (copy != null) {
+ obj.setOutsideCopies(false);
+ obj.setWcUrl(copy.getUrl());
+ } else {
+ obj.setOutsideCopies(true);
+ }
+ return copy == null ? null : copy.getUrl();
+ }
+
+ /**
+ * Bases on presence of notifications!
+ */
+ public ThreeState isAuthenticatedFor(final VirtualFile vf) {
+ final WorkingCopy wcCopy = myRootsToWorkingCopies.getWcRoot(vf);
+ if (wcCopy == null) return ThreeState.UNSURE;
+
+ // check there's no cancellation yet
+ final boolean haveCancellation = getStateFor(wcCopy.getUrl());
+ if (haveCancellation) return ThreeState.NO;
+
+ final Boolean keptResult = myCopiesPassiveResults.get(wcCopy.getUrl());
+ if (Boolean.TRUE.equals(keptResult)) return ThreeState.YES;
+ if (Boolean.FALSE.equals(keptResult)) return ThreeState.NO;
+
+ // check have credentials
+ final boolean calculatedResult = passiveValidation(myVcs.getProject(), wcCopy.getUrl());
+ myCopiesPassiveResults.put(wcCopy.getUrl(), calculatedResult);
+ return calculatedResult ? ThreeState.YES : ThreeState.NO;
+ }
+
+ @NotNull
+ @Override
+ protected String getNotificationContent(AuthenticationRequest obj) {
+ return "Click to fix. Not logged In to Subversion '" + obj.getRealm() + "' (" + obj.getUrl().toDecodedString() + ")";
+ }
+
+ @NotNull
+ @Override
+ protected String getToString(AuthenticationRequest obj) {
+ return "Click to fix. Not logged In to Subversion '" + obj.getRealm() + "' (" + obj.getUrl().toDecodedString() + ")";
+ }
+
+ public static class AuthenticationRequest {
+ private final Project myProject;
+ private final String myKind;
+ private final SVNURL myUrl;
+ private final String myRealm;
+
+ private SVNURL myWcUrl;
+ private boolean myOutsideCopies;
+
+ public AuthenticationRequest(Project project, String kind, SVNURL url, String realm) {
+ myProject = project;
+ myKind = kind;
+ myUrl = url;
+ myRealm = realm;
+ }
+
+ public boolean isOutsideCopies() {
+ return myOutsideCopies;
+ }
+
+ public void setOutsideCopies(boolean outsideCopies) {
+ myOutsideCopies = outsideCopies;
+ }
+
+ public SVNURL getWcUrl() {
+ return myWcUrl;
+ }
+
+ public void setWcUrl(SVNURL wcUrl) {
+ myWcUrl = wcUrl;
+ }
+
+ public String getKind() {
+ return myKind;
+ }
+
+ public SVNURL getUrl() {
+ return myUrl;
+ }
+
+ public String getRealm() {
+ return myRealm;
+ }
+ }
+
+ static void log(final Throwable t) {
+ LOG.debug(t);
+ }
+
+ static void log(final String s) {
+ LOG.debug(s);
+ }
+
+ public static boolean passiveValidation(final Project project, final SVNURL url) {
+ final SvnConfiguration17 configuration = SvnConfiguration17.getInstance(project);
+ final SvnAuthenticationManager passiveManager = configuration.getPassiveAuthenticationManager();
+ return validationImpl(project, url, configuration, passiveManager, false, null, null, false);
+ }
+
+ public static boolean interactiveValidation(final Project project, final SVNURL url, final String realm, final String kind) {
+ final SvnConfiguration17 configuration = SvnConfiguration17.getInstance(project);
+ final SvnAuthenticationManager passiveManager = configuration.getInteractiveManager(SvnVcs17.getInstance(project));
+ return validationImpl(project, url, configuration, passiveManager, true, realm, kind, true);
+ }
+
+ private static boolean validationImpl(final Project project, final SVNURL url,
+ final SvnConfiguration17 configuration, final SvnAuthenticationManager manager,
+ final boolean checkWrite,
+ final String realm,
+ final String kind, boolean interactive) {
+ SvnInteractiveAuthenticationProvider.clearCallState();
+ try {
+ new SVNWCClient(manager, configuration.getOptions(project)).doInfo(url, SVNRevision.UNDEFINED, SVNRevision.HEAD);
+ } catch (SVNAuthenticationException e) {
+ log(e);
+ return false;
+ } catch (SVNCancelException e) {
+ log(e); // auth canceled
+ return false;
+ } catch (SVNException e) {
+ if (e.getErrorMessage().getErrorCode().isAuthentication()) {
+ log(e);
+ return false;
+ }
+ LOG.info("some other exc", e);
+ if (interactive) {
+ VcsBalloonProblemNotifier.showOverChangesView(project, "Authentication failed: " + e.getMessage(), MessageType.ERROR);
+ }
+ return false; /// !!!! any exception means user should be notified that authorization failed
+ }
+
+ if (! checkWrite) {
+ return true;
+ }
+ /*if (passive) {
+ return SvnInteractiveAuthenticationProvider.wasCalled();
+ }*/
+
+ if (SvnInteractiveAuthenticationProvider.wasCalled() && SvnInteractiveAuthenticationProvider.wasCancelled()) return false;
+ if (SvnInteractiveAuthenticationProvider.wasCalled()) return true;
+
+ final SvnVcs17 svnVcs = SvnVcs17.getInstance(project);
+
+ final SvnInteractiveAuthenticationProvider provider = new SvnInteractiveAuthenticationProvider(svnVcs, manager);
+ final SVNAuthentication svnAuthentication = provider.requestClientAuthentication(kind, url, realm, null, null, true);
+ if (svnAuthentication != null) {
+ configuration.acknowledge(kind, realm, svnAuthentication);
+ try {
+ configuration.getAuthenticationManager(svnVcs).acknowledgeAuthentication(true, kind, realm, null, svnAuthentication);
+ }
+ catch (SVNException e) {
+ LOG.info(e);
+ }
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnBinaryContentRevision.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnBinaryContentRevision.java
new file mode 100644
index 000000000000..cd3d5b57a80b
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnBinaryContentRevision.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2000-2011 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.svn17;
+
+import com.intellij.openapi.vcs.FilePath;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vcs.changes.BinaryContentRevision;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.tmatesoft.svn.core.wc.SVNRevision;
+
+/**
+ * @author yole
+*/
+class SvnBinaryContentRevision extends SvnContentRevision implements BinaryContentRevision {
+ public SvnBinaryContentRevision(@NotNull SvnVcs17 vcs, @NotNull final FilePath file, final SVNRevision revision, final boolean useBaseRevision) {
+ super(vcs, file, revision, useBaseRevision);
+ }
+
+ @Nullable
+ public byte[] getBinaryContent() throws VcsException {
+ try {
+ return getUpToDateBinaryContent();
+ }
+ catch(Exception ex) {
+ throw new VcsException(ex);
+ }
+ }
+
+ @NonNls
+ public String toString() {
+ return "SvnBinaryContentRevision:" + myFile;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnBranchConfiguration.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnBranchConfiguration.java
new file mode 100644
index 000000000000..ad7459dfbe96
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnBranchConfiguration.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author yole
+ */
+public class SvnBranchConfiguration {
+ private String myTrunkUrl;
+ private List myBranchUrls;
+ private boolean myUserinfoInUrl;
+
+ public SvnBranchConfiguration() {
+ myBranchUrls = new ArrayList();
+ }
+
+ public boolean isUserinfoInUrl() {
+ return myUserinfoInUrl;
+ }
+
+ public void setUserinfoInUrl(final boolean userinfoInUrl) {
+ myUserinfoInUrl = userinfoInUrl;
+ }
+
+ public void setBranchUrls(final List branchUrls) {
+ myBranchUrls = branchUrls;
+ Collections.sort(myBranchUrls);
+ }
+
+ public void setTrunkUrl(final String trunkUrl) {
+ myTrunkUrl = trunkUrl;
+ }
+
+ public String getTrunkUrl() {
+ return myTrunkUrl;
+ }
+
+ public List getBranchUrls() {
+ return myBranchUrls;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnBranchConfigurationManager17.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnBranchConfigurationManager17.java
new file mode 100644
index 000000000000..eb6940bd02f9
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnBranchConfigurationManager17.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.lifecycle.PeriodicalTasksCloser;
+import com.intellij.openapi.application.Application;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.vcs.ProjectLevelVcsManager;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vcs.changes.committed.VcsConfigurationChangeListener;
+import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl;
+import com.intellij.openapi.vcs.impl.VcsInitObject;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.PairConsumer;
+import com.intellij.util.messages.MessageBus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.idea.svn17.branchConfig.*;
+import org.jetbrains.idea.svn17.integrate.SvnBranchItem;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNURL;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * @author yole
+ */
+@State(
+ name = "SvnBranchConfigurationManager17",
+ storages = {
+ @Storage(
+ file = "$PROJECT_FILE$"
+ )}
+)
+public class SvnBranchConfigurationManager17 implements PersistentStateComponent {
+ private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnBranchConfigurationManager");
+ private final Project myProject;
+ private final ProjectLevelVcsManager myVcsManager;
+ private final SvnLoadedBrachesStorage17 myStorage;
+
+ public SvnBranchConfigurationManager17(final Project project,
+ final ProjectLevelVcsManager vcsManager,
+ final SvnLoadedBrachesStorage17 storage) {
+ myProject = project;
+ myVcsManager = vcsManager;
+ myStorage = storage;
+ myBunch = new NewRootBunch(project);
+ }
+
+ public static SvnBranchConfigurationManager17 getInstance(final Project project) {
+ return PeriodicalTasksCloser.getInstance().safeGetService(project, SvnBranchConfigurationManager17.class);
+ }
+
+ public static class ConfigurationBean {
+ public Map myConfigurationMap = new TreeMap();
+ /**
+ * version of "support SVN in IDEA". for features tracking. should grow
+ */
+ public Long myVersion;
+ public boolean mySupportsUserInfoFilter;
+ }
+
+ public Long getSupportValue() {
+ return myConfigurationBean.myVersion;
+ }
+
+ private ConfigurationBean myConfigurationBean = new ConfigurationBean();
+ private final SvnBranchConfigManager myBunch;
+
+ public SvnBranchConfigurationNew get(@NotNull final VirtualFile vcsRoot) throws VcsException {
+ return myBunch.getConfig(vcsRoot);
+ }
+
+ public SvnBranchConfigManager getSvnBranchConfigManager() {
+ return myBunch;
+ }
+
+ public void setConfiguration(final VirtualFile vcsRoot, final SvnBranchConfigurationNew configuration) {
+ myBunch.updateForRoot(vcsRoot, new InfoStorage(configuration, InfoReliability.setByUser),
+ new BranchesPreloader(myProject, myBunch, vcsRoot));
+
+ SvnBranchMapperManager17.getInstance().notifyBranchesChanged(myProject, vcsRoot, configuration);
+
+ final MessageBus messageBus = myProject.getMessageBus();
+ messageBus.syncPublisher(VcsConfigurationChangeListener.BRANCHES_CHANGED).execute(myProject, vcsRoot);
+ }
+
+ public ConfigurationBean getState() {
+ final ConfigurationBean result = new ConfigurationBean();
+ result.myVersion = myConfigurationBean.myVersion;
+ final UrlSerializationHelper helper = new UrlSerializationHelper(SvnVcs17.getInstance(myProject));
+
+ for (VirtualFile root : myBunch.getMapCopy().keySet()) {
+ final String key = root.getPath();
+ final SvnBranchConfigurationNew configOrig = myBunch.getConfig(root);
+ final SvnBranchConfiguration configuration = new SvnBranchConfiguration();
+ configuration.setTrunkUrl(configOrig.getTrunkUrl());
+ configuration.setUserinfoInUrl(configOrig.isUserinfoInUrl());
+ configuration.setBranchUrls(configOrig.getBranchUrls());
+ final HashMap> map = new HashMap>();
+ final Map>> origMap = configOrig.getBranchMap();
+ for (String origKey : origMap.keySet()) {
+ map.put(origKey, origMap.get(origKey).getValue());
+ }
+ result.myConfigurationMap.put(key, helper.prepareForSerialization(configuration));
+ }
+ result.mySupportsUserInfoFilter = true;
+ return result;
+ }
+
+ private static class BranchesPreloader implements PairConsumer {
+ private final Project myProject;
+ private final VirtualFile myRoot;
+ private final SvnBranchConfigManager myBunch;
+ private boolean myAll;
+
+ public BranchesPreloader(Project project, @NotNull final SvnBranchConfigManager bunch, VirtualFile root) {
+ myBunch = bunch;
+ myProject = project;
+ myRoot = root;
+ }
+
+ public void consume(final SvnBranchConfigurationNew prev, final SvnBranchConfigurationNew next) {
+ final Application application = ApplicationManager.getApplication();
+ application.executeOnPooledThread(new Runnable() {
+ public void run() {
+ loadImpl(prev, next);
+ }
+ });
+ }
+
+ protected void loadImpl(final SvnBranchConfigurationNew prev, final SvnBranchConfigurationNew next) {
+ final Set oldUrls = (prev == null) ? Collections.emptySet() : new HashSet(prev.getBranchUrls());
+ final SvnVcs17 vcs = SvnVcs17.getInstance(myProject);
+ if (! vcs.isVcsBackgroundOperationsAllowed(myRoot)) return;
+
+ for (String newBranchUrl : next.getBranchUrls()) {
+ // check if cancel had been put
+ if (! vcs.isVcsBackgroundOperationsAllowed(myRoot)) return;
+ if (myAll || (! oldUrls.contains(newBranchUrl))) {
+ new NewRootBunch.BranchesLoadRunnable(myProject, myBunch, newBranchUrl, InfoReliability.defaultValues, myRoot, null, true).run();
+ }
+ }
+ }
+
+ public void setAll(boolean all) {
+ myAll = all;
+ }
+ }
+
+ public void loadState(final ConfigurationBean object) {
+ final UrlSerializationHelper helper = new UrlSerializationHelper(SvnVcs17.getInstance(myProject));
+ final Map map = object.myConfigurationMap;
+ final Map newMap = new HashMap(map.size(), 1);
+ final LocalFileSystem lfs = LocalFileSystem.getInstance();
+
+ final Set> whatToInit = new HashSet>();
+ for (Map.Entry entry : map.entrySet()) {
+ final SvnBranchConfiguration configuration = entry.getValue();
+ final VirtualFile root = lfs.refreshAndFindFileByIoFile(new File(entry.getKey()));
+ if (root == null) {
+ LOG.info("root not found: " + entry.getKey());
+ continue;
+ }
+
+ final SvnBranchConfiguration configToConvert;
+ if ((! myConfigurationBean.mySupportsUserInfoFilter) || configuration.isUserinfoInUrl()) {
+ configToConvert = helper.afterDeserialization(entry.getKey(), configuration);
+ } else {
+ configToConvert = configuration;
+ }
+ final SvnBranchConfigurationNew newConfig = new SvnBranchConfigurationNew();
+ newConfig.setTrunkUrl(configToConvert.getTrunkUrl());
+ newConfig.setUserinfoInUrl(configToConvert.isUserinfoInUrl());
+ for (String branchUrl : configToConvert.getBranchUrls()) {
+ List stored = getStored(branchUrl);
+ if (stored != null && ! stored.isEmpty()) {
+ newConfig.addBranches(branchUrl, new InfoStorage>(stored, InfoReliability.setByUser));
+ } else {
+ whatToInit.add(new Pair(root, newConfig));
+ newConfig.addBranches(branchUrl, new InfoStorage>(new ArrayList(), InfoReliability.empty));
+ }
+ }
+
+ myBunch.updateForRoot(root, new InfoStorage(newConfig, InfoReliability.setByUser), null);
+ }
+ ((ProjectLevelVcsManagerImpl) myVcsManager).addInitializationRequest(VcsInitObject.BRANCHES, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
+ public void run() {
+ try {
+ for (Pair pair : whatToInit) {
+ final BranchesPreloader branchesPreloader = new BranchesPreloader(myProject, myBunch, pair.getFirst());
+ branchesPreloader.setAll(true);
+ branchesPreloader.loadImpl(null, pair.getSecond());
+ }
+ }
+ catch (ProcessCanceledException e) {
+ //
+ }
+ }
+ });
+ }
+ });
+ object.myConfigurationMap.clear();
+ object.myConfigurationMap.putAll(newMap);
+ myConfigurationBean = object;
+ }
+
+ private List getStored(String branchUrl) {
+ Collection collection = myStorage.get(branchUrl);
+ if (collection == null) return null;
+ final List items = new ArrayList(collection);
+ Collections.sort(items);
+ return items;
+ }
+
+ private static class UrlSerializationHelper {
+ private final SvnVcs17 myVcs;
+
+ private UrlSerializationHelper(final SvnVcs17 vcs) {
+ myVcs = vcs;
+ }
+
+ public SvnBranchConfiguration prepareForSerialization(final SvnBranchConfiguration configuration) {
+ final Ref withUserInfo = new Ref();
+ final String trunkUrl = serializeUrl(configuration.getTrunkUrl(), withUserInfo);
+
+ if (Boolean.FALSE.equals(withUserInfo.get())) {
+ return configuration;
+ }
+
+ final List branches = configuration.getBranchUrls();
+ final List newBranchesList = new ArrayList(branches.size());
+ for (String s : branches) {
+ newBranchesList.add(serializeUrl(s, withUserInfo));
+ }
+
+ final SvnBranchConfiguration result = new SvnBranchConfiguration();
+ result.setTrunkUrl(trunkUrl);
+ result.setBranchUrls(newBranchesList);
+ result.setUserinfoInUrl(withUserInfo.isNull() ? false : withUserInfo.get());
+ return result;
+ }
+
+ public SvnBranchConfiguration afterDeserialization(final String path, final SvnBranchConfiguration configuration) {
+ if (! configuration.isUserinfoInUrl()) {
+ return configuration;
+ }
+ final String userInfo = getUserInfo(path);
+ if (userInfo == null) {
+ return configuration;
+ }
+
+ final String newTrunkUrl = deserializeUrl(configuration.getTrunkUrl(), userInfo);
+ final List branches = configuration.getBranchUrls();
+ final List newBranchesList = new ArrayList(branches.size());
+ for (String s : branches) {
+ newBranchesList.add(deserializeUrl(s, userInfo));
+ }
+
+ final SvnBranchConfiguration result = new SvnBranchConfiguration();
+ result.setTrunkUrl(newTrunkUrl);
+ result.setBranchUrls(newBranchesList);
+ result.setUserinfoInUrl(userInfo != null && userInfo.length() > 0);
+ return result;
+ }
+
+ private String serializeUrl(final String url, final Ref withUserInfo) {
+ if (Boolean.FALSE.equals(withUserInfo.get())) {
+ return url;
+ }
+ try {
+ final SVNURL svnurl = SVNURL.parseURIEncoded(url);
+ if (withUserInfo.isNull()) {
+ final String userInfo = svnurl.getUserInfo();
+ withUserInfo.set((userInfo != null) && (userInfo.length() > 0));
+ }
+ if (withUserInfo.get()) {
+ return SVNURL.create(svnurl.getProtocol(), null, svnurl.getHost(), svnurl.getPort(), svnurl.getURIEncodedPath(), true).toString();
+ }
+ }
+ catch (SVNException e) {
+ //
+ }
+ return url;
+ }
+
+ @Nullable
+ private String getUserInfo(final String path) {
+ final SVNURL svnurl = myVcs.getSvnFileUrlMapping().getUrlForFile(new File(path));
+ return svnurl != null ? svnurl.getUserInfo() : null;
+ }
+
+ private String deserializeUrl(final String url, final String userInfo) {
+ try {
+ final SVNURL svnurl = SVNURL.parseURIEncoded(url);
+ return SVNURL.create(svnurl.getProtocol(), userInfo, svnurl.getHost(), svnurl.getPort(), svnurl.getURIEncodedPath(), true).toString();
+ } catch (SVNException e) {
+ return url;
+ }
+ }
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnBranchMapperManager17.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnBranchMapperManager17.java
new file mode 100644
index 000000000000..0dd1d05d1f1d
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnBranchMapperManager17.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.messages.Topic;
+import org.jetbrains.idea.svn17.branchConfig.SvnBranchConfigurationNew;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * Holds what working copies we have for URLs
+ */
+@State(
+ name = "SvnBranchMapperManager17",
+ storages = {
+ @Storage(
+ file = "$APP_CONFIG$/other.xml"
+ )}
+)
+public class SvnBranchMapperManager17 implements PersistentStateComponent {
+ private SvnBranchMapperHolder myStateHolder;
+
+ public static SvnBranchMapperManager17 getInstance() {
+ return ServiceManager.getService(SvnBranchMapperManager17.class);
+ }
+
+ public SvnBranchMapperManager17() {
+ myStateHolder = new SvnBranchMapperHolder();
+ }
+
+ public SvnBranchMapperHolder getState() {
+ return myStateHolder;
+ }
+
+ public void loadState(final SvnBranchMapperHolder state) {
+ myStateHolder = state;
+ }
+
+ public void put(final String url, final String value) {
+ myStateHolder.put(url, value);
+ notifyWcRootsChanged(url, Collections.unmodifiableCollection(myStateHolder.get(url)));
+ }
+
+ public void remove(final String url, final File value) {
+ final Set set = myStateHolder.get(url);
+ if (set != null) {
+ set.remove(value.getAbsolutePath());
+ }
+ notifyWcRootsChanged(url, Collections.unmodifiableCollection(set));
+ }
+
+ private static void notifyWcRootsChanged(final String url, final Collection roots) {
+ ApplicationManager.getApplication().getMessageBus().syncPublisher(WC_ROOTS_CHANGED).rootsChanged(url, roots);
+ }
+
+ public void notifyBranchesChanged(final Project project, final VirtualFile vcsRoot, final SvnBranchConfigurationNew configuration) {
+ final Map map = configuration.getUrl2FileMappings(project, vcsRoot);
+ if (map != null) {
+ for (Map.Entry entry : map.entrySet()) {
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+
+ public Set get(final String key) {
+ return myStateHolder.get(key);
+ }
+
+ public static class SvnBranchMapperHolder {
+ public Map> myMapping;
+
+ public SvnBranchMapperHolder() {
+ myMapping = new HashMap>();
+ }
+
+ public void put(final String key, final String value) {
+ Set files = myMapping.get(key);
+ if (files == null) {
+ files = new HashSet();
+ myMapping.put(key, files);
+ }
+ files.add(value);
+ }
+
+ public Set get(final String key) {
+ return myMapping.get(key);
+ }
+ }
+
+ public static interface WcRootsChangeConsumer {
+ void rootsChanged(final String url, final Collection roots);
+ }
+
+ public static final Topic WC_ROOTS_CHANGED =
+ new Topic("SVN_WC_ROOTS_CHANGED", WcRootsChangeConsumer.class);
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnBundle.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnBundle.java
new file mode 100644
index 000000000000..b7b1101f48a3
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnBundle.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.CommonBundle;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.PropertyKey;
+
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.util.ResourceBundle;
+
+public class SvnBundle {
+ private static Reference ourBundle;
+
+ @NonNls private static final String BUNDLE = "org.jetbrains.idea.svn17.SvnBundle";
+
+ private SvnBundle() {
+ }
+
+ public static String message(@PropertyKey(resourceBundle = BUNDLE)String key, Object... params) {
+ return CommonBundle.message(getBundle(), key, params);
+ }
+
+ public static String getString(@PropertyKey(resourceBundle = BUNDLE)String key) {
+ return getBundle().getString(key);
+ }
+
+ private static ResourceBundle getBundle() {
+ ResourceBundle bundle = null;
+ if (ourBundle != null) bundle = ourBundle.get();
+ if (bundle == null) {
+ bundle = ResourceBundle.getBundle(BUNDLE);
+ ourBundle = new SoftReference(bundle);
+ }
+ return bundle;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnBundle.properties b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnBundle.properties
new file mode 100644
index 000000000000..f932ca1b634e
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnBundle.properties
@@ -0,0 +1,635 @@
+#Actions&Plugin info
+group.SubversionFileGroup.text=SVN
+action.Subversion.BrowseSVNRepository.text=Bro_wse Subversion Repository...
+action.Subversion.BrowseSVNRepository.description=Browse a Subversion repository
+group.SubversionFilePopupGroup.text=_Subversion
+action.Subversion.Add.text=_Add
+action.Subversion.Add.description=Add the file to Subversion
+group.IgnoreChoicesGroup.text=Ignore
+group.RevertIgnoreChoicesGroup.text=Remove from ignore list
+action.Subversion.UndoIgnore.text=Selected file names
+action.Subversion.UndoIgnore.description=Remove file name/mask from folder ignore list
+action.Subversion.Ignore.ExactMatch.text=Selected file names
+action.Subversion.Ignore.ExactMatch.description=Adds names of all selected files into folder ignore list
+action.Subversion.Ignore.MatchExtension.text=Extension mask
+action.Subversion.Ignore.MatchExtension.description=Adds {0} mask into folder ignore list
+action.Subversion.SetProperty.text=Set Proper_ty...
+action.Subversion.SetProperty.description=Set versioned property on file or directory
+action.Subversion.Revert.text=_Revert...
+action.Subversion.Revert.description=Revert file to pristine state
+action.Subversion.Resolve.text=Re_solve Text Conflict...
+action.Subversion.Resolve.description=Resolve text conflict on file
+action.Subversion.MarkResolved.text=_Mark Resolved...
+action.Subversion.MarkResolved.description=Mark text and properties conflicts as resolved
+action.Subversion.Clenaup.text=Cl_eanup
+action.Subversion.Clenaup.description=Unlock locked directories and run all remaining incompleted operations
+action.Subversion.Copy.text=_Branch or Tag...
+action.Subversion.Copy.description=Copy selected file or directory to the new repository location
+action.Subversion.Lock.text=_Lock...
+action.Subversion.Lock.description=Lock files
+action.Subversion.Unlock.text=Unl_ock
+action.Subversion.Unlock.description=Unlock files
+action.SvnChangesBrowser.text=Bro_wse Changes...
+plugin.Subversion.description=Provides integration with Subversion version control system
+action.name.refresh=Refresh
+action.Subversion.ImportToSVNRepository.text=I_mport into Subversion...
+action.Subversion.Share.text=_Share Directory...
+action.Subversion.ShowProperties.text=Edit _Properties
+action.Subversion.ConfigureBranches.text=Configure Branches...
+action.Subversion.CompareWithBranch.text=Compare with Branch...
+action.Subversion.Relocate.text=Relocate...
+action.Subversion.Relocate.description=Relocate working copy to a different URL
+action.Subversion.CleanupProject.text=Cleanup Project
+action.Subversion.CleanupProject.description=Perform cleanup for all working copy directories in the project
+
+#Copy dialog
+dialog.title.branch=Create Branch or Tag
+label.copy.from=&From:
+label.copy.to=&To:
+label.copy.from.revision=Copy from revision:
+radio.copy.working.copy=&Working copy
+radio.copy.head=&HEAD revision
+radio.copy.specific=Specific &revision:
+label.copy.comment=&Comment
+label.copy.select.location.dialog.copy.as=Copy &as:
+
+#Locl dialog
+dialog.title.lock.files=Lock Files
+dialog.title.lock.file=Lock File
+label.lock.comment=&Lock comment:
+label.locl.steal.existing=&Steal existing lock
+
+#SSHCredentialsDialog
+label.ssh.authentication.realm=Authentication realm: ''{0}''
+dialog.title.openssh.v2.private.key=OpenSSH v2 Private Key
+dialog.description.openssh.v2.private.key=Select private key file for SSH authentication
+checkbox.ssh.keep.for.current.session=&Save Credentials
+label.ssh.user.name=&User name:
+radio.ssh.authentication.with.password=Authentication &with password
+label.ssh.password=&Password:
+radio.ssh.authentication.private.key=Authentication with private &key
+label.ssh.key.file=Key &File:
+label.ssh.passphrase=Passph&rase:
+label.ssh.port=Port &Number:
+
+label.ssl.certificate.path=Path to certificate:
+label.ssl.certificate.password=Certificate passphrase:
+checkbox.ssl.keep.for.current.session=&Save Credentials
+
+progress.text.create.remote.folder=Create Remote Folder
+message.text.error=Error
+
+#Repository Browser
+dialog.title.browser.copy.or.move=Copy or Move
+dialog.title.browser.delete=Delete
+dialog.title.browser.new.folder=New Folder
+label.browser.url1.delete=Delete
+label.browser.url1.create=Create
+label.browser.url1.from=From
+label.browser.to=To
+label.browser.name.copy.as=Copy &as:
+label.browser.name.name=&Name:
+checkbox.browser.move.source.to.target=&Move source to target
+label.browser.comment=&Comment:
+label.browser.select.location=&Select location in repository:
+node.text.browser.type.valid.url=Type valid URL and click 'Refresh' to select location
+dialog.title.browser.browse.repository=Browse Subversion Repository
+label.text.browser.repository.url=Repository &URL:
+button.text.new.folder=&New Folder...
+button.text.delete=&Delete...
+button.text.copy=&Copy
+button.text.paste=&Paste...
+progres.text.deleting=Deleting {0}
+progress.title.browser.delete=Delete
+progress.text.browser.creating=Creating {0}
+progress.text.browser.moving=Moving {0}
+progress.text.browser.copying=Copying {0}
+progress.text.browser.remote.destination=To {0}
+progress.title.browser.move=Move
+progress.title.browser.copy=Copy
+node.text.browser.cannot.access.repository=Cannot access repository at ''{0}''
+progress.text.browser.establishing.connection=Establishing connection to Subversion repository
+progress.title.browser.establishing.connection=Establishing Connection...
+node.text.browser.cannot.establish.connection=Cannot establish connection to ''{0}''
+
+#authentication
+label.auth.authentication.realm=Authentication realm: ''{0}''
+label.auth.user.name=&User name:
+label.auth.password=&Password:
+checkbox.auth.keep.for.current.session=&Save credentials
+label.credentials.user.name.required=User name is required for ''{0}''
+label.credentials.user.name=User name:
+checkbox.credentials.save.for.future=Save for future use
+dialog.title.authentication.required=Authentication Required
+dialog.title.authentication.required.was.failed=Authentication Required (Authentication Failed)
+
+#history
+progress.title.revisions.history=Revisions History
+progress.text2.collecting.history=Collecting revisions history for ''{0}''
+checkbox.changes.filter.filter.by.author=A&uthor:
+progress.text2.processing.revision=Processing revision ''{0}''
+progress.text.changes.collecting.changes=Collecting information on changes
+progress.text2.changes.establishing.connection=Establishing connection to ''{0}''
+progress.text2.revision.processed=Revision ''{0}'' processed
+
+#status
+status.configurable.display.name=Status
+status.configurable.get.out.of.date.information=Check status against se&rver
+progress.text.status.analyzing.local=Analyzing status of ''{0}''
+progress.text.status.analizyng.remote=Discovering remote status of ''{0}''
+status.group.name.missing=Missing
+status.group.name.replaced=Replaced
+status.group.name.obstructed=Obstructed
+status.group.name.externals=Externals
+status.group.name.switched=Switched
+status.group.name.locked=Locked
+
+#Configuration
+checkbox.configure.use.system.default.configuration.directory=&Use system default Subversion configuration directory
+label.configuration.configuration.directory=Subversion configuration &directory:
+dialog.title.select.configuration.directory=Select Configuration Directory
+dialog.description.select.configuration.directory=Select Subversion configuration directory or create new one
+button.text.clear.authentication.cache=&Clear Auth Cache
+label.text.delete.stored.credentials=Delete all stored credentials for 'http', 'svn' and 'svn+ssh' protocols
+button.text.edit.proxies=Edit Network &Options...
+label.text.edit.proxies=Edit 'servers' Subversion runtime configuration file
+dialog.title.edit.http.proxies.settings = Edit Subversion options related to the network layers
+dialog.edit.http.proxies.settings.error.same.group.names.text=Groups with identical names detected: ''{0}''
+dialog.edit.http.proxies.settings.error.ambiguous.group.patterns.text=Found repository urls that match to several groups: {0}
+dialog.edit.http.proxies.settings.error.ambiguous.group.patterns.to.text=url {0} matches to {1}\n
+dialog.edit.http.proxies.settings.tab.edit.user.file.title=User file
+dialog.edit.http.proxies.settings.tab.edit.system.file.title=System file
+dialog.edit.http.proxies.settings.port.must.be.number.error=Port must be a number (group ''{0}'')
+dialog.edit.http.proxies.settings.timeout.must.be.number.error=HTTP timeout must be a number (group ''{0}'')
+dialog.edit.http.proxies.settings.dialog.select.ssl.client.certificate.path.title=Select path to your certificate
+dialog.edit.http.proxies.settings.dialog.select.ssl.client.certificate.path.text=Select path to your certificate
+dialog.edit.http.proxies.settings.panel.proxy.title=HTTP proxy settings
+dialog.edit.http.proxies.settings.panel.ssl.title=SSL settings
+dialog.edit.http.proxies.settings.panel.repositories.title=Repositories
+dialog.edit.http.proxies.settings.patterns.text=U&RL patterns:
+dialog.edit.http.proxies.settings.exceptions.text=E&xceptions:
+dialog.edit.http.proxies.settings.server.text=&Server:
+dialog.edit.http.proxies.settings.user.text=&User:
+dialog.edit.http.proxies.settings.port.text=P&ort:
+dialog.edit.http.proxies.settings.password.text=Pass&word:
+dialog.edit.http.proxies.settings.connection.timeout.text=Connection t&imeout:
+dialog.edit.http.proxies.settings.seconds.text=seconds
+dialog.edit.http.proxies.settings.paths.to.authority.certificates.text=Comma separated &paths to CAs certificate files:
+dialog.edit.http.proxies.settings.ssl.client.certificate.file.text=SSL &client certificate file:
+dialog.edit.http.proxies.settings.client.certificate.passphrase.text=SSL client certificate passp&hrase:
+dialog.edit.http.proxies.settings.trust.default.cas.text=Trust default C&As
+dialog.edit.http.proxies.settings.test.connection.button.text=&Test connection
+dialog.edit.http.proxies.settings.test.connection.settings.will.be.stored.title=Warning
+dialog.edit.http.proxies.settings.test.connection.settings.will.be.stored.text=Settings will be saved
+dialog.edit.http.proxies.settings.test.connection.error.title=Connection test failed
+dialog.edit.http.proxies.settings.test.connection.succes.title=Connection test successful
+ dialog.edit.http.proxies.settings.test.connection.succes.text=Connection test successful
+confirmation.text.delete.stored.authentication.information=You are about to delete all stored Subversion authentication information.\nWould you like to proceed with deletion?
+confirmation.title.clear.authentication.cache=Clear Authentication Cache
+
+label.configure.upgrade.label=Working Copy upgrade strategy:
+radio.configure.upgrade.ask=As&k me on attempt to upgrade
+radio.configure.upgrade.none=&Never upgrade (keep Subversion 1.3)
+radio.configure.upgrade.auto=Upgrade &automatically (use Subversion 1.4)
+radio.configure.upgrade.auto.15format=Upgrade automatically (use Subversion 1.&5)
+radio.configure.upgrade.auto.16format=Upgrade automatically (use Subversion 1.&6)
+
+label.configure.create.label={0} is about to create new Subversion working copy,\n\
+please, select desired working copy format:
+label.configure.create.strategy=Create working copy directory in:
+radio.configure.create.none=1.&3 format
+radio.configure.create.auto=1.&4 format
+radio.configure.create.auto.15format=1.&5 format
+radio.configure.create.auto.16format=1.&6 format
+radio.configure.create.auto.17format=1.&7 format
+
+label.configure.change.label=Change working copy ''{0}'' format to:
+radio.configure.change.none=1.&3 format
+radio.configure.change.auto=1.&4 format
+radio.configure.change.auto.15format=1.&5 format
+radio.configure.change.auto.16format=1.&6 format
+radio.configure.change.auto.17format=1.&7 format
+
+label.where.svn.format.settings.text=To change above setting later, visit 'File | Settings | Version Control'
+label.where.svn.format.can.be.changed.text=''Version Control | Subversion | {0}''
+dialog.upgrade.wcopy.format.title=Subversion Working Copy Format
+
+label.working.copy.root.outside.text=Working copy root is outside the directory specified in Project Settings.\nThe whole working copy will be converted.
+
+progress.text.loading.contents=Loading contents of ''{0}''
+progress.text2.revision.information=Revision {0}
+progress.title.loading.file.content=Loading Remote File Content
+exception.text.file.miss.svn=File ''{0}'' is readonly, but miss svn:needs-lock property
+confirmation.text.edit.file=File(s) you're are going to edit needs to be locked before editing
+
+#File statuses
+file.status.external=External (svn)
+file.status.obstructed=Obstructed (svn)
+file.status.replaced=Replaced (svn)
+file.status.switched=Switched (svn)
+
+#Adding
+confirmation.text.add.file=Do you want to schedule the following file for addition to Subversion?\n{0}
+confirmation.text.add.dir=Do you want to schedule the following directory for addition to Subversion?\n{0}
+confirmation.title.add.file=Schedule for Addition
+confirmation.title.add.multiple.files=Select Files to Add to Subversion
+action.name.add.files=Adding files to {0}
+
+#Ignore
+action.name.ignore.files=Adding ignore file(s) mask to {0}
+action.name.undo.ignore.files=Removing file names(or mask) from ignore list from {0}
+
+# Delete
+confirmation.text.delete.file=Do you want to schedule the following file for deletion from Subversion?\n{0}
+confirmation.text.delete.dir=Do you want to schedule the following directory for deletion from Subversion?\n{0}\nNOTE: The directory will be deleted from disk when the change is committed.
+confirmation.title.delete.file=Schedule for Deletion
+confirmation.title.delete.multiple.files=Select Files to Delete from Subversion
+
+progress.text.discovering.location=Discovering location for ''{0}''
+
+#Lock/Unlock
+progress.text.locking.files=Locking files in repository...
+progress.text2.processing.file=Processing file {0}
+progress.title.lock.files=Lock Files
+exception.text.locking.file.failed=Failed to lock file: {0}
+message.title.lock.failures=Lock Failures
+message.text.files.locked={0,choice, 0#Failed to lock files.|1#1 file locked|2#{0,number} files locked}
+progress.text.unlocking.files=Unlocking files in repository...
+progress.title.unlock.files=Unlock Files
+exception.text.failed.to.unlock.file=Failed to unlock file: {0}
+message.title.unlock.failures=Unlock Failures
+message.text.files.unlocked={0,choice, 0#Failed to unlock files.|1#1 file unlocked|2#{0,number} files unlocked}
+
+
+progress.text.collection.locations=Collection Subversion Repository Locations
+message.text.no.working.copies.found=No subversion working copies found in project
+message.title.no.working.copies.found=Not working copies found
+progress.text.collecting.roots.in.module=Collecting working copy roots in module ''{0}''
+dialog.title.select.location=Select Location
+border.select.location.repository.location=Repository Location
+radio.select.location.use.project=Use project location:
+radio.select.location.use.custom=Use custom location:
+exception.text.cleanupaction.batchperform.not.implemented=CleanupAction.batchPerform not implemented
+progress.text2.adding=Adding ''{0}''
+progress.text2.deleting=Deleting ''{0}''
+progress.text2.sending=Sending ''{0}''
+progress.text2.replacing=Replacing ''{0}''
+progress.text2.transmitting.delta=Transmitting delta for ''{0}''
+progress.title.copy=Subversion Copy
+status.text.comitted.revision=Committed revision {0}.
+progress.text.copy.to=Copy to ''{0}''
+message.text.no.conflicts.found=No conflicts found
+message.title.no.conflicts.found=No Conflicts
+label.select.files.and.directories.to.mark.resolved=Select files and directories to mark resolved:
+dialog.title.mark.resolved=Mark Resolved
+action.name.mark.resolved=Mark Resolved
+action.name.resolve.conflict=Resolve Conflict
+message.text.revert.no.modifications.found=No modifications to revert found
+label.select.files.and.directories.to.revert=Select files and directories to revert:
+dialog.title.revert.files=Revert Files
+action.name.revert=&Revert
+message.title.revert.no.modifications.found=No Modifications
+action.name.revert.files=Revert files
+action.name.set.property=Set Property
+action.name.show.all.paths.affected=Show all paths affected in selected transaction
+dialog.title.affected.paths=Paths Affected in Transaction ''{0}''
+progress.text.loading.log=Loading revision log...
+exception.text.cannot.load.version=Cannot load repository version {0}
+progress.text.processing.changes=Processing changes...
+message.text.cannot.load.version=Cannot load repository version {0} :{1}
+message.title.error.fetching.affected.paths=Error Fetching Affected Paths
+exception.text.cannot.annotate.directory=Annotation operation only makes sence for files
+progress.text.computing.annotation=Computing annotation for ''{0}''
+action.text.annotate=Annotate
+checkbox.chckin.keep.files.locked=&Keep files locked
+border.show.changes.dialog.subversion.group=Subversion
+progress.title.commit=Commit
+progress.text.committing.changes.below=Creating commit packets...
+status.text.committed.revision=Committed revision {0}.
+checkin.operation.name=Comm_it
+progress.text.collecting.commitables=Collecting commitables at ''{0}''
+progress.text2.fetching.external.location=Fetching external location to ''{0}''
+progress.text2.checked.out=Checked out {1} files, checking out {0}
+progress.text2.exported=Exported {1} files, exporting {0}
+progress.text2.checked.out.revision=Checked out revision {0}.
+progress.text2.exported.revision=Exported revision {0}.
+status.text.checked.out.revision=Checked out revision {0}.
+message.text.cannot.checkout=Cannot checkout from svn: {0}
+message.text.cannot.export=Cannot export from svn: {0}
+message.title.check.out=Check Out from Subversion
+message.title.export=Export from Subversion
+progress.text.checking.out=Checking out files to ''{0}''
+progress.text.export=Exporting files to ''{0}''
+dialog.text.check.out=Check Out from Repository
+label.text.checkout.repository.url=Repository &URL:
+label.text.checkout.checkout.from=Checkout From:
+label.checkout.checkout.to=Checkout &To:
+dialof.title.select.checkout.destination=Select Checkout Destination
+dialog.description.select.checkout.destination=Select directory to checkout remote directory into or create new one
+button.text.select.all=Select &All
+button.text.deselect.all=&Deselect All
+dialog.title.select.repository.location=Select Repository Location
+button.text.ssl.accept=_Accept
+button.text.ssl.reject=_Reject
+button.text.ssh.accept=_Yes
+button.text.ssh.reject=_No
+dialog.title.ssl.examine.server.crertificate=Examine Server Certificate
+dialog.title.ssh.examine.server.fingerprints=Verify Server Key Fingerprint
+label.ssl.server.provided.certificate=Server provided the following certificate:
+label.ssh.server.provided.fingerprints=The authenticity of host {0} can''t be established.
+label.ssh.server.provided.fingerprints2={0} key fingerprint is
+label.ssh.server.provided.fingerprints3=Are you sure you want to continue connecting?
+checkbox.svn.ssh.cache.fingerprint=&Add key to Subversion cache
+dialog.title.set.property=Set Property
+label.set.property.property.name=Property &name:
+radio.set.property.set.property.value=&Set property value:
+radio.set.property.delete.property=&Delete property
+checkbox.set.property.update.properties.recursively=Update properties &recursively
+exception.text.invalid.revision=Invalid revision ''{0}''
+
+#update
+update.configurable.name=Update
+checkbox.update.configurable.descend.into.child.directories=&Descend into child directories
+checkbox.update.configurable.run.status=Run &status after update
+update.switch.configurable.name=Update/Switch
+radio.update.switch.configurable.update.or.switch=U&pdate or Switch
+label.update.switch.configurable.url=&URL:
+checkbox.update.switch.configurable.to.specific.revision=Update/Switch to specific &revision:
+radio.update.switch.configurable.merge=&Merge
+label.update.switch.configurable.url1=URL &1:
+label.update.switch.configurable.revision1=Re&vision:
+lable.update.switch.configurable.url2=URL &2:
+update.switch.configurable.revision2=R&evision:
+checkbox.update.switch.configurable.try.merge.without.changes=&Try merge, but make no changes
+checkbox.update.switch.configurable.descend.into.child.directories=&Descend into child directories
+checkbox.update.switch.configurable.run.status=Run &status after update
+progress.text.merging.dry.run.changes=Merging (dry run) changes into ''{0}''
+progress.text.merging.changes=Merging changes into ''{0}''
+progress.text.updating=Updating ''{0}''
+exception.text.root.was.not.properly.updated=svn: {0} was not properly updated; may be it is already removed from the repository along with its parent.
+progress.text.update.computing.post.update.status=Computing post-update status of ''{0}''
+message.text.update.no.directories.found=No versioned directories to update was found
+messate.text.update.error=SVN: Update Error
+
+progress.text2.added=Added {0}
+progress.text2.deleted=Deleted {0}
+progress.text2.conflicted=Conflicted {0}
+progress.text2.treeconflicted=Conflicted {0} (tree conflict)
+progres.text2.merged=Merged {0}
+progres.text2.updated=Updated {0}
+progress.text2.restored.file=Restored {0}
+progres.text2.updated.to.revision=Updated to revision {0}.
+status.text.updated.to.revision=Updated to revision {0}.
+progress.text2.skipped.file=Skipped {0}
+update.group.name.skipped=Skipped
+progress.text.updating.external.location=Updating external location at ''{0}''
+exception.text.update.operation.cancelled=Operation cancelled
+server.ssl.accept.temporary.action.name=Accept _Temporary
+changes.browser.change.column.name=Change
+changes.browser.author.column.name=Author
+changes.browser.date.column.name=Date
+cannot.mark.file.as.resolved.error.message=Cannot mark file as resolved: {0}
+mark.resolved.dialog.title=Mark Resolved
+mark.resolved.action.description=Mark resolved
+commit.dialog.setings.show.unresolved.checkbox=Show &unresolved
+unresolved.difference.type.name=Unresolved
+integrate.display.name=Integrate
+invalid.svn.revision.error.message=Invalid svn revision: {0}
+nulty.roots.update.configuration.affected.roots.label=Affected roots:
+nulty.roots.update.configuration.selected.root.setting.group.title=Selected Root Setting:
+integrate.configuration.revision1.label=re&vision
+integrate.configuration.source2.label=Source 2:
+integrate.configuration.revision2.label=&revision
+integrate.configuration.source1.label=Source 1:
+integrate.configuration.description.label=Integrate differences between sources into working copy (compare Source 2 to Source 1)
+source.url.could.not.be.empty.error.message=Source url could not be empty
+no.differences.between.sources.error.message=There are no differences between source and the same source
+configure.revision.specified.radio=Specified
+configure.revision.head.radio=HEAD
+update.configuration.specific.url=Update/Switch to specific &url:
+progress.text.import=Importing {0}
+message.text.cannot.import=Cannot import to svn: {0}
+message.title.import=Import to Subversion
+revision.title=Revision
+checkout.dialog.title=Checkout from Subversion
+checkout.dialog.button=Checkout
+checkout.directory.chooser.title=Checkout Directory
+checkout.directory.chooser.prompt=Select directory to checkout from Subversion
+checkout.options.dialog.title=SVN Checkout Options
+checkout.options.checkout.label=Checkout:
+checkout.options.destination.label=Destination:
+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.trunk.location=Trunk location:
+configure.branches.branch.locations=Branch locations:
+configure.branches.item=Configure Branches...
+refresh.branches.item=Refresh Branches...
+copy.column.title=Copy
+copy.column.tooltip=Copied from {0}
+compare.with.branch.progress.loading.branches=Loading Branches
+compare.with.branch.progress.loading.content=Loading Content
+compare.with.branch.diff.title=Compare with Branch
+compare.with.branch.error.title=Compare with Branch
+compare.with.branch.popup.title=Compare with Branch
+getting.branch.configuration.error=Error retrieving branch configuration: {0}
+compare.with.branch.location.error=Couldn''t find repository location for file ''{0}'' under branch ''{1}''\nPlease check your branch configuration.
+compare.with.branch.list.error=Error Loading Branches
+create.branch.invalid.revision.error=Invalid revision ''{0}''
+create.branch.no.working.copy.error=No working copy found at {0}
+create.branch.no.base.location.error=No branch base location selected
+import.dialog.title=Import into Subversion
+import.dialog.button=Import
+repository.browser.add.location.action=Add Repository Location
+repository.browser.add.location.prompt=Repository URL:
+repository.browser.add.location.title=New Repository Location
+repository.browser.details.action=Show/Hide Details
+repository.browser.refresh.action=Refresh selected tree node
+repository.browser.add.location.menu.item=_Repository Location...
+repository.browser.edit.location.menu.item= Edit Location _Url...
+repository.browser.discard.location.action=Discard _Location
+repository.browser.discard.location.prompt=Would you like to discard the location ''{0}''?
+repository.browser.discard.location.title=Discard Location
+repository.browser.new.folder.action=Remote _Folder...
+repository.browser.history.action=Show History
+repository.browser.import.action=_Import...
+diff.options.title=Compare With Branch or Tag
+diff.options.no.url.error=Please select an URL to compare with
+diff.options.same.url.error=Please select different URLs to compare
+diff.options.no.patch.file.error=Please specify the path to save the patch file
+repository.browser.browse.changes.action=_Browse Changes...
+repository.browser.browse.changes.description=View history of changes in the selected node
+repository.browser.compare.title=Compare of ''{0} and ''{1}''
+repository.browser.compare.no.difference.message=No textual difference found between ''{0} and ''{1}''
+repository.browser.compare.no.difference.title=No Difference Found
+cleanup.action.name=Cleanup
+changes.browser.revision.term=Revision
+progress.computing.difference=Computing Difference
+progress.searching.for.files.with.conflicts=Searching for files with conflicts
+add.files.errors.title=Errors Adding Files
+move.files.errors.title=Errors Moving Files
+remove.ignore.patterns.errors.title=Errors Removing 'svn:ignore' patterns
+gather.ignore.patterns.info.progress.title=Gathering not used 'svn:ignore' patterns
+delete.files.errors.title=Errors Deleting Files
+copy.dialog.title=Branch or Tag
+move.dialog.title=Move or Rename
+select.location.invalid.url.message=Invalid Subversion repository URL: ''{0}''
+share.directory.action=Share Directory...
+share.directory.commit.message=Directory ''{0}'' created by {1}
+share.directory.title=Share Directory
+share.directory.info.message=To complete share operation commit ''{0}''.
+share.directory.create.dir.progress.text=Creating remote folder {0}
+share.directory.checkout.back.progress.text=Checking out {0}
+share.or.import.add.progress.text=Scheduling {0} for addition
+action.share.whole.project.text=Share Project (Subversion)...
+action.Subversion.integrate.changes.open.in.changelist.text=Open in changelist:
+action.Subversion.integrate.changes.select.working.copy.text=Integrate into working copy:
+action.Subversion.integrate.changes.dialog.title=Integrate To Branch
+action.Subversion.integrate.changes.message.not.under.control.text=Selected destination is not under Subversion control. Continue anyway?
+action.Subversion.integrate.changes.message.another.wc.text=Selected destination is inside working copy of another repository. Continue anyway?
+action.Subversion.integrate.changes.branch.info.source.label.text=Source branch URL: {0}
+action.Subversion.integrate.changes.branch.info.target.label.text=Target branch URL: {0}
+action.Subversion.integrate.changes.actionname=Integrate To Branch...
+action.Subversion.integrate.changes.description=Integrates selected changelist(s)/changeset into selected branch
+action.Subversion.integrate.changes.messages.title=Integrate To Branch
+action.Subversion.integrate.changes.error.no.available.files.text=No files available for integration
+action.Subversion.integrate.changes.warning.failed.list.text=Changelist {0} ({1}) integration faced problems
+action.Subversion.integrate.changes.warning.skipped.lists.text=Skipped change lists: {0}
+action.Subversion.integrate.changes.select.branch.text=Select branch to integrate to...
+action.Subversion.integrate.changes.error.source.and.target.same.text=Can not start integration: Target and source branches are the same.
+action.Subversion.integrate.changes.error.target.not.dir.text=Can not start integration: target directory does not exist or is not a directory.
+action.Subversion.integrate.changes.error.not.versioned.text=Can not start integration: target directory is not under Subversion control or does not belong to the selected branch.
+action.Subversion.integrate.changes.alien.commit.changelist.title=Integrated
+action.Subversion.integrate.changes.dialog.add.wc.text=Add
+action.Subversion.integrate.changes.dialog.remove.wc.text=Remove
+action.Subversion.integrate.changes.message.files.up.to.date.text=All files are up to date
+action.Subversion.integrate.changes.error.unable.to.collect.changes.text=Error while collecting changes to commit: {0}
+error.cannot.load.revisions=Can not load revision list
+action.Subversion.integrate.changes.message.canceled.text=Integration was canceled
+action.Subversion.integrate.changes.progress.integrating.text=Merging changes
+action.Subversion.integrate.changes.progress.integrating.details.text=Merging changelist {0}
+update.switch.to.branch.text=Use branch:
+select.branch.popup.general.title=Select branch
+progress.live.provider.loading.revisions.text=Loading revisions from server
+progress.live.provider.loading.revisions.details.text=Loading revisions from server...
+action.show.svn.map.text=Show Working Copies...
+action.show.svn.map.text.reference=Show Working Copies
+action.show.svn.map.description=Shows working copies information: working copy formats and URLs
+dialog.show.svn.map.title=Subversion Working Copies Information
+dialog.show.svn.map.label.table.header=Detected working copies
+dialog.show.svn.map.change.format.button.text=&Change Working Copy Format
+dialog.show.svn.map.table.header.column.wcpath.title=Root path
+dialog.show.svn.map.table.header.column.wcurl.title=URL
+dialog.show.svn.map.table.header.column.wcroot.title=Working copy root
+dialog.show.svn.map.table.header.column.format.title=Format
+dialog.show.svn.map.table.version13.text=1.3
+dialog.show.svn.map.table.version14.text=1.4
+dialog.show.svn.map.table.version15.text=1.5
+dialog.show.svn.map.table.version16.text=1.6
+dialog.show.svn.map.table.version17.text=1.7
+action.change.wcopy.format.task.title=Convert working copy format
+action.change.wcopy.format.task.progress.text=Converting working copy at {0} format from {1} to {2}
+action.change.wcopy.format.after.change.settings=Selected working copy format ({0}) is older than upgrade mode selected in Project Settings ({1}).\
+\nChange Project Settings upgrade mode to {0}?
+committed.changes.action.enable.merge.highlighting=Highlight integrated
+committed.changes.action.enable.merge.highlighting.description.text=Option is available for SVN repositories and working copies with 1.5 version only
+committed.changes.action.merge.highlighting.select.root=from:
+committed.changes.action.merge.highlighting.select.branch=to:
+committed.changes.action.merge.highlighting.refresh.text=Refresh
+committed.changes.action.merge.highlighting.refresh.description=Refresh merged revisions info
+committed.changes.merge.status.refreshing.text=?
+committed.changes.merge.status.integrated.text=+
+committed.changes.merge.status.not.integrated.text=-
+committed.changes.action.merge.highlighting.select.wcopy=working copy:
+dialog.Subversion.select.working.copy.title=Configure working copy paths
+dialog.Subversion.select.working.copy.wcopy.list.title=Known working copies
+upgrade.format.to15.question.title=Upgrade working copies format
+upgrade.format.to15.question.text=Do you want to upgrade Subversion working copies format to 1.5?\n\
+Note: Working copy formats can be changed through {0}
+upgrade.format.clarify.for.outside.copies.text=Working copy root ''{0}'' is outside the directory specified in Project Settings.\nConvert anyway?
+upgrade.format.to16.question.title=Upgrade Subversion working copies format
+upgrade.format.to16.question.text=Do you want to upgrade Subversion working copies format to 1.6?\n\
+Note: Working copy formats can be changed through {0}
+action.Subversion.cleanup.progress.title=Subversion cleanup
+action.Subversion.cleanup.progress.text=Performing ''{0}'' cleanup...
+action.Subversion.cleanup.project.title=Cleanup Project
+action.Subversion.cleanup.error.message=Error performing cleanup for ''{0}'': {1}
+action.working.copies.map.correct.roots.button.text=Correct &Roots
+action.working.copies.map.correct.warning.text=Some folders marked in 'Project Settings | Version Control' as under Subversion control,\n\
+are unversioned (Versioned folders may be under them).\n\
+You can use 'Correct Roots' button to perform auto correction.
+action.Subversion.properties.diff.with.local.name=Properties Diff with Local
+action.Subversion.properties.diff.name=Properties Diff
+action.Subversion.properties.difference.diff.title=Subversion properties difference: {0}
+action.Subversion.properties.difference.diff.for.move.title=Subversion properties difference: {0} -> {1}
+message.Subversion.files.can.not.be.added.to.subversion.are.not.under.working.copy.text=File(s) {0} can not be added to Subversion control since they are not under working copy.
+action.Subversion.integrate.difference.option.use.ancestry.text=&Use ancestry
+fatal.Subversion.javasvn.jar.library.version.test=javasvn.jar library is old;\nPlease make sure installation was made properly.
+subversion.problem.text=Subversion problem
+use.idea.proxy.as.default=Use {0} general proxy settings as default for Subversion
+use.idea.proxy.as.default.label.text=Use proxy settings from Settings | Plugins | HTTP Proxy Settings as default
+action.Subversion.update.use.locks.on.demand.text.configurable=Updat&e administrative information only in changed subtrees
+action.Subversion.update.use.locks.on.demand.text=Update administrative information only in &changed subtrees
+action.Subversion.update.use.locks.on.demand.description=When enabled, update only locks those parts of working copy that is about to be updated.\n\
+As a result administrative information will only be updated for working copy subtrees that has been modified during update.\n\
+Enabling this option may improve update operation performance.\n\
+Note that under some circumstances, you might have to perform "normal" update.
+switch.target.not.copy.current=Switch target URL is not a copy of current.\n Do you want to continue?
+switch.target.problem.title=Switch target problem
+tab.repository.merge.panel.title=Subversion
+tab.repository.merge.panel.root.panel.to=To:
+tab.repository.merge.panel.root.panel.from=From:
+tab.repository.merge.panel.root.panel.branch=Branch:
+tab.repository.merge.panel.root.panel.url=Url:
+tab.repository.merge.panel.root.panel.local=Local Path:
+tab.repository.merge.panel.root.panel.select.local=Please select path
+tab.repository.merge.panel.filter.plus=Filter out integrated
+tab.repository.merge.panel.filter.minus=Filter out not integrated
+tab.repository.merge.panel.filter.others=Filter out others
+browse.changes.settings.stop.on.copy=&Stop On Copy
+action.mark.list.as.merged.text=Mark As Merged
+action.mark.list.as.merged.description=Mark selected revision as merged, but do not actually merge
+action.mark.list.as.not.merged.title=Mark As Not Merged
+action.mark.list.as.not.merged.descrition=Mark selected revision as not merged, but do not change anything besides svn:mergeinfo
+label.depth.text=&Depth:
+label.depth.description=Replaces old "recursive" option. "Recursive"=false is equivalent to "immediates", "recursive"=true is equivalent to "infinity"
+undo.integrate.to.branch=Undo Integrate To Branch...
+undo.integrate.to.branch.description=Removes changes done in revision from branch
+undo.integrate.to.branch.dialog.title=Undo Integrate To Branch
+merge.source.details.title=Merge source details for {0} [{1}]
+repository.browser.edit.location.dialog.title= Edit Location Url
+loading.default.branches.configuration.text=Loading default branches configuration...
+loading.data.for.root.text=Loading data for {0}
+action.PropertiesDiff.text=Compare Subversion Properties
+action.AlienPropertiesLocalDiff.text=Compare Subversion Properties with Branch
+action.IntegrateChangeSetAction.text=Integrate to Branch
+dialog.map.working.copies.button.refresh.text=Re&fresh
+refreshing.working.copies.roots.progress.text=Refreshing working copies roots...
+retrieving.subversion.status.text=Retrieving Subversion status...
+fetching.properties.contents.progress.title=Fetching properties contents...
+show.properties.diff.progress.text.revision.information=Revision {0}
+show.properties.diff.progress.text2.property.information=Property {0}
+svn.checkout.destination.directory.title=Destination directory
+svn.checkout.destination.directory.description=Select checkout destination directory
+checkbox.ignore.whitespace.when.merge.text=&Ignore whitespaces
+svn.dialog.select.ignore.patterns.to.remove.title=Select svn:ignore patterns to remove
+svn.dialog.select.ignore.patterns.to.remove.prompt=Files being deleted are ignored by Subversion.\n\
+Some of 'svn:ignore' patterns may become outdated.\n\
+Select 'svn:ignore' patterns to be deleted.
+detect.nested.working.copies=Detect nested working copies
+svn.changeview.item.in.conflict.text=File has {0} conflict(s) and can not be committed
+svn.changeview.locally.deleted.item.in.conflict.text=File has {0} conflict(s)
+action.mark.tree.conflict.resolved.text=Mark Tree Conflict Resolved...
+action.mark.tree.conflict.resolved.confirmation.text=Do you want to mark tree conflict as resolved?
+action.mark.tree.conflict.resolved.confirmation.title=Mark Tree Conflict Resolved
+svn.integrate.changelist.warning.unresolved.conflicts.text=Integration interrupted. There are unresolved conflicts or skipped items.
+svn.option.ignore.whitespace.in.annotate=Ignore whitespace differences in annotations
+annotation.show.merge.sources.default.text=Show merge source in annotations
+svn.cannot.save.credentials.store-auth-creds=Cannot store credentials: forbidden by "store-auth-creds = no"
+annotation.original.revision=Original revision
+svn17.plugin.not.ready=This plugin provides only basic support for Subversion 1.7. \
+ Many operations are not implemented. \
+ Subversion 1.7 integration is not ready yet. \
+ We do NOT guarantee data safety and consistency.
Also, we do NOT guarantee that plugin correctly works with Subversion 1.6 and lower working copies!
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnChangeProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnChangeProvider.java
new file mode 100644
index 000000000000..cb5b61e69c2b
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnChangeProvider.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vcs.*;
+import com.intellij.openapi.vcs.actions.VcsContextFactory;
+import com.intellij.openapi.vcs.changes.*;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.EventDispatcher;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.idea.svn17.actions.CleanupWorker;
+import org.tmatesoft.svn.core.SVNDepth;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNNodeKind;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
+import org.tmatesoft.svn.core.wc.ISVNStatusFileProvider;
+import org.tmatesoft.svn.core.wc.SVNStatus;
+import org.tmatesoft.svn.core.wc.SVNStatusType;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * @author max
+ * @author yole
+ */
+public class SvnChangeProvider implements ChangeProvider {
+ private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnChangeProvider");
+ public static final String ourDefaultListName = VcsBundle.message("changes.default.changlist.name");
+
+ private final SvnVcs17 myVcs;
+ private final VcsContextFactory myFactory;
+ private final SvnFileUrlMappingImpl17 mySvnFileUrlMapping;
+
+ public SvnChangeProvider(final SvnVcs17 vcs) {
+ myVcs = vcs;
+ myFactory = VcsContextFactory.SERVICE.getInstance();
+ mySvnFileUrlMapping = (SvnFileUrlMappingImpl17) vcs.getSvnFileUrlMapping();
+ }
+
+ public void getChanges(final VcsDirtyScope dirtyScope, final ChangelistBuilder builder, ProgressIndicator progress,
+ final ChangeListManagerGate addGate) throws VcsException {
+ final SvnScopeZipper zipper = new SvnScopeZipper(dirtyScope);
+ zipper.run();
+
+ final Map nonRecursiveMap = zipper.getNonRecursiveDirs();
+ final ISVNStatusFileProvider fileProvider = createFileProvider(nonRecursiveMap);
+
+ try {
+ final SvnChangeProviderContext context = new SvnChangeProviderContext(myVcs, builder, progress);
+
+ final StatusWalkerPartnerImpl partner = new StatusWalkerPartnerImpl(myVcs, progress);
+ final NestedCopiesBuilder nestedCopiesBuilder = new NestedCopiesBuilder();
+
+ final EventDispatcher statusReceiver = EventDispatcher.create(StatusReceiver.class);
+ statusReceiver.addListener(context);
+ statusReceiver.addListener(nestedCopiesBuilder);
+
+ final SvnRecursiveStatusWalker walker = new SvnRecursiveStatusWalker(statusReceiver.getMulticaster(), partner);
+
+ for (FilePath path : zipper.getRecursiveDirs()) {
+ walker.go(path, SVNDepth.INFINITY);
+ }
+
+ partner.setFileProvider(fileProvider);
+ for (SvnScopeZipper.MyDirNonRecursive item : nonRecursiveMap.values()) {
+ walker.go(item.getDir(), SVNDepth.FILES);
+ }
+
+ // they are taken under non recursive: ENTRIES file is read anyway, so we get to know parent status also for free
+ /*for (FilePath path : zipper.getSingleFiles()) {
+ FileStatus status = getParentStatus(context, path);
+ processFile(path, context, status, false, context.getClient());
+ }*/
+
+ processCopiedAndDeleted(context);
+ processUnsaved(dirtyScope, addGate, context);
+
+ mySvnFileUrlMapping.acceptNestedData(nestedCopiesBuilder.getSet());
+ }
+ catch (SVNException e) {
+ throw new VcsException(e);
+ }
+ }
+
+ private void processUnsaved(VcsDirtyScope dirtyScope, ChangeListManagerGate addGate, SvnChangeProviderContext context)
+ throws SVNException {
+ final FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
+ final Document[] unsavedDocuments = fileDocumentManager.getUnsavedDocuments();
+ for (Document unsavedDocument : unsavedDocuments) {
+ final VirtualFile file = fileDocumentManager.getFile(unsavedDocument);
+ if (file != null && dirtyScope.belongsTo(new FilePathImpl(file)) && fileDocumentManager.isFileModified(file)) {
+ final FileStatus status = addGate.getStatus(file);
+ if (status == null || FileStatus.NOT_CHANGED.equals(status)) {
+ context.addModifiedNotSavedChange(file);
+ }
+ }
+ }
+ }
+
+ private ISVNStatusFileProvider createFileProvider(Map nonRecursiveMap) {
+ // translate into terms of File.getAbsolutePath()
+ final Map preparedMap = new HashMap();
+ for (SvnScopeZipper.MyDirNonRecursive item : nonRecursiveMap.values()) {
+ final Map result = new HashMap();
+ for (FilePath path : item.getChildrenList()) {
+ result.put(path.getName(), path.getIOFile());
+ }
+ preparedMap.put(item.getDir().getIOFile().getAbsolutePath(), result);
+ }
+ return new ISVNStatusFileProvider() {
+ public Map getChildrenFiles(File parent) {
+ return preparedMap.get(parent.getAbsolutePath());
+ }
+ };
+ }
+
+ private void processCopiedAndDeleted(final SvnChangeProviderContext context) throws SVNException {
+ for(SvnChangedFile copiedFile: context.getCopiedFiles()) {
+ if (context.isCanceled()) {
+ throw new ProcessCanceledException();
+ }
+ processCopiedFile(copiedFile, context.getBuilder(), context);
+ }
+ for(SvnChangedFile deletedFile: context.getDeletedFiles()) {
+ if (context.isCanceled()) {
+ throw new ProcessCanceledException();
+ }
+ context.processStatus(deletedFile.getFilePath(), deletedFile.getStatus());
+ }
+ }
+
+ public void getChanges(final FilePath path, final boolean recursive, final ChangelistBuilder builder) throws SVNException {
+ final SvnChangeProviderContext context = new SvnChangeProviderContext(myVcs, builder, null);
+ final StatusWalkerPartnerImpl partner = new StatusWalkerPartnerImpl(myVcs, ProgressManager.getInstance().getProgressIndicator());
+ final SvnRecursiveStatusWalker walker = new SvnRecursiveStatusWalker(context, partner);
+ walker.go(path, recursive ? SVNDepth.INFINITY : SVNDepth.IMMEDIATES);
+ processCopiedAndDeleted(context);
+ }
+
+ @Nullable
+ private String changeListNameFromStatus(final SVNStatus status) {
+ if (WorkingCopyFormat.getInstance(status.getWorkingCopyFormat()).supportsChangelists()) {
+ if (SVNNodeKind.FILE.equals(status.getKind())) {
+ final String clName = status.getChangelistName();
+ return (clName == null) ? null : clName;
+ }
+ }
+ // always null for earlier versions
+ return null;
+ }
+
+ private void processCopiedFile(SvnChangedFile copiedFile, ChangelistBuilder builder, SvnChangeProviderContext context) throws SVNException {
+ boolean foundRename = false;
+ final SVNStatus copiedStatus = copiedFile.getStatus();
+ final String copyFromURL = copiedFile.getCopyFromURL();
+ final FilePath copiedToPath = copiedFile.getFilePath();
+
+ // if copy target is _deleted_, treat like deleted, not moved!
+ /*for (Iterator iterator = context.getDeletedFiles().iterator(); iterator.hasNext();) {
+ final SvnChangedFile deletedFile = iterator.next();
+ final FilePath deletedPath = deletedFile.getFilePath();
+
+ if (Comparing.equal(deletedPath, copiedToPath)) {
+ return;
+ }
+ }*/
+
+ final Set deletedToDelete = new HashSet();
+
+ for (Iterator iterator = context.getDeletedFiles().iterator(); iterator.hasNext();) {
+ SvnChangedFile deletedFile = iterator.next();
+ final SVNStatus deletedStatus = deletedFile.getStatus();
+ if ((deletedStatus != null) && (deletedStatus.getURL() != null) && Comparing.equal(copyFromURL, deletedStatus.getURL().toString())) {
+ final String clName = changeListNameFromStatus(copiedFile.getStatus());
+ builder.processChangeInList(context.createMovedChange(createBeforeRevision(deletedFile, true),
+ CurrentContentRevision.create(copiedFile.getFilePath()), copiedStatus, deletedStatus), clName, SvnVcs17
+ .getKey());
+ deletedToDelete.add(deletedFile);
+ for(Iterator iterChild = context.getDeletedFiles().iterator(); iterChild.hasNext();) {
+ SvnChangedFile deletedChild = iterChild.next();
+ final SVNStatus childStatus = deletedChild.getStatus();
+ if (childStatus == null) {
+ continue;
+ }
+ final SVNURL childUrl = childStatus.getURL();
+ if (childUrl == null) {
+ continue;
+ }
+ final String childURL = childUrl.toString();
+ if (StringUtil.startsWithConcatenationOf(childURL, copyFromURL, "/")) {
+ String relativePath = childURL.substring(copyFromURL.length());
+ File newPath = new File(copiedFile.getFilePath().getIOFile(), relativePath);
+ FilePath newFilePath = myFactory.createFilePathOn(newPath);
+ if (!context.isDeleted(newFilePath)) {
+ builder.processChangeInList(context.createMovedChange(createBeforeRevision(deletedChild, true),
+ CurrentContentRevision.create(newFilePath),
+ context.getTreeConflictStatus(newPath), childStatus), clName, SvnVcs17.getKey());
+ deletedToDelete.add(deletedChild);
+ }
+ }
+ }
+ foundRename = true;
+ break;
+ }
+ }
+
+ final List deletedFiles = context.getDeletedFiles();
+ for (SvnChangedFile file : deletedToDelete) {
+ deletedFiles.remove(file);
+ }
+
+ // handle the case when the deleted file wasn't included in the dirty scope - try searching for the local copy
+ // by building a relative url
+ if (!foundRename && copiedStatus.getURL() != null) {
+ File wcPath = guessWorkingCopyPath(copiedStatus.getFile(), copiedStatus.getURL(), copyFromURL);
+ SVNStatus status;
+ try {
+ status = context.getClient().doStatus(wcPath, false);
+ }
+ catch(SVNException ex) {
+ status = null;
+ }
+ if (status != null && SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_DELETED)) {
+ final FilePath filePath = myFactory.createFilePathOnDeleted(wcPath, false);
+ final SvnContentRevision beforeRevision = SvnContentRevision.createBaseRevision(myVcs, filePath, status.getRevision());
+ final ContentRevision afterRevision = CurrentContentRevision.create(copiedFile.getFilePath());
+ builder.processChangeInList(context.createMovedChange(beforeRevision, afterRevision, copiedStatus, status), changeListNameFromStatus(status),
+ SvnVcs17.getKey());
+ foundRename = true;
+ }
+ }
+
+ if (!foundRename) {
+ // for debug
+ LOG.info("Rename not found for " + copiedFile.getFilePath().getPresentableUrl());
+ context.processStatus(copiedFile.getFilePath(), copiedStatus);
+ }
+ }
+
+ private SvnContentRevision createBeforeRevision(final SvnChangedFile changedFile, final boolean forDeleted) {
+ return SvnContentRevision.createBaseRevision(myVcs,
+ forDeleted ? FilePathImpl.createForDeletedFile(changedFile.getStatus().getFile(),
+ changedFile.getFilePath().isDirectory()) :
+ changedFile.getFilePath(), changedFile.getStatus().getRevision());
+ }
+
+ private static File guessWorkingCopyPath(final File file, @NotNull final SVNURL url, final String copyFromURL) throws SVNException {
+ String copiedPath = url.getPath();
+ String copyFromPath = SVNURL.parseURIEncoded(copyFromURL).getPath();
+ String commonPathAncestor = SVNPathUtil.getCommonPathAncestor(copiedPath, copyFromPath);
+ int pathSegmentCount = SVNPathUtil.getSegmentsCount(copiedPath);
+ int ancestorSegmentCount = SVNPathUtil.getSegmentsCount(commonPathAncestor);
+ boolean startsWithSlash = file.getAbsolutePath().startsWith("/");
+ List segments = StringUtil.split(file.getPath(), File.separator);
+ List copyFromPathSegments = StringUtil.split(copyFromPath, "/");
+ List resultSegments = new ArrayList();
+ final int keepSegments = segments.size() - pathSegmentCount + ancestorSegmentCount;
+ for(int i=0; i< keepSegments; i++) {
+ resultSegments.add(segments.get(i));
+ }
+ for(int i=ancestorSegmentCount; i files) {
+ new CleanupWorker(VfsUtil.toVirtualFileArray(files), myVcs.getProject(), "action.Subversion.cleanup.progress.title").execute();
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnChangeProviderContext.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnChangeProviderContext.java
new file mode 100644
index 000000000000..b8237fb51074
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnChangeProviderContext.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.vcs.*;
+import com.intellij.openapi.vcs.changes.*;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.tmatesoft.svn.core.SVNErrorCode;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNLock;
+import org.tmatesoft.svn.core.SVNNodeKind;
+import org.tmatesoft.svn.core.wc.*;
+
+import java.io.File;
+import java.util.*;
+
+class SvnChangeProviderContext implements StatusReceiver {
+ private static final Logger LOG = Logger.getInstance("org.jetbrains.idea.svn.SvnChangeProviderContext");
+
+ private final ChangelistBuilder myChangelistBuilder;
+ private final SVNStatusClient myStatusClient;
+ private List myCopiedFiles = null;
+ private final List myDeletedFiles = new ArrayList();
+ // for files moved in a subtree, which were the targets of merge (for instance).
+ private final Map myTreeConflicted;
+ private Map myCopyFromURLs = null;
+ private final SvnVcs17 myVcs;
+ private final SvnBranchConfigurationManager17 myBranchConfigurationManager;
+
+ private final ProgressIndicator myProgress;
+
+ public SvnChangeProviderContext(SvnVcs17 vcs, final ChangelistBuilder changelistBuilder, final ProgressIndicator progress) {
+ myVcs = vcs;
+ myStatusClient = vcs.createStatusClient();
+ myChangelistBuilder = changelistBuilder;
+ myProgress = progress;
+ myTreeConflicted = new HashMap();
+ myBranchConfigurationManager = SvnBranchConfigurationManager17.getInstance(myVcs.getProject());
+ }
+
+ public void process(FilePath path, SVNStatus status, boolean isInnerCopyRoot) throws SVNException {
+ processStatusFirstPass(path, status);
+ }
+
+ public void processIgnored(VirtualFile vFile) {
+ myChangelistBuilder.processIgnoredFile(vFile);
+ }
+
+ public void processUnversioned(VirtualFile vFile) {
+ myChangelistBuilder.processUnversionedFile(vFile);
+ }
+
+ public ChangelistBuilder getBuilder() {
+ return myChangelistBuilder;
+ }
+
+ public SVNStatusClient getClient() {
+ return myStatusClient;
+ }
+
+ public void reportTreeConflict(final SVNStatus status) {
+ myTreeConflicted.put(status.getFile().getAbsolutePath(), status);
+ }
+
+ @Nullable
+ public SVNStatus getTreeConflictStatus(final File file) {
+ return myTreeConflicted.get(file.getAbsolutePath());
+ }
+
+ @NotNull
+ public List getCopiedFiles() {
+ if (myCopiedFiles == null) {
+ return Collections.emptyList();
+ }
+ return myCopiedFiles;
+ }
+
+ public List getDeletedFiles() {
+ return myDeletedFiles;
+ }
+
+ public boolean isDeleted(final FilePath path) {
+ for (SvnChangedFile deletedFile : myDeletedFiles) {
+ if (Comparing.equal(path, deletedFile.getFilePath())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean isCanceled() {
+ return (myProgress != null) && myProgress.isCanceled();
+ }
+
+ /**
+ * If the specified filepath or its parent was added with history, returns the URL of the copy source for this filepath.
+ *
+ * @param filePath the original filepath
+ * @return the copy source url, or null if the file isn't a copy of anything
+ */
+ @Nullable
+ public String getParentCopyFromURL(FilePath filePath) {
+ if (myCopyFromURLs == null) {
+ return null;
+ }
+ StringBuilder relPathBuilder = new StringBuilder();
+ while(filePath != null) {
+ String copyFromURL = myCopyFromURLs.get(filePath);
+ if (copyFromURL != null) {
+ return copyFromURL + relPathBuilder.toString();
+ }
+ relPathBuilder.insert(0, "/" + filePath.getName());
+ filePath = filePath.getParentPath();
+ }
+ return null;
+ }
+
+ public void addCopiedFile(final FilePath filePath, final SVNStatus status, final String copyFromURL) {
+ if (myCopiedFiles == null) {
+ myCopiedFiles = new ArrayList();
+ }
+ myCopiedFiles.add(new SvnChangedFile(filePath, status, copyFromURL));
+ final String url = status.getCopyFromURL();
+ if (url != null) {
+ addCopyFromURL(filePath, url);
+ }
+ }
+
+ public void addCopyFromURL(final FilePath filePath, final String url) {
+ if (myCopyFromURLs == null) {
+ myCopyFromURLs = new HashMap();
+ }
+ myCopyFromURLs.put(filePath, url);
+ }
+
+//
+
+ void processStatusFirstPass(final FilePath filePath, final SVNStatus status) throws SVNException {
+ if (status == null) {
+ // external to wc
+ return;
+ }
+ if (status.getRemoteLock() != null) {
+ final SVNLock lock = status.getRemoteLock();
+ myChangelistBuilder.processLogicallyLockedFolder(filePath.getVirtualFile(),
+ new LogicalLock(false, lock.getOwner(), lock.getComment(), lock.getCreationDate(), lock.getExpirationDate()));
+ }
+ if (status.getLocalLock() != null) {
+ final SVNLock lock = status.getLocalLock();
+ myChangelistBuilder.processLogicallyLockedFolder(filePath.getVirtualFile(),
+ new LogicalLock(true, lock.getOwner(), lock.getComment(), lock.getCreationDate(), lock.getExpirationDate()));
+ }
+ if (filePath.isDirectory() && status.isLocked()) {
+ myChangelistBuilder.processLockedFolder(filePath.getVirtualFile());
+ }
+ if (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_ADDED) && status.getCopyFromURL() != null) {
+ addCopiedFile(filePath, status, status.getCopyFromURL());
+ }
+ else if (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_DELETED)) {
+ myDeletedFiles.add(new SvnChangedFile(filePath, status));
+ }
+ else {
+ String parentCopyFromURL = getParentCopyFromURL(filePath);
+ if (parentCopyFromURL != null) {
+ addCopiedFile(filePath, status, parentCopyFromURL);
+ } else {
+ processStatus(filePath, status);
+ }
+ }
+ }
+
+ void processStatus(final FilePath filePath, final SVNStatus status) throws SVNException {
+ loadEntriesFile(filePath);
+ if (status != null) {
+ FileStatus fStatus = SvnStatusConvertor.convertStatus(status);
+
+ final SVNStatusType statusType = status.getContentsStatus();
+ final SVNStatusType propStatus = status.getPropertiesStatus();
+ if (SvnVcs17.svnStatusIsUnversioned(status) || SvnVcs17.svnStatusIs(status, SVNStatusType.UNKNOWN)) {
+ final VirtualFile file = filePath.getVirtualFile();
+ if (file != null) {
+ myChangelistBuilder.processUnversionedFile(file);
+ }
+ }
+ else if (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_ADDED)) {
+ myChangelistBuilder.processChangeInList(createChange(null, CurrentContentRevision.create(filePath), fStatus, status),
+ changeListNameFromStatus(status), SvnVcs17.getKey());
+ }
+ else if (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_CONFLICTED) ||
+ SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_MODIFIED) ||
+ SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_REPLACED) ||
+ propStatus == SVNStatusType.STATUS_MODIFIED ||
+ propStatus == SVNStatusType.STATUS_CONFLICTED) {
+ myChangelistBuilder.processChangeInList(createChange(SvnContentRevision.createBaseRevision(myVcs, filePath, status.getRevision()),
+ CurrentContentRevision.create(filePath), fStatus, status), changeListNameFromStatus(status), SvnVcs17.getKey());
+ checkSwitched(filePath, myChangelistBuilder, status, fStatus);
+ }
+ else if (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_DELETED)) {
+ myChangelistBuilder.processChangeInList(
+ createChange(SvnContentRevision.createBaseRevision(myVcs, filePath, status.getRevision()), null, fStatus, status),
+ changeListNameFromStatus(status), SvnVcs17.getKey());
+ }
+ else if (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_MISSING)) {
+ myChangelistBuilder.processLocallyDeletedFile(createLocallyDeletedChange(filePath, status));
+ }
+ else if (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_IGNORED)) {
+ myChangelistBuilder.processIgnoredFile(filePath.getVirtualFile());
+ }
+ else if (status.isCopied()) {
+ //
+ }
+ else if ((fStatus == FileStatus.NOT_CHANGED || fStatus == FileStatus.SWITCHED) && statusType != SVNStatusType.STATUS_NONE) {
+ VirtualFile file = filePath.getVirtualFile();
+ if (file != null && FileDocumentManager.getInstance().isFileModified(file)) {
+ myChangelistBuilder.processChangeInList(createChange(SvnContentRevision.createBaseRevision(myVcs, filePath, status.getRevision()),
+ CurrentContentRevision.create(filePath), FileStatus.MODIFIED, status), changeListNameFromStatus(status),
+ SvnVcs17.getKey());
+ } else if (status.getTreeConflict() != null) {
+ myChangelistBuilder.processChange(createChange(SvnContentRevision.createBaseRevision(myVcs, filePath, status.getRevision()),
+ CurrentContentRevision.create(filePath), FileStatus.MODIFIED, status), SvnVcs17.getKey());
+ }
+ checkSwitched(filePath, myChangelistBuilder, status, fStatus);
+ }
+ }
+ }
+
+ public void addModifiedNotSavedChange(final VirtualFile file) throws SVNException {
+ final FilePath filePath = new FilePathImpl(file);
+ final SVNInfo svnInfo;
+ try {
+ svnInfo = myVcs.createWCClient().doInfo(new File(file.getPath()), SVNRevision.UNDEFINED);
+ }
+ catch (SVNException e) {
+ if (SVNErrorCode.WC_PATH_NOT_FOUND.equals(e.getErrorMessage().getErrorCode())) {
+ return;
+ }
+ throw e;
+ }
+ final SVNStatus svnStatus = new SVNStatus();
+ svnStatus.setRevision(svnInfo.getRevision());
+ myChangelistBuilder.processChangeInList(createChange(SvnContentRevision.createBaseRevision(myVcs, filePath, svnInfo.getRevision()),
+ CurrentContentRevision.create(filePath), FileStatus.MODIFIED, svnStatus), (String) null,
+ SvnVcs17.getKey());
+ }
+
+ private void checkSwitched(final FilePath filePath, final ChangelistBuilder builder, final SVNStatus status,
+ final FileStatus convertedStatus) {
+ if (status.isSwitched() || (convertedStatus == FileStatus.SWITCHED)) {
+ final VirtualFile virtualFile = filePath.getVirtualFile();
+ if (virtualFile == null) return;
+ final String switchUrl = status.getURL().toString();
+ final VirtualFile vcsRoot = ProjectLevelVcsManager.getInstance(myVcs.getProject()).getVcsRootFor(virtualFile);
+ if (vcsRoot != null) { // it will be null if we walked into an excluded directory
+ String baseUrl = null;
+ try {
+ baseUrl = myBranchConfigurationManager.get(vcsRoot).getBaseName(switchUrl);
+ }
+ catch (VcsException e) {
+ LOG.info(e);
+ }
+ builder.processSwitchedFile(virtualFile, baseUrl == null ? switchUrl : baseUrl, true);
+ }
+ }
+ }
+
+ /**
+ * Ensures that the contents of the 'entries' file is cached in the VFS, so that the VFS will send
+ * correct events when the 'entries' file is changed externally (to be received by SvnEntriesFileListener)
+ *
+ * @param filePath the path of a changed file.
+ */
+ private static void loadEntriesFile(final FilePath filePath) {
+ final FilePath parentPath = filePath.getParentPath();
+ if (parentPath == null) {
+ return;
+ }
+ File svnSubdirectory = new File(parentPath.getIOFile(), SvnUtil.SVN_ADMIN_DIR_NAME);
+ LocalFileSystem localFileSystem = LocalFileSystem.getInstance();
+ VirtualFile file = localFileSystem.refreshAndFindFileByIoFile(svnSubdirectory);
+ if (file != null) {
+ localFileSystem.refreshAndFindFileByIoFile(new File(svnSubdirectory, SvnUtil.ENTRIES_FILE_NAME));
+ }
+ if (filePath.isDirectory()) {
+ svnSubdirectory = new File(filePath.getPath(), SvnUtil.SVN_ADMIN_DIR_NAME);
+ file = localFileSystem.refreshAndFindFileByIoFile(svnSubdirectory);
+ if (file != null) {
+ localFileSystem.refreshAndFindFileByIoFile(new File(svnSubdirectory, SvnUtil.ENTRIES_FILE_NAME));
+ }
+ }
+ }
+
+ // seems here we can only have a tree conflict; which can be marked on either path (?)
+ // .. ok try to merge states
+ Change createMovedChange(final ContentRevision before, final ContentRevision after, final SVNStatus copiedStatus,
+ final SVNStatus deletedStatus) {
+ return new ConflictedSvnChange(before, after, ConflictState.mergeState(getState(copiedStatus), getState(deletedStatus)),
+ ((copiedStatus != null) && (copiedStatus.getTreeConflict() != null)) ? after.getFile() : before.getFile());
+ }
+
+ private Change createChange(final ContentRevision before, final ContentRevision after, final FileStatus fStatus, final SVNStatus svnStatus) {
+ return new ConflictedSvnChange(before, after, fStatus, getState(svnStatus), after == null ? before.getFile() : after.getFile());
+ }
+
+ private LocallyDeletedChange createLocallyDeletedChange(@NotNull FilePath filePath, final SVNStatus status) {
+ return new SvnLocallyDeletedChange(filePath, getState(status));
+ }
+
+ private ConflictState getState(@Nullable final SVNStatus svnStatus) {
+ if (svnStatus == null) {
+ return ConflictState.none;
+ }
+
+ final boolean treeConflict = svnStatus.getTreeConflict() != null;
+ final boolean textConflict = SVNStatusType.STATUS_CONFLICTED == svnStatus.getContentsStatus();
+ final boolean propertyConflict = SVNStatusType.STATUS_CONFLICTED == svnStatus.getPropertiesStatus();
+ if (treeConflict) {
+ reportTreeConflict(svnStatus);
+ }
+
+ return ConflictState.getInstance(treeConflict, textConflict, propertyConflict);
+ }
+
+ @Nullable
+ private static String changeListNameFromStatus(final SVNStatus status) {
+ if (WorkingCopyFormat.getInstance(status.getWorkingCopyFormat()).supportsChangelists()) {
+ if (SVNNodeKind.FILE.equals(status.getKind())) {
+ final String clName = status.getChangelistName();
+ return (clName == null) ? null : clName;
+ }
+ }
+ // always null for earlier versions
+ return null;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnChangedFile.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnChangedFile.java
new file mode 100644
index 000000000000..5e206c0974d0
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnChangedFile.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.vcs.FilePath;
+import org.tmatesoft.svn.core.wc.SVNStatus;
+
+class SvnChangedFile {
+ private final FilePath myFilePath;
+ private final SVNStatus myStatus;
+ private String myCopyFromURL;
+
+ public SvnChangedFile(final FilePath filePath, final SVNStatus status) {
+ myFilePath = filePath;
+ myStatus = status;
+ }
+
+ public SvnChangedFile(final FilePath filePath, final SVNStatus status, final String copyFromURL) {
+ myFilePath = filePath;
+ myStatus = status;
+ myCopyFromURL = copyFromURL;
+ }
+
+ public FilePath getFilePath() {
+ return myFilePath;
+ }
+
+ public SVNStatus getStatus() {
+ return myStatus;
+ }
+
+ public String getCopyFromURL() {
+ if (myCopyFromURL == null) {
+ return myStatus.getCopyFromURL();
+ }
+ return myCopyFromURL;
+ }
+
+ @Override
+ public String toString() {
+ return myFilePath.getPath();
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final SvnChangedFile that = (SvnChangedFile)o;
+
+ if (myFilePath != null ? !myFilePath.equals(that.myFilePath) : that.myFilePath != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return myFilePath != null ? myFilePath.hashCode() : 0;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnChangelistListener.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnChangelistListener.java
new file mode 100644
index 000000000000..327ff5b71212
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnChangelistListener.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.vcs.AbstractVcs;
+import com.intellij.openapi.vcs.FilePath;
+import com.intellij.openapi.vcs.ProjectLevelVcsManager;
+import com.intellij.openapi.vcs.changes.Change;
+import com.intellij.openapi.vcs.changes.ChangeList;
+import com.intellij.openapi.vcs.changes.ChangeListListener;
+import org.jetbrains.annotations.Nullable;
+import org.tmatesoft.svn.core.SVNDepth;
+import org.tmatesoft.svn.core.SVNErrorCode;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.wc.ISVNChangelistHandler;
+import org.tmatesoft.svn.core.wc.SVNChangelistClient;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class SvnChangelistListener implements ChangeListListener {
+ private final static Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnChangelistListener");
+
+ private final Project myProject;
+ private final SVNChangelistClient myClient;
+
+ public SvnChangelistListener(final Project project, final SVNChangelistClient client) {
+ myProject = project;
+ myClient = client;
+ }
+
+ public void changeListAdded(final ChangeList list) {
+ // SVN change list exists only when there are any files in it
+ }
+
+ public void changesRemoved(final Collection changes, final ChangeList fromList) {
+ if (SvnChangeProvider.ourDefaultListName.equals(fromList.getName())) {
+ return;
+ }
+ final List paths = getPathsFromChanges(changes);
+ for (String path : paths) {
+ try {
+ myClient.doRemoveFromChangelist(new File[]{new File(path)}, SVNDepth.EMPTY, null);
+ }
+ catch (SVNException e) {
+ LOG.info(e);
+ }
+ }
+ }
+
+ public void changesAdded(Collection changes, ChangeList toList) {
+ if (toList == null) return;
+ if (SvnChangeProvider.ourDefaultListName.equals(toList.getName())) {
+ return;
+ }
+ final List paths = getPathsFromChanges(changes);
+ for (String path : paths) {
+ try {
+ myClient.doAddToChangelist(new File[]{new File(path)}, SVNDepth.EMPTY, toList.getName(), null);
+ }
+ catch (SVNException e) {
+ LOG.info(e);
+ }
+ }
+ }
+
+ public void changeListRemoved(final ChangeList list) {
+ final List paths = getPathsFromChanges(list.getChanges());
+ for (String path : paths) {
+ try {
+ myClient.doRemoveFromChangelist(new File[]{new File(path)}, SVNDepth.EMPTY, null);
+ }
+ catch (SVNException e) {
+ LOG.info(e);
+ }
+ }
+ }
+
+ private boolean isUnderSvn(final FilePath path) {
+ final AbstractVcs vcs = ProjectLevelVcsManager.getInstance(myProject).getVcsFor(path);
+ return ((vcs != null) && (SvnVcs17.VCS_NAME.equals(vcs.getName())));
+ }
+
+ private List getPathsFromChanges(final Collection changes) {
+ final List paths = new ArrayList();
+ for (Change change : changes) {
+ if ((change.getBeforeRevision() != null) && (isUnderSvn(change.getBeforeRevision().getFile()))) {
+ final String path = change.getBeforeRevision().getFile().getIOFile().getAbsolutePath();
+ if (! paths.contains(path)) {
+ paths.add(path);
+ }
+ }
+ if ((change.getAfterRevision() != null) && (isUnderSvn(change.getAfterRevision().getFile()))) {
+ final String path = change.getAfterRevision().getFile().getIOFile().getAbsolutePath();
+ if (! paths.contains(path)) {
+ paths.add(path);
+ }
+ }
+ }
+ return paths;
+ }
+
+ public void changeListChanged(final ChangeList list) {
+ }
+
+ public void changeListRenamed(final ChangeList list, final String oldName) {
+ if (Comparing.equal(list.getName(), oldName)) {
+ return;
+ }
+ if (SvnChangeProvider.ourDefaultListName.equals(list.getName())) {
+ changeListRemoved(list);
+ return;
+ }
+ final List paths = getPathsFromChanges(list.getChanges());
+ for (String path : paths) {
+ try {
+ myClient.doAddToChangelist(new File[]{new File(path)}, SVNDepth.EMPTY, list.getName(), null);
+ }
+ catch (SVNException e) {
+ LOG.info(e);
+ }
+ }
+ }
+
+ public void changeListCommentChanged(final ChangeList list, final String oldComment) {
+ }
+
+ public void changesMoved(final Collection changes, final ChangeList fromList, final ChangeList toList) {
+ if (fromList.getName().equals(toList.getName())) {
+ return;
+ }
+ if (SvnChangeProvider.ourDefaultListName.equals(toList.getName())) {
+ changeListRemoved(toList);
+ return;
+ }
+ final String[] fromLists = SvnChangeProvider.ourDefaultListName.equals(fromList.getName()) ? null : new String[] {fromList.getName()};
+ final List paths = getPathsFromChanges(changes);
+ for (final String path : paths) {
+ try {
+ myClient.doAddToChangelist(new File[]{new File(path)}, SVNDepth.EMPTY, toList.getName(), fromLists);
+ }
+ catch (SVNException e) {
+ LOG.info(e);
+ }
+ }
+ }
+
+ public void defaultListChanged(final ChangeList oldDefaultList, final ChangeList newDefaultList) {
+ }
+
+ public void unchangedFileStatusChanged() {
+ }
+
+ public void changeListUpdateDone() {
+ }
+
+ @Nullable
+ public static String getCurrentMapping(final Project project, final File file) {
+ final SvnVcs17 vcs = SvnVcs17.getInstance(project);
+ final SVNChangelistClient client = vcs.createChangelistClient();
+ try {
+ final Ref refResult = new Ref();
+ final ISVNChangelistHandler handler = new ISVNChangelistHandler() {
+ public void handle(final File path, final String changelistName) {
+ if (refResult.isNull() && Comparing.equal(path, file)) {
+ refResult.set(changelistName);
+ }
+ }
+ };
+ if (file.exists()) {
+ client.doGetChangeLists(file, null, SVNDepth.EMPTY, handler);
+ } else if (file.getParentFile() != null) {
+ client.doGetChangeLists(file.getParentFile(), null, SVNDepth.IMMEDIATES, handler);
+ }
+ return refResult.get();
+ }
+ catch (SVNException e) {
+ final SVNErrorCode errorCode = e.getErrorMessage().getErrorCode();
+ if (SVNErrorCode.WC_NOT_DIRECTORY.equals(errorCode) || SVNErrorCode.WC_NOT_FILE.equals(errorCode)) {
+ LOG.debug("Logging only, exception is valid (caught) here", e);
+ } else {
+ LOG.info("Logging only, exception is valid (caught) here", e);
+ }
+ }
+ return null;
+ }
+
+ public static void putUnderList(final Project project, final String list, final File after) throws SVNException {
+ final SvnVcs17 vcs = SvnVcs17.getInstance(project);
+ final SVNChangelistClient client = vcs.createChangelistClient();
+ try {
+ client.doAddToChangelist(new File[]{after}, SVNDepth.EMPTY, list, null);
+ }
+ catch (SVNException e) {
+ LOG.info(e);
+ if ((! SVNErrorCode.WC_NOT_DIRECTORY.equals(e.getErrorMessage().getErrorCode()) && (! SVNErrorCode.WC_NOT_FILE.equals(e.getErrorMessage().getErrorCode())))) {
+ throw e;
+ }
+ }
+ }
+
+ public static void removeFromList(final Project project, final File after) throws SVNException {
+ final SvnVcs17 vcs = SvnVcs17.getInstance(project);
+ final SVNChangelistClient client = vcs.createChangelistClient();
+ try {
+ client.doRemoveFromChangelist(new File[]{after}, SVNDepth.EMPTY, null);
+ }
+ catch (SVNException e) {
+ LOG.info(e);
+ if ((! SVNErrorCode.WC_NOT_DIRECTORY.equals(e.getErrorMessage().getErrorCode()) && (! SVNErrorCode.WC_NOT_FILE.equals(e.getErrorMessage().getErrorCode())))) {
+ throw e;
+ }
+ }
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnCompatibilityChecker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnCompatibilityChecker.java
new file mode 100644
index 000000000000..538b6416c79e
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnCompatibilityChecker.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.application.Application;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ModalityState;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.MessageType;
+import com.intellij.openapi.util.Condition;
+import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier;
+import com.intellij.openapi.vfs.VirtualFile;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SvnCompatibilityChecker {
+ private final Project myProject;
+ private final static long ourFrequency = 10;
+ private final static long ourInvocationMax = 10;
+ private final static long ourInitCounter = 3;
+
+ private long myCounter;
+ private long myDownStartCounter;
+ private long myInvocationCounter;
+
+ private final Object myLock = new Object();
+
+ public SvnCompatibilityChecker(Project project) {
+ myProject = project;
+ myCounter= 0;
+ myDownStartCounter = ourInitCounter;
+ myInvocationCounter = 0;
+ }
+
+ public void reportNoRoots(final List result) {
+ synchronized (myLock) {
+ if (myInvocationCounter >= ourInvocationMax) return;
+ ++ myCounter;
+ -- myDownStartCounter;
+ if ((myCounter > ourFrequency) || (myDownStartCounter >= 0)) {
+ myCounter = 0;
+ ++ myInvocationCounter;
+ final Application application = ApplicationManager.getApplication();
+ application.executeOnPooledThread(new Runnable() {
+ public void run() {
+ final List suspicious = new ArrayList();
+ for (VirtualFile vf : result) {
+ if (SvnUtil.seemsLikeVersionedDir(vf)) {
+ suspicious.add(vf);
+ }
+ }
+ if (! suspicious.isEmpty()) {
+ final String message = (suspicious.size() == 1) ?
+ "Root '" + suspicious.get(0).getPresentableName() + "' is likely to be of unsupported Subversion format" :
+ "Some roots are likely to be of unsupported Subversion format";
+ application.invokeLater(new Runnable() {
+ public void run() {
+ new VcsBalloonProblemNotifier(myProject, message, MessageType.WARNING).run();
+ }
+ }, ModalityState.NON_MODAL, new Condition() {
+ @Override
+ public boolean value(Object o) {
+ return (! myProject.isOpen()) || myProject.isDisposed();
+ }
+ });
+ }
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnConfigurable.form b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnConfigurable.form
new file mode 100644
index 000000000000..190408f8c1d4
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnConfigurable.form
@@ -0,0 +1,277 @@
+
+
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnConfigurable.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnConfigurable.java
new file mode 100644
index 000000000000..2a3aebe96036
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnConfigurable.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.ide.util.PropertiesComponent;
+import com.intellij.openapi.application.ApplicationNamesInfo;
+import com.intellij.openapi.fileChooser.FileChooser;
+import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
+import com.intellij.openapi.options.Configurable;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.ui.TextFieldWithBrowseButton;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.ui.HyperlinkLabel;
+import com.intellij.ui.MultiLineTooltipUI;
+import com.intellij.ui.components.JBLabel;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.idea.svn17.config.ConfigureProxiesListener;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+
+public class SvnConfigurable implements Configurable {
+
+ private final Project myProject;
+ private JCheckBox myUseDefaultCheckBox;
+ private TextFieldWithBrowseButton myConfigurationDirectoryText;
+ private JButton myClearAuthButton;
+ private JCheckBox myUseCommonProxy;
+ private JButton myEditProxiesButton;
+ private JPanel myComponent;
+
+ private JLabel myConfigurationDirectoryLabel;
+ private JLabel myClearCacheLabel;
+ private JLabel myUseCommonProxyLabel;
+ private JLabel myEditProxyLabel;
+ private JCheckBox myLockOnDemand;
+ private JCheckBox myCheckNestedInQuickMerge;
+ private JCheckBox myDetectNestedWorkingCopiesCheckBox;
+ private JCheckBox myIgnoreWhitespaceDifferenciesInCheckBox;
+ private JCheckBox myShowMergeSourceInAnnotate;
+ private JSpinner myNumRevsInAnnotations;
+ private JCheckBox myMaximumNumberOfRevisionsCheckBox;
+ private JSpinner mySSHConnectionTimeout;
+ private JSpinner mySSHReadTimeout;
+ private JBLabel myWarningLabel;
+ private HyperlinkLabel myLinkLabel;
+
+ @NonNls private static final String HELP_ID = "project.propSubversion";
+
+ public SvnConfigurable(Project project) {
+ myProject = project;
+ myLinkLabel.setHyperlinkTarget("http://youtrack.jetbrains.net/issue/IDEA-72400");
+ myLinkLabel.setHyperlinkText("More information");
+
+ myUseDefaultCheckBox.addActionListener(new ActionListener() {
+ public void actionPerformed(final ActionEvent e) {
+ boolean enabled = !myUseDefaultCheckBox.isSelected();
+ myConfigurationDirectoryText.setEnabled(enabled);
+ myConfigurationDirectoryLabel.setEnabled(enabled);
+ SvnConfiguration17 configuration = SvnConfiguration17.getInstance(myProject);
+ String path = configuration.getConfigurationDirectory();
+ if (!enabled || path == null) {
+ myConfigurationDirectoryText.setText(IdeaSubversionConfigurationDirectory.getPath());
+ }
+ else {
+ myConfigurationDirectoryText.setText(path);
+ }
+ }
+ });
+
+ myClearAuthButton.addActionListener(new ActionListener(){
+ public void actionPerformed(final ActionEvent e) {
+ String path = myConfigurationDirectoryText.getText();
+ if (path != null) {
+ int result = Messages.showYesNoDialog(myComponent, SvnBundle.message("confirmation.text.delete.stored.authentication.information"),
+ SvnBundle.message("confirmation.title.clear.authentication.cache"),
+ Messages.getWarningIcon());
+ if (result == 0) {
+ SvnConfiguration17.RUNTIME_AUTH_CACHE.clear();
+ SvnConfiguration17.getInstance(myProject).clearAuthenticationDirectory();
+ }
+ }
+
+ }
+ });
+
+
+ final FileChooserDescriptor descriptor = createFileDescriptor();
+
+ myConfigurationDirectoryText.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ @NonNls String path = myConfigurationDirectoryText.getText().trim();
+ path = "file://" + path.replace(File.separatorChar, '/');
+ VirtualFile root = VirtualFileManager.getInstance().findFileByUrl(path);
+
+ String oldValue = PropertiesComponent.getInstance().getValue("FileChooser.showHiddens");
+ PropertiesComponent.getInstance().setValue("FileChooser.showHiddens", Boolean.TRUE.toString());
+ VirtualFile file = FileChooser.chooseFile(myComponent, descriptor, root);
+ PropertiesComponent.getInstance().setValue("FileChooser.showHiddens", oldValue);
+ if (file == null) {
+ return;
+ }
+ myConfigurationDirectoryText.setText(file.getPath().replace('/', File.separatorChar));
+ }
+ });
+ myConfigurationDirectoryText.setEditable(false);
+
+ myConfigurationDirectoryLabel.setLabelFor(myConfigurationDirectoryText);
+
+ myUseCommonProxy.setText(SvnBundle.message("use.idea.proxy.as.default", ApplicationNamesInfo.getInstance().getProductName()));
+ myEditProxiesButton.addActionListener(new ConfigureProxiesListener(myProject));
+
+ myMaximumNumberOfRevisionsCheckBox.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ myNumRevsInAnnotations.setEnabled(myMaximumNumberOfRevisionsCheckBox.isSelected());
+ }
+ });
+ myNumRevsInAnnotations.setEnabled(myMaximumNumberOfRevisionsCheckBox.isSelected());
+ }
+
+ private static FileChooserDescriptor createFileDescriptor() {
+ final FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor();
+ descriptor.setShowFileSystemRoots(true);
+ descriptor.setTitle(SvnBundle.message("dialog.title.select.configuration.directory"));
+ descriptor.setDescription(SvnBundle.message("dialog.description.select.configuration.directory"));
+ descriptor.setHideIgnored(false);
+ return descriptor;
+ }
+
+ public JComponent createComponent() {
+
+ return myComponent;
+ }
+
+ public String getDisplayName() {
+ return null;
+ }
+
+ public Icon getIcon() {
+ return null;
+ }
+
+ public String getHelpTopic() {
+ return HELP_ID;
+ }
+
+ public boolean isModified() {
+ if (myComponent == null) {
+ return false;
+ }
+ SvnConfiguration17 configuration = SvnConfiguration17.getInstance(myProject);
+ if (configuration.isUseDefaultConfiguation() != myUseDefaultCheckBox.isSelected()) {
+ return true;
+ }
+ if (configuration.isIsUseDefaultProxy() != myUseCommonProxy.isSelected()) {
+ return true;
+ }
+ if (configuration.UPDATE_LOCK_ON_DEMAND != myLockOnDemand.isSelected()) {
+ return true;
+ }
+ if (configuration.DETECT_NESTED_COPIES != myDetectNestedWorkingCopiesCheckBox.isSelected()) {
+ return true;
+ }
+ if (configuration.CHECK_NESTED_FOR_QUICK_MERGE != myCheckNestedInQuickMerge.isSelected()) {
+ return true;
+ }
+ if (configuration.IGNORE_SPACES_IN_ANNOTATE != myIgnoreWhitespaceDifferenciesInCheckBox.isSelected()) {
+ return true;
+ }
+ if (configuration.SHOW_MERGE_SOURCES_IN_ANNOTATE != myShowMergeSourceInAnnotate.isSelected()) {
+ return true;
+ }
+ final int annotateRevisions = configuration.getMaxAnnotateRevisions();
+ final boolean useMaxInAnnot = annotateRevisions != -1;
+ if (useMaxInAnnot != myMaximumNumberOfRevisionsCheckBox.isSelected()) {
+ return true;
+ }
+ if (myMaximumNumberOfRevisionsCheckBox.isSelected()) {
+ if (annotateRevisions != ((SpinnerNumberModel) myNumRevsInAnnotations.getModel()).getNumber().intValue()) {
+ return true;
+ }
+ }
+ if (configuration.mySSHConnectionTimeout/1000 != ((SpinnerNumberModel) mySSHConnectionTimeout.getModel()).getNumber().longValue()) {
+ return true;
+ }
+ if (configuration.mySSHReadTimeout/1000 != ((SpinnerNumberModel) mySSHReadTimeout.getModel()).getNumber().longValue()) {
+ return true;
+ }
+ return !configuration.getConfigurationDirectory().equals(myConfigurationDirectoryText.getText().trim());
+ }
+
+ public void apply() throws ConfigurationException {
+ SvnConfiguration17 configuration = SvnConfiguration17.getInstance(myProject);
+ configuration.setConfigurationDirectory(myConfigurationDirectoryText.getText());
+ configuration.setUseDefaultConfiguation(myUseDefaultCheckBox.isSelected());
+ configuration.setIsUseDefaultProxy(myUseCommonProxy.isSelected());
+ if ((! configuration.DETECT_NESTED_COPIES) && (configuration.DETECT_NESTED_COPIES != myDetectNestedWorkingCopiesCheckBox.isSelected())) {
+ SvnVcs17.getInstance(myProject).invokeRefreshSvnRoots(true);
+ }
+ configuration.DETECT_NESTED_COPIES = myDetectNestedWorkingCopiesCheckBox.isSelected();
+ configuration.CHECK_NESTED_FOR_QUICK_MERGE = myCheckNestedInQuickMerge.isSelected();
+ configuration.UPDATE_LOCK_ON_DEMAND = myLockOnDemand.isSelected();
+ configuration.setIgnoreSpacesInAnnotate(myIgnoreWhitespaceDifferenciesInCheckBox.isSelected());
+ configuration.SHOW_MERGE_SOURCES_IN_ANNOTATE = myShowMergeSourceInAnnotate.isSelected();
+ if (! myMaximumNumberOfRevisionsCheckBox.isSelected()) {
+ configuration.setMaxAnnotateRevisions(-1);
+ } else {
+ configuration.setMaxAnnotateRevisions(((SpinnerNumberModel) myNumRevsInAnnotations.getModel()).getNumber().intValue());
+ }
+ configuration.mySSHConnectionTimeout = ((SpinnerNumberModel) mySSHConnectionTimeout.getModel()).getNumber().longValue() * 1000;
+ configuration.mySSHReadTimeout = ((SpinnerNumberModel) mySSHReadTimeout.getModel()).getNumber().longValue() * 1000;
+ }
+
+ public void reset() {
+ SvnConfiguration17 configuration = SvnConfiguration17.getInstance(myProject);
+ String path = configuration.getConfigurationDirectory();
+ if (configuration.isUseDefaultConfiguation() || path == null) {
+ path = IdeaSubversionConfigurationDirectory.getPath();
+ }
+ myConfigurationDirectoryText.setText(path);
+ myUseDefaultCheckBox.setSelected(configuration.isUseDefaultConfiguation());
+ myUseCommonProxy.setSelected(configuration.isIsUseDefaultProxy());
+ myDetectNestedWorkingCopiesCheckBox.setSelected(configuration.DETECT_NESTED_COPIES);
+ myCheckNestedInQuickMerge.setSelected(configuration.CHECK_NESTED_FOR_QUICK_MERGE);
+
+ boolean enabled = !myUseDefaultCheckBox.isSelected();
+ myConfigurationDirectoryText.setEnabled(enabled);
+ myConfigurationDirectoryLabel.setEnabled(enabled);
+ myLockOnDemand.setSelected(configuration.UPDATE_LOCK_ON_DEMAND);
+ myIgnoreWhitespaceDifferenciesInCheckBox.setSelected(configuration.IGNORE_SPACES_IN_ANNOTATE);
+ myShowMergeSourceInAnnotate.setSelected(configuration.SHOW_MERGE_SOURCES_IN_ANNOTATE);
+
+ final int annotateRevisions = configuration.getMaxAnnotateRevisions();
+ if (annotateRevisions == -1) {
+ myMaximumNumberOfRevisionsCheckBox.setSelected(false);
+ myNumRevsInAnnotations.setValue(SvnConfiguration17.ourMaxAnnotateRevisionsDefault);
+ } else {
+ myMaximumNumberOfRevisionsCheckBox.setSelected(true);
+ myNumRevsInAnnotations.setValue(annotateRevisions);
+ }
+ myNumRevsInAnnotations.setEnabled(myMaximumNumberOfRevisionsCheckBox.isSelected());
+ mySSHConnectionTimeout.setValue(Long.valueOf(configuration.mySSHConnectionTimeout / 1000));
+ mySSHReadTimeout.setValue(Long.valueOf(configuration.mySSHReadTimeout / 1000));
+ }
+
+ public void disposeUIResources() {
+ }
+
+ private void createUIComponents() {
+ myLockOnDemand = new JCheckBox() {
+ @Override
+ public JToolTip createToolTip() {
+ JToolTip toolTip = new JToolTip(){{
+ setUI(new MultiLineTooltipUI());
+ }};
+ toolTip.setComponent(this);
+ return toolTip;
+ }
+ };
+
+ final SvnConfiguration17 configuration = SvnConfiguration17.getInstance(myProject);
+ int value = configuration.getMaxAnnotateRevisions();
+ value = (value == -1) ? SvnConfiguration17.ourMaxAnnotateRevisionsDefault : value;
+ myNumRevsInAnnotations = new JSpinner(new SpinnerNumberModel(value, 10, 100000, 100));
+
+ final int maximum = 30 * 60 * 1000;
+ final long connection = configuration.mySSHConnectionTimeout <= maximum ? configuration.mySSHConnectionTimeout : maximum;
+ final long read = configuration.mySSHReadTimeout <= maximum ? configuration.mySSHReadTimeout : maximum;
+ mySSHConnectionTimeout = new JSpinner(new SpinnerNumberModel(Long.valueOf(connection / 1000).longValue(), 0, maximum, 10));
+ mySSHReadTimeout = new JSpinner(new SpinnerNumberModel(Long.valueOf(read / 1000).longValue(), 0, maximum, 10));
+ }
+}
+
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnConfiguration17.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnConfiguration17.java
new file mode 100644
index 000000000000..f27d00de0342
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnConfiguration17.java
@@ -0,0 +1,527 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ModalityState;
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.DefaultJDOMExternalizer;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vcs.annotate.AnnotationListener;
+import org.jdom.Attribute;
+import org.jdom.DataConversionException;
+import org.jdom.Element;
+import org.jetbrains.idea.svn17.dialogs.SvnAuthenticationProvider;
+import org.jetbrains.idea.svn17.dialogs.SvnInteractiveAuthenticationProvider;
+import org.jetbrains.idea.svn17.update.MergeRootInfo;
+import org.jetbrains.idea.svn17.update.UpdateRootInfo;
+import org.tmatesoft.svn.core.SVNDepth;
+import org.tmatesoft.svn.core.SVNErrorMessage;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
+import org.tmatesoft.svn.core.auth.ISVNAuthenticationProvider;
+import org.tmatesoft.svn.core.auth.SVNAuthentication;
+import org.tmatesoft.svn.core.internal.wc.ISVNAuthenticationStorage;
+import org.tmatesoft.svn.core.internal.wc.SVNConfigFile;
+import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
+import org.tmatesoft.svn.core.wc.ISVNOptions;
+import org.tmatesoft.svn.core.wc.SVNWCUtil;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.util.*;
+
+@State(
+ name = "SvnConfiguration17",
+ storages = {
+ @Storage(
+ file = "$WORKSPACE_FILE$"
+ )
+ }
+)
+public class SvnConfiguration17 implements PersistentStateComponent {
+ private static final Logger LOG = Logger.getInstance("org.jetbrains.idea.svn.SvnConfiguration");
+ public final static int ourMaxAnnotateRevisionsDefault = 500;
+
+ private final static String SERVERS_FILE_NAME = "servers";
+
+ public static final String UPGRADE_AUTO = "auto";
+ public static final String UPGRADE_AUTO_15 = "auto1.5";
+ public static final String UPGRADE_AUTO_16 = "auto1.6";
+ public static final String UPGRADE_AUTO_17 = "auto1.7";
+ public static final String UPGRADE_NONE = "none";
+
+ public String USER = "";
+ public String PASSWORD = "";
+ public String[] ADD_PATHS = null;
+
+ private String myConfigurationDirectory;
+ private boolean myIsUseDefaultConfiguration;
+ private boolean myIsUseDefaultProxy;
+ private ISVNOptions myOptions;
+ private boolean myIsKeepLocks;
+ private boolean myRemoteStatus;
+ private final Project myProject;
+ private SvnAuthenticationManager myAuthManager;
+ private SvnAuthenticationManager myPassiveAuthManager;
+ private SvnAuthenticationManager myInteractiveManager;
+ private String myUpgradeMode;
+ private SvnSupportOptions mySupportOptions;
+ private int myMaxAnnotateRevisions = ourMaxAnnotateRevisionsDefault;
+ private final static long DEFAULT_SSH_TIMEOUT = 30 * 1000;
+ public long mySSHConnectionTimeout = DEFAULT_SSH_TIMEOUT;
+ public long mySSHReadTimeout = DEFAULT_SSH_TIMEOUT;
+
+ public static final AuthStorage RUNTIME_AUTH_CACHE = new AuthStorage();
+ public String LAST_MERGED_REVISION = null;
+ public SVNDepth UPDATE_DEPTH = SVNDepth.INFINITY;
+
+ public boolean MERGE_DRY_RUN = false;
+ public boolean MERGE_DIFF_USE_ANCESTRY = true;
+ public boolean UPDATE_LOCK_ON_DEMAND = false;
+ public boolean IGNORE_SPACES_IN_MERGE = false;
+ public boolean DETECT_NESTED_COPIES = true;
+ public boolean CHECK_NESTED_FOR_QUICK_MERGE = false;
+ public boolean IGNORE_SPACES_IN_ANNOTATE = true;
+ public boolean SHOW_MERGE_SOURCES_IN_ANNOTATE = true;
+ public boolean FORCE_UPDATE = false;
+
+ private final Map myMergeRootInfos = new HashMap();
+ private final Map myUpdateRootInfos = new HashMap();
+ private final List myAnnotationListeners;
+ private SvnInteractiveAuthenticationProvider myInteractiveProvider;
+
+ @Override
+ public Element getState() {
+ Element element = new Element("state");
+ try {
+ writeExternal(element);
+ }
+ catch (WriteExternalException e) {
+ LOG.error(e);
+ }
+ return element;
+ }
+
+ @Override
+ public void loadState(Element state) {
+ try {
+ readExternal(state);
+ }
+ catch (InvalidDataException e) {
+ LOG.error(e);
+ }
+ }
+
+ public static SvnConfiguration17 getInstance(final Project project) {
+ return ServiceManager.getService(project, SvnConfiguration17.class);
+ }
+
+ public SvnConfiguration17(final Project project) {
+ myProject = project;
+ myAnnotationListeners = new ArrayList();
+ }
+
+ // accessed on AWT
+ public void addAnnotationListener(final AnnotationListener listener) {
+ myAnnotationListeners.add(listener);
+ }
+
+ public void removeAnnotationListener(final AnnotationListener listener) {
+ myAnnotationListeners.remove(listener);
+ }
+
+ public void setIgnoreSpacesInAnnotate(final boolean value) {
+ final boolean changed = IGNORE_SPACES_IN_ANNOTATE != value;
+ IGNORE_SPACES_IN_ANNOTATE = value;
+ if (changed) {
+ fireForAnnotationListeners();
+ }
+ }
+
+ private void fireForAnnotationListeners() {
+ final AnnotationListener[] listeners = myAnnotationListeners.toArray(new AnnotationListener[myAnnotationListeners.size()]);
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ public void run() {
+ for (int i = 0; i < listeners.length; i++) {
+ final AnnotationListener listener = listeners[i];
+ listener.onAnnotationChanged();
+ }
+ }
+ }, ModalityState.NON_MODAL);
+ }
+
+ public class SvnSupportOptions {
+ /**
+ * version of "support SVN in IDEA". for features tracking. should grow
+ */
+ private Long myVersion;
+
+ public SvnSupportOptions(final Long version) {
+ myVersion = version;
+ // will be set to SvnSupportOptions.CHANGELIST_SUPPORT after sync
+ if (myVersion == null || myVersion.longValue() < SvnSupportOptions.CHANGELIST_SUPPORT) {
+ myVersion = SvnSupportOptions.UPGRADE_TO_15_VERSION_ASKED;
+ }
+ }
+
+ private final static long UPGRADE_TO_15_VERSION_ASKED = 123;
+ private final static long CHANGELIST_SUPPORT = 124;
+ private final static long UPGRADE_TO_16_VERSION_ASKED = 125;
+
+ public boolean upgradeTo16Asked() {
+ return (myVersion != null) && (UPGRADE_TO_16_VERSION_ASKED <= myVersion);
+ }
+
+ public boolean changeListsSynchronized() {
+ return (myVersion != null) && (CHANGELIST_SUPPORT <= myVersion);
+ }
+
+ public void upgrade() {
+ myVersion = UPGRADE_TO_16_VERSION_ASKED;
+ }
+ }
+
+ public SvnSupportOptions getSupportOptions() {
+ if (mySupportOptions == null) {
+ // used to be kept in SvnBranchConfigurationManager
+ mySupportOptions = new SvnSupportOptions(SvnBranchConfigurationManager17.getInstance(myProject).getSupportValue());
+ }
+ return mySupportOptions;
+ }
+
+ public String getConfigurationDirectory() {
+ if (myConfigurationDirectory == null || isUseDefaultConfiguation()) {
+ myConfigurationDirectory = IdeaSubversionConfigurationDirectory.getPath();
+ }
+ return myConfigurationDirectory;
+ }
+
+ public boolean isUseDefaultConfiguation() {
+ return myIsUseDefaultConfiguration;
+ }
+
+ public void setConfigurationDirectory(String path) {
+ myConfigurationDirectory = path;
+ File dir = path == null ? new File(IdeaSubversionConfigurationDirectory.getPath()) : new File(path);
+ SVNConfigFile.createDefaultConfiguration(dir);
+
+ myOptions = null;
+ myAuthManager = null;
+ myPassiveAuthManager = null;
+ myInteractiveManager = null;
+ myInteractiveProvider = null;
+ RUNTIME_AUTH_CACHE.clear();
+ }
+
+ public void setUseDefaultConfiguation(boolean useDefault) {
+ myIsUseDefaultConfiguration = useDefault;
+ myOptions = null;
+ myAuthManager = null;
+ myPassiveAuthManager = null;
+ myInteractiveManager = null;
+ myInteractiveProvider = null;
+ RUNTIME_AUTH_CACHE.clear();
+ }
+
+ public ISVNOptions getOptions(Project project) {
+ if (myOptions == null) {
+ File path = new File(getConfigurationDirectory());
+ myOptions = SVNWCUtil.createDefaultOptions(path.getAbsoluteFile(), true);
+ }
+ return myOptions;
+ }
+
+ public SvnAuthenticationManager getAuthenticationManager(final SvnVcs17 svnVcs) {
+ if (myAuthManager == null) {
+ // reloaded when configuration directory changes
+ myAuthManager = new SvnAuthenticationManager(myProject, new File(getConfigurationDirectory()));
+ getInteractiveManager(svnVcs);
+ // to init
+ myAuthManager.setAuthenticationProvider(new SvnAuthenticationProvider(svnVcs, myInteractiveProvider));
+ myAuthManager.setRuntimeStorage(RUNTIME_AUTH_CACHE);
+ }
+ return myAuthManager;
+ }
+
+ public SvnAuthenticationManager getPassiveAuthenticationManager() {
+ if (myPassiveAuthManager == null) {
+ myPassiveAuthManager = new SvnAuthenticationManager(myProject, new File(getConfigurationDirectory()));
+ myPassiveAuthManager.setAuthenticationProvider(new ISVNAuthenticationProvider() {
+ @Override
+ public SVNAuthentication requestClientAuthentication(String kind,
+ SVNURL url,
+ String realm,
+ SVNErrorMessage errorMessage,
+ SVNAuthentication previousAuth,
+ boolean authMayBeStored) {
+ return null;
+ }
+
+ @Override
+ public int acceptServerAuthentication(SVNURL url, String realm, Object certificate, boolean resultMayBeStored) {
+ return REJECTED;
+ }
+ });
+ myPassiveAuthManager.setRuntimeStorage(RUNTIME_AUTH_CACHE);
+ }
+ return myPassiveAuthManager;
+ }
+
+ public SvnAuthenticationManager getInteractiveManager(final SvnVcs17 svnVcs) {
+ if (myInteractiveManager == null) {
+ myInteractiveManager = new SvnAuthenticationManager(myProject, new File(getConfigurationDirectory()));
+ myInteractiveManager.setRuntimeStorage(RUNTIME_AUTH_CACHE);
+ myInteractiveProvider = new SvnInteractiveAuthenticationProvider(svnVcs, myInteractiveManager);
+ myInteractiveManager.setAuthenticationProvider(myInteractiveProvider);
+ }
+ return myInteractiveManager;
+ }
+
+ public void getServerFilesManagers(final Ref systemManager, final Ref userManager) {
+ // created only if does not exist
+ SVNConfigFile.createDefaultConfiguration(new File(getConfigurationDirectory()));
+
+ systemManager.set(new SvnServerFileManagerImpl(new IdeaSVNConfigFile(new File(SVNFileUtil.getSystemConfigurationDirectory(), SERVERS_FILE_NAME))));
+ userManager.set(new SvnServerFileManagerImpl(new IdeaSVNConfigFile(new File(getConfigurationDirectory(), SERVERS_FILE_NAME))));
+ }
+
+ public String getUpgradeMode() {
+ return myUpgradeMode;
+ }
+
+ public void setUpgradeMode(String upgradeMode) {
+ myUpgradeMode = upgradeMode;
+ }
+
+ @SuppressWarnings({"HardCodedStringLiteral"})
+ public void readExternal(Element element) throws InvalidDataException {
+ DefaultJDOMExternalizer.readExternal(this, element);
+ List elems = element.getChildren("addpath");
+ LOG.debug(elems.toString());
+ ADD_PATHS = new String[elems.size()];
+ for (int i = 0; i < elems.size(); i++) {
+ Element elem = (Element)elems.get(i);
+ ADD_PATHS[i] = elem.getAttributeValue("path");
+ }
+ Element configurationDirectory = element.getChild("configuration");
+ if (configurationDirectory != null) {
+ myConfigurationDirectory = configurationDirectory.getText();
+ Attribute defaultAttr = configurationDirectory.getAttribute("useDefault");
+ try {
+ myIsUseDefaultConfiguration = defaultAttr != null && defaultAttr.getBooleanValue();
+ }
+ catch (DataConversionException e) {
+ myIsUseDefaultConfiguration = false;
+ }
+ }
+ else {
+ myIsUseDefaultConfiguration = true;
+ }
+ // compatibility: this setting was moved from .iws to global settings
+ List urls = element.getChildren("checkoutURL");
+ for (Object url1 : urls) {
+ Element child = (Element)url1;
+ String url = child.getText();
+ if (url != null) {
+ SvnApplicationSettings17.getInstance().addCheckoutURL(url);
+ }
+ }
+ myIsKeepLocks = element.getChild("keepLocks") != null;
+ myRemoteStatus = element.getChild("remoteStatus") != null;
+ myUpgradeMode = element.getChild("upgradeMode") != null ? element.getChild("upgradeMode").getText() : null;
+ final Element useProxy = element.getChild("myIsUseDefaultProxy");
+ if (useProxy == null) {
+ myIsUseDefaultProxy = false;
+ } else {
+ myIsUseDefaultProxy = Boolean.parseBoolean(useProxy.getText());
+ }
+ final Element supportedVersion = element.getChild("supportedVersion");
+ if (supportedVersion != null) {
+ try {
+ mySupportOptions = new SvnSupportOptions(Long.parseLong(supportedVersion.getText().trim()));
+ } catch (NumberFormatException e) {
+ mySupportOptions = new SvnSupportOptions(null);
+ }
+ }
+ final Attribute maxAnnotateRevisions = element.getAttribute("maxAnnotateRevisions");
+ if (maxAnnotateRevisions != null) {
+ try {
+ myMaxAnnotateRevisions = maxAnnotateRevisions.getIntValue();
+ }
+ catch (DataConversionException e) {
+ //
+ }
+ }
+ }
+
+ @SuppressWarnings({"HardCodedStringLiteral"})
+ public void writeExternal(Element element) throws WriteExternalException {
+ DefaultJDOMExternalizer.writeExternal(this, element);
+ if (ADD_PATHS != null) {
+ for (String aADD_PATHS : ADD_PATHS) {
+ Element elem = new Element("addpath");
+ elem.setAttribute("path", aADD_PATHS);
+ element.addContent(elem);
+ }
+ }
+ if (myConfigurationDirectory != null) {
+ Element configurationDirectory = new Element("configuration");
+ configurationDirectory.setText(myConfigurationDirectory);
+ configurationDirectory.setAttribute("useDefault", myIsUseDefaultConfiguration ? "true" : "false");
+ element.addContent(configurationDirectory);
+ }
+ if (myIsKeepLocks) {
+ element.addContent(new Element("keepLocks"));
+ }
+ if (myRemoteStatus) {
+ element.addContent(new Element("remoteStatus"));
+ }
+ if (myUpgradeMode != null) {
+ element.addContent(new Element("upgradeMode").setText(myUpgradeMode));
+ }
+ element.addContent(new Element("myIsUseDefaultProxy").setText(myIsUseDefaultProxy ? "true" : "false"));
+ if (mySupportOptions != null) {
+ element.addContent(new Element("supportedVersion").setText("" + mySupportOptions.myVersion));
+ }
+ element.setAttribute("maxAnnotateRevisions", "" + myMaxAnnotateRevisions);
+ }
+
+ public boolean isKeepLocks() {
+ return myIsKeepLocks;
+ }
+
+ public void setKeepLocks(boolean keepLocks) {
+ myIsKeepLocks = keepLocks;
+ }
+
+ public boolean isRemoteStatus() {
+ return myRemoteStatus;
+ }
+
+ public void setRemoteStatus(boolean remote) {
+ myRemoteStatus = remote;
+ }
+
+ public boolean isIsUseDefaultProxy() {
+ return myIsUseDefaultProxy;
+ }
+
+ public void setIsUseDefaultProxy(final boolean isUseDefaultProxy) {
+ myIsUseDefaultProxy = isUseDefaultProxy;
+ }
+
+ public static class AuthStorage implements ISVNAuthenticationStorage {
+
+ private final Map myStorage = new Hashtable();
+
+ public void clear() {
+ myStorage.clear();
+ }
+
+ public void putData(String kind, String realm, Object data) {
+ if (data == null) {
+ myStorage.remove(kind + "$" + realm);
+ } else {
+ myStorage.put(kind + "$" + realm, data);
+ }
+ }
+
+ public Object getData(String kind, String realm) {
+ return myStorage.get(kind + "$" + realm);
+ }
+ }
+
+ public MergeRootInfo getMergeRootInfo(final File file, final SvnVcs17 svnVcs) {
+ if (!myMergeRootInfos.containsKey(file)) {
+ myMergeRootInfos.put(file, new MergeRootInfo(file, svnVcs));
+ }
+ return myMergeRootInfos.get(file);
+ }
+
+ public UpdateRootInfo getUpdateRootInfo(File file, final SvnVcs17 svnVcs) {
+ if (!myUpdateRootInfos.containsKey(file)) {
+ myUpdateRootInfos.put(file, new UpdateRootInfo(file, svnVcs));
+ }
+ return myUpdateRootInfos.get(file);
+ }
+
+ public Map getUpdateInfosMap() {
+ return Collections.unmodifiableMap(myUpdateRootInfos);
+ }
+
+ private static final List ourAuthKinds = Arrays.asList(ISVNAuthenticationManager.PASSWORD, ISVNAuthenticationManager.SSH,
+ ISVNAuthenticationManager.SSL, ISVNAuthenticationManager.USERNAME, "svn.ssl.server", "svn.ssh.server");
+
+ public void clearAuthenticationDirectory() {
+ final File authDir = new File(getConfigurationDirectory(), "auth");
+ if (authDir.exists()) {
+ ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
+ public void run() {
+ final ProgressIndicator ind = ProgressManager.getInstance().getProgressIndicator();
+ if (ind != null) {
+ ind.setIndeterminate(true);
+ ind.setText("Clearing stored credentials in " + authDir.getAbsolutePath());
+ }
+ final File[] files = authDir.listFiles(new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ return ourAuthKinds.contains(name);
+ }
+ });
+
+ for (File dir : files) {
+ if (ind != null) {
+ ind.setText("Deleting " + dir.getAbsolutePath());
+ }
+ FileUtil.delete(dir);
+ }
+ }
+ }, "button.text.clear.authentication.cache", false, myProject);
+ }
+ }
+
+ public boolean haveCredentialsFor(final String kind, final String realm) {
+ return RUNTIME_AUTH_CACHE.getData(kind, realm) != null;
+ }
+
+ public void acknowledge(final String kind, final String realm, final Object object) {
+ RUNTIME_AUTH_CACHE.putData(kind, realm, object);
+ }
+
+ public void clearCredentials(final String kind, final String realm) {
+ RUNTIME_AUTH_CACHE.putData(kind, realm, null);
+ }
+
+ public int getMaxAnnotateRevisions() {
+ return myMaxAnnotateRevisions;
+ }
+
+ public void setMaxAnnotateRevisions(int maxAnnotateRevisions) {
+ myMaxAnnotateRevisions = maxAnnotateRevisions;
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnContentRevision.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnContentRevision.java
new file mode 100644
index 000000000000..a7f51bc4b60e
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnContentRevision.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.Throwable2Computable;
+import com.intellij.openapi.vcs.FilePath;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vcs.changes.ContentRevision;
+import com.intellij.openapi.vcs.history.VcsRevisionNumber;
+import com.intellij.openapi.vcs.impl.ContentRevisionCache;
+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.SVNWCClient;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author yole
+*/
+class SvnContentRevision implements ContentRevision {
+ private final SvnVcs17 myVcs;
+ protected final FilePath myFile;
+ private final SVNRevision myRevision;
+ /**
+ * this flag is necessary since SVN would not do remote request only if constant SVNRevision.BASE
+ * -> usual current revision content class can't be used
+ */
+ private final boolean myUseBaseRevision;
+
+ protected SvnContentRevision(SvnVcs17 vcs, @NotNull final FilePath file, final SVNRevision revision, final boolean useBaseRevision) {
+ myVcs = vcs;
+ myRevision = revision;
+ myUseBaseRevision = useBaseRevision;
+ myFile = file;
+ }
+
+ public static SvnContentRevision createBaseRevision(@NotNull SvnVcs17 vcs, @NotNull final FilePath file, final SVNRevision revision) {
+ if (file.getFileType().isBinary()) {
+ return new SvnBinaryContentRevision(vcs, file, revision, true);
+ }
+ return new SvnContentRevision(vcs, file, revision, true);
+ }
+
+ public static SvnContentRevision createRemote(@NotNull SvnVcs17 vcs, @NotNull final FilePath file, final SVNRevision revision) {
+ if (file.getFileType().isBinary()) {
+ return new SvnBinaryContentRevision(vcs, file, revision, false);
+ }
+ return new SvnContentRevision(vcs, file, revision, false);
+ }
+
+ @Nullable
+ public String getContent() throws VcsException {
+ try {
+ if (myUseBaseRevision) {
+ return ContentRevisionCache.getOrLoadCurrentAsString(myVcs.getProject(), myFile, myVcs.getKeyInstanceMethod(),
+ new CurrentRevisionProvider() {
+ @Override
+ public VcsRevisionNumber getCurrentRevision() throws VcsException {
+ return getRevisionNumber();
+ }
+
+ @Override
+ public Pair get()
+ throws VcsException, IOException {
+ return new Pair(getRevisionNumber(), getUpToDateBinaryContent());
+ }
+ }).getSecond();
+ } else {
+ return ContentRevisionCache.getOrLoadAsString(myVcs.getProject(), myFile, getRevisionNumber(), myVcs.getKeyInstanceMethod(),
+ ContentRevisionCache.UniqueType.REPOSITORY_CONTENT,
+ new Throwable2Computable() {
+ @Override
+ public byte[] compute() throws VcsException, IOException {
+ return getUpToDateBinaryContent();
+ }
+ });
+ }
+ }
+ catch (IOException e) {
+ throw new VcsException(e);
+ }
+ }
+
+ protected byte[] getUpToDateBinaryContent() throws VcsException {
+ File file = myFile.getIOFile();
+ File lock = new File(file.getParentFile(), SvnUtil.PATH_TO_LOCK_FILE);
+ 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) {
+ throw new VcsException(e);
+ }
+ catch (IOException e) {
+ throw new VcsException(e);
+ }
+ return buffer.toByteArray();
+ }
+
+ @NotNull
+ public FilePath getFile() {
+ return myFile;
+ }
+
+ @NotNull
+ public VcsRevisionNumber getRevisionNumber() {
+ return new SvnRevisionNumber(myRevision);
+ }
+
+ @NonNls
+ public String toString() {
+ return myFile.getPath();
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnCopiesRefreshManager.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnCopiesRefreshManager.java
new file mode 100644
index 000000000000..eef7e1ea9a31
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnCopiesRefreshManager.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.RequestsMerger;
+import com.intellij.util.Consumer;
+import com.intellij.util.concurrency.Semaphore;
+
+public class SvnCopiesRefreshManager {
+ private final CopiesRefresh myCopiesRefresh;
+
+ public SvnCopiesRefreshManager(final Project project, final SvnFileUrlMappingImpl17 mapping) {
+ myCopiesRefresh = new MyVeryRefresh();
+
+ final Runnable refresher = new MyRefresher(mapping);
+
+ final RequestsMerger requestsMerger = new RequestsMerger(refresher, new Consumer() {
+ public void consume(final Runnable runnable) {
+ ApplicationManager.getApplication().executeOnPooledThread(runnable);
+ }
+ });
+ ((MyVeryRefresh) myCopiesRefresh).setRequestMerger(requestsMerger);
+ }
+
+ public CopiesRefresh getCopiesRefresh() {
+ return myCopiesRefresh;
+ }
+
+ private class MyVeryRefresh implements CopiesRefresh {
+ private static final long ourQueryInterval = 1000;
+ private RequestsMerger myRequestMerger;
+ private final ProgressManager myPm;
+
+ private MyVeryRefresh() {
+ myPm = ProgressManager.getInstance();
+ }
+
+ public void setRequestMerger(RequestsMerger requestMerger) {
+ myRequestMerger = requestMerger;
+ }
+
+ public void ensureInit() {
+ synchRequest(myPm.getProgressIndicator(), true);
+ }
+
+ public void asynchRequest() {
+ myRequestMerger.request();
+ }
+
+ public void synchRequest() {
+ synchRequest(myPm.getProgressIndicator(), false);
+ }
+
+ private void synchRequest(final ProgressIndicator pi, final boolean isOnlyInit) {
+ final Semaphore semaphore = new Semaphore();
+ final Runnable waiter = new Runnable() {
+ public void run() {
+ semaphore.up();
+ }
+ };
+ semaphore.down();
+ if (isOnlyInit) {
+ myRequestMerger.ensureInitialization(waiter);
+ } else {
+ myRequestMerger.waitRefresh(waiter);
+ }
+ while (true) {
+ if (semaphore.waitFor(ourQueryInterval)) break;
+ if (pi != null) {
+ pi.checkCanceled();
+ }
+ }
+ }
+ }
+
+ private static class MyRefresher implements Runnable {
+ private final SvnFileUrlMappingImpl17 myMapping;
+
+ private MyRefresher(final SvnFileUrlMappingImpl17 mapping) {
+ myMapping = mapping;
+ }
+
+ public void run() {
+ try {
+ myMapping.realRefresh();
+ }
+ catch (ProcessCanceledException e) {
+ //
+ }
+ }
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnCopyRootSimple.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnCopyRootSimple.java
new file mode 100644
index 000000000000..9dee5aaa6a7f
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnCopyRootSimple.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+public class SvnCopyRootSimple {
+ public String myVcsRoot;
+ public String myCopyRoot;
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnDiffProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnDiffProvider.java
new file mode 100644
index 000000000000..094fbcf016cc
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnDiffProvider.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.vcs.FilePath;
+import com.intellij.openapi.vcs.actions.VcsContextFactory;
+import com.intellij.openapi.vcs.changes.ContentRevision;
+import com.intellij.openapi.vcs.diff.DiffMixin;
+import com.intellij.openapi.vcs.diff.DiffProvider;
+import com.intellij.openapi.vcs.diff.ItemLatestState;
+import com.intellij.openapi.vcs.history.VcsRevisionDescription;
+import com.intellij.openapi.vcs.history.VcsRevisionDescriptionImpl;
+import com.intellij.openapi.vcs.history.VcsRevisionNumber;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.idea.svn17.history.LatestExistentSearcher;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNURL;
+import org.tmatesoft.svn.core.wc.*;
+
+import java.io.File;
+
+public class SvnDiffProvider implements DiffProvider, DiffMixin {
+ private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnDiffProvider");
+ public static final String COMMIT_MESSAGE = "svn:log";
+ private final SvnVcs17 myVcs;
+
+ public SvnDiffProvider(final SvnVcs17 vcs) {
+ myVcs = vcs;
+ }
+
+ public VcsRevisionNumber getCurrentRevision(VirtualFile file) {
+ final SVNStatusClient client = myVcs.createStatusClient();
+ try {
+ final SVNStatus svnStatus = client.doStatus(new File(file.getPresentableUrl()), false, false);
+ if (svnStatus.getCommittedRevision().equals(SVNRevision.UNDEFINED) && svnStatus.isCopied()) {
+ return new SvnRevisionNumber(svnStatus.getCopyFromRevision());
+ }
+ return new SvnRevisionNumber(svnStatus.getRevision());
+ }
+ catch (SVNException e) {
+ LOG.debug(e); // most likely the file is unversioned
+ return null;
+ }
+ }
+
+ @Override
+ public VcsRevisionDescription getCurrentRevisionDescription(VirtualFile file) {
+ File path = new File(file.getPresentableUrl());
+ return getCurrentRevisionDescription(path);
+ }
+
+ private VcsRevisionDescription getCurrentRevisionDescription(File path) {
+ final SVNWCClient client = myVcs.createWCClient();
+ try {
+ final SVNInfo svnInfo = client.doInfo(path, SVNRevision.UNDEFINED);
+
+ if (svnInfo.getCommittedRevision().equals(SVNRevision.UNDEFINED) && ! svnInfo.getCopyFromRevision().equals(SVNRevision.UNDEFINED) &&
+ svnInfo.getCopyFromURL() != null) {
+ SVNURL copyUrl = svnInfo.getCopyFromURL();
+ String localPath = myVcs.getSvnFileUrlMapping().getLocalPath(copyUrl.toString());
+ if (localPath != null) {
+ return getCurrentRevisionDescription(new File(localPath));
+ }
+ }
+ final String message = getProperties(client, path);
+ return new VcsRevisionDescriptionImpl(new SvnRevisionNumber(svnInfo.getCommittedRevision()), svnInfo.getCommittedDate(),
+ svnInfo.getAuthor(), message);
+ }
+ catch (SVNException e) {
+ LOG.debug(e); // most likely the file is unversioned
+ return null;
+ }
+ }
+
+ private String getProperties(SVNWCClient client, File path) throws SVNException {
+ final String[] message = new String[1];
+ client.doGetRevisionProperty(path, null, SVNRevision.BASE, new ISVNPropertyHandler() {
+ @Override
+ public void handleProperty(File path, SVNPropertyData property) throws SVNException {
+ handle(property);
+ }
+
+ @Override
+ public void handleProperty(SVNURL url, SVNPropertyData property) throws SVNException {
+ handle(property);
+ }
+
+ @Override
+ public void handleProperty(long revision, SVNPropertyData property) throws SVNException {
+ handle(property);
+ }
+
+ private void handle(SVNPropertyData data) {
+ if (COMMIT_MESSAGE.equals(data.getName())) {
+ message[0] = data.getValue().getString();
+ }
+ }
+ });
+ return message[0];
+ }
+
+ private static ItemLatestState defaultResult() {
+ return createResult(SVNRevision.HEAD, true, true);
+ }
+
+ private static ItemLatestState createResult(final SVNRevision revision, final boolean exists, boolean defaultHead) {
+ return new ItemLatestState(new SvnRevisionNumber(revision), exists, defaultHead);
+ }
+
+ public ItemLatestState getLastRevision(VirtualFile file) {
+ return getLastRevision(new File(file.getPath()));
+ }
+
+ public ContentRevision createFileContent(final VcsRevisionNumber revisionNumber, final VirtualFile selectedFile) {
+ FilePath filePath = VcsContextFactory.SERVICE.getInstance().createFilePathOn(selectedFile);
+ final SVNRevision svnRevision = ((SvnRevisionNumber)revisionNumber).getRevision();
+
+ if (! SVNRevision.HEAD.equals(svnRevision)) {
+ if (revisionNumber.equals(getCurrentRevision(selectedFile))) {
+ return SvnContentRevision.createBaseRevision(myVcs, filePath, svnRevision);
+ }
+ }
+ // not clear why we need it, with remote check..
+ final SVNStatusClient client = myVcs.createStatusClient();
+ try {
+ final SVNStatus svnStatus = client.doStatus(new File(selectedFile.getPresentableUrl()), false, false);
+ if (svnRevision.equals(svnStatus.getRevision())) {
+ return SvnContentRevision.createBaseRevision(myVcs, filePath, svnRevision);
+ }
+ }
+ catch (SVNException e) {
+ LOG.debug(e); // most likely the file is unversioned
+ }
+ return SvnContentRevision.createRemote(myVcs, filePath, svnRevision);
+ }
+
+ public ItemLatestState getLastRevision(FilePath filePath) {
+ return getLastRevision(filePath.getIOFile());
+ }
+
+ public VcsRevisionNumber getLatestCommittedRevision(VirtualFile vcsRoot) {
+ // todo
+ return null;
+ }
+
+ private ItemLatestState getLastRevision(final File file) {
+ final SVNStatusClient client = myVcs.createStatusClient();
+ try {
+ final SVNStatus svnStatus = client.doStatus(file, true, false);
+ if (svnStatus == null) {
+ // IDEADEV-21785 (no idea why this can happen)
+ LOG.info("No SVN status returned for " + file.getPath());
+ return defaultResult();
+ }
+ final boolean exists = ! SVNStatusType.STATUS_DELETED.equals(svnStatus.getRemoteContentsStatus()) &&
+ ! SVNStatusType.STATUS_DELETED.equals(svnStatus.getRemoteNodeStatus());
+ if (! exists) {
+ // get really latest revision
+ final LatestExistentSearcher searcher = new LatestExistentSearcher(myVcs, svnStatus.getURL());
+ final long revision = searcher.getDeletionRevision();
+
+ return createResult(SVNRevision.create(revision), exists, false);
+ }
+ final SVNRevision remoteRevision = svnStatus.getRemoteRevision();
+ if (remoteRevision != null) {
+ return createResult(remoteRevision, exists, false);
+ }
+ return createResult(svnStatus.getRevision(), exists, false);
+ }
+ catch (SVNException e) {
+ LOG.debug(e); // most likely the file is unversioned
+ return defaultResult();
+ }
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnEditFileProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnEditFileProvider.java
new file mode 100644
index 000000000000..235c06ad8273
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnEditFileProvider.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.vcs.EditFileProvider;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNProperty;
+import org.tmatesoft.svn.core.wc.SVNPropertyData;
+import org.tmatesoft.svn.core.wc.SVNRevision;
+import org.tmatesoft.svn.core.wc.SVNWCClient;
+
+import java.io.File;
+
+/**
+ * @author alex
+ */
+public class SvnEditFileProvider implements EditFileProvider {
+ private final SvnVcs17 myVCS;
+
+ public SvnEditFileProvider(SvnVcs17 vcs) {
+ myVCS = vcs;
+ }
+
+ public void editFiles(VirtualFile[] files) throws VcsException {
+ File[] ioFiles = new File[files.length];
+ SVNWCClient client = myVCS.createWCClient();
+ for (int i = 0; i < files.length; i++) {
+ ioFiles[i] = new File(files[i].getPath());
+ try {
+ SVNPropertyData property = client
+ .doGetProperty(ioFiles[i], SVNProperty.NEEDS_LOCK, SVNRevision.WORKING, SVNRevision.WORKING);
+ if (property == null || property.getValue() == null) {
+ throw new VcsException(SvnBundle.message("exception.text.file.miss.svn", ioFiles[i].getName()));
+ }
+ }
+ catch (SVNException e) {
+ throw new VcsException(e);
+ }
+ }
+ SvnUtil.doLockFiles(myVCS.getProject(), myVCS, ioFiles);
+ }
+
+ public String getRequestText() {
+ return SvnBundle.message("confirmation.text.edit.file");
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnEntriesFileListener.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnEntriesFileListener.java
new file mode 100644
index 000000000000..616617c84a2d
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnEntriesFileListener.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.AbstractVcs;
+import com.intellij.openapi.vcs.FilePath;
+import com.intellij.openapi.vcs.FilePathImpl;
+import com.intellij.openapi.vcs.ProjectLevelVcsManager;
+import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileAdapter;
+import com.intellij.openapi.vfs.VirtualFileEvent;
+
+import javax.swing.*;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public class SvnEntriesFileListener extends VirtualFileAdapter {
+ private final Project myProject;
+ private final Collection myListeners = new ArrayList();
+ private final ProjectLevelVcsManager myVcsManager;
+
+ public SvnEntriesFileListener(final Project project) {
+ myProject = project;
+ myVcsManager = ProjectLevelVcsManager.getInstance(myProject);
+ }
+
+ public void fileCreated(VirtualFileEvent event) {
+ if (! event.isFromRefresh()) {
+ return;
+ }
+ final VirtualFile file = event.getFile();
+
+ if (SvnUtil.SVN_ADMIN_DIR_NAME.equals(file.getName())) {
+ if (event.getParent() != null) {
+ VirtualFile parent = event.getParent();
+ fireFileStatusesChanged(parent);
+ fileEntriesChanged(parent);
+ }
+ return;
+ }
+
+ final AbstractVcs vcsFor = myVcsManager.getVcsFor(file);
+ if (vcsFor == null) return;
+ if (SvnVcs17.VCS_NAME.equals(vcsFor.getName())) {
+ final RootUrlInfo path = ((SvnVcs17)vcsFor).getSvnFileUrlMapping().getWcRootForFilePath(new File(file.getPath()));
+ if (path != null && WorkingCopyFormat.ONE_DOT_SEVEN.equals(path.getFormat())) {
+ VcsDirtyScopeManager.getInstance(myProject).filesDirty(Collections.singletonList(file), null);
+ }
+ }
+ }
+
+ public void contentsChanged(VirtualFileEvent event) {
+ if (! event.isFromRefresh()) {
+ return;
+ }
+ final VirtualFile file = event.getFile();
+ if (isEntriesFile(file) && file.getParent() != null) {
+ VirtualFile parent = file.getParent();
+ if (parent != null) {
+ VirtualFile grandParent = parent.getParent();
+ if (grandParent != null) {
+ fireFileStatusesChanged(grandParent);
+ fileEntriesChanged(grandParent);
+ }
+ }
+ return;
+ }
+
+ final AbstractVcs vcsFor = myVcsManager.getVcsFor(file);
+ if (vcsFor == null) return;
+ if (SvnVcs17.VCS_NAME.equals(vcsFor.getName())) {
+ final RootUrlInfo path = ((SvnVcs17)vcsFor).getSvnFileUrlMapping().getWcRootForFilePath(new File(file.getPath()));
+ if (path != null && WorkingCopyFormat.ONE_DOT_SEVEN.equals(path.getFormat())) {
+ VcsDirtyScopeManager.getInstance(myProject).filesDirty(Collections.singletonList(file), null);
+ fileRevisionProbablyChanged(file);
+ }
+ }
+ }
+
+ public void fileDeleted(VirtualFileEvent event) {
+ if (!event.isFromRefresh()) {
+ return;
+ }
+ final VirtualFile file = event.getFile();
+ if (SvnUtil.SVN_ADMIN_DIR_NAME.equals(file.getName())) {
+ if (event.getParent() != null) {
+ VirtualFile parent = event.getParent();
+ fireFileStatusesChanged(parent);
+ fileEntriesChanged(parent);
+ }
+ return;
+ }
+
+ if (event.getParent() != null) {
+ final VirtualFile parent = event.getParent();
+ final AbstractVcs vcsFor = myVcsManager.getVcsFor(parent);
+ if (vcsFor == null) return;
+ if (SvnVcs17.VCS_NAME.equals(vcsFor.getName())) {
+ final RootUrlInfo path = ((SvnVcs17)vcsFor).getSvnFileUrlMapping().getWcRootForFilePath(new File(parent.getPath()));
+ if (path != null && WorkingCopyFormat.ONE_DOT_SEVEN.equals(path.getFormat())) {
+ VcsDirtyScopeManager.getInstance(myProject).filePathsDirty(
+ Collections.singletonList(new FilePathImpl(new File(file.getPath()), file.isDirectory())), null);
+ }
+ }
+ }
+ }
+
+ private void fileRevisionProbablyChanged(final VirtualFile file) {
+ final SvnEntriesListener[] listeners = myListeners.toArray(new SvnEntriesListener[myListeners.size()]);
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ for (SvnEntriesListener listener : listeners) {
+ listener.fileVersionProbablyChanged(file);
+ }
+ }
+ });
+ }
+
+ private void fileEntriesChanged(final VirtualFile parent) {
+ final SvnEntriesListener[] listeners = myListeners.toArray(new SvnEntriesListener[myListeners.size()]);
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ for (SvnEntriesListener listener : listeners) {
+ listener.onEntriesChanged(parent);
+ }
+ }
+ });
+ }
+
+ public void addListener(SvnEntriesListener listener) {
+ myListeners.add(listener);
+ }
+
+ public void removeListener(SvnEntriesListener listener) {
+ myListeners.remove(listener);
+ }
+
+ private void fireFileStatusesChanged(VirtualFile parent) {
+ final VirtualFile[] children = parent.getChildren();
+ final List files = new ArrayList(children.length + 1);
+ files.add(parent);
+ Collections.addAll(files, children);
+ VcsDirtyScopeManager.getInstance(myProject).filesDirty(files, null);
+ /*
+ final FileStatusManager fileStatusManager = FileStatusManager.getInstance(myProject);
+ final VirtualFile[] children = parent.getChildren();
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ public void run() {
+ for (int i = 0; i < children.length; i++) {
+ VirtualFile child = children[i];
+ fileStatusManager.fileStatusChanged(child);
+ }
+ }
+ }, ModalityState.NON_MMODAL);
+ */
+ }
+
+ private static boolean isEntriesFile(final VirtualFile file) {
+ VirtualFile parent = file.getParent();
+ return !file.isDirectory() && SvnUtil.ENTRIES_FILE_NAME.equals(file.getName()) && parent != null && SvnUtil.SVN_ADMIN_DIR_NAME.equals(parent.getName());
+ }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnEntriesListener.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnEntriesListener.java
new file mode 100644
index 000000000000..e66ed0be444d
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnEntriesListener.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.vfs.VirtualFile;
+
+public interface SvnEntriesListener {
+ void onEntriesChanged(VirtualFile directory);
+ void fileVersionProbablyChanged(final VirtualFile file);
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnFileStatus.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnFileStatus.java
new file mode 100644
index 000000000000..cefdb816d8e2
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnFileStatus.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.vcs.FileStatus;
+import com.intellij.openapi.vcs.FileStatusFactory;
+
+import java.awt.*;
+
+public class SvnFileStatus {
+ private SvnFileStatus() {
+ }
+
+ public static final FileStatus EXTERNAL =
+ FileStatusFactory.SERVICE.getInstance()
+ .createFileStatus("IDEA_SVN_FILESTATUS_EXTERNAL", SvnBundle.message("file.status.external"), new Color(0x72A038));
+
+ public static final FileStatus OBSTRUCTED =
+ FileStatusFactory.SERVICE.getInstance()
+ .createFileStatus("IDEA_SVN_FILESTATUS_OBSTRUCTED", SvnBundle.message("file.status.obstructed"), new Color(0x727238));
+
+ public static final FileStatus REPLACED =
+ FileStatusFactory.SERVICE.getInstance().createFileStatus("IDEA_SVN_REPLACED",
+ SvnBundle.message("file.status.replaced"),
+ FileStatus.COLOR_ADDED);
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnFileSystemListener.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnFileSystemListener.java
new file mode 100644
index 000000000000..e6b6ba29c9c0
--- /dev/null
+++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnFileSystemListener.java
@@ -0,0 +1,874 @@
+/*
+ * Copyright 2000-2009 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.svn17;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.CommandAdapter;
+import com.intellij.openapi.command.CommandEvent;
+import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vcs.*;
+import com.intellij.openapi.vcs.actions.VcsContextFactory;
+import com.intellij.openapi.vcs.changes.ChangeListManager;
+import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
+import com.intellij.openapi.vfs.LocalFileOperationsHandler;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.newvfs.RefreshQueue;
+import com.intellij.openapi.vfs.newvfs.RefreshSession;
+import com.intellij.util.ThrowableConsumer;
+import com.intellij.util.containers.MultiMap;
+import com.intellij.vcsUtil.ActionWithTempFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.tmatesoft.svn.core.SVNErrorCode;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNNodeKind;
+import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
+import org.tmatesoft.svn.core.wc.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+public class SvnFileSystemListener extends CommandAdapter implements LocalFileOperationsHandler {
+ private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnFileSystemListener");
+ private final LocalFileSystem myLfs;
+
+ private static class AddedFileInfo {
+ private final VirtualFile myDir;
+ private final String myName;
+ @Nullable private final File myCopyFrom;
+ private final boolean myRecursive;
+
+ public AddedFileInfo(final VirtualFile dir, final String name, @Nullable final File copyFrom, boolean recursive) {
+ myDir = dir;
+ myName = name;
+ myCopyFrom = copyFrom;
+ myRecursive = recursive;
+ }
+ }
+
+ private static class MovedFileInfo {
+ private final Project myProject;
+ private final File mySrc;
+ private final File myDst;
+
+ private MovedFileInfo(final Project project, final File src, final File dst) {
+ myProject = project;
+ mySrc = src;
+ myDst = dst;
+ }
+ }
+
+ private final MultiMap myAddedFiles = new MultiMap();
+ private final MultiMap myDeletedFiles = new MultiMap();
+ private final List myMovedFiles = new ArrayList();
+ private final Map> myMoveExceptions = new HashMap>();
+ private final List myFilesToRefresh = new ArrayList();
+ @Nullable private File myStorageForUndo;
+ private final List> myUndoStorageContents = new ArrayList>();
+ private boolean myUndoingMove = false;
+
+ public SvnFileSystemListener() {
+ myLfs = LocalFileSystem.getInstance();
+ }
+
+ private void addToMoveExceptions(final Project project, final SVNException e) {
+ List exceptionList = myMoveExceptions.get(project);
+ if (exceptionList == null) {
+ exceptionList = new ArrayList();
+ myMoveExceptions.put(project, exceptionList);
+ }
+ VcsException vcsException;
+ if (SVNErrorCode.ENTRY_EXISTS.equals(e.getErrorMessage().getErrorCode())) {
+ vcsException = new VcsException(Arrays.asList("Target of move operation is already under version control.",
+ "Subversion move had not been performed. ", e.getMessage()));
+ } else {
+ vcsException = new VcsException(e);
+ }
+ exceptionList.add(vcsException);
+ }
+
+ @Nullable
+ public File copy(final VirtualFile file, final VirtualFile toDir, final String copyName) throws IOException {
+ SvnVcs17 vcs = getVCS(toDir);
+ if (vcs == null) {
+ vcs = getVCS(file);
+ }
+ if (vcs == null) {
+ return null;
+ }
+
+ File srcFile = new File(file.getPath());
+ File destFile = new File(new File(toDir.getPath()), copyName);
+ final boolean dstDirUnderControl = SvnUtil.isSvnVersioned(destFile.getParentFile());
+ if (! dstDirUnderControl && !isPendingAdd(vcs.getProject(), toDir)) {
+ return null;
+ }
+
+ if (! SvnUtil.isSvnVersioned(srcFile.getParentFile())) {
+ myAddedFiles.putValue(vcs.getProject(), new AddedFileInfo(toDir, copyName, null, false));
+ return null;
+ }
+
+ final SVNStatus fileStatus = getFileStatus(vcs, srcFile);
+ if (fileStatus != null && SvnVcs17.svnStatusIs(fileStatus, SVNStatusType.STATUS_ADDED)) {
+ myAddedFiles.putValue(vcs.getProject(), new AddedFileInfo(toDir, copyName, null, false));
+ return null;
+ }
+
+ if (sameRoot(vcs, file.getParent(), toDir)) {
+ myAddedFiles.putValue(vcs.getProject(), new AddedFileInfo(toDir, copyName, srcFile, false));
+ return null;
+ }
+
+ myAddedFiles.putValue(vcs.getProject(), new AddedFileInfo(toDir, copyName, null, false));
+ return null;
+ }
+
+ private boolean sameRoot(final SvnVcs17 vcs, final VirtualFile srcDir, final VirtualFile dstDir) {
+ final UUIDHelper helper = new UUIDHelper(vcs);
+ final String srcUUID = helper.getRepositoryUUID(vcs.getProject(), srcDir);
+ final String dstUUID = helper.getRepositoryUUID(vcs.getProject(), dstDir);
+
+ return srcUUID != null && dstUUID != null && srcUUID.equals(dstUUID);
+ }
+
+ private class UUIDHelper {
+ private final SVNWCClient myWcClient;
+
+ private UUIDHelper(final SvnVcs17 vcs) {
+ myWcClient = vcs.createWCClient();
+ }
+
+ /**
+ * passed dir must be under VC control (it is assumed)
+ */
+ @Nullable
+ public String getRepositoryUUID(final Project project, final VirtualFile dir) {
+ try {
+ final SVNInfo info1 = myWcClient.doInfo(new File(dir.getPath()), SVNRevision.UNDEFINED);
+ if (info1 == null || info1.getRepositoryUUID() == null) {
+ // go deeper if current parent was added (if parent was added, it theoretically could NOT know its repo UUID)
+ final VirtualFile parent = dir.getParent();
+ if (parent == null) {
+ return null;
+ }
+ if (isPendingAdd(project, parent)) {
+ return getRepositoryUUID(project, parent);
+ }
+ } else {
+ return info1.getRepositoryUUID();
+ }
+ } catch (SVNException e) {
+ // go to return default
+ }
+ return null;
+ }
+ }
+
+ public boolean move(VirtualFile file, VirtualFile toDir) throws IOException {
+ File srcFile = getIOFile(file);
+ File dstFile = new File(getIOFile(toDir), file.getName());
+
+ final SvnVcs17 vcs = getVCS(toDir);
+ final SvnVcs17 sourceVcs = getVCS(file);
+ if (vcs == null && sourceVcs == null) return false;
+
+ if (vcs == null) {
+ return false;
+ }
+ if (sourceVcs == null) {
+ return createItem(toDir, file.getName(), file.isDirectory(), true);
+ }
+
+ if (isPendingAdd(vcs.getProject(), toDir)) {
+ myMovedFiles.add(new MovedFileInfo(sourceVcs.getProject(), srcFile, dstFile));
+ return true;
+ }
+ else {
+ final VirtualFile oldParent = file.getParent();
+ myFilesToRefresh.add(oldParent);
+ myFilesToRefresh.add(toDir);
+ return doMove(sourceVcs, srcFile, dstFile);
+ }
+ }
+
+ public boolean rename(VirtualFile file, String newName) throws IOException {
+ File srcFile = getIOFile(file);
+ File dstFile = new File(srcFile.getParentFile(), newName);
+ SvnVcs17 vcs = getVCS(file);
+ if (vcs != null) {
+ myFilesToRefresh.add(file.getParent());
+ return doMove(vcs, srcFile, dstFile);
+ }
+ return false;
+ }
+
+ private boolean doMove(@NotNull SvnVcs17 vcs, final File src, final File dst) {
+ long srcTime = src.lastModified();
+ try {
+ final boolean isUndo = isUndo(vcs);
+ final String list = isUndo ? null : SvnChangelistListener.getCurrentMapping(vcs.getProject(), src);
+
+ final boolean is17 = SvnUtil.is17CopyPart(src);
+ if (is17) {
+ if (for17move(vcs, src, dst, isUndo)) return false;
+ } else {
+ if (for16move(vcs, src, dst, isUndo)) return false;
+ }
+
+ if (! isUndo && list != null) {
+ SvnChangelistListener.putUnderList(vcs.getProject(), list, dst);
+ }
+ dst.setLastModified(srcTime);
+ }
+ catch (SVNException e) {
+ addToMoveExceptions(vcs.getProject(), e);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean for17move(SvnVcs17 vcs, File src, File dst, boolean undo) throws SVNException {
+ if (undo) {
+ final SVNWCClient wcClient = vcs.createWCClient();
+ myUndoingMove = true;
+ wcClient.doRevert(dst, true);
+ wcClient.doRevert(src, true);
+ restoreFromUndoStorage(dst);
+ } else {
+ if (doUsualMove(vcs, src)) return true;
+ final SVNCopyClient copyClient = vcs.createCopyClient();
+ final SVNCopySource svnCopySource = new SVNCopySource(SVNRevision.UNDEFINED, SVNRevision.BASE, src);
+ copyClient.doCopy(new SVNCopySource[]{svnCopySource}, dst, true, false, true);
+ }
+ return false;
+ }
+
+ private boolean doUsualMove(SvnVcs17 vcs, File src) {
+ // if src is not under version control, do usual move.
+ SVNStatus srcStatus = getFileStatus(vcs, src);
+ if (srcStatus == null || SvnVcs17.svnStatusIsUnversioned(srcStatus) ||
+ SvnVcs17.svnStatusIs(srcStatus, SVNStatusType.STATUS_OBSTRUCTED) ||
+ SvnVcs17.svnStatusIs(srcStatus, SVNStatusType.STATUS_MISSING) ||
+ SvnVcs17.svnStatusIs(srcStatus, SVNStatusType.STATUS_EXTERNAL)) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean for16move(SvnVcs17 vcs, File src, File dst, boolean undo) throws SVNException {
+ SVNMoveClient mover = vcs.createMoveClient();
+ if (undo) {
+ myUndoingMove = true;
+ restoreFromUndoStorage(dst);
+ mover.undoMove(src, dst);
+ }
+ else {
+ // if src is not under version control, do usual move.
+ if (doUsualMove(vcs, src)) return true;
+ mover.doMove(src, dst);
+ }
+ return false;
+ }
+
+ private void restoreFromUndoStorage(final File dst) {
+ String normPath = FileUtil.toSystemIndependentName(dst.getPath());
+ for (Iterator> it = myUndoStorageContents.iterator(); it.hasNext();) {
+ Pair e = it.next();
+ final String p = FileUtil.toSystemIndependentName(e.first.getPath());
+ if (p.startsWith(normPath)) {
+ try {
+ FileUtil.rename(e.second, e.first);
+ }
+ catch (IOException ex) {
+ LOG.error(ex);
+ FileUtil.asyncDelete(e.second);
+ }
+ it.remove();
+ }
+ }
+ if (myStorageForUndo != null) {
+ final File[] files = myStorageForUndo.listFiles();
+ if (files == null || files.length == 0) {
+ FileUtil.asyncDelete(myStorageForUndo);
+ myStorageForUndo = null;
+ }
+ }
+ }
+
+
+ public boolean createFile(VirtualFile dir, String name) throws IOException {
+ return createItem(dir, name, false, false);
+ }
+
+ public boolean createDirectory(VirtualFile dir, String name) throws IOException {
+ return createItem(dir, name, true, false);
+ }
+
+ /**
+ * delete file or directory (both 'undo' and 'do' modes)
+ * unversioned: do nothing, return false
+ * obstructed: do nothing, return false
+ * external or wc root: do nothing, return false
+ * missing: do nothing, return false
+ *
+ * versioned: schedule for deletion, return true
+ * added: schedule for deletion (make unversioned), return true
+ * copied, but not scheduled: schedule for deletion, return true
+ * replaced: schedule for deletion, return true
+ *
+ * deleted: do nothing, return true (strange)
+ */
+ public boolean delete(VirtualFile file) throws IOException {
+ SvnVcs17 vcs = getVCS(file);
+ if (vcs != null && SvnUtil.isAdminDirectory(file)) {
+ return true;
+ }
+ File ioFile = getIOFile(file);
+ if (! SvnUtil.isSvnVersioned(ioFile.getParentFile())) {
+ return false;
+ }
+ try {
+ if (SVNWCUtil.isWorkingCopyRoot(ioFile)) {
+ return false;
+ }
+ } catch (SVNException e) {
+ //
+ }
+
+ SVNStatus status = getFileStatus(ioFile);
+
+ if (status == null ||
+ SvnVcs17.svnStatusIsUnversioned(status) ||
+ SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_OBSTRUCTED) ||
+ SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_MISSING) ||
+ SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_EXTERNAL) ||
+ SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_IGNORED)) {
+ return false;
+ } else if (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_DELETED)) {
+ if (isUndo(vcs)) {
+ moveToUndoStorage(file);
+ }
+ return true;
+ }
+ else {
+ if (vcs != null) {
+ if (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_ADDED)) {
+ try {
+ final SVNWCClient wcClient = vcs.createWCClient();
+ wcClient.doRevert(ioFile, false);
+ }
+ catch (SVNException e) {
+ // ignore
+ }
+ }
+ else {
+ myDeletedFiles.putValue(vcs.getProject(), ioFile);
+ // packages deleted from disk should not be deleted from svn (IDEADEV-16066)
+ if (file.isDirectory() || isUndo(vcs)) return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private void moveToUndoStorage(final VirtualFile file) {
+ if (myStorageForUndo == null) {
+ try {
+ myStorageForUndo = FileUtil.createTempDirectory("svnUndoStorage", "");
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ return;
+ }
+ }
+ final File tmpFile = FileUtil.findSequentNonexistentFile(myStorageForUndo, "tmp", "");
+ myUndoStorageContents.add(0, new Pair(new File(file.getPath()), tmpFile));
+ new File(file.getPath()).renameTo(tmpFile);
+ }
+
+ /**
+ * add file or directory:
+ *
+ * parent directory is:
+ * unversioned: do nothing, return false
+ * versioned:
+ * entry is:
+ * null: create entry, schedule for addition
+ * missing: do nothing, return false
+ * deleted, 'do' mode: try to create entry and it schedule for addition if kind is the same, otherwise do nothing, return false.
+ * deleted: 'undo' mode: try to revert non-recursively, if kind is the same, otherwise do nothing, return false.
+ * anything else: return false.
+ */
+ private boolean createItem(VirtualFile dir, String name, boolean directory, final boolean recursive) {
+ SvnVcs17 vcs = getVCS(dir);
+ if (vcs == null) {
+ return false;
+ }
+ if (isUndo(vcs) && SvnUtil.isAdminDirectory(dir, name)) {
+ return false;
+ }
+ File ioDir = getIOFile(dir);
+ boolean pendingAdd = isPendingAdd(vcs.getProject(), dir);
+ if (! SvnUtil.isSvnVersioned(ioDir) && ! pendingAdd) {
+ return false;
+ }
+ SVNWCClient wcClient = vcs.createWCClient();
+ File targetFile = new File(ioDir, name);
+ SVNStatus status = getFileStatus(vcs, targetFile);
+
+ if (status == null || status.getContentsStatus() == SVNStatusType.STATUS_NONE ||
+ status.getContentsStatus() == SVNStatusType.STATUS_UNVERSIONED) {
+ myAddedFiles.putValue(vcs.getProject(), new AddedFileInfo(dir, name, null, recursive));
+ return false;
+ }
+ else if (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_MISSING)) {
+ return false;
+ }
+ else if (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_DELETED)) {
+ SVNNodeKind kind = status.getKind();
+ // kind differs.
+ if (directory && kind != SVNNodeKind.DIR || !directory && kind != SVNNodeKind.FILE) {
+ return false;
+ }
+ try {
+ if (isUndo(vcs)) {
+ wcClient.doRevert(targetFile, false);
+ return true;
+ }
+ myAddedFiles.putValue(vcs.getProject(), new AddedFileInfo(dir, name, null, recursive));
+ return false;
+ }
+ catch (SVNException e) {
+ SVNFileUtil.deleteAll(targetFile, true);
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private boolean isPendingAdd(final Project project, final VirtualFile dir) {
+ final Collection addedFileInfos = myAddedFiles.get(project);
+ for(AddedFileInfo i: addedFileInfos) {
+ if (i.myDir == dir.getParent() && i.myName.equals(dir.getName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void commandStarted(CommandEvent event) {
+ myUndoingMove = false;
+ final Project project = event.getProject();
+ if (project == null) return;
+ commandStarted(project);
+ }
+
+ void commandStarted(final Project project) {
+ myUndoingMove = false;
+ myMoveExceptions.remove(project);
+ }
+
+ public void commandFinished(CommandEvent event) {
+ final Project project = event.getProject();
+ if (project == null) return;
+ commandFinished(project);
+ }
+
+ void commandFinished(final Project project) {
+ checkOverwrites(project);
+ if (myAddedFiles.containsKey(project)) {
+ processAddedFiles(project);
+ }
+ processMovedFiles(project);
+ if (myDeletedFiles.containsKey(project)) {
+ processDeletedFiles(project);
+ }
+
+ final List exceptionList = myMoveExceptions.get(project);
+ if (exceptionList != null && ! exceptionList.isEmpty()) {
+ AbstractVcsHelper.getInstance(project).showErrors(exceptionList, SvnBundle.message("move.files.errors.title"));
+ }
+
+ if (!myFilesToRefresh.isEmpty()) {
+ refreshFiles(project);
+ }
+ }
+
+ private void checkOverwrites(final Project project) {
+ final Collection addedFileInfos = myAddedFiles.get(project);
+ final Collection deletedFiles = myDeletedFiles.get(project);
+ if (addedFileInfos.isEmpty() || deletedFiles.isEmpty()) return;
+ final Iterator iterator = addedFileInfos.iterator();
+ while (iterator.hasNext()) {
+ AddedFileInfo addedFileInfo = iterator.next();
+ final File ioFile = new File(addedFileInfo.myDir.getPath(), addedFileInfo.myName);
+ if (deletedFiles.remove(ioFile)) {
+ iterator.remove();
+ }
+ }
+ }
+
+ private void refreshFiles(final Project project) {
+ final List toRefreshFiles = new ArrayList();
+ final List toRefreshDirs = new ArrayList();
+ for (VirtualFile file : myFilesToRefresh) {
+ if (file == null) continue;
+ if (file.isDirectory()) {
+ toRefreshDirs.add(file);
+ } else {
+ toRefreshFiles.add(file);
+ }
+ }
+ // if refresh asynchronously, local changes would also be notified that they are dirty asynchronously,
+ // and commit could be executed while not all changes are visible
+ final RefreshSession session = RefreshQueue.getInstance().createSession(true, true, new Runnable() {
+ public void run() {
+ if (project.isDisposed()) return;
+ filterOutInvalid(toRefreshFiles);
+ filterOutInvalid(toRefreshDirs);
+
+ final VcsDirtyScopeManager vcsDirtyScopeManager = VcsDirtyScopeManager.getInstance(project);
+ vcsDirtyScopeManager.filesDirty(toRefreshFiles, toRefreshDirs);
+ }
+ });
+ filterOutInvalid(myFilesToRefresh);
+ session.addAllFiles(myFilesToRefresh);
+ session.launch();
+ myFilesToRefresh.clear();
+ }
+
+ private static void filterOutInvalid(final Collection files) {
+ for (Iterator