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.7 1.0 VCS Integration com.intellij.modules.vcs JetBrains - 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 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 iterator = files.iterator(); iterator.hasNext();) { + final VirtualFile file = iterator.next(); + if (! file.isValid() || ! file.exists()) { + LOG.info("Refresh root is not valid: " + file.getPath()); + iterator.remove(); + } + } + } + + private void processAddedFiles(Project project) { + SvnVcs17 vcs = SvnVcs17.getInstance(project); + List addedVFiles = new ArrayList(); + Map copyFromMap = new HashMap(); + final Set recursiveItems = new HashSet(); + fillAddedFiles(project, vcs, addedVFiles, copyFromMap, recursiveItems); + if (addedVFiles.isEmpty()) return; + final VcsShowConfirmationOption.Value value = vcs.getAddConfirmation().getValue(); + if (value != VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY) { + final AbstractVcsHelper vcsHelper = AbstractVcsHelper.getInstance(project); + final Collection filesToProcess = promptAboutAddition(vcs, addedVFiles, value, vcsHelper); + if (filesToProcess != null && !filesToProcess.isEmpty()) { + final List exceptions = new ArrayList(); + runInBackground(project, "Adding files to Subversion", + createAdditionRunnable(project, vcs, copyFromMap, filesToProcess, exceptions)); + if (!exceptions.isEmpty()) { + vcsHelper.showErrors(exceptions, SvnBundle.message("add.files.errors.title")); + } + } + } + } + + private void runInBackground(final Project project, final String name, final Runnable runnable) { + if (ApplicationManager.getApplication().isDispatchThread()) { + ProgressManager.getInstance().runProcessWithProgressSynchronously(runnable, name, false, project); + } else { + runnable.run(); + } + } + + private Runnable createAdditionRunnable(final Project project, + final SvnVcs17 vcs, + final Map copyFromMap, + final Collection filesToProcess, + final List exceptions) { + return new Runnable() { + @Override + public void run() { + SVNWCClient wcClient = vcs.createWCClient(); + final SVNCopyClient copyClient = vcs.createCopyClient(); + for(VirtualFile file: filesToProcess) { + final File ioFile = new File(file.getPath()); + try { + final File copyFrom = copyFromMap.get(file); + if (copyFrom != null) { + try { + new ActionWithTempFile(ioFile) { + protected void executeInternal() throws VcsException { + try { + // not recursive + final SVNCopySource[] copySource = {new SVNCopySource(SVNRevision.WORKING, SVNRevision.WORKING, copyFrom)}; + copyClient.doCopy(copySource, ioFile, false, true, true); + } + catch (SVNException e) { + throw new VcsException(e); + } + } + }.execute(); + } + catch (VcsException e) { + exceptions.add(e); + } + } + else { + wcClient.doAdd(ioFile, true, false, false, true); + } + VcsDirtyScopeManager.getInstance(project).fileDirty(file); + } + catch (SVNException e) { + exceptions.add(new VcsException(e)); + } + } + } + }; + } + + private Collection promptAboutAddition(SvnVcs17 vcs, + List addedVFiles, + VcsShowConfirmationOption.Value value, + AbstractVcsHelper vcsHelper) { + Collection filesToProcess; + if (value == VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY) { + filesToProcess = addedVFiles; + } + else { + final String singleFilePrompt; + if (addedVFiles.size() == 1 && addedVFiles.get(0).isDirectory()) { + singleFilePrompt = SvnBundle.getString("confirmation.text.add.dir"); + } + else { + singleFilePrompt = SvnBundle.getString("confirmation.text.add.file"); + } + filesToProcess = vcsHelper.selectFilesToProcess(addedVFiles, SvnBundle.message("confirmation.title.add.multiple.files"), + null, + SvnBundle.message("confirmation.title.add.file"), singleFilePrompt, + vcs.getAddConfirmation()); + } + return filesToProcess; + } + + private void fillAddedFiles(Project project, + SvnVcs17 vcs, + List addedVFiles, + Map copyFromMap, + Set recursiveItems) { + final Collection addedFileInfos = myAddedFiles.remove(project); + final ChangeListManager changeListManager = ChangeListManager.getInstance(project); + + for (AddedFileInfo addedFileInfo : addedFileInfos) { + final File ioFile = new File(getIOFile(addedFileInfo.myDir), addedFileInfo.myName); + VirtualFile addedFile = addedFileInfo.myDir.findChild(addedFileInfo.myName); + if (addedFile == null) { + addedFile = myLfs.refreshAndFindFileByIoFile(ioFile); + } + if (addedFile != null) { + final SVNStatus fileStatus = getFileStatus(vcs, ioFile); + if (fileStatus == null || ! SvnVcs17.svnStatusIs(fileStatus, SVNStatusType.STATUS_IGNORED)) { + boolean isIgnored = changeListManager.isIgnoredFile(addedFile); + if (!isIgnored) { + addedVFiles.add(addedFile); + copyFromMap.put(addedFile, addedFileInfo.myCopyFrom); + if (addedFileInfo.myRecursive) { + recursiveItems.add(addedFile); + } + } + } + } + } + } + + private void processDeletedFiles(Project project) { + final List deletedFiles = new ArrayList(); + fillDeletedFiles(project, deletedFiles); + if (deletedFiles.isEmpty() || myUndoingMove) return; + SvnVcs17 vcs = SvnVcs17.getInstance(project); + final VcsShowConfirmationOption.Value value = vcs.getDeleteConfirmation().getValue(); + if (value != VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY) { + final AbstractVcsHelper vcsHelper = AbstractVcsHelper.getInstance(project); + Collection filesToProcess; + filesToProcess = promptAboutDeletion(deletedFiles, vcs, value, vcsHelper); + if (filesToProcess != null && !filesToProcess.isEmpty()) { + List exceptions = new ArrayList(); + runInBackground(project, "Deleting files from Subversion", createDeleteRunnable(project, vcs, filesToProcess, exceptions)); + if (!exceptions.isEmpty()) { + vcsHelper.showErrors(exceptions, SvnBundle.message("delete.files.errors.title")); + } + } + for (FilePath file : deletedFiles) { + final FilePath parent = file.getParentPath(); + if (parent != null) { + myFilesToRefresh.add(parent.getVirtualFile()); + } + } + if (filesToProcess != null) { + deletedFiles.removeAll(filesToProcess); + } + for (FilePath file : deletedFiles) { + FileUtil.delete(file.getIOFile()); + } + } + } + + private Runnable createDeleteRunnable(final Project project, + final SvnVcs17 vcs, + final Collection filesToProcess, + final List exceptions) { + return new Runnable() { + public void run() { + SVNWCClient wcClient = vcs.createWCClient(); + for(FilePath file: filesToProcess) { + VirtualFile vFile = file.getVirtualFile(); // for deleted directories + File ioFile = new File(file.getPath()); + try { + wcClient.doDelete(ioFile, true, false); + if (vFile != null && vFile.isValid() && vFile.isDirectory()) { + vFile.refresh(true, true); + VcsDirtyScopeManager.getInstance(project).dirDirtyRecursively(vFile); + } + else { + VcsDirtyScopeManager.getInstance(project).fileDirty(file); + } + } + catch (SVNException e) { + exceptions.add(new VcsException(e)); + } + } + } + }; + } + + private Collection promptAboutDeletion(List deletedFiles, + SvnVcs17 vcs, + VcsShowConfirmationOption.Value value, + AbstractVcsHelper vcsHelper) { + Collection filesToProcess; + if (value == VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY) { + filesToProcess = new ArrayList(deletedFiles); + } + else { + + final String singleFilePrompt; + if (deletedFiles.size() == 1 && deletedFiles.get(0).isDirectory()) { + singleFilePrompt = SvnBundle.getString("confirmation.text.delete.dir"); + } + else { + singleFilePrompt = SvnBundle.getString("confirmation.text.delete.file"); + } + final Collection files = vcsHelper + .selectFilePathsToProcess(deletedFiles, SvnBundle.message("confirmation.title.delete.multiple.files"), null, + SvnBundle.message("confirmation.title.delete.file"), singleFilePrompt, vcs.getDeleteConfirmation()); + filesToProcess = files == null ? null : new ArrayList(files); + } + return filesToProcess; + } + + private void fillDeletedFiles(Project project, List deletedFiles) { + final Collection files = myDeletedFiles.remove(project); + for (File file : files) { + final FilePath filePath = VcsContextFactory.SERVICE.getInstance().createFilePathOn(file); + deletedFiles.add(filePath); + } + } + + private void processMovedFiles(final Project project) { + if (myMovedFiles.isEmpty()) return; + + final Runnable runnable = new Runnable() { + public void run() { + for (Iterator iterator = myMovedFiles.iterator(); iterator.hasNext();) { + MovedFileInfo movedFileInfo = iterator.next(); + if (movedFileInfo.myProject == project) { + doMove(SvnVcs17.getInstance(project), movedFileInfo.mySrc, movedFileInfo.myDst); + iterator.remove(); + } + } + } + }; + runInBackground(project, "Moving files in Subversion", runnable); + } + + @Nullable + private static SvnVcs17 getVCS(VirtualFile file) { + Project[] projects = ProjectManager.getInstance().getOpenProjects(); + for (Project project : projects) { + AbstractVcs vcs = ProjectLevelVcsManager.getInstance(project).getVcsFor(file); + if (vcs instanceof SvnVcs17) { + return (SvnVcs17)vcs; + } + } + return null; + } + + + private static File getIOFile(VirtualFile vf) { + return new File(vf.getPath()).getAbsoluteFile(); + } + + @Nullable + private static SVNStatus getFileStatus(File file) { + final SVNClientManager clientManager = SVNClientManager.newInstance(); + try { + SVNStatusClient stClient = clientManager.getStatusClient(); + return getFileStatus(file, stClient); + } + finally { + clientManager.dispose(); + } + } + + @Nullable + private static SVNStatus getFileStatus(SvnVcs17 vcs, File file) { + SVNStatusClient stClient = vcs.createStatusClient(); + return getFileStatus(file, stClient); + } + + @Nullable + private static SVNStatus getFileStatus(final File file, final SVNStatusClient stClient) { + try { + return stClient.doStatus(file, false); + } + catch (SVNException e) { + return null; + } + } + + private static boolean isUndoOrRedo(@NotNull final Project project) { + final UndoManager undoManager = UndoManager.getInstance(project); + return undoManager.isUndoInProgress() || undoManager.isRedoInProgress(); + } + + private static boolean isUndo(SvnVcs17 vcs) { + if (vcs == null || vcs.getProject() == null) { + return false; + } + Project p = vcs.getProject(); + return UndoManager.getInstance(p).isUndoInProgress(); + } + + public void afterDone(final ThrowableConsumer invoker) { + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnFileSystemListenerWrapper.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnFileSystemListenerWrapper.java new file mode 100644 index 000000000000..6da82533cd83 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnFileSystemListenerWrapper.java @@ -0,0 +1,187 @@ +/* + * 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.Disposable; +import com.intellij.openapi.command.CommandEvent; +import com.intellij.openapi.command.CommandListener; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectLocator; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.LocalFileOperationsHandler; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.ThrowableConsumer; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class SvnFileSystemListenerWrapper { + private final LocalFileOperationsHandler myProxy; + private final CommandListener myListener; + + public SvnFileSystemListenerWrapper(final SvnFileSystemListener delegate) { + final MyCommandListener listener = new MyCommandListener(delegate); + myListener = listener; + final MyStorage storage = new MyStorage(listener); + myProxy = (LocalFileOperationsHandler) Proxy.newProxyInstance(LocalFileOperationsHandler.class.getClassLoader(), + new Class[]{LocalFileOperationsHandler.class}, new MyInvoker(storage, delegate)); + } + + public void registerSelf() { + LocalFileSystem.getInstance().registerAuxiliaryFileOperationsHandler(myProxy); + CommandProcessor.getInstance().addCommandListener(myListener); + } + + public void unregisterSelf() { + LocalFileSystem.getInstance().unregisterAuxiliaryFileOperationsHandler(myProxy); + CommandProcessor.getInstance().removeCommandListener(myListener); + } + + private static class MyCommandListener implements CommandListener, MyMarker { + private volatile boolean myInCommand; + private final SvnFileSystemListener myDelegate; + + public MyCommandListener(final SvnFileSystemListener delegate) { + myDelegate = delegate; + } + + public void start(final Project project) { + if (!myInCommand && project != null) { + myDelegate.commandStarted(project); + } + } + + public void finish(final Project project) { + if (! myInCommand && project != null) { + myDelegate.commandFinished(project); + } + } + + public void commandStarted(CommandEvent event) { + myInCommand = true; + myDelegate.commandStarted(event); + } + + public void beforeCommandFinished(CommandEvent event) { + myDelegate.beforeCommandFinished(event); + } + + public void commandFinished(CommandEvent event) { + myInCommand = false; + myDelegate.commandFinished(event); + } + + public void undoTransparentActionStarted() { + myDelegate.undoTransparentActionStarted(); + } + + public void undoTransparentActionFinished() { + myDelegate.undoTransparentActionFinished(); + } + } + + private interface MyMarker { + void start(final Project project); + void finish(final Project project); + } + + private static class MyStorage implements InvocationHandler { + private final MyMarker myMarker; + private final Map> myStarted; + + private MyStorage(final MyMarker marker) { + myMarker = marker; + myStarted = new HashMap>(); + } + + @Nullable + private static Project getProject(Object[] args) { + for (Object arg : args) { + if (arg instanceof VirtualFile) { + return ProjectLocator.getInstance().guessProjectForFile((VirtualFile) arg); + } + } + return null; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + final Project project = getProject(args); + if (project != null) { + final Pair pair = myStarted.get(project); + if (pair != null && method.getName().equals(pair.getFirst()) && Arrays.equals(args, pair.getSecond())) { + myMarker.finish(project); + } + } + //dont return null for auto unboxing to not face NPE + return "boolean".equals(method.getReturnType().getName()) ? Boolean.TRUE : null; + } + + private void register(final Method method, final Object[] args) { + final Object[] newArr = new Object[args.length]; + System.arraycopy(args, 0, newArr, 0, args.length); + final Project project = getProject(args); + if (project != null) { + myMarker.start(project); + myStarted.put(project, new Pair(method.getName(), newArr)); + Disposer.register(project, new Disposable() { + public void dispose() { + myStarted.remove(project); + } + }); + } + } + } + + private static class MyInvoker implements InvocationHandler { + private final Object myDelegate; + private final MyStorage myParent; + private final LocalFileOperationsHandler myParentProxy; + + private MyInvoker(final MyStorage parent, Object delegate) { + myParent = parent; + myDelegate = delegate; + myParentProxy = (LocalFileOperationsHandler) Proxy.newProxyInstance(LocalFileOperationsHandler.class.getClassLoader(), + new Class[]{LocalFileOperationsHandler.class}, myParent); + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ("afterDone".equals(method.getName()) && args.length == 1) { + ((ThrowableConsumer)args[0]).consume(myParentProxy); + return null; + } + + if (LocalFileOperationsHandler.class.equals(method.getDeclaringClass())) { + myParent.register(method, args); + } + if ("equals".equals(method.getName())) { + return args[0].equals(this); + } + else if ("hashCode".equals(method.getName())) { + return 1; + } + return method.invoke(myDelegate, args); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnFileUrlMapping17.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnFileUrlMapping17.java new file mode 100644 index 000000000000..75240d47c457 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnFileUrlMapping17.java @@ -0,0 +1,49 @@ +/* + * 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.AbstractVcs; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.Nullable; +import org.tmatesoft.svn.core.SVNURL; + +import java.io.File; +import java.util.List; + +public interface SvnFileUrlMapping17 extends AbstractVcs.RootsConvertor { + @Nullable + SVNURL getUrlForFile(final File file); + + @Nullable + String getLocalPath(final String url); + + @Nullable + RootUrlInfo getWcRootForUrl(final String url); + + List getAllWcInfos(); + + @Nullable + RootUrlInfo getWcRootForFilePath(final File file); + + /** + * @return true if roots under SVN set by the user differs from real WC roots (are under specified roots) + */ + boolean rootsDiffer(); + + VirtualFile[] getNotFilteredRoots(); + + boolean isEmpty(); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnFileUrlMappingImpl17.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnFileUrlMappingImpl17.java new file mode 100644 index 000000000000..e3fb9e9868cc --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnFileUrlMappingImpl17.java @@ -0,0 +1,578 @@ +/* + * 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.ApplicationManager; +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.ProjectComponent; +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.DumbAwareRunnable; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Getter; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.ObjectsConvertor; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.ThreadLocalDefendedInvoker; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.changes.InvokeAfterUpdateMode; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +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.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; +import com.intellij.util.containers.Convertor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; +import org.tmatesoft.svn.core.internal.util.SVNURLUtil; +import org.tmatesoft.svn.core.wc.*; + +import java.io.File; +import java.util.*; + +@State( + name = "SvnFileUrlMappingImpl17", + storages = { + @Storage( + file = "$WORKSPACE_FILE$" + )} +) +class SvnFileUrlMappingImpl17 implements SvnFileUrlMapping17, PersistentStateComponent, ProjectComponent { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnFileUrlMappingImpl17"); + + private final SvnCompatibilityChecker myChecker; + private final Object myMonitor = new Object(); + // strictly: what real roots are under what vcs mappings + private final SvnMapping myMapping; + // grouped; if there are several mappings one under another, will return the upmost + private final SvnMapping myMoreRealMapping; + private final MyRootsHelper myHelper; + private final Project myProject; + private final NestedCopiesSink myTempSink; + private boolean myInitialized; + + private static class MyRootsHelper extends ThreadLocalDefendedInvoker { + private final ProjectLevelVcsManager myPlVcsManager; + + private MyRootsHelper(final ProjectLevelVcsManager vcsManager) { + myPlVcsManager = vcsManager; + } + + protected VirtualFile[] execute(Project project) { + return myPlVcsManager.getRootsUnderVcs(SvnVcs17.getInstance(project)); + } + } + + public static SvnFileUrlMappingImpl17 getInstance(final Project project) { + return PeriodicalTasksCloser.getInstance().safeGetComponent(project, SvnFileUrlMappingImpl17.class); + } + + private SvnFileUrlMappingImpl17(final Project project, final ProjectLevelVcsManager vcsManager) { + myProject = project; + myMapping = new SvnMapping(); + myMoreRealMapping = new SvnMapping(); + myHelper = new MyRootsHelper(vcsManager); + myChecker = new SvnCompatibilityChecker(project); + myTempSink = new NestedCopiesSink(); + } + + @Nullable + public SVNURL getUrlForFile(final File file) { + final RootUrlInfo rootUrlInfo = getWcRootForFilePath(file); + if (rootUrlInfo == null) { + return null; + } + + final String absolutePath = file.getAbsolutePath(); + final String rootAbsPath = rootUrlInfo.getIoFile().getAbsolutePath(); + if (absolutePath.length() < rootAbsPath.length()) { + // remove last separator from etalon name + if (absolutePath.equals(rootAbsPath.substring(0, rootAbsPath.length() - 1))) { + return rootUrlInfo.getAbsoluteUrlAsUrl(); + } + return null; + } + final String relativePath = absolutePath.substring(rootAbsPath.length()); + try { + return rootUrlInfo.getAbsoluteUrlAsUrl().appendPath(FileUtil.toSystemIndependentName(relativePath), true); + } + catch (SVNException e) { + LOG.info(e); + return null; + } + } + + @Nullable + public String getLocalPath(final String url) { + synchronized (myMonitor) { + final String rootUrl = getUrlRootForUrl(url); + if (rootUrl == null) { + return null; + } + final RootUrlInfo parentInfo = myMapping.byUrl(rootUrl); + if (parentInfo == null) { + return null; + } + + return fileByUrl(parentInfo.getIoFile().getAbsolutePath(), rootUrl, url).getAbsolutePath(); + } + } + + public static File fileByUrl(final String parentPath, final String parentUrl, final String childUrl) { + return new File(parentPath, childUrl.substring(parentUrl.length())); + } + + @Nullable + public RootUrlInfo getWcRootForFilePath(final File file) { + synchronized (myMonitor) { + final String root = getRootForPath(file); + if (root == null) { + return null; + } + + return myMapping.byFile(root); + } + } + + public boolean rootsDiffer() { + synchronized (myMonitor) { + return myMapping.isRootsDifferFromSettings(); + } + } + + @Nullable + public RootUrlInfo getWcRootForUrl(final String url) { + synchronized (myMonitor) { + final String rootUrl = getUrlRootForUrl(url); + if (rootUrl == null) { + return null; + } + + final RootUrlInfo result = myMapping.byUrl(rootUrl); + if (result == null) { + LOG.info("Inconsistent maps for url:" + url + " found root url: " + rootUrl); + return null; + } + return result; + } + } + + /** + * Returns real working copies roots - if there is -> Subversion setting, + * and there is one working copy, will return one root + */ + public List getAllWcInfos() { + synchronized (myMonitor) { + // a copy is created inside + return myMoreRealMapping.getAllCopies(); + } + } + + public List convertRoots(final List result) { + if (ThreadLocalDefendedInvoker.isInside()) return result; + + synchronized (myMonitor) { + final List cachedRoots = myMapping.getUnderVcsRoots(); + final List lonelyRoots = myMapping.getLonelyRoots(); + if (! lonelyRoots.isEmpty()) { + myChecker.reportNoRoots(lonelyRoots); + } + if (cachedRoots.isEmpty()) { + // todo +- + return result; + } + return cachedRoots; + } + } + + public void acceptNestedData(final Set set) { + myTempSink.add(set); + } + + private boolean init() { + synchronized (myMonitor) { + final boolean result = myInitialized; + myInitialized = true; + return result; + } + } + + public void realRefresh() { + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final VirtualFile[] roots = myHelper.executeDefended(myProject); + + final CopiesApplier copiesApplier = new CopiesApplier(); + final CopiesDetector copiesDetector = new CopiesDetector(vcs, copiesApplier, new Getter() { + public NestedCopiesData get() { + return myTempSink.receive(); + } + }); + // do not send additional request for nested copies when in init state + copiesDetector.detectCopyRoots(roots, init()); + } + + private class CopiesApplier { + public void apply(final SvnVcs17 vcs, final List roots, final List lonelyRoots) { + final SvnMapping mapping = new SvnMapping(); + mapping.addAll(roots); + mapping.reportLonelyRoots(lonelyRoots); + + final SvnMapping groupedMapping = new SvnMapping(); + final List filtered = new ArrayList(); + ForNestedRootChecker.filterOutSuperfluousChildren(vcs, roots, filtered); + + groupedMapping.addAll(filtered); + + ApplicationManager.getApplication().runReadAction(new Runnable() { + @Override + public void run() { + synchronized (myMonitor) { + myMapping.copyFrom(mapping); + myMoreRealMapping.copyFrom(groupedMapping); + } + } + }); + myProject.getMessageBus().syncPublisher(SvnVcs17.ROOTS_RELOADED).run(); + } + } + + private static class CopiesDetector { + private final SvnVcs17 myVcs; + private final CopiesApplier myApplier; + private final List myLonelyRoots; + private final List myTopRoots; + private final RepositoryRoots myRepositoryRoots; + private final Getter myGate; + + private CopiesDetector(final SvnVcs17 vcs, final CopiesApplier applier, final Getter gate) { + myVcs = vcs; + myApplier = applier; + myGate = gate; + myTopRoots = new ArrayList(); + myLonelyRoots = new ArrayList(); + myRepositoryRoots = new RepositoryRoots(myVcs); + } + + public void detectCopyRoots(final VirtualFile[] roots, final boolean clearState) { + final Getter cancelGetter = new Getter() { + public Boolean get() { + return myVcs.getProject().isDisposed(); + } + }; + + for (final VirtualFile vcsRoot : roots) { + final List foundRoots = ForNestedRootChecker.getAllNestedWorkingCopies(vcsRoot, myVcs, false, cancelGetter); + if (foundRoots.isEmpty()) { + myLonelyRoots.add(vcsRoot); + } + // filter out bad(?) items + for (Real foundRoot : foundRoots) { + final SVNURL repoRoot = foundRoot.getInfo().getRepositoryRootURL(); + if (repoRoot == null) { + LOG.info("Error: cannot find repository URL for versioned folder: " + foundRoot.getFile().getPath()); + } else { + myRepositoryRoots.register(repoRoot); + myTopRoots.add(new RootUrlInfo(repoRoot, foundRoot.getInfo().getURL(), + SvnFormatSelector.getWorkingCopyFormat(new File(foundRoot.getFile().getPath())), foundRoot.getFile(), vcsRoot)); + } + } + } + + if (! SvnConfiguration17.getInstance(myVcs.getProject()).DETECT_NESTED_COPIES) { + myApplier.apply(myVcs, myTopRoots, myLonelyRoots); + } else { + addNestedRoots(clearState); + } + } + + private void addNestedRoots(final boolean clearState) { + final List basicVfRoots = ObjectsConvertor.convert(myTopRoots, new Convertor() { + public VirtualFile convert(final RootUrlInfo real) { + return real.getVirtualFile(); + } + }); + + final ChangeListManager clManager = ChangeListManager.getInstance(myVcs.getProject()); + + if (clearState) { + // clear what was reported before (could be for currently-not-existing roots) + myGate.get(); + } + clManager.invokeAfterUpdate(new Runnable() { + public void run() { + final List nestedRoots = new ArrayList(); + + final NestedCopiesData data = myGate.get(); + for (NestedCopiesBuilder.MyPointInfo info : data.getSet()) { + if (NestedCopyType.external.equals(info.getType()) || NestedCopyType.switched.equals(info.getType())) { + final File infoFile = new File(info.getFile().getPath()); + boolean copyFound = false; + for (RootUrlInfo topRoot : myTopRoots) { + if (topRoot.getIoFile().equals(infoFile)) { + topRoot.setType(info.getType()); + copyFound = true; + break; + } + } + if (copyFound) { + continue; + } + try { + final SVNStatus svnStatus = SvnUtil.getStatus(myVcs, infoFile); + if (svnStatus.getURL() == null) continue; + info.setUrl(svnStatus.getURL()); + info.setFormat(WorkingCopyFormat.getInstance(svnStatus.getWorkingCopyFormat())); + } + catch (Exception e) { + continue; + } + } + for (RootUrlInfo topRoot : myTopRoots) { + if (VfsUtil.isAncestor(topRoot.getVirtualFile(), info.getFile(), true)) { + final SVNURL repoRoot = myRepositoryRoots.ask(info.getUrl()); + if (repoRoot != null) { + final RootUrlInfo rootInfo = new RootUrlInfo(repoRoot, info.getUrl(), info.getFormat(), info.getFile(), topRoot.getRoot()); + rootInfo.setType(info.getType()); + nestedRoots.add(rootInfo); + } + break; + } + } + } + // check those top roots which ARE externals, but that was not detected due to they itself were the status request target + //new SvnNestedTypeRechecker(myVcs.getProject(), myTopRoots).run(); + + myTopRoots.addAll(nestedRoots); + myApplier.apply(myVcs, myTopRoots, myLonelyRoots); + } + }, InvokeAfterUpdateMode.SILENT_CALLBACK_POOLED, null, new Consumer() { + public void consume(VcsDirtyScopeManager vcsDirtyScopeManager) { + if (clearState) { + vcsDirtyScopeManager.filesDirty(null, basicVfRoots); + } + } + }, null); + } + } + + private static SVNStatus getExternalItemStatus(final SvnVcs17 vcs, final File file) { + final SVNStatusClient statusClient = vcs.createStatusClient(); + try { + if (file.isDirectory()) { + return statusClient.doStatus(file, false); + } else { + final File parent = file.getParentFile(); + if (parent != null) { + statusClient.setFilesProvider(new ISVNStatusFileProvider() { + public Map getChildrenFiles(File parent) { + return Collections.singletonMap(file.getAbsolutePath(), file); + } + }); + final Ref refStatus = new Ref(); + statusClient.doStatus(parent, SVNRevision.WORKING, SVNDepth.FILES, false, true, false, false, new ISVNStatusHandler() { + public void handleStatus(final SVNStatus status) throws SVNException { + if (file.equals(status.getFile())) { + refStatus.set(status); + } + } + }, null); + return refStatus.get(); + } + } + } + catch (SVNException e) { + // + } + return null; + } + + private static class RepositoryRoots { + private final SvnVcs17 myVcs; + private final Set myRoots; + + private RepositoryRoots(final SvnVcs17 vcs) { + myVcs = vcs; + myRoots = new HashSet(); + } + + public void register(final SVNURL url) { + myRoots.add(url); + } + + public SVNURL ask(final SVNURL url) { + for (SVNURL root : myRoots) { + if (root.equals(SVNURLUtil.getCommonURLAncestor(root, url))) { + return root; + } + } + final SVNURL newUrl = SvnUtil.getRepositoryRoot(myVcs, url); + if (newUrl != null) { + myRoots.add(newUrl); + return newUrl; + } + return null; + } + } + + private static class RepoInfo { + private final SVNURL myUrl; + private final boolean myRepoSupportsMergeinfo; + + private RepoInfo(final SVNURL url, final boolean repoSupportsMergeinfo) { + myRepoSupportsMergeinfo = repoSupportsMergeinfo; + myUrl = url; + } + + public boolean isRepoSupportsMergeinfo() { + return myRepoSupportsMergeinfo; + } + + public SVNURL getUrl() { + return myUrl; + } + } + + @Nullable + public String getUrlRootForUrl(final String currentUrl) { + for (String url : myMapping.getUrls()) { + if (SVNPathUtil.isAncestor(url, currentUrl)) { + return url; + } + } + return null; + } + + @Nullable + public String getRootForPath(final File currentPath) { + String convertedPath = currentPath.getAbsolutePath(); + convertedPath = (currentPath.isDirectory() && (! convertedPath.endsWith(File.separator))) ? convertedPath + File.separator : + convertedPath; + synchronized (myMonitor) { + return myMapping.getRootForPath(convertedPath); + } + } + + public VirtualFile[] getNotFilteredRoots() { + return myHelper.executeDefended(myProject); + } + + public boolean isEmpty() { + synchronized (myMonitor) { + return myMapping.isEmpty(); + } + } + + public SvnMappingSavedPart getState() { + final SvnMappingSavedPart result = new SvnMappingSavedPart(); + + final SvnMapping mapping = new SvnMapping(); + final SvnMapping realMapping = new SvnMapping(); + synchronized (myMonitor) { + mapping.copyFrom(myMapping); + realMapping.copyFrom(myMoreRealMapping); + } + + for (RootUrlInfo info : mapping.getAllCopies()) { + result.add(convert(info)); + } + for (RootUrlInfo info : realMapping.getAllCopies()) { + result.addReal(convert(info)); + } + return result; + } + + private SvnCopyRootSimple convert(final RootUrlInfo info) { + final SvnCopyRootSimple copy = new SvnCopyRootSimple(); + copy.myVcsRoot = FileUtil.toSystemDependentName(info.getRoot().getPath()); + copy.myCopyRoot = info.getIoFile().getAbsolutePath(); + return copy; + } + + public void loadState(final SvnMappingSavedPart state) { + ((ProjectLevelVcsManagerImpl) ProjectLevelVcsManager.getInstance(myProject)).addInitializationRequest( + VcsInitObject.AFTER_COMMON, new DumbAwareRunnable() { + public void run() { + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + final SvnMapping mapping = new SvnMapping(); + final SvnMapping realMapping = new SvnMapping(); + try { + fillMapping(mapping, state.getMappingRoots()); + fillMapping(realMapping, state.getMoreRealMappingRoots()); + } catch (ProcessCanceledException e) { + throw e; + } catch (Throwable t) { + LOG.info(t); + return; + } + + synchronized (myMonitor) { + myMapping.copyFrom(mapping); + myMoreRealMapping.copyFrom(realMapping); + } + } + }); + } + }); + } + + private void fillMapping(final SvnMapping mapping, final List list) { + final LocalFileSystem lfs = LocalFileSystem.getInstance(); + + for (SvnCopyRootSimple simple : list) { + final VirtualFile copyRoot = lfs.findFileByIoFile(new File(simple.myCopyRoot)); + final VirtualFile vcsRoot = lfs.findFileByIoFile(new File(simple.myVcsRoot)); + + if (copyRoot == null || vcsRoot == null) continue; + + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final SVNInfo svnInfo = vcs.getInfo(copyRoot); + if ((svnInfo == null) || (svnInfo.getRepositoryRootURL() == null)) continue; + + final RootUrlInfo info = new RootUrlInfo(svnInfo.getRepositoryRootURL(), svnInfo.getURL(), + SvnFormatSelector.getWorkingCopyFormat(svnInfo.getFile()), copyRoot, vcsRoot); + mapping.add(info); + } + } + + public void projectOpened() { + } + + public void projectClosed() { + } + + @NotNull + public String getComponentName() { + return "SvnFileUrlMappingImpl17"; + } + + public void initComponent() { + } + + public void disposeComponent() { + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnFormatSelector.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnFormatSelector.java new file mode 100644 index 000000000000..b86802e1dacb --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnFormatSelector.java @@ -0,0 +1,165 @@ +/* + * 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.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.util.WaitForProgressToShow; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.dialogs.UpgradeFormatDialog; +import org.tmatesoft.svn.core.SVNErrorCode; +import org.tmatesoft.svn.core.SVNErrorMessage; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.internal.wc.admin.ISVNAdminAreaFactorySelector; +import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaFactory; +import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration; +import org.tmatesoft.svn.core.wc2.SvnOperationFactory; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; + +public class SvnFormatSelector implements ISVNAdminAreaFactorySelector { + public Collection getEnabledFactories(File path, Collection factories, boolean writeAccess) throws SVNException { + if (ApplicationManager.getApplication().isUnitTestMode()) { + return factories; + } + + if (! writeAccess) { + return factories; + } + + final WorkingCopyFormat format = getWorkingCopyFormat(path); + Collection result = format2Factories(format, factories); + + if (result == null) { + final WorkingCopyFormat presetFormat = SvnWorkingCopyFormatHolder.getPresetFormat(); + if (presetFormat != null) { + result = format2Factories(presetFormat, factories); + } + } + + if (result == null) { + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.WC_NOT_DIRECTORY)); + } + return result; + } + + @Nullable + static Collection format2Factories(final WorkingCopyFormat format, final Collection factories) { + if (WorkingCopyFormat.ONE_DOT_SIX.equals(format)) { + return factories; + } else if (WorkingCopyFormat.ONE_DOT_FIVE.equals(format)) { + return factoriesFor15(factories); + } else if (WorkingCopyFormat.ONE_DOT_FOUR.equals(format)) { + return factoriesFor14(factories); + } else if (WorkingCopyFormat.ONE_DOT_THREE.equals(format)) { + return factoriesFor13(factories); + } + return null; + } + + private static Collection factoriesFor13(final Collection factories) { + for (Iterator iterator = factories.iterator(); iterator.hasNext();) { + final SVNAdminAreaFactory factory = (SVNAdminAreaFactory) iterator.next(); + final int supportedVersion = factory.getSupportedVersion(); + if (WorkingCopyFormat.ONE_DOT_THREE.getFormat() == supportedVersion) { + return Collections.singletonList(factory); + } + } + return Collections.emptyList(); + } + + private static Collection factoriesFor14(final Collection factories) { + final Collection result = new ArrayList(2); + for (Iterator iterator = factories.iterator(); iterator.hasNext();) { + final SVNAdminAreaFactory factory = (SVNAdminAreaFactory) iterator.next(); + final int supportedVersion = factory.getSupportedVersion(); + if ((WorkingCopyFormat.ONE_DOT_FOUR.getFormat() == supportedVersion) || + (WorkingCopyFormat.ONE_DOT_THREE.getFormat() == supportedVersion)) { + result.add(factory); + } + } + return result; + } + + private static Collection factoriesFor15(final Collection factories) { + final Collection result = new ArrayList(2); + for (Iterator iterator = factories.iterator(); iterator.hasNext();) { + final SVNAdminAreaFactory factory = (SVNAdminAreaFactory) iterator.next(); + final int supportedVersion = factory.getSupportedVersion(); + if ((WorkingCopyFormat.ONE_DOT_FOUR.getFormat() == supportedVersion) || + (WorkingCopyFormat.ONE_DOT_THREE.getFormat() == supportedVersion) || + (WorkingCopyFormat.ONE_DOT_FIVE.getFormat() == supportedVersion)) { + result.add(factory); + } + } + return result; + } + + public static String showUpgradeDialog(final File path, final Project project, final boolean display13format, final String mode, + @NotNull final Ref wasOk) { + assert ! ApplicationManager.getApplication().isUnitTestMode(); + final String[] newMode = new String[] {mode}; + WaitForProgressToShow.runOrInvokeAndWaitAboveProgress(new Runnable() { + public void run() { + wasOk.set(displayUpgradeDialog(project, path, display13format, newMode)); + } + }); + ApplicationManager.getApplication().getMessageBus().syncPublisher(SvnVcs17.WC_CONVERTED).run(); + return newMode[0]; + } + + public static WorkingCopyFormat getWorkingCopyFormat(final File path) { + try { + final SvnWcGeneration svnWcGeneration = SvnOperationFactory.detectWcGeneration(path, true); + if (SvnWcGeneration.V17.equals(svnWcGeneration)) return WorkingCopyFormat.ONE_DOT_SEVEN; + } + catch (SVNException e) { + // + } + int format = 0; + // it is enough to check parent and this. + try { + format = SVNAdminAreaFactory.checkWC(path, false); + } catch (SVNException e) { + // + } + try { + if (format == 0 && path.getParentFile() != null) { + format = SVNAdminAreaFactory.checkWC(path.getParentFile(), false); + } + } catch (SVNException e) { + // + } + + return WorkingCopyFormat.getInstance(format); + } + + private static boolean displayUpgradeDialog(Project project, File path, final boolean dispay13format, String[] newMode) { + UpgradeFormatDialog dialog = new UpgradeFormatDialog(project, path, false); + dialog.setData(dispay13format, newMode[0]); + dialog.show(); + if (dialog.isOK()) { + newMode[0] = dialog.getUpgradeMode(); + } + return dialog.isOK(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnHttpAuthMethodsDefaultChecker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnHttpAuthMethodsDefaultChecker.java new file mode 100644 index 000000000000..4ced2158717c --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnHttpAuthMethodsDefaultChecker.java @@ -0,0 +1,28 @@ +/* + * 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 SvnHttpAuthMethodsDefaultChecker { + private static final String AUTH_METHODS_PROPERTY = "svnkit.http.methods"; + private static final String OLD_AUTH_METHODS_PROPERTY = "javasvn.http.methods"; + + public static void check() { + final String priorities = System.getProperty(AUTH_METHODS_PROPERTY, System.getProperty(OLD_AUTH_METHODS_PROPERTY)); + if (priorities == null) { + System.setProperty(AUTH_METHODS_PROPERTY, "Basic,Digest,NTLM"); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnLoadedBrachesStorage17.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnLoadedBrachesStorage17.java new file mode 100644 index 000000000000..2bec1bfb57c8 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnLoadedBrachesStorage17.java @@ -0,0 +1,152 @@ +/* + * 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.application.PathManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.persistent.SmallMapSerializer; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.io.DataExternalizer; +import com.intellij.util.io.EnumeratorStringDescriptor; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.branchConfig.InfoStorage; +import org.jetbrains.idea.svn17.branchConfig.SvnBranchConfigurationNew; +import org.jetbrains.idea.svn17.integrate.SvnBranchItem; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.File; +import java.io.IOException; +import java.util.*; + +/** + * Created by IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 8/24/11 + * Time: 1:21 PM + */ +public class SvnLoadedBrachesStorage17 { + private final Object myLock; + private SmallMapSerializer>> myState; + private final File myFile; + private final Project myProject; + + public SvnLoadedBrachesStorage17(final Project project) { + myProject = project; + final File vcsFile = new File(PathManager.getSystemPath(), "vcs"); + File file = new File(vcsFile, "svn_branches17"); + file.mkdirs(); + myFile = new File(file, project.getLocationHash()); + myLock = new Object(); + } + + @Nullable + public Collection get(final String url) { + synchronized (myLock) { + if (myState == null) return null; + Map> map = myState.get(""); + return map == null ? null : map.get(SvnBranchConfigurationNew.ensureEndSlash(url)); + } + } + + public void put(final String url, final Collection items) { + synchronized (myLock) { + if (myState == null) return; + Map> map = myState.get(""); + if (map == null) { + map = new HashMap>(); + myState.put("", map); + } + map.put(url, items); + } + } + + public void activate() { + synchronized (myLock) { + myState = new SmallMapSerializer>>(myFile, new EnumeratorStringDescriptor(), createExternalizer()); + } + } + + + public void deactivate() { + SvnBranchConfigurationManager17 manager = SvnBranchConfigurationManager17.getInstance(myProject); + Map mapCopy = manager.getSvnBranchConfigManager().getMapCopy(); + for (Map.Entry entry : mapCopy.entrySet()) { + Map>> branchMap = entry.getValue().getBranchMap(); + for (Map.Entry>> storageEntry : branchMap.entrySet()) { + put(storageEntry.getKey(), storageEntry.getValue().getValue()); + } + } + synchronized (myLock) { + myState.force(); + myState = null; + } + } + + private DataExternalizer>> createExternalizer() { + return new DataExternalizer>>() { + @Override + public void save(DataOutput out, Map> value) throws IOException { + out.writeInt(value.size()); + ArrayList keys = new ArrayList(value.keySet()); + Collections.sort(keys); + for (String key : keys) { + out.writeUTF(key); + List list = new ArrayList(value.get(key)); + Collections.sort(list, SerializationComparator.getInstance()); + out.writeInt(list.size()); + for (SvnBranchItem item : list) { + out.writeUTF(item.getUrl()); + out.writeLong(item.getCreationDateMillis()); + out.writeLong(item.getRevision()); + } + } + } + + @Override + public Map> read(DataInput in) throws IOException { + final HashMap> map = new HashMap>(); + int mapSize = in.readInt(); + for (int i = 0; i < mapSize; i++) { + final String key = in.readUTF(); + final int size = in.readInt(); + final ArrayList list = new ArrayList(size); + for (int j = 0; j < size; j++) { + String url = in.readUTF(); + long creation = in.readLong(); + long revision = in.readLong(); + list.add(new SvnBranchItem(url, new Date(creation), revision)); + } + map.put(key, list); + } + return map; + } + }; + } + + private static class SerializationComparator implements Comparator { + private final static SerializationComparator ourInstance = new SerializationComparator(); + + public static SerializationComparator getInstance() { + return ourInstance; + } + + @Override + public int compare(SvnBranchItem o1, SvnBranchItem o2) { + return o1.getUrl().compareToIgnoreCase(o2.getUrl()); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnLoadedBranches.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnLoadedBranches.java new file mode 100644 index 000000000000..1de63d281e53 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnLoadedBranches.java @@ -0,0 +1,26 @@ +/* + * 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; + +/** + * Created by IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 8/24/11 + * Time: 1:07 PM + * To change this template use File | Settings | File Templates. + */ +public class SvnLoadedBranches { +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnLocallyDeletedChange.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnLocallyDeletedChange.java new file mode 100644 index 000000000000..5943f5684892 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnLocallyDeletedChange.java @@ -0,0 +1,49 @@ +/* + * 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.changes.LocallyDeletedChange; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +public class SvnLocallyDeletedChange extends LocallyDeletedChange { + private final ConflictState myConflictState; + + public SvnLocallyDeletedChange(@NotNull final FilePath path, final ConflictState state) { + super(path); + myConflictState = state; + } + + @Override + public Icon getAddIcon() { + return myConflictState.getIcon(); + } + + @Override + public String getDescription() { + final String description = myConflictState.getDescription(); + if (description != null) { + return SvnBundle.message("svn.changeview.locally.deleted.item.in.conflict.text", description); + } + return description; + } + + public ConflictState getConflictState() { + return myConflictState; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnMapping.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnMapping.java new file mode 100644 index 000000000000..1c6caa717a94 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnMapping.java @@ -0,0 +1,148 @@ +/* + * 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.SystemInfo; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.*; + +public class SvnMapping { + private final List myLonelyRoots; + private final TreeMap myFile2UrlMap; + private final Map myUrl2FileMap; + private boolean myRootsDifferFromSettings; + // no additional info. for caching only (convert roots) + private List myPreCalculatedUnderVcsRoots; + + public SvnMapping() { + myFile2UrlMap = new TreeMap(FilePathsComparator.getInstance()); + myUrl2FileMap = new HashMap(); + myLonelyRoots = new ArrayList(); + + myPreCalculatedUnderVcsRoots = null; + + myRootsDifferFromSettings = false; + } + + public void copyFrom(final SvnMapping other) { + myFile2UrlMap.clear(); + myUrl2FileMap.clear(); + myLonelyRoots.clear(); + + myFile2UrlMap.putAll(other.myFile2UrlMap); + myUrl2FileMap.putAll(other.myUrl2FileMap); + myLonelyRoots.addAll(other.myLonelyRoots); + myRootsDifferFromSettings = other.myRootsDifferFromSettings; + myPreCalculatedUnderVcsRoots = null; + } + + public void addAll(final Collection roots) { + for (RootUrlInfo rootInfo : roots) { + final VirtualFile file = rootInfo.getVirtualFile(); + final File ioFile = new File(file.getPath()); + + myRootsDifferFromSettings |= ! rootInfo.getRoot().getPath().equals(file.getPath()); + + myFile2UrlMap.put(ioFile.getAbsolutePath(), rootInfo); + myUrl2FileMap.put(rootInfo.getAbsoluteUrl(), rootInfo); + } + } + + public void add(final RootUrlInfo rootInfo) { + final VirtualFile file = rootInfo.getVirtualFile(); + final File ioFile = new File(file.getPath()); + + myRootsDifferFromSettings |= ! rootInfo.getRoot().getPath().equals(file.getPath()); + + myFile2UrlMap.put(ioFile.getAbsolutePath(), rootInfo); + myUrl2FileMap.put(rootInfo.getAbsoluteUrl(), rootInfo); + } + + public List getUnderVcsRoots() { + if (myPreCalculatedUnderVcsRoots == null) { + myPreCalculatedUnderVcsRoots = new ArrayList(); + for (RootUrlInfo info : myFile2UrlMap.values()) { + myPreCalculatedUnderVcsRoots.add(info.getVirtualFile()); + } + } + return myPreCalculatedUnderVcsRoots; + } + + public List getAllCopies() { + return new ArrayList(myFile2UrlMap.values()); + } + + @Nullable + public String getRootForPath(final String path) { + String floor = myFile2UrlMap.floorKey(path); + if (floor == null) return null; + NavigableMap head = myFile2UrlMap.headMap(floor, true); + for (String root : head.descendingKeySet()) { + if (startsWithForPaths(root, path)) return root; + } + return null; + } + + public Collection getUrls() { + return myUrl2FileMap.keySet(); + } + + public RootUrlInfo byFile(final String path) { + return myFile2UrlMap.get(path); + } + + public RootUrlInfo byUrl(final String url) { + return myUrl2FileMap.get(url); + } + + public boolean isEmpty() { + return myUrl2FileMap.isEmpty(); + } + + public boolean isRootsDifferFromSettings() { + return myRootsDifferFromSettings; + } + + public void reportLonelyRoots(final Collection roots) { + myLonelyRoots.addAll(roots); + } + + public List getLonelyRoots() { + return myLonelyRoots; + } + + private static class FilePathsComparator implements Comparator { + private final static FilePathsComparator ourInstance = new FilePathsComparator(); + + public static FilePathsComparator getInstance() { + return ourInstance; + } + + @Override + public int compare(String o1, String o2) { + return FileUtil.comparePaths(o1, o2); + } + } + + private boolean startsWithForPaths(final String parent, final String child) { + return SystemInfo.isFileSystemCaseSensitive ? child.startsWith(parent) : (StringUtil.startsWithIgnoreCase(child, parent)); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnMappingSavedPart.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnMappingSavedPart.java new file mode 100644 index 000000000000..29d071e13b8b --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnMappingSavedPart.java @@ -0,0 +1,45 @@ +/* + * 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.List; + +public class SvnMappingSavedPart { + public List myMappingRoots; + public List myMoreRealMappingRoots; + + public SvnMappingSavedPart() { + myMappingRoots = new ArrayList(); + myMoreRealMappingRoots = new ArrayList(); + } + + public void add(final SvnCopyRootSimple copy) { + myMappingRoots.add(copy); + } + + public void addReal(final SvnCopyRootSimple copy) { + myMoreRealMappingRoots.add(copy); + } + + public List getMappingRoots() { + return myMappingRoots; + } + + public List getMoreRealMappingRoots() { + return myMoreRealMappingRoots; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnNestedTypeRechecker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnNestedTypeRechecker.java new file mode 100644 index 000000000000..f19db85389e8 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnNestedTypeRechecker.java @@ -0,0 +1,183 @@ +/* + * 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.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.AbstractFilterChildren; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.containers.MultiMap; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.*; + +import java.io.File; +import java.util.*; + +// referencies of RootUrlInfo will be kept; so type flag will be raised right in original object +public class SvnNestedTypeRechecker { + private final static Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnNestedTypeRechecker"); + + private final MultiMap myRoots; + private final Map mySourceItems; + private Map> myTreeTravellingMap; + private final Project myProject; + + public SvnNestedTypeRechecker(final Project project, final List roots) { + myProject = project; + final NestedGatherer nestedGatherer = new NestedGatherer(); + nestedGatherer.doFilter(new ArrayList(roots)); + myRoots = nestedGatherer.getNested(); + mySourceItems = new HashMap(); + for (VirtualFile root : myRoots.keySet()) { + final Collection items = myRoots.get(root); + for (RootUrlInfo item : items) { + mySourceItems.put(FileUtil.nameToCompare(item.getVirtualFile().getPath()), item); + } + } + } + + private void initTravellingMap() { + myTreeTravellingMap = new HashMap>(); + + final MultiMap tmpMap = new MultiMap(); + for (VirtualFile parent : myRoots.keySet()) { + final Collection children = myRoots.get(parent); + for (RootUrlInfo child : children) { + + VirtualFile current = child.getVirtualFile(); + while (! current.equals(parent)) { + tmpMap.putValue(current.getParent(), new File(current.getPath())); + current = current.getParent(); + if (current.getParent() == null) break; //+- + } + } + } + + for (VirtualFile dir : tmpMap.keySet()) { + final Map currMap = new HashMap(); + final Collection coll = tmpMap.get(dir); + for (File file : coll) { + currMap.put(file.getAbsolutePath(), file); + } + myTreeTravellingMap.put(FileUtil.nameToCompare(dir.getPath()), currMap); + } + } + + public void run() { + if (! myRoots.isEmpty()) { + initTravellingMap(); + + final ISVNStatusFileProvider fileProvider = new ISVNStatusFileProvider() { + public Map getChildrenFiles(File parent) { + final Map children = myTreeTravellingMap.get(FileUtil.nameToCompare(parent.getAbsolutePath())); + return children == null ? Collections.emptyMap() : children; + } + }; + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final SVNStatusClient client = vcs.createStatusClient(); + client.setFilesProvider(fileProvider); + + for (VirtualFile parent : myRoots.keySet()) { + try { + client.doStatus(new File(parent.getPath()), SVNRevision.WORKING, SVNDepth.INFINITY, false, true, true, false, new ISVNStatusHandler() { + public void handleStatus(final SVNStatus status) throws SVNException { + final boolean switched = status.isSwitched(); + final boolean externals = SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_EXTERNAL); + + if (switched || externals) { + final RootUrlInfo urlInfo = mySourceItems.get(FileUtil.nameToCompare(status.getFile().getAbsolutePath())); + if (urlInfo != null) { + if (switched) { + urlInfo.setType(NestedCopyType.switched); + } else { + urlInfo.setType(NestedCopyType.external); + } + } + } + } + }, null); + } + catch (SVNException e) { + LOG.debug(e); + } + } + } + } + + /*private static SVNStatus getExternalItemStatus(final SvnVcs vcs, final File file) { + final SVNStatusClient statusClient = vcs.createStatusClient(); + try { + if (file.isDirectory()) { + return statusClient.doStatus(file, false); + } else { + final File parent = file.getParentFile(); + if (parent != null) { + statusClient.setFilesProvider(new ISVNStatusFileProvider() { + public Map getChildrenFiles(File parent) { + return Collections.singletonMap(file.getAbsolutePath(), file); + } + }); + final Ref refStatus = new Ref(); + statusClient.doStatus(parent, SVNRevision.WORKING, SVNDepth.FILES, false, true, false, false, new ISVNStatusHandler() { + public void handleStatus(final SVNStatus status) throws SVNException { + if (file.equals(status.getFile())) { + refStatus.set(status); + } + } + }, null); + return refStatus.get(); + } + } + } + catch (SVNException e) { + // + } + return null; + }*/ + + private static class NestedGatherer extends AbstractFilterChildren { + private final MultiMap myNested; + + private NestedGatherer() { + myNested = new MultiMap(); + } + + @Override + protected void sortAscending(List rootUrlInfos) { + Collections.sort(rootUrlInfos, new Comparator() { + public int compare(final RootUrlInfo r1, final RootUrlInfo r2) { + return new Integer(r1.getVirtualFile().getPath().length()).compareTo(r2.getVirtualFile().getPath().length()); + } + }); + } + + @Override + protected boolean isAncestor(RootUrlInfo parent, RootUrlInfo child) { + final boolean result = VfsUtil.isAncestor(parent.getVirtualFile(), child.getVirtualFile(), true); + if (result) { + myNested.putValue(parent.getVirtualFile(), child); + } + return result; + } + + public MultiMap getNested() { + return myNested; + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnProgressCanceller.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnProgressCanceller.java new file mode 100644 index 000000000000..5c4c966e9894 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnProgressCanceller.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.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +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.SVNEvent; + +/** + * @author yole + */ +public class SvnProgressCanceller implements ISVNEventHandler { + public void checkCancelled() throws SVNCancelException { + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (indicator != null && indicator.isCanceled()) { + throw new SVNCancelException(); + } + } + + public void handleEvent(final SVNEvent event, final double progress) throws SVNException { + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnPropertyKeys.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnPropertyKeys.java new file mode 100644 index 000000000000..6b1254dfce45 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnPropertyKeys.java @@ -0,0 +1,27 @@ +/* + * 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; + +@SuppressWarnings({"HardCodedStringLiteral"}) +public interface SvnPropertyKeys { + String SVN_EOL_STYLE = "svn:eol-style"; + String SVN_KEYWORDS = "svn:keywords"; + String SVN_NEEDS_LOCK = "svn:needs-lock"; + String SVN_MIME_TYPE = "svn:mime-type"; + String SVN_EXECUTABLE = "svn:executable"; + String SVN_IGNORE = "svn:ignore"; + String SVN_EXTERNALS = "svn:externals"; +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnProxyAuthenticationNotifier.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnProxyAuthenticationNotifier.java new file mode 100644 index 000000000000..501ac5d60a69 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnProxyAuthenticationNotifier.java @@ -0,0 +1,52 @@ +/* + * 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.project.Project; +import com.intellij.openapi.vcs.impl.GenericNotifierImpl; +import org.jetbrains.annotations.NotNull; + +// todo be done +// todo : two kinds. positive: either into common proxy, or into system properties +public class SvnProxyAuthenticationNotifier extends GenericNotifierImpl { + public SvnProxyAuthenticationNotifier(Project project, @NotNull String groupId, @NotNull String title, @NotNull NotificationType type) { + super(project, groupId, title, type); + } + + @Override + protected boolean ask(String obj, String description) { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + @NotNull + @Override + protected String getKey(String obj) { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @NotNull + @Override + protected String getNotificationContent(String obj) { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @NotNull + @Override + protected String getToString(String obj) { + return null; //To change body of implemented methods use File | Settings | File Templates. + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnRecursiveStatusWalker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnRecursiveStatusWalker.java new file mode 100644 index 000000000000..df091c073937 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnRecursiveStatusWalker.java @@ -0,0 +1,164 @@ +/* + * 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 com.intellij.vcsUtil.VcsUtil; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNErrorCode; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNNodeKind; +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.SVNStatusClient; + +import java.io.File; +import java.util.LinkedList; + +public class SvnRecursiveStatusWalker { + private final StatusWalkerPartner myPartner; + private final StatusReceiver myReceiver; + private final LinkedList myQueue; + private final MyHandler myHandler; + + public SvnRecursiveStatusWalker(final StatusReceiver receiver, final StatusWalkerPartner partner) { + myReceiver = receiver; + myPartner = partner; + myQueue = new LinkedList(); + myHandler = new MyHandler(); + } + + public void go(final FilePath rootPath, final SVNDepth depth) throws SVNException { + final MyItem root = new MyItem(rootPath, depth, myPartner.createStatusClient(), false); + myQueue.add(root); + + while (! myQueue.isEmpty()) { + myPartner.checkCanceled(); + + final MyItem item = myQueue.removeFirst(); + final FilePath path = item.getPath(); + final File ioFile = path.getIOFile(); + + if (path.isDirectory()) { + myHandler.setCurrentItem(item); + try { + item.getClient().doStatus(ioFile, SVNRevision.WORKING, item.getDepth(), false, false, true, false, myHandler, null); + } + catch (SVNException e) { + if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_DIRECTORY) { + final VirtualFile virtualFile = path.getVirtualFile(); + if (virtualFile != null) { + if (myPartner.isExcluded(virtualFile)) return; + // self is unversioned + myReceiver.processUnversioned(virtualFile); + + processRecursively(virtualFile, item.getDepth()); + } + } else { + throw e; + } + } + } else { + if (item.isIsInnerCopyRoot()) { + // this means that the status of parent directory had already been checked and is unversioned; + // to avoid SVN exception on status query to unversioned directory; and since we already know then that the file + // status is "unversioned" -> just register the unversioned file + if (path.getVirtualFile() != null) { + myReceiver.processUnversioned(path.getVirtualFile()); + } + } else { + final SVNStatus status = item.getClient().doStatus(ioFile, false, false); + myReceiver.process(path, status, false); + } + } + } + } + + private static class MyItem { + private final FilePath myPath; + private final SVNDepth myDepth; + private final SVNStatusClient myClient; + private final boolean myIsInnerCopyRoot; + + private MyItem(FilePath path, SVNDepth depth, SVNStatusClient client, boolean isInnerCopyRoot) { + myPath = path; + myDepth = depth; + myClient = client; + myIsInnerCopyRoot = isInnerCopyRoot; + } + + public FilePath getPath() { + return myPath; + } + + public SVNDepth getDepth() { + return myDepth; + } + + public SVNStatusClient getClient() { + return myClient; + } + + public boolean isIsInnerCopyRoot() { + return myIsInnerCopyRoot; + } + } + + private void processRecursively(final VirtualFile vFile, final SVNDepth prevDepth) { + if (SVNDepth.EMPTY.equals(prevDepth)) return; + if (myPartner.isIgnoredIdeaLevel(vFile)) { + myReceiver.processIgnored(vFile); + return; + } + final SVNDepth newDepth = SVNDepth.INFINITY.equals(prevDepth) ? SVNDepth.INFINITY : SVNDepth.EMPTY; + + final SVNStatusClient childClient = myPartner.createStatusClient(); + final VirtualFile[] children = vFile.getChildren(); + for (VirtualFile child : children) { + final FilePath filePath = VcsUtil.getFilePath(child.getPath(), child.isDirectory()); + // recursiveness is used ONLY for search of working copies that have unversioned files above + final MyItem childItem = new MyItem(filePath, newDepth, childClient, true); + myQueue.add(childItem); + } + } + + private class MyHandler implements ISVNStatusHandler { + private MyItem myCurrentItem; + + public void setCurrentItem(MyItem currentItem) { + myCurrentItem = currentItem; + } + + public void handleStatus(final SVNStatus status) throws SVNException { + myPartner.checkCanceled(); + final FilePath path = VcsUtil.getFilePath(status.getFile(), status.getKind().equals(SVNNodeKind.DIR)); + final VirtualFile vFile = path.getVirtualFile(); + + if ((vFile != null) && myPartner.isExcluded(vFile)) return; + + if ((vFile != null) && (SvnVcs17.svnStatusIsUnversioned(status))) { + myReceiver.processUnversioned(vFile); + if (vFile.isDirectory()) { + processRecursively(vFile, myCurrentItem.getDepth()); + } + } else { + myReceiver.process(path, status, myCurrentItem.isIsInnerCopyRoot()); + } + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnRevisionNumber.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnRevisionNumber.java new file mode 100644 index 000000000000..06923a9bc463 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnRevisionNumber.java @@ -0,0 +1,79 @@ +/* + * 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.history.VcsRevisionNumber; +import org.jetbrains.annotations.NotNull; +import org.tmatesoft.svn.core.wc.SVNRevision; + +/** + * @author alex + */ +public class SvnRevisionNumber implements VcsRevisionNumber { + @NotNull + private final SVNRevision myRevision; + + public SvnRevisionNumber(@NotNull SVNRevision revision) { + myRevision = revision; + } + + public String asString() { + return myRevision.toString(); + } + + public int compareTo(VcsRevisionNumber vcsRevisionNumber) { + if (vcsRevisionNumber == null || vcsRevisionNumber.getClass() != SvnRevisionNumber.class) { + return -1; + } + SVNRevision rev = ((SvnRevisionNumber)vcsRevisionNumber).myRevision; + if (!myRevision.isValid()) { + return !rev.isValid() ? 0 : -1; + } + if (myRevision.getNumber() >= 0 && rev.getNumber() >= 0) { + return myRevision.getNumber() == rev.getNumber() ? 0 : myRevision.getNumber() > rev.getNumber() ? 1 : -1; + } + else if (myRevision.getDate() != null && rev.getDate() != null) { + return myRevision.getDate().compareTo(rev.getDate()); + } + if (myRevision.equals(SVNRevision.HEAD)) { + return rev.equals(SVNRevision.HEAD) ? 0 : 1; // HEAD is greater than a specific rev + } + return myRevision.getID() == rev.getID() ? 0 : myRevision.getID() > rev.getID() ? 1 : -1; + } + + @NotNull + public SVNRevision getRevision() { + return myRevision; + } + + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final SvnRevisionNumber that = (SvnRevisionNumber)o; + + return myRevision.equals(that.myRevision); + + } + + public int hashCode() { + return myRevision.hashCode(); + } + + public String toString() { + return myRevision.toString(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnScopeZipper.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnScopeZipper.java new file mode 100644 index 000000000000..858be1e49fac --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnScopeZipper.java @@ -0,0 +1,131 @@ +/* + * 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.FilePathImpl; +import com.intellij.openapi.vcs.changes.VcsDirtyScope; +import com.intellij.openapi.vfs.VirtualFile; + +import java.util.*; + +public class SvnScopeZipper implements Runnable { + private final VcsDirtyScope myIn; + private final List myRecursiveDirs; + // instead of set and heavy equals of file path + private final Map myNonRecursiveDirs; + // those alone in their immediate parent + private final List mySingleFiles; + + public SvnScopeZipper(final VcsDirtyScope in) { + myIn = in; + myRecursiveDirs = new ArrayList(in.getRecursivelyDirtyDirectories()); + myNonRecursiveDirs = new HashMap(); + mySingleFiles = new ArrayList(); + } + + public void run() { + // if put directly into dirty scope, to access a copy will be created every time + final Set files = myIn.getDirtyFilesNoExpand(); + + for (FilePath file : files) { + if (file.isDirectory()) { + final VirtualFile vFile = file.getVirtualFile(); + // todo take care about this 'not valid' - right now keeping things as they used to be + if (vFile != null && vFile.isValid()) { + final MyDirNonRecursive me = createOrGet(file); + me.setInterestedInParent(true); + for (VirtualFile child : vFile.getChildren()) { + me.add(new FilePathImpl(child)); + } + } + } else { + final FilePath parent = file.getParentPath(); + if (parent != null) { + final MyDirNonRecursive item = createOrGet(parent); + item.add(file); + } + } + } + + // move alone files into a separate list + /*for (Iterator> iterator = myNonRecursiveDirs.entrySet().iterator(); iterator.hasNext();) { + final Map.Entry entry = iterator.next(); + final MyDirNonRecursive item = entry.getValue(); + if ((! item.isInterestedInParent()) && (item.getChildrenList().size() == 1)) { + iterator.remove(); + mySingleFiles.add(item.getChildrenList().iterator().next()); + } + }*/ + } + + private MyDirNonRecursive createOrGet(final FilePath parent) { + final String key = getKey(parent); + final MyDirNonRecursive result = myNonRecursiveDirs.get(key); + if (result != null) return result; + final MyDirNonRecursive newItem = new MyDirNonRecursive(parent); + myNonRecursiveDirs.put(key, newItem); + return newItem; + } + + public List getRecursiveDirs() { + return myRecursiveDirs; + } + + public Map getNonRecursiveDirs() { + return myNonRecursiveDirs; + } + + public List getSingleFiles() { + return mySingleFiles; + } + + static class MyDirNonRecursive { + private boolean myInterestedInParent; + private final FilePath myDir; + // instead of set and heavy equals of file path + private final Map myChildren; + + private MyDirNonRecursive(final FilePath dir) { + myDir = dir; + myChildren = new HashMap(); + } + + public boolean isInterestedInParent() { + return myInterestedInParent; + } + + public void setInterestedInParent(boolean interestedInParent) { + myInterestedInParent = interestedInParent; + } + + public void add(final FilePath path) { + myChildren.put(getKey(path), path); + } + + public Collection getChildrenList() { + return myChildren.values(); + } + + public FilePath getDir() { + return myDir; + } + } + + public static String getKey(final FilePath path) { + return path.getPresentableUrl(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnServerFileManager.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnServerFileManager.java new file mode 100644 index 000000000000..6d3f6396ba45 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnServerFileManager.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 org.jetbrains.idea.svn17.config.DefaultProxyGroup; +import org.jetbrains.idea.svn17.config.ProxyGroup; + +import java.util.Collection; +import java.util.Map; + +public interface SvnServerFileManager { + DefaultProxyGroup getDefaultGroup(); + Map getGroups(); + + void updateUserServerFile(final Collection newUserGroups); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnServerFileManagerImpl.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnServerFileManagerImpl.java new file mode 100644 index 000000000000..55290721fe19 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnServerFileManagerImpl.java @@ -0,0 +1,105 @@ +/* + * 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 java.util.*; + +public class SvnServerFileManagerImpl implements SvnServerFileManager { + private final DefaultProxyGroup myDefaultGroup; + private final Map myGroups; + private final IdeaSVNConfigFile myFile; + + public SvnServerFileManagerImpl(final IdeaSVNConfigFile file) { + myFile = file; + myFile.updateGroups(); + + myGroups = new HashMap(); + myGroups.putAll(file.getAllGroups()); + myDefaultGroup = file.getDefaultGroup(); + } + + public DefaultProxyGroup getDefaultGroup() { + return (DefaultProxyGroup) myDefaultGroup.copy(); + } + + public Map getGroups() { + // return deep copy + final Map result = new HashMap(myGroups); + for (Map.Entry entry : myGroups.entrySet()) { + result.put(entry.getKey(), entry.getValue().copy()); + } + return result; + } + + public void updateUserServerFile(final Collection newUserGroups) { + final Map oldGroups = getGroups(); + + for (ProxyGroup proxyGroup : newUserGroups) { + if (proxyGroup.isDefault()) { + processGroup(proxyGroup, getDefaultGroup(), false); + } else { + findAndProcessGroup(proxyGroup, oldGroups); + } + } + + for (String groupName : oldGroups.keySet()) { + myFile.deleteGroup(groupName); + } + + myFile.save(); + } + + private void processGroup(final ProxyGroup newGroup, final ProxyGroup oldGroup, final boolean groupWasAdded) { + final String newGroupName = newGroup.getName(); + if (groupWasAdded) { + myFile.addGroup(newGroupName, newGroup.getPatterns(), newGroup.getProperties()); + } else { + final Map oldProperties = oldGroup.getProperties(); + final Map newProperties = newGroup.getProperties(); + + final Set deletedProperties = new HashSet(); + for (String oldKey : oldProperties.keySet()) { + if (! newProperties.containsKey(oldKey)) { + deletedProperties.add(oldKey); + } + } + + final Map newOrModifiedProperties = new HashMap(); + for (Map.Entry entry : newProperties.entrySet()) { + final String oldValue = oldProperties.get(entry.getKey()); + if ((oldValue == null) || (! oldValue.equals(entry.getValue()))) { + newOrModifiedProperties.put(entry.getKey(), entry.getValue()); + } + } + + myFile.modifyGroup(newGroupName, newGroup.getPatterns(), deletedProperties, newOrModifiedProperties, newGroup.isDefault()); + } + } + + private void findAndProcessGroup(final ProxyGroup newGroup, final Map oldGroups) { + final String newGroupName = newGroup.getName(); + final ProxyGroup oldGroup = oldGroups.get(newGroupName); + final boolean groupWasAdded = (oldGroup == null) && (! newGroup.isDefault()); + if (! groupWasAdded) { + // to track deleted + oldGroups.remove(newGroupName); + } + processGroup(newGroup, oldGroup, groupWasAdded); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnStatusConvertor.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnStatusConvertor.java new file mode 100644 index 000000000000..222abce7ae16 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnStatusConvertor.java @@ -0,0 +1,77 @@ +/* + * 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 org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.SVNStatus; +import org.tmatesoft.svn.core.wc.SVNStatusType; + +public class SvnStatusConvertor { + private SvnStatusConvertor() { + } + + public static FileStatus convertStatus(final SVNStatus status) throws SVNException { + if (status == null) { + return FileStatus.UNKNOWN; + } + if (SvnVcs17.svnStatusIsUnversioned(status)) { + return FileStatus.UNKNOWN; + } + else if (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_MISSING)) { + return FileStatus.DELETED_FROM_FS; + } + else if (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_EXTERNAL)) { + return SvnFileStatus.EXTERNAL; + } + else if (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_OBSTRUCTED)) { + return SvnFileStatus.OBSTRUCTED; + } + else if (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_IGNORED)) { + return FileStatus.IGNORED; + } + else if (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_ADDED)) { + return FileStatus.ADDED; + } + else if (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_DELETED)) { + return FileStatus.DELETED; + } + else if (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_REPLACED)) { + return SvnFileStatus.REPLACED; + } + else if (status.getContentsStatus() == SVNStatusType.STATUS_CONFLICTED || + status.getPropertiesStatus() == SVNStatusType.STATUS_CONFLICTED) { + if (status.getContentsStatus() == SVNStatusType.STATUS_CONFLICTED && + status.getPropertiesStatus() == SVNStatusType.STATUS_CONFLICTED) { + return FileStatus.MERGED_WITH_BOTH_CONFLICTS; + } else if (status.getContentsStatus() == SVNStatusType.STATUS_CONFLICTED) { + return FileStatus.MERGED_WITH_CONFLICTS; + } + return FileStatus.MERGED_WITH_PROPERTY_CONFLICTS; + } + else if (status.getContentsStatus() == SVNStatusType.STATUS_MODIFIED || + status.getPropertiesStatus() == SVNStatusType.STATUS_MODIFIED) { + return FileStatus.MODIFIED; + } + else if (status.isSwitched()) { + return FileStatus.SWITCHED; + } + else if (status.isCopied()) { + return FileStatus.ADDED; + } + return FileStatus.NOT_CHANGED; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnStatusUtil.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnStatusUtil.java new file mode 100644 index 000000000000..f1f13303c5ea --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnStatusUtil.java @@ -0,0 +1,52 @@ +/* + * 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.FileStatus; +import com.intellij.openapi.vcs.FileStatusManager; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.changes.ChangeListManagerImpl; +import com.intellij.openapi.vfs.VirtualFile; + +public class SvnStatusUtil { + private SvnStatusUtil() { + } + + public static boolean isUnderControl(final Project project, final VirtualFile file) { + final ChangeListManager clManager = ChangeListManager.getInstance(project); + return (! isIgnoredInAnySense(clManager, file)) && (! clManager.isUnversioned(file)); + } + + public static boolean isAdded(final Project project, final VirtualFile file) { + final FileStatus status = FileStatusManager.getInstance(project).getStatus(file); + return FileStatus.ADDED.equals(status); + } + + public static boolean isExplicitlyLocked(final Project project, final VirtualFile file) { + final ChangeListManager clManager = ChangeListManager.getInstance(project); + return ((ChangeListManagerImpl) clManager).isLogicallyLocked(file); + } + + public static boolean isIgnoredInAnySense(final ChangeListManager clManager, final VirtualFile file) { + return clManager.isIgnoredFile(file) || FileStatus.IGNORED.equals(clManager.getStatus(file)); + } + + public static boolean fileCanBeAdded(final Project project, final VirtualFile file) { + final ChangeListManager clManager = ChangeListManager.getInstance(project); + return isIgnoredInAnySense(clManager, file) || clManager.isUnversioned(file); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnUtil.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnUtil.java new file mode 100644 index 000000000000..73503a503d9d --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnUtil.java @@ -0,0 +1,691 @@ +/* + * 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.util.Computable; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.AbstractVcsHelper; +import com.intellij.openapi.vcs.RepositoryLocation; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vfs.*; +import com.intellij.openapi.wm.WindowManager; +import com.intellij.util.ArrayUtil; +import com.intellij.util.NotNullFunction; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.branchConfig.SvnBranchConfigurationNew; +import org.jetbrains.idea.svn17.dialogs.LockDialog; +import org.jetbrains.idea.svn17.dialogs.WCInfo; +import org.tmatesoft.svn.core.*; +import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; +import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry; +import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess; +import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration; +import org.tmatesoft.svn.core.io.SVNCapability; +import org.tmatesoft.svn.core.io.SVNRepository; +import org.tmatesoft.svn.core.wc.*; +import org.tmatesoft.svn.core.wc2.SvnOperationFactory; + +import java.io.File; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.*; + +public class SvnUtil { + @NonNls public static final String SVN_ADMIN_DIR_NAME = SVNFileUtil.getAdminDirectoryName(); + @NonNls public static final String ENTRIES_FILE_NAME = "entries"; + @NonNls public static final String DIR_PROPS_FILE_NAME = "dir-props"; + @NonNls public static final String PATH_TO_LOCK_FILE = SVN_ADMIN_DIR_NAME + "/lock"; + @NonNls public static final String LOCK_FILE_NAME = "lock"; + + private SvnUtil() { + } + + public static void crawlWCRoots(VirtualFile path, NotNullFunction> callback) { + if (path == null) { + return; + } + final boolean isDirectory = path.isDirectory(); + final VirtualFile parentVFile = (!isDirectory) || (!path.exists()) ? path.getParent() : path; + if (parentVFile == null) { + return; + } + final File parent = new File(parentVFile.getPath()); + + if (isSvnVersioned(parent)) { + checkCanceled(); + final Collection pending = callback.fun(path); + checkCanceled(); + for (VirtualFile virtualFile : pending) { + crawlWCRoots(virtualFile, callback); + } + } else if (isDirectory) { + checkCanceled(); + VirtualFile[] children = path.getChildren(); + for (int i = 0; children != null && i < children.length; i++) { + checkCanceled(); + VirtualFile child = children[i]; + if (child.isDirectory()) { + crawlWCRoots(child, callback); + } + } + } + } + + public static boolean isSvnVersioned(File parent) { + return SVNWCUtil.isVersionedDirectory(parent) || is17CopyPart(parent); + } + + public static Collection crawlWCRoots(File path, SvnWCRootCrawler callback, ProgressIndicator progress) { + final LocalFileSystem lfs = LocalFileSystem.getInstance(); + VirtualFile vf = lfs.findFileByIoFile(path); + if (vf == null) { + vf = lfs.refreshAndFindFileByIoFile(path); + } + if (vf == null) return Collections.emptyList(); + return crawlWCRoots(vf, callback, progress); + } + + private static Collection crawlWCRoots(VirtualFile vf, SvnWCRootCrawler callback, ProgressIndicator progress) { + final Collection result = new HashSet(); + final boolean isDirectory = vf.isDirectory(); + VirtualFile parent = ! isDirectory || !vf.exists() ? vf.getParent() : vf; + + final File parentIo = new File(parent.getPath()); + if (isSvnVersioned(parentIo)) { + checkCanceled(progress); + callback.handleWorkingCopyRoot(parentIo, progress); + checkCanceled(progress); + result.add(parent); + } else if (isDirectory) { + checkCanceled(progress); + final VirtualFile[] childrenVF = parent.getChildren(); + for (VirtualFile file : childrenVF) { + checkCanceled(progress); + if (file.isDirectory()) { + result.addAll(crawlWCRoots(file, callback, progress)); + } + } + } + return result; + } + + private static void checkCanceled() { + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + checkCanceled(indicator); + } + + private static void checkCanceled(final ProgressIndicator progress) { + if (progress != null && progress.isCanceled()) { + throw new ProcessCanceledException(); + } + } + + public static String[] getLocationsForModule(final SvnVcs17 vcs, File path, ProgressIndicator progress) { + LocationsCrawler crawler = new LocationsCrawler(vcs); + crawlWCRoots(path, crawler, progress); + return crawler.getLocations(); + } + + public static Map getLocationInfoForModule(final SvnVcs17 vcs, File path, ProgressIndicator progress) { + final LocationsCrawler crawler = new LocationsCrawler(vcs); + crawlWCRoots(path, crawler, progress); + return crawler.getLocationInfos(); + } + + public static void doLockFiles(Project project, final SvnVcs17 activeVcs, final File[] ioFiles) throws VcsException { + final String lockMessage; + final boolean force; + // TODO[yole]: check for shift pressed + if (activeVcs.getCheckoutOptions().getValue()) { + LockDialog dialog = new LockDialog(project, true, ioFiles != null && ioFiles.length > 1); + dialog.show(); + if (!dialog.isOK()) { + return; + } + lockMessage = dialog.getComment(); + force = dialog.isForce(); + } + else { + lockMessage = ""; + force = false; + } + + final SVNException[] exception = new SVNException[1]; + final Collection failedLocks = new ArrayList(); + final int[] count = new int[]{ioFiles.length}; + final ISVNEventHandler eventHandler = new ISVNEventHandler() { + public void handleEvent(SVNEvent event, double progress) { + if (event.getAction() == SVNEventAction.LOCK_FAILED) { + failedLocks.add(event.getErrorMessage() != null ? + event.getErrorMessage().getFullMessage() : + event.getFile().getAbsolutePath()); + count[0]--; + } + } + + public void checkCancelled() { + } + }; + + Runnable command = new Runnable() { + public void run() { + ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + SVNWCClient wcClient; + + try { + wcClient = activeVcs.createWCClient(); + wcClient.setEventHandler(eventHandler); + if (progress != null) { + progress.setText(SvnBundle.message("progress.text.locking.files")); + } + for (File ioFile : ioFiles) { + if (progress != null) { + progress.checkCanceled(); + } + File file = ioFile; + if (progress != null) { + progress.setText2(SvnBundle.message("progress.text2.processing.file", file.getName())); + } + wcClient.doLock(new File[]{file}, force, lockMessage); + } + } + catch (SVNException e) { + exception[0] = e; + } + } + }; + + ProgressManager.getInstance().runProcessWithProgressSynchronously(command, SvnBundle.message("progress.title.lock.files"), false, project); + if (!failedLocks.isEmpty()) { + String[] failedFiles = ArrayUtil.toStringArray(failedLocks); + List exceptions = new ArrayList(); + + for (String file : failedFiles) { + exceptions.add(new VcsException(SvnBundle.message("exception.text.locking.file.failed", file))); + } + AbstractVcsHelper.getInstance(project).showErrors(exceptions, SvnBundle.message("message.title.lock.failures")); + } + + WindowManager.getInstance().getStatusBar(project).setInfo(SvnBundle.message("message.text.files.locked", count[0])); + if (exception[0] != null) { + throw new VcsException(exception[0]); + } + } + + public static void doUnlockFiles(Project project, final SvnVcs17 activeVcs, final File[] ioFiles) throws VcsException { + final boolean force = true; + final SVNException[] exception = new SVNException[1]; + final Collection failedUnlocks = new ArrayList(); + final int[] count = new int[]{ioFiles.length}; + final ISVNEventHandler eventHandler = new ISVNEventHandler() { + public void handleEvent(SVNEvent event, double progress) { + if (event.getAction() == SVNEventAction.UNLOCK_FAILED) { + failedUnlocks.add(event.getErrorMessage() != null ? + event.getErrorMessage().getFullMessage() : + event.getFile().getAbsolutePath()); + count[0]--; + } + } + + public void checkCancelled() { + } + }; + + Runnable command = new Runnable() { + public void run() { + ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + SVNWCClient wcClient; + + try { + wcClient = activeVcs.createWCClient(); + wcClient.setEventHandler(eventHandler); + if (progress != null) { + progress.setText(SvnBundle.message("progress.text.unlocking.files")); + } + for (File ioFile : ioFiles) { + if (progress != null) { + progress.checkCanceled(); + } + File file = ioFile; + if (progress != null) { + progress.setText2(SvnBundle.message("progress.text2.processing.file", file.getName())); + } + wcClient.doUnlock(new File[]{file}, force); + } + } + catch (SVNException e) { + exception[0] = e; + } + } + }; + + ProgressManager.getInstance().runProcessWithProgressSynchronously(command, SvnBundle.message("progress.title.unlock.files"), false, project); + if (!failedUnlocks.isEmpty()) { + String[] failedFiles = ArrayUtil.toStringArray(failedUnlocks); + List exceptions = new ArrayList(); + + for (String file : failedFiles) { + exceptions.add(new VcsException(SvnBundle.message("exception.text.failed.to.unlock.file", file))); + } + AbstractVcsHelper.getInstance(project).showErrors(exceptions, SvnBundle.message("message.title.unlock.failures")); + } + + WindowManager.getInstance().getStatusBar(project).setInfo(SvnBundle.message("message.text.files.unlocked", count[0])); + if (exception[0] != null) { + throw new VcsException(exception[0]); + } + } + + public static String formatRepresentation(final WorkingCopyFormat format) { + if (WorkingCopyFormat.ONE_DOT_SIX.equals(format)) { + return SvnBundle.message("dialog.show.svn.map.table.version16.text"); + } else if (WorkingCopyFormat.ONE_DOT_FIVE.equals(format)) { + return SvnBundle.message("dialog.show.svn.map.table.version15.text"); + } else if (WorkingCopyFormat.ONE_DOT_FOUR.equals(format)) { + return SvnBundle.message("dialog.show.svn.map.table.version14.text"); + } else if (WorkingCopyFormat.ONE_DOT_THREE.equals(format)) { + return SvnBundle.message("dialog.show.svn.map.table.version13.text"); + } + return ""; + } + + private static class LocationsCrawler implements SvnWCRootCrawler { + private final SvnVcs17 myVcs; + private final Map myLocations; + + public LocationsCrawler(SvnVcs17 vcs) { + myVcs = vcs; + myLocations = new HashMap(); + } + + public String[] getLocations() { + final Set set = myLocations.keySet(); + return ArrayUtil.toStringArray(set); + } + + public Map getLocationInfos() { + return Collections.unmodifiableMap(myLocations); + } + + public void handleWorkingCopyRoot(File root, ProgressIndicator progress) { + String oldText = null; + if (progress != null) { + oldText = progress.getText(); + progress.setText(SvnBundle.message("progress.text.discovering.location", root.getAbsolutePath())); + } + try { + SVNWCClient wcClient = myVcs.createWCClient(); + SVNInfo info = wcClient.doInfo(root, SVNRevision.UNDEFINED); + if (info != null && info.getURL() != null) { + myLocations.put(info.getURL().toString(), info.getFile()); + } + } + catch (SVNException e) { + // + } + if (progress != null) { + progress.setText(oldText); + } + } + } + + @Nullable + public static String getRepositoryUUID(final SvnVcs17 vcs, final File file) { + final SVNWCClient client = vcs.createWCClient(); + try { + final SVNInfo info = client.doInfo(file, SVNRevision.UNDEFINED); + return (info == null) ? null : info.getRepositoryUUID(); + } catch (SVNException e) { + return null; + } + } + + @Nullable + public static String getRepositoryUUID(final SvnVcs17 vcs, final SVNURL url) { + final SVNWCClient client = vcs.createWCClient(); + try { + final SVNInfo info = client.doInfo(url, SVNRevision.UNDEFINED, SVNRevision.UNDEFINED); + return (info == null) ? null : info.getRepositoryUUID(); + } catch (SVNException e) { + return null; + } + } + + @Nullable + public static SVNURL getRepositoryRoot(final SvnVcs17 vcs, final File file) { + final SVNWCClient client = vcs.createWCClient(); + try { + final SVNInfo info = client.doInfo(file, SVNRevision.UNDEFINED); + return (info == null) ? null : info.getRepositoryRootURL(); + } catch (SVNException e) { + return null; + } + } + + @Nullable + public static SVNURL getRepositoryRoot(final SvnVcs17 vcs, final String url) { + try { + return getRepositoryRoot(vcs, SVNURL.parseURIEncoded(url)); + } + catch (SVNException e) { + return null; + } + } + + @Nullable + public static SVNURL getRepositoryRoot(final SvnVcs17 vcs, final SVNURL url) { + final SVNWCClient client = vcs.createWCClient(); + try { + SVNInfo info = client.doInfo(url, SVNRevision.UNDEFINED, SVNRevision.HEAD); + return (info == null) ? null : info.getRepositoryRootURL(); + } catch (SVNException e) { + return null; + } + } + + public static boolean isWorkingCopyRoot(final File file) { + try { + return SVNWCUtil.isWorkingCopyRoot(file); + } catch (SVNException e) { + return false; + } + } + + @Nullable + public static File getWorkingCopyRoot(final File inFile) { + File file = inFile; + while ((file != null) && (file.isFile() || (! file.exists()))) { + file = file.getParentFile(); + } + + if (file == null) { + return null; + } + + File workingCopyRoot = null; + try { + workingCopyRoot = SVNWCUtil.getWorkingCopyRoot(file, true); + } catch (SVNException e) { + // + } + if (workingCopyRoot == null) { + workingCopyRoot = getWcCopyRootIf17(file, null); + } + return workingCopyRoot; + } + + public static File fileFromUrl(final File baseDir, final String baseUrl, final String fullUrl) throws SVNException { + assert fullUrl.startsWith(baseUrl); + + final String part = fullUrl.substring(baseUrl.length()).replace('/', File.separatorChar).replace('\\', File.separatorChar); + return new File(baseDir, part); + } + + public static VirtualFile getVirtualFile(final String filePath) { + @NonNls final String path = VfsUtil.pathToUrl(filePath.replace(File.separatorChar, '/')); + return ApplicationManager.getApplication().runReadAction(new Computable() { + @Nullable + public VirtualFile compute() { + return VirtualFileManager.getInstance().findFileByUrl(path); + } + }); + } + + @Nullable + public static SVNURL getBranchForUrl(final SvnVcs17 vcs, final VirtualFile vcsRoot, final String urlPath) { + final SvnBranchConfigurationNew configuration; + try { + final SVNURL url = SVNURL.parseURIEncoded(urlPath); + configuration = SvnBranchConfigurationManager17.getInstance(vcs.getProject()).get(vcsRoot); + return (configuration == null) ? null : configuration.getWorkingBranch(url); + } + catch (SVNException e) { + return null; + } catch (VcsException e1) { + return null; + } + } + + @Nullable + public static String getPathForProgress(final SVNEvent event) { + if (event.getFile() != null) { + return event.getFile().getName(); + } + if (event.getURL() != null) { + return event.getURL().toString(); + } + return null; + } + + @Nullable + public static VirtualFile correctRoot(final Project project, final String path) { + if (path.length() == 0) { + // project root + return project.getBaseDir(); + } + return LocalFileSystem.getInstance().findFileByPath(path); + } + + @Nullable + public static VirtualFile correctRoot(final Project project, final VirtualFile file) { + if (file.getPath().length() == 0) { + // project root + return project.getBaseDir(); + } + return file; + } + + public static boolean isOneDotFiveAvailable(final Project project, final RepositoryLocation location) { + final SvnVcs17 vcs = SvnVcs17.getInstance(project); + final List infos = vcs.getAllWcInfos(); + + for (WCInfo info : infos) { + if (! info.getFormat().supportsMergeInfo()) { + continue; + } + + final String url = info.getUrl().toString(); + if ((location != null) && (! location.toPresentableString().startsWith(url)) && + (! url.startsWith(location.toPresentableString()))) { + continue; + } + if (! checkRepositoryVersion15(vcs, url)) { + continue; + } + return true; + } + return false; + } + + public static boolean checkRepositoryVersion15(final SvnVcs17 vcs, final String url) { + SVNRepository repository = null; + try { + repository = vcs.createRepository(url); + return repository.hasCapability(SVNCapability.MERGE_INFO); + } + catch (SVNException e) { + return false; + } + finally { + if (repository != null) { + repository.closeSession(); + } + } + } + + public static SVNStatus getStatus(final SvnVcs17 vcs, final File file) { + final SVNStatusClient statusClient = vcs.createStatusClient(); + try { + return statusClient.doStatus(file, false); + } + catch (SVNException e) { + return null; + } + } + + public static SVNDepth getDepth(final SvnVcs17 vcs, final File file) { + final SVNWCClient client = vcs.createWCClient(); + try { + final SVNInfo svnInfo = client.doInfo(file, SVNRevision.UNDEFINED); + if (svnInfo != null) { + return svnInfo.getDepth(); + } + } + catch (SVNException e) { + // + } + return SVNDepth.UNKNOWN; + } + + public static boolean seemsLikeVersionedDir(final VirtualFile file) { + final String adminName = SVNFileUtil.getAdminDirectoryName(); + final VirtualFile child = file.findChild(adminName); + return child != null && child.isDirectory(); + } + + public static boolean isAdminDirectory(final VirtualFile file) { + return isAdminDirectory(file.getParent(), file.getName()); + } + + public static boolean isAdminDirectory(File parent, final String name) { + if (name.equals(SVN_ADMIN_DIR_NAME)) { + return true; + } + if (parent != null) { + if (parent.getName().equals(SVN_ADMIN_DIR_NAME)) { + return true; + } + parent = parent.getParentFile(); + if (parent != null && parent.getName().equals(SVN_ADMIN_DIR_NAME)) { + return true; + } + } + return false; + } + + public static boolean isAdminDirectory(VirtualFile parent, String name) { + // never allow to delete admin directories by themselves (this can happen during LVCS undo, + // which deletes created directories from bottom to top) + if (name.equals(SVN_ADMIN_DIR_NAME)) { + return true; + } + if (parent != null) { + if (parent.getName().equals(SVN_ADMIN_DIR_NAME)) { + return true; + } + parent = parent.getParent(); + if (parent != null && parent.getName().equals(SVN_ADMIN_DIR_NAME)) { + return true; + } + } + return false; + } + + @Nullable + public static SVNURL getUrl(final File file) { + SVNWCAccess wcAccess = SVNWCAccess.newInstance(null); + try { + wcAccess.probeOpen(file, false, 0); + SVNEntry entry = wcAccess.getVersionedEntry(file, false); + return entry.getSVNURL(); + } catch (SVNException e) { + // + } finally { + try { + wcAccess.close(); + } + catch (SVNException e) { + // + } + } + return null; + } + + public static boolean doesRepositorySupportMergeinfo(final SvnVcs17 vcs, final SVNURL url) { + SVNRepository repository = null; + try { + repository = vcs.createRepository(url); + return repository.hasCapability(SVNCapability.MERGE_INFO); + } + catch (SVNException e) { + return false; + } finally { + if (repository != null) { + repository.closeSession(); + } + } + } + + public static boolean remoteFolderIsEmpty(final SvnVcs17 vcs, final String url) throws SVNException { + SVNRepository repository = null; + try { + repository = vcs.createRepository(url); + final Ref result = new Ref(true); + repository.getDir("", -1, null, new ISVNDirEntryHandler() { + public void handleDirEntry(final SVNDirEntry dirEntry) throws SVNException { + if (dirEntry != null) { + result.set(false); + } + } + }); + return result.get(); + } finally { + if (repository != null) { + repository.closeSession(); + } + } + } + + public static byte[] decode(final Charset charset, final byte[] buffer) { + if (charset != null && ! CharsetToolkit.UTF8_CHARSET.equals(charset)) { + return CharsetToolkit.UTF8_CHARSET.encode(charset.decode(ByteBuffer.wrap(buffer))).array(); + } + return buffer; + } + + @Nullable + public static File getWcCopyRootIf17(final File file, @Nullable final File upperBound) { + File current = file; + while (current != null) { + try { + final SvnWcGeneration svnWcGeneration = SvnOperationFactory.detectWcGeneration(current, false); + if (SvnWcGeneration.V17.equals(svnWcGeneration)) return current; + if (SvnWcGeneration.V16.equals(svnWcGeneration)) return null; + if (upperBound != null && upperBound.equals(current)) return null; + current = current.getParentFile(); + } + catch (SVNException e) { + return null; + } + } + return null; + } + + public static boolean is17CopyPart(final File file) { + try { + return SvnWcGeneration.V17.equals(SvnOperationFactory.detectWcGeneration(file, true)); + } + catch (SVNException e) { + return false; + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnVcs17.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnVcs17.java new file mode 100644 index 000000000000..297ca698fda4 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnVcs17.java @@ -0,0 +1,1097 @@ +/* + * 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.ide.FrameStateListener; +import com.intellij.ide.FrameStateManager; +import com.intellij.idea.RareLogger; +import com.intellij.notification.*; +import com.intellij.notification.impl.NotificationsConfigurationImpl; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.DumbAwareRunnable; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.startup.StartupManager; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.Trinity; +import com.intellij.openapi.vcs.*; +import com.intellij.openapi.vcs.annotate.AnnotationProvider; +import com.intellij.openapi.vcs.changes.*; +import com.intellij.openapi.vcs.checkin.CheckinEnvironment; +import com.intellij.openapi.vcs.diff.DiffProvider; +import com.intellij.openapi.vcs.history.VcsHistoryProvider; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import com.intellij.openapi.vcs.merge.MergeProvider; +import com.intellij.openapi.vcs.rollback.RollbackEnvironment; +import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; +import com.intellij.openapi.vcs.update.UpdateEnvironment; +import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.util.ThreeState; +import com.intellij.util.containers.Convertor; +import com.intellij.util.containers.SoftHashMap; +import com.intellij.util.messages.MessageBus; +import com.intellij.util.messages.MessageBusConnection; +import com.intellij.util.messages.Topic; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.actions.ShowPropertiesDiffWithLocalAction; +import org.jetbrains.idea.svn17.actions.SvnMergeProvider; +import org.jetbrains.idea.svn17.annotate.SvnAnnotationProvider; +import org.jetbrains.idea.svn17.checkin.SvnCheckinEnvironment17; +import org.jetbrains.idea.svn17.dialogs.SvnBranchPointsCalculator; +import org.jetbrains.idea.svn17.dialogs.SvnFormatWorker; +import org.jetbrains.idea.svn17.dialogs.WCInfo; +import org.jetbrains.idea.svn17.history.LoadedRevisionsCache17; +import org.jetbrains.idea.svn17.history.SvnChangeList; +import org.jetbrains.idea.svn17.history.SvnCommittedChangesProvider; +import org.jetbrains.idea.svn17.history.SvnHistoryProvider; +import org.jetbrains.idea.svn17.rollback.SvnRollbackEnvironment17; +import org.jetbrains.idea.svn17.update.SvnIntegrateEnvironment17; +import org.jetbrains.idea.svn17.update.SvnUpdateEnvironment17; +import org.tmatesoft.svn.core.*; +import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; +import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory; +import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory; +import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl; +import org.tmatesoft.svn.core.internal.util.jna.SVNJNAUtil; +import org.tmatesoft.svn.core.internal.wc.SVNAdminUtil; +import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea14; +import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaFactory; +import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess; +import org.tmatesoft.svn.core.io.SVNRepository; +import org.tmatesoft.svn.core.io.SVNRepositoryFactory; +import org.tmatesoft.svn.core.wc.*; +import org.tmatesoft.svn.util.SVNDebugLog; +import org.tmatesoft.svn.util.SVNDebugLogAdapter; +import org.tmatesoft.svn.util.SVNLogType; + +import javax.swing.event.HyperlinkEvent; +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.util.*; +import java.util.logging.Level; + +@SuppressWarnings({"IOResourceOpenedButNotSafelyClosed"}) +public class SvnVcs17 extends AbstractVcs { + private final static Logger REFRESH_LOG = Logger.getInstance("#svn_refresh"); + + private final static int ourLogUsualInterval = 20 * 1000; + private final static int ourLogRareInterval = 30 * 1000; + + private final static Set ourLogRarely = new HashSet( + Arrays.asList(new SVNErrorCode[]{SVNErrorCode.WC_UNSUPPORTED_FORMAT, SVNErrorCode.WC_CORRUPT, SVNErrorCode.WC_CORRUPT_TEXT_BASE, + SVNErrorCode.WC_NOT_FILE, SVNErrorCode.WC_NOT_DIRECTORY, SVNErrorCode.WC_PATH_NOT_FOUND})); + + private static final Logger LOG = wrapLogger(Logger.getInstance("org.jetbrains.idea.svn.SvnVcs")); + @NonNls public static final String VCS_NAME = "svn17"; + private static final VcsKey ourKey = createKey(VCS_NAME); + public static final Topic WC_CONVERTED = new Topic("WC_CONVERTED", Runnable.class); + private final Map>>> myPropertyCache = new SoftHashMap>>>(); + + private final SvnConfiguration17 myConfiguration; + private final SvnEntriesFileListener myEntriesFileListener; + + private CheckinEnvironment myCheckinEnvironment; + private RollbackEnvironment myRollbackEnvironment; + private UpdateEnvironment mySvnUpdateEnvironment; + private UpdateEnvironment mySvnIntegrateEnvironment; + private VcsHistoryProvider mySvnHistoryProvider; + private AnnotationProvider myAnnotationProvider; + private DiffProvider mySvnDiffProvider; + private final VcsShowConfirmationOption myAddConfirmation; + private final VcsShowConfirmationOption myDeleteConfirmation; + private EditFileProvider myEditFilesProvider; + private SvnCommittedChangesProvider myCommittedChangesProvider; + private final VcsShowSettingOption myCheckoutOptions; + + private ChangeProvider myChangeProvider; + private MergeProvider myMergeProvider; + private final WorkingCopiesContent myWorkingCopiesContent; + + @NonNls public static final String LOG_PARAMETER_NAME = "javasvn.log"; + public static final String pathToEntries = SvnUtil.SVN_ADMIN_DIR_NAME + File.separatorChar + SvnUtil.ENTRIES_FILE_NAME; + public static final String pathToDirProps = SvnUtil.SVN_ADMIN_DIR_NAME + File.separatorChar + SvnUtil.DIR_PROPS_FILE_NAME; + private final SvnChangelistListener myChangeListListener; + + private SvnCopiesRefreshManager myCopiesRefreshManager; + private SvnFileUrlMappingImpl17 myMapping; + private final MyFrameStateListener myFrameStateListener; + + public static final Topic ROOTS_RELOADED = new Topic("ROOTS_RELOADED", Runnable.class); + private VcsListener myVcsListener; + + private SvnBranchPointsCalculator mySvnBranchPointsCalculator; + + private final RootsToWorkingCopies myRootsToWorkingCopies; + private final SvnAuthenticationNotifier myAuthNotifier; + private static RareLogger.LogFilter[] ourLogFilters; + private final SvnLoadedBrachesStorage17 myLoadedBranchesStorage; + + static { + SVNJNAUtil.setJNAEnabled(true); + SvnHttpAuthMethodsDefaultChecker.check(); + + //noinspection UseOfArchaicSystemPropertyAccessors + final JavaSVNDebugLogger logger = new JavaSVNDebugLogger(Boolean.getBoolean(LOG_PARAMETER_NAME), LOG); + SVNDebugLog.setDefaultLog(logger); + SVNAdminAreaFactory.setSelector(new SvnFormatSelector()); + + DAVRepositoryFactory.setup(); + SVNRepositoryFactoryImpl.setup(); + FSRepositoryFactory.setup(); + + // non-optimized writing is fast enough on Linux/MacOS, and somewhat more reliable + if (SystemInfo.isWindows) { + SVNAdminArea14.setOptimizedWritingEnabled(true); + } + + if (! SVNJNAUtil.isJNAPresent()) { + LOG.warn("JNA is not found by svnkit library"); + } + initLogFilters(); + } + + private static Boolean booleanProperty(final String systemParameterName) { + return Boolean.valueOf(System.getProperty(systemParameterName)); + } + + public SvnVcs17(final Project project, MessageBus bus, SvnConfiguration17 svnConfiguration, final SvnLoadedBrachesStorage17 storage) { + super(project, VCS_NAME); + myLoadedBranchesStorage = storage; + LOG.debug("ct"); + myRootsToWorkingCopies = new RootsToWorkingCopies(myProject); + myConfiguration = svnConfiguration; + myAuthNotifier = new SvnAuthenticationNotifier(this); + + dumpFileStatus(FileStatus.ADDED); + dumpFileStatus(FileStatus.DELETED); + dumpFileStatus(FileStatus.MERGE); + dumpFileStatus(FileStatus.MODIFIED); + dumpFileStatus(FileStatus.NOT_CHANGED); + dumpFileStatus(FileStatus.UNKNOWN); + + dumpFileStatus(SvnFileStatus.REPLACED); + dumpFileStatus(SvnFileStatus.EXTERNAL); + dumpFileStatus(SvnFileStatus.OBSTRUCTED); + + myEntriesFileListener = new SvnEntriesFileListener(project); + + final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(project); + myAddConfirmation = vcsManager.getStandardConfirmation(VcsConfiguration.StandardConfirmation.ADD, this); + myDeleteConfirmation = vcsManager.getStandardConfirmation(VcsConfiguration.StandardConfirmation.REMOVE, this); + myCheckoutOptions = vcsManager.getStandardOption(VcsConfiguration.StandardOption.CHECKOUT, this); + + if (myProject.isDefault()) { + myChangeListListener = null; + } + else { + upgradeIfNeeded(bus); + + myChangeListListener = new SvnChangelistListener(myProject, createChangelistClient()); + + myVcsListener = new VcsListener() { + public void directoryMappingChanged() { + invokeRefreshSvnRoots(true); + } + }; + } + + myFrameStateListener = project.isDefault() ? null : new MyFrameStateListener(ChangeListManager.getInstance(project), + VcsDirtyScopeManager.getInstance(project)); + myWorkingCopiesContent = new WorkingCopiesContent(this); + + // remove used some time before old notification group ids + correctNotificationIds(); + } + + private void correctNotificationIds() { + boolean notEmpty = NotificationsConfigurationImpl.getNotificationsConfigurationImpl().isRegistered("SVN_NO_JNA") || + NotificationsConfigurationImpl.getNotificationsConfigurationImpl().isRegistered("SVN_NO_CRYPT32") || + NotificationsConfigurationImpl.getNotificationsConfigurationImpl().isRegistered("SubversionId"); + if (notEmpty) { + NotificationsConfigurationImpl.remove("SVN_NO_JNA", "SVN_NO_CRYPT32", "SubversionId"); + NotificationsConfiguration.getNotificationsConfiguration().register(getDisplayName(), NotificationDisplayType.BALLOON); + } + } + + public void postStartup() { + if (myProject.isDefault()) return; + myCopiesRefreshManager = new SvnCopiesRefreshManager(myProject, (SvnFileUrlMappingImpl17) getSvnFileUrlMapping()); + + invokeRefreshSvnRoots(true); + myWorkingCopiesContent.activate(); + } + + public void invokeRefreshSvnRoots(final boolean asynchronous) { + REFRESH_LOG.debug("refresh: ", new Throwable()); + if (myCopiesRefreshManager != null) { + if (asynchronous) { + myCopiesRefreshManager.getCopiesRefresh().asynchRequest(); + } else { + if (ApplicationManager.getApplication().isDispatchThread()) { + ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + public void run() { + myCopiesRefreshManager.getCopiesRefresh().synchRequest(); + } + }, SvnBundle.message("refreshing.working.copies.roots.progress.text"), true, myProject); + } else { + myCopiesRefreshManager.getCopiesRefresh().synchRequest(); + } + } + } + } + + @Override + public boolean checkImmediateParentsBeforeCommit() { + return true; + } + + private void upgradeIfNeeded(final MessageBus bus) { + final MessageBusConnection connection = bus.connect(); + connection.subscribe(ChangeListManagerImpl.LISTS_LOADED, new LocalChangeListsLoadedListener() { + public void processLoadedLists(final List lists) { + if (lists.isEmpty()) return; + SvnConfiguration17.SvnSupportOptions supportOptions = null; + try { + ChangeListManager.getInstance(myProject).setReadOnly(SvnChangeProvider.ourDefaultListName, true); + + supportOptions = myConfiguration.getSupportOptions(); + + upgradeToRecentVersion(supportOptions); + if (! supportOptions.changeListsSynchronized()) { + processChangeLists(lists); + } + } + catch (ProcessCanceledException e) { + // + } finally { + if (supportOptions != null) { + supportOptions.upgrade(); + } + } + + connection.disconnect(); + } + }); + } + + public void processChangeLists(final List lists) { + final ProjectLevelVcsManager plVcsManager = ProjectLevelVcsManager.getInstanceChecked(myProject); + plVcsManager.startBackgroundVcsOperation(); + try { + final SVNChangelistClient client = createChangelistClient(); + for (LocalChangeList list : lists) { + if (! list.isDefault()) { + final Collection changes = list.getChanges(); + for (Change change : changes) { + correctListForRevision(plVcsManager, change.getBeforeRevision(), client, list.getName()); + correctListForRevision(plVcsManager, change.getAfterRevision(), client, list.getName()); + } + } + } + } + finally { + final Application appManager = ApplicationManager.getApplication(); + if (appManager.isDispatchThread()) { + appManager.executeOnPooledThread(new Runnable() { + public void run() { + plVcsManager.stopBackgroundVcsOperation(); + } + }); + } else { + plVcsManager.stopBackgroundVcsOperation(); + } + } + } + + private void correctListForRevision(final ProjectLevelVcsManager plVcsManager, final ContentRevision revision, + final SVNChangelistClient client, final String name) { + if (revision != null) { + final FilePath path = revision.getFile(); + final AbstractVcs vcs = plVcsManager.getVcsFor(path); + if ((vcs != null) && VCS_NAME.equals(vcs.getName())) { + try { + client.doAddToChangelist(new File[] {path.getIOFile()}, SVNDepth.EMPTY, name, null); + } + catch (SVNException e) { + // left in default list + } + } + } + } + + private void upgradeToRecentVersion(final SvnConfiguration17.SvnSupportOptions supportOptions) { + if (! supportOptions.upgradeTo16Asked()) { + final SvnWorkingCopyChecker workingCopyChecker = new SvnWorkingCopyChecker(); + + if (workingCopyChecker.upgradeNeeded()) { + + Notifications.Bus.notify(new Notification(getDisplayName(), SvnBundle.message("upgrade.format.to16.question.title"), + "Old format Subversion working copies could be upgraded to version 1.6.", + NotificationType.INFORMATION, new NotificationListener() { + public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { + final int upgradeAnswer = Messages.showYesNoDialog(SvnBundle.message("upgrade.format.to16.question.text", + SvnBundle.message("label.where.svn.format.can.be.changed.text", SvnBundle.message("action.show.svn.map.text.reference"))), + SvnBundle.message("upgrade.format.to16.question.title"), Messages.getWarningIcon()); + if (DialogWrapper.OK_EXIT_CODE == upgradeAnswer) { + workingCopyChecker.doUpgrade(); + } + + notification.expire(); + } + })); + } + } + } + + @Override + public void activate() { + final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject); + if (! myProject.isDefault()) { + ChangeListManager.getInstance(myProject).addChangeListListener(myChangeListListener); + vcsManager.addVcsListener(myVcsListener); + } + + SvnApplicationSettings17.getInstance().svnActivated(); + VirtualFileManager.getInstance().addVirtualFileListener(myEntriesFileListener); + // this will initialize its inner listener for committed changes upload + LoadedRevisionsCache17.getInstance(myProject); + FrameStateManager.getInstance().addListener(myFrameStateListener); + + myAuthNotifier.init(); + mySvnBranchPointsCalculator = new SvnBranchPointsCalculator(myProject); + mySvnBranchPointsCalculator.activate(); + + if (SystemInfo.isWindows) { + if (! SVNJNAUtil.isJNAPresent()) { + Notifications.Bus.notify(new Notification(getDisplayName(), "Subversion plugin: no JNA", + "A problem with JNA initialization for svnkit library. Encryption is not available.", NotificationType.WARNING), NotificationDisplayType.BALLOON, myProject); + } else if (! SVNJNAUtil.isWinCryptEnabled()) { + Notifications.Bus.notify(new Notification(getDisplayName(), "Subversion plugin: no encryption", + "A problem with encryption module (Crypt32.dll) initialization for svnkit library. Encryption is not available.", NotificationType.WARNING), NotificationDisplayType.BALLOON, myProject); + } + } + + // do one time after project loaded + StartupManager.getInstance(myProject).runWhenProjectIsInitialized(new DumbAwareRunnable() { + public void run() { + postStartup(); + + // for IDEA, it takes 2 minutes - and anyway this can be done in background, no sence... + // once it could be mistaken about copies for 2 minutes on start... + + /*if (! myMapping.getAllWcInfos().isEmpty()) { + invokeRefreshSvnRoots(); + return; + } + ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + public void run() { + myCopiesRefreshManager.getCopiesRefresh().ensureInit(); + } + }, SvnBundle.message("refreshing.working.copies.roots.progress.text"), true, myProject);*/ + } + }); + + vcsManager.addVcsListener(myRootsToWorkingCopies); + + myLoadedBranchesStorage.activate(); + + VcsBalloonProblemNotifier.showOverChangesView(myProject, SvnBundle.message("svn17.plugin.not.ready"), MessageType.ERROR); + } + + private static void initLogFilters() { + if (ourLogFilters != null) return; + ourLogFilters = new RareLogger.LogFilter[] {new RareLogger.LogFilter() { + public Object getKey(@NotNull org.apache.log4j.Level level, + @NonNls String message, + @Nullable Throwable t, + @NonNls String... details) { + SVNException svnExc = null; + if (t instanceof SVNException) { + svnExc = (SVNException) t; + } else if (t instanceof VcsException && (t.getCause() instanceof SVNException)) { + svnExc = (SVNException) t.getCause(); + } + if (svnExc != null) { + // only filter a few cases + if (ourLogRarely.contains(svnExc.getErrorMessage().getErrorCode())) { + return svnExc.getErrorMessage().getErrorCode(); + } + } + return null; + } + @NotNull + public Integer getAllowedLoggingInterval(org.apache.log4j.Level level, String message, Throwable t, String[] details) { + SVNException svnExc = null; + if (t instanceof SVNException) { + svnExc = (SVNException) t; + } else if (t instanceof VcsException && (t.getCause() instanceof SVNException)) { + svnExc = (SVNException) t.getCause(); + } + if (svnExc != null) { + if (ourLogRarely.contains(svnExc.getErrorMessage().getErrorCode())) { + return ourLogRareInterval; + } else { + return ourLogUsualInterval; + } + } + return 0; + } + }}; + } + + public static Logger wrapLogger(final Logger logger) { + initLogFilters(); + return RareLogger.wrap(logger, Boolean.getBoolean("svn.logger.fairsynch"), ourLogFilters); + } + + public RootsToWorkingCopies getRootsToWorkingCopies() { + return myRootsToWorkingCopies; + } + + public SvnAuthenticationNotifier getAuthNotifier() { + return myAuthNotifier; + } + + @Override + public void deactivate() { + FrameStateManager.getInstance().removeListener(myFrameStateListener); + + final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject); + if (myVcsListener != null) { + vcsManager.removeVcsListener(myVcsListener); + } + + VirtualFileManager.getInstance().removeVirtualFileListener(myEntriesFileListener); + SvnApplicationSettings17.getInstance().svnDeactivated(); + new DefaultSVNRepositoryPool(null, null).shutdownConnections(true); + if (myCommittedChangesProvider != null) { + myCommittedChangesProvider.deactivate(); + } + if (myChangeListListener != null && (! myProject.isDefault())) { + ChangeListManager.getInstance(myProject).removeChangeListListener(myChangeListListener); + } + vcsManager.removeVcsListener(myRootsToWorkingCopies); + myRootsToWorkingCopies.clear(); + + myAuthNotifier.stop(); + myAuthNotifier.clear(); + + mySvnBranchPointsCalculator.deactivate(); + mySvnBranchPointsCalculator = null; + myWorkingCopiesContent.deactivate(); + myLoadedBranchesStorage.deactivate(); + } + + public VcsShowConfirmationOption getAddConfirmation() { + return myAddConfirmation; + } + + public VcsShowConfirmationOption getDeleteConfirmation() { + return myDeleteConfirmation; + } + + public VcsShowSettingOption getCheckoutOptions() { + return myCheckoutOptions; + } + + public EditFileProvider getEditFileProvider() { + if (myEditFilesProvider == null) { + myEditFilesProvider = new SvnEditFileProvider(this); + } + return myEditFilesProvider; + } + + @NotNull + public ChangeProvider getChangeProvider() { + if (myChangeProvider == null) { + myChangeProvider = new SvnChangeProvider(this); + } + return myChangeProvider; + } + + public SVNRepository createRepository(String url) throws SVNException { + SVNRepository repos = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(url)); + repos.setAuthenticationManager(myConfiguration.getAuthenticationManager(this)); + repos.setTunnelProvider(myConfiguration.getOptions(myProject)); + return repos; + } + + public SVNRepository createRepository(SVNURL url) throws SVNException { + SVNRepository repos = SVNRepositoryFactory.create(url); + repos.setAuthenticationManager(myConfiguration.getAuthenticationManager(this)); + repos.setTunnelProvider(myConfiguration.getOptions(myProject)); + return repos; + } + + public SVNUpdateClient createUpdateClient() { + return new SVNUpdateClient(myConfiguration.getAuthenticationManager(this), myConfiguration.getOptions(myProject)); + } + + public SVNStatusClient createStatusClient() { + return new SVNStatusClient(myConfiguration.getAuthenticationManager(this), myConfiguration.getOptions(myProject)); + } + + public SVNWCClient createWCClient() { + return new SVNWCClient(myConfiguration.getAuthenticationManager(this), myConfiguration.getOptions(myProject)); + } + + public SVNCopyClient createCopyClient() { + return new SVNCopyClient(myConfiguration.getAuthenticationManager(this), myConfiguration.getOptions(myProject)); + } + + public SVNMoveClient createMoveClient() { + return new SVNMoveClient(myConfiguration.getAuthenticationManager(this), myConfiguration.getOptions(myProject)); + } + + public SVNLogClient createLogClient() { + return new SVNLogClient(myConfiguration.getAuthenticationManager(this), myConfiguration.getOptions(myProject)); + } + + public SVNCommitClient createCommitClient() { + return new SVNCommitClient(myConfiguration.getAuthenticationManager(this), myConfiguration.getOptions(myProject)); + } + + public SVNDiffClient createDiffClient() { + return new SVNDiffClient(myConfiguration.getAuthenticationManager(this), myConfiguration.getOptions(myProject)); + } + + public SVNChangelistClient createChangelistClient() { + return new SVNChangelistClient(myConfiguration.getAuthenticationManager(this), myConfiguration.getOptions(myProject)); + } + + public SVNWCAccess createWCAccess() { + final SVNWCAccess access = SVNWCAccess.newInstance(null); + access.setOptions(myConfiguration.getOptions(myProject)); + return access; + } + + public ISVNOptions getSvnOptions() { + return myConfiguration.getOptions(myProject); + } + + public ISVNAuthenticationManager getSvnAuthenticationManager() { + return myConfiguration.getAuthenticationManager(this); + } + + void dumpFileStatus(FileStatus fs) { + if (LOG.isDebugEnabled()) { + LOG.debug("FileStatus:" + fs.getText() + " " + fs.getColor() + " " + " " + fs.getClass().getName()); + } + } + + public UpdateEnvironment getIntegrateEnvironment() { + if (mySvnIntegrateEnvironment == null) { + mySvnIntegrateEnvironment = new SvnIntegrateEnvironment17(this); + } + return mySvnIntegrateEnvironment; + } + + public UpdateEnvironment createUpdateEnvironment() { + if (mySvnUpdateEnvironment == null) { + mySvnUpdateEnvironment = new SvnUpdateEnvironment17(this); + } + return mySvnUpdateEnvironment; + } + + public String getDisplayName() { + LOG.debug("getDisplayName"); + return "Subversion 1.7 experimental"; + } + + public Configurable getConfigurable() { + LOG.debug("createConfigurable"); + return new SvnConfigurable(myProject); + } + + + public SvnConfiguration17 getSvnConfiguration() { + return myConfiguration; + } + + public static SvnVcs17 getInstance(Project project) { + return (SvnVcs17) ProjectLevelVcsManager.getInstance(project).findVcsByName(VCS_NAME); + } + + @NotNull + public CheckinEnvironment createCheckinEnvironment() { + if (myCheckinEnvironment == null) { + myCheckinEnvironment = new SvnCheckinEnvironment17(this); + } + return myCheckinEnvironment; + } + + @NotNull + public RollbackEnvironment createRollbackEnvironment() { + if (myRollbackEnvironment == null) { + myRollbackEnvironment = new SvnRollbackEnvironment17(this); + } + return myRollbackEnvironment; + } + + public VcsHistoryProvider getVcsHistoryProvider() { + // no heavy state, but it would be useful to have place to keep state in -> do not reuse instance + return new SvnHistoryProvider(this); + } + + public VcsHistoryProvider getVcsBlockHistoryProvider() { + return getVcsHistoryProvider(); + } + + public AnnotationProvider getAnnotationProvider() { + if (myAnnotationProvider == null) { + myAnnotationProvider = new SvnAnnotationProvider(this); + } + return myAnnotationProvider; + } + + public SvnEntriesFileListener getSvnEntriesFileListener() { + return myEntriesFileListener; + } + + public DiffProvider getDiffProvider() { + if (mySvnDiffProvider == null) { + mySvnDiffProvider = new SvnDiffProvider(this); + } + return mySvnDiffProvider; + } + + private Trinity getTimestampForPropertiesChange(final File ioFile, final boolean isDir) { + final File dir = isDir ? ioFile : ioFile.getParentFile(); + final String relPath = SVNAdminUtil.getPropPath(ioFile.getName(), isDir ? SVNNodeKind.DIR : SVNNodeKind.FILE, false); + final String relPathBase = SVNAdminUtil.getPropBasePath(ioFile.getName(), isDir ? SVNNodeKind.DIR : SVNNodeKind.FILE, false); + final String relPathRevert = SVNAdminUtil.getPropRevertPath(ioFile.getName(), isDir ? SVNNodeKind.DIR : SVNNodeKind.FILE, false); + return new Trinity(new File(dir, relPath).lastModified(), new File(dir, relPathBase).lastModified(), + new File(dir, relPathRevert).lastModified()); + } + + private boolean trinitiesEqual(final Trinity t1, final Trinity t2) { + if (t2.first == 0 && t2.second == 0 && t2.third == 0) return false; + return t1.equals(t2); + } + + @Nullable + public SVNPropertyValue getPropertyWithCaching(final VirtualFile file, final String propName) throws SVNException { + Map>> cachedMap = myPropertyCache.get(keyForVf(file)); + final Pair> cachedValue = (cachedMap == null) ? null : cachedMap.get(propName); + + final File ioFile = new File(file.getPath()); + final Trinity tsTrinity = getTimestampForPropertiesChange(ioFile, file.isDirectory()); + + if (cachedValue != null) { + // zero means that a file was not found + if (trinitiesEqual(cachedValue.getSecond(), tsTrinity)) { + return cachedValue.getFirst(); + } + } + + final SVNPropertyData value = createWCClient().doGetProperty(ioFile, propName, SVNRevision.WORKING, SVNRevision.WORKING); + final SVNPropertyValue propValue = (value == null) ? null : value.getValue(); + + if (cachedMap == null) { + cachedMap = new HashMap>>(); + myPropertyCache.put(keyForVf(file), cachedMap); + } + + cachedMap.put(propName, new Pair>(propValue, tsTrinity)); + + return propValue; + } + + public boolean fileExistsInVcs(FilePath path) { + File file = path.getIOFile(); + SVNStatus status; + try { + status = createStatusClient().doStatus(file, false); + if (status != null) { + if (svnStatusIs(status, SVNStatusType.STATUS_ADDED)) { + return status.isCopied(); + } + return !(SvnVcs17.svnStatusIsUnversioned(status) || + svnStatusIs(status, SVNStatusType.STATUS_IGNORED) || + svnStatusIs(status, SVNStatusType.STATUS_OBSTRUCTED)); + } + } + catch (SVNException e) { + // + } + return false; + } + + public static boolean svnStatusIsUnversioned(final SVNStatus status) { + return svnStatusIs(status, SVNStatusType.STATUS_UNVERSIONED); + } + + public static boolean svnStatusIs(final SVNStatus status, @NotNull final SVNStatusType value) { + return value.equals(status.getNodeStatus()) || value.equals(status.getContentsStatus()); + } + + public boolean fileIsUnderVcs(FilePath path) { + final ChangeListManager clManager = ChangeListManager.getInstance(myProject); + final VirtualFile file = path.getVirtualFile(); + if (file == null) { + return false; + } + return (! SvnStatusUtil.isIgnoredInAnySense(clManager, file)) && (! clManager.isUnversioned(file)); + } + + private static File getEntriesFile(File file) { + return file.isDirectory() ? new File(file, pathToEntries) : new File(file.getParentFile(), pathToEntries); + } + + private static File getDirPropsFile(File file) { + return new File(file, pathToDirProps); + } + + @Nullable + public SVNInfo getInfo(final VirtualFile file) { + try { + SVNWCClient wcClient = new SVNWCClient(getSvnAuthenticationManager(), getSvnOptions()); + SVNInfo info = wcClient.doInfo(new File(file.getPath()), SVNRevision.UNDEFINED); + if (info == null || info.getRepositoryRootURL() == null) { + info = wcClient.doInfo(new File(file.getPath()), SVNRevision.HEAD); + } + return info; + } + catch (SVNException e) { + return null; + } + } + + public static class SVNStatusHolder { + + private final SVNStatus myValue; + private final long myEntriesTimestamp; + private final long myFileTimestamp; + private final boolean myIsLocked; + + public SVNStatusHolder(long entriesStamp, long fileStamp, SVNStatus value) { + myValue = value; + myEntriesTimestamp = entriesStamp; + myFileTimestamp = fileStamp; + myIsLocked = value != null && value.isLocked(); + } + + public long getEntriesTimestamp() { + return myEntriesTimestamp; + } + + public long getFileTimestamp() { + return myFileTimestamp; + } + + public boolean isLocked() { + return myIsLocked; + } + + public SVNStatus getStatus() { + return myValue; + } + } + + public static class SVNInfoHolder { + + private final SVNInfo myValue; + private final long myEntriesTimestamp; + private final long myFileTimestamp; + + public SVNInfoHolder(long entriesStamp, long fileStamp, SVNInfo value) { + myValue = value; + myEntriesTimestamp = entriesStamp; + myFileTimestamp = fileStamp; + } + + public long getEntriesTimestamp() { + return myEntriesTimestamp; + } + + public long getFileTimestamp() { + return myFileTimestamp; + } + + public SVNInfo getInfo() { + return myValue; + } + } + + private static class JavaSVNDebugLogger extends SVNDebugLogAdapter { + private final boolean myLoggingEnabled; + private final Logger myLog; + @NonNls public static final String TRACE_LOG_PARAMETER_NAME = "javasvn.log.trace"; + + public JavaSVNDebugLogger(boolean loggingEnabled, Logger log) { + myLoggingEnabled = loggingEnabled; + myLog = log; + } + + public void log(final SVNLogType logType, final Throwable th, final Level logLevel) { + if (myLoggingEnabled) { + myLog.info(th); + } + } + + public void log(final SVNLogType logType, final String message, final Level logLevel) { + if (myLoggingEnabled) { + myLog.info(message); + } + } + + public void log(final SVNLogType logType, final String message, final byte[] data) { + if (myLoggingEnabled) { + if (data != null) { + try { + myLog.info(message + "\n" + new String(data, "UTF-8")); + } + catch (UnsupportedEncodingException e) { + myLog.info(message + "\n" + new String(data)); + } + } else { + myLog.info(message); + } + } + } + } + + public FileStatus[] getProvidedStatuses() { + return new FileStatus[]{SvnFileStatus.EXTERNAL, + SvnFileStatus.OBSTRUCTED, + SvnFileStatus.REPLACED}; + } + + + @Override @NotNull + public CommittedChangesProvider getCommittedChangesProvider() { + if (myCommittedChangesProvider == null) { + myCommittedChangesProvider = new SvnCommittedChangesProvider(myProject); + } + return myCommittedChangesProvider; + } + + @Nullable + @Override + public VcsRevisionNumber parseRevisionNumber(final String revisionNumberString) { + final SVNRevision revision = SVNRevision.parse(revisionNumberString); + if (revision.equals(SVNRevision.UNDEFINED)) { + return null; + } + return new SvnRevisionNumber(revision); + } + + @Override + public String getRevisionPattern() { + return ourIntegerPattern; + } + + @Override + public boolean isVersionedDirectory(final VirtualFile dir) { + return SvnUtil.seemsLikeVersionedDir(dir); + } + + @NotNull + public SvnFileUrlMapping17 getSvnFileUrlMapping() { + if (myMapping == null) { + myMapping = SvnFileUrlMappingImpl17.getInstance(myProject); + } + return myMapping; + } + + /** + * Returns real working copies roots - if there is -> Subversion setting, + * and there is one working copy, will return one root + */ + public List getAllWcInfos() { + final SvnFileUrlMapping17 urlMapping = getSvnFileUrlMapping(); + + final List infoList = urlMapping.getAllWcInfos(); + final List infos = new ArrayList(); + for (RootUrlInfo info : infoList) { + final File file = info.getIoFile(); + infos.add(new WCInfo(file.getAbsolutePath(), info.getAbsoluteUrlAsUrl(), + SvnFormatSelector.getWorkingCopyFormat(file), info.getRepositoryUrl(), SvnUtil.isWorkingCopyRoot(file), info.getType(), + SvnUtil.getDepth(this, file))); + } + return infos; + } + + private class SvnWorkingCopyChecker { + private List myAllWcInfos; + + public boolean upgradeNeeded() { + myAllWcInfos = getAllWcInfos(); + for (WCInfo info : myAllWcInfos) { + if (! WorkingCopyFormat.ONE_DOT_SIX.equals(info.getFormat())) { + return true; + } + } + return false; + } + + public void doUpgrade() { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + final SvnFormatWorker formatWorker = new SvnFormatWorker(myProject, WorkingCopyFormat.ONE_DOT_SIX, myAllWcInfos); + // additionally ask about working copies with roots above the project root + formatWorker.checkForOutsideCopies(); + if (formatWorker.haveStuffToConvert()) { + ProgressManager.getInstance().run(formatWorker); + } + } + }); + } + } + + @Override + public RootsConvertor getCustomConvertor() { + if (myProject.isDefault()) return null; + return getSvnFileUrlMapping(); + } + + @Override + public MergeProvider getMergeProvider() { + if (myMergeProvider == null) { + myMergeProvider = new SvnMergeProvider(myProject); + } + return myMergeProvider; + } + + @Override + public List getAdditionalActionsForLocalChange() { + return Arrays.asList(new ShowPropertiesDiffWithLocalAction()); + } + + private String keyForVf(final VirtualFile vf) { + return vf.getUrl(); + } + + @Override + public boolean allowsNestedRoots() { + return SvnConfiguration17.getInstance(myProject).DETECT_NESTED_COPIES; + } + + @Override + public List filterUniqueRoots(final List in, final Convertor convertor) { + if (in.size() <= 1) return in; + + final List> infos = new ArrayList>(in.size()); + final SvnFileUrlMappingImpl17 mapping = (SvnFileUrlMappingImpl17) getSvnFileUrlMapping(); + final List notMatched = new LinkedList(); + for (S s : in) { + final VirtualFile vf = convertor.convert(s); + if (vf == null) continue; + + final File ioFile = new File(vf.getPath()); + SVNURL url = mapping.getUrlForFile(ioFile); + if (url == null) { + url = SvnUtil.getUrl(ioFile); + if (url == null) { + notMatched.add(s); + continue; + } + } + infos.add(new MyPair(vf, url.toString(), s)); + } + final List> filtered = new ArrayList>(infos.size()); + ForNestedRootChecker.filterOutSuperfluousChildren(this, infos, filtered); + + final List converted = ObjectsConvertor.convert(filtered, new Convertor, S>() { + public S convert(final MyPair o) { + return o.getSrc(); + } + }); + if (! notMatched.isEmpty()) { + // potential bug is here: order is not kept. but seems it only occurs for cases where result is sorted after filtering so ok + converted.addAll(notMatched); + } + return converted; + } + + private static class MyPair implements RootUrlPair { + private final VirtualFile myFile; + private final String myUrl; + private final T mySrc; + + private MyPair(VirtualFile file, String url, T src) { + myFile = file; + myUrl = url; + mySrc = src; + } + + public T getSrc() { + return mySrc; + } + + public VirtualFile getVirtualFile() { + return myFile; + } + + public String getUrl() { + return myUrl; + } + } + + private static class MyFrameStateListener implements FrameStateListener { + private final ChangeListManager myClManager; + private final VcsDirtyScopeManager myDirtyScopeManager; + + private MyFrameStateListener(ChangeListManager clManager, VcsDirtyScopeManager dirtyScopeManager) { + myClManager = clManager; + myDirtyScopeManager = dirtyScopeManager; + } + + public void onFrameDeactivated() { + } + + public void onFrameActivated() { + final List folders = ((ChangeListManagerImpl)myClManager).getLockedFolders(); + if (! folders.isEmpty()) { + myDirtyScopeManager.filesDirty(null, folders); + } + } + } + + public static VcsKey getKey() { + return ourKey; + } + + @Override + public boolean isVcsBackgroundOperationsAllowed(VirtualFile root) { + return ThreeState.YES.equals(myAuthNotifier.isAuthenticatedFor(root)); + } + + public SvnBranchPointsCalculator getSvnBranchPointsCalculator() { + return mySvnBranchPointsCalculator; + } + + @Override + public boolean areDirectoriesVersionedItems() { + return true; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnWCRootCrawler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnWCRootCrawler.java new file mode 100644 index 000000000000..36fd914ec346 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnWCRootCrawler.java @@ -0,0 +1,27 @@ +/* + * 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.ProgressIndicator; + +import java.io.File; + +/** + * @author alex + */ +public interface SvnWCRootCrawler { + void handleWorkingCopyRoot(File root, ProgressIndicator progress); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnWorkingCopyFormatHolder.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnWorkingCopyFormatHolder.java new file mode 100644 index 000000000000..a1a4f5444b6a --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnWorkingCopyFormatHolder.java @@ -0,0 +1,31 @@ +/* + * 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 SvnWorkingCopyFormatHolder { + private static final ThreadLocal myPresetFormat = new ThreadLocal(); + + private SvnWorkingCopyFormatHolder() { + } + + public static WorkingCopyFormat getPresetFormat() { + return myPresetFormat.get(); + } + + public static void setPresetFormat(final WorkingCopyFormat value) { + myPresetFormat.set(value); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/WorkingCopiesContent.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/WorkingCopiesContent.java new file mode 100644 index 000000000000..e61105e8b9f3 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/WorkingCopiesContent.java @@ -0,0 +1,50 @@ +/* + * 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.vcs.changes.ui.ChangesViewContentI; +import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager; +import com.intellij.ui.content.Content; +import com.intellij.ui.content.ContentFactory; +import org.jetbrains.idea.svn17.dialogs.CopiesPanel; + +public class WorkingCopiesContent { + private final SvnVcs17 myVcs; + private Content myShownContent; + + public WorkingCopiesContent(SvnVcs17 vcs) { + myVcs = vcs; + } + + public void activate() { + final ChangesViewContentI cvcm = ChangesViewContentManager.getInstance(myVcs.getProject()); + final ContentFactory contentFactory = ContentFactory.SERVICE.getInstance(); + + final CopiesPanel copiesPanel = new CopiesPanel(myVcs.getProject()); + myShownContent = contentFactory.createContent(copiesPanel.getComponent(), SvnBundle.message("dialog.show.svn.map.title"), true); + myShownContent.setCloseable(false); + cvcm.addContent(myShownContent); + myShownContent.setPreferredFocusableComponent(copiesPanel.getPreferredFocusedComponent()); + } + + public void deactivate() { + if (myShownContent != null) { + final ChangesViewContentI cvcm = ChangesViewContentManager.getInstance(myVcs.getProject()); + cvcm.removeContent(myShownContent); + myShownContent = null; + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/WorkingCopy.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/WorkingCopy.java new file mode 100644 index 000000000000..41967444a20e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/WorkingCopy.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 org.tmatesoft.svn.core.SVNURL; + +import java.io.File; + +public class WorkingCopy { + private final File myFile; + private final SVNURL myUrl; + + public WorkingCopy(File file, SVNURL url) { + myFile = file; + myUrl = url; + } + + public File getFile() { + return myFile; + } + + public SVNURL getUrl() { + return myUrl; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + WorkingCopy that = (WorkingCopy)o; + + if (!myFile.equals(that.myFile)) return false; + + return true; + } + + @Override + public int hashCode() { + return myFile.hashCode(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/WorkingCopyFormat.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/WorkingCopyFormat.java new file mode 100644 index 000000000000..b47ecaafa4b2 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/WorkingCopyFormat.java @@ -0,0 +1,103 @@ +/* + * 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; + +/** + * since not all constants are available from svnkit & constants are fixed + */ +public enum WorkingCopyFormat { + ONE_DOT_THREE(4, false, false, false, SvnBundle.message("dialog.show.svn.map.table.version13.text")), + ONE_DOT_FOUR(8, false, false, false, SvnBundle.message("dialog.show.svn.map.table.version14.text")), + ONE_DOT_FIVE(9, true, true, false, SvnBundle.message("dialog.show.svn.map.table.version15.text")), + ONE_DOT_SIX(10, true, true, true, SvnBundle.message("dialog.show.svn.map.table.version16.text")), + ONE_DOT_SEVEN(12, true, true, true, SvnBundle.message("dialog.show.svn.map.table.version17.text")), + UNKNOWN(0, false, false, false, "unknown"); + + private final int myFormat; + private final boolean myChangelistSupport; + private final boolean myMergeInfoSupport; + private final boolean myTreeConflictSupport; + private final String myName; + + private WorkingCopyFormat(final int format, boolean changelistSupport, boolean mergeInfoSupport, boolean treeConflictSupport, String name) { + myFormat = format; + myChangelistSupport = changelistSupport; + myMergeInfoSupport = mergeInfoSupport; + myTreeConflictSupport = treeConflictSupport; + myName = name; + } + + public boolean supportsChangelists() { + return myChangelistSupport; + } + + public boolean supportsMergeInfo() { + return myMergeInfoSupport; + } + + public boolean supportsTreeConflicts() { + return myTreeConflictSupport; + } + + public String getName() { + return myName; + } + + public static WorkingCopyFormat getInstance(final int value) { + if (ONE_DOT_FIVE.getFormat() == value) { + return ONE_DOT_FIVE; + } else if (ONE_DOT_FOUR.getFormat() == value) { + return ONE_DOT_FOUR; + } else if (ONE_DOT_THREE.getFormat() == value) { + return ONE_DOT_THREE; + } else if (ONE_DOT_SIX.getFormat() == value) { + return ONE_DOT_SIX; + } else if (ONE_DOT_SEVEN.getFormat() == value) { + return ONE_DOT_SEVEN; + } + return UNKNOWN; + } + + public static WorkingCopyFormat getInstance(final String updateOption) { + if (SvnConfiguration17.UPGRADE_AUTO_17.equals(updateOption)) { + return ONE_DOT_SEVEN; + } else if (SvnConfiguration17.UPGRADE_AUTO_16.equals(updateOption)) { + return ONE_DOT_SIX; + } else if (SvnConfiguration17.UPGRADE_AUTO_15.equals(updateOption)) { + return ONE_DOT_FIVE; + } else if (SvnConfiguration17.UPGRADE_AUTO.equals(updateOption)) { + return ONE_DOT_FOUR; + } + return ONE_DOT_THREE; + } + + public int getFormat() { + return myFormat; + } + + public String getOption() { + if (ONE_DOT_SEVEN.equals(this)) { + return SvnConfiguration17.UPGRADE_AUTO_17; + } else if (ONE_DOT_SIX.equals(this)) { + return SvnConfiguration17.UPGRADE_AUTO_16; + } else if (ONE_DOT_FIVE.equals(this)) { + return SvnConfiguration17.UPGRADE_AUTO_15; + } else if (ONE_DOT_FOUR.equals(this)) { + return SvnConfiguration17.UPGRADE_AUTO; + } + return SvnConfiguration17.UPGRADE_NONE; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/Wrapper.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/Wrapper.java new file mode 100644 index 000000000000..608ce58d5755 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/Wrapper.java @@ -0,0 +1,28 @@ +/* + * 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 Wrapper { + private final T myT; + + public Wrapper(T t) { + myT = t; + } + + public T getT() { + return myT; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/AbstractIntegrateChangesAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/AbstractIntegrateChangesAction.java new file mode 100644 index 000000000000..123995ebcbfb --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/AbstractIntegrateChangesAction.java @@ -0,0 +1,105 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.changes.committed.CommittedChangesBrowserUseCase; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.integrate.MergerFactory; +import org.jetbrains.idea.svn17.integrate.SelectedCommittedStuffChecker; +import org.jetbrains.idea.svn17.integrate.SvnIntegrateChangesActionPerformer; + +public abstract class AbstractIntegrateChangesAction extends AnAction implements DumbAware { + private final boolean myCheckUseCase; + + protected AbstractIntegrateChangesAction(final boolean checkUseCase) { + myCheckUseCase = checkUseCase; + } + + @NotNull + protected abstract MergerFactory createMergerFactory(final T checker); + @NotNull + protected abstract T createChecker(); + + public final void update(final AnActionEvent e) { + final Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext()); + final CommittedChangesBrowserUseCase useCase = CommittedChangesBrowserUseCase.DATA_KEY.getData(e.getDataContext()); + final Presentation presentation = e.getPresentation(); + + if ((project == null) || (myCheckUseCase) && ((useCase == null) || (! CommittedChangesBrowserUseCase.COMMITTED.equals(useCase)))) { + presentation.setEnabled(false); + presentation.setVisible(false); + return; + } + + presentation.setText(SvnBundle.message("action.Subversion.integrate.changes.actionname")); + presentation.setDescription(SvnBundle.message("action.Subversion.integrate.changes.description")); + + final T checker = createChecker(); + checker.execute(e); + + presentation.setVisible(true); + presentation.setEnabled(checker.isValid()); + + if (presentation.isVisible() && presentation.isEnabled() && + ProjectLevelVcsManager.getInstance(project).isBackgroundVcsOperationRunning()) { + presentation.setEnabled(false); + } + + updateWithChecker(e, checker); + } + + protected void updateWithChecker(final AnActionEvent e, SelectedCommittedStuffChecker checker) { + } + + @Nullable + protected abstract String getSelectedBranchUrl(SelectedCommittedStuffChecker checker); + @Nullable + protected abstract String getSelectedBranchLocalPath(SelectedCommittedStuffChecker checker); + @Nullable + protected abstract String getDialogTitle(); + + public void actionPerformed(final AnActionEvent e) { + final DataContext dataContext = e.getDataContext(); + final Project project = PlatformDataKeys.PROJECT.getData(dataContext); + + final T checker = createChecker(); + checker.execute(e); + + if (! checker.isValid()) { + Messages.showErrorDialog(SvnBundle.message("action.Subversion.integrate.changes.error.no.available.files.text"), + SvnBundle.message("action.Subversion.integrate.changes.messages.title")); + return; + } + + final SvnIntegrateChangesActionPerformer changesActionPerformer = + new SvnIntegrateChangesActionPerformer(project, checker.getSameBranch(), createMergerFactory(checker)); + + final String selectedBranchUrl = getSelectedBranchUrl(checker); + if (selectedBranchUrl == null) { + SelectBranchPopup.showForBranchRoot(project, checker.getRoot(), changesActionPerformer, + SvnBundle.message("action.Subversion.integrate.changes.select.branch.text")); + } else { + changesActionPerformer.onBranchSelected(selectedBranchUrl, getSelectedBranchLocalPath(checker), getDialogTitle()); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/AbstractShowPropertiesDiffAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/AbstractShowPropertiesDiffAction.java new file mode 100644 index 000000000000..0bca0eab303e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/AbstractShowPropertiesDiffAction.java @@ -0,0 +1,281 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.diff.DiffManager; +import com.intellij.openapi.diff.SimpleContent; +import com.intellij.openapi.diff.SimpleDiffRequest; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsDataKeys; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangesUtil; +import com.intellij.openapi.vcs.changes.ContentRevision; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnRevisionNumber; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.history.SvnRepositoryContentRevision; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNPropertyValue; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.ISVNPropertyHandler; +import org.tmatesoft.svn.core.wc.SVNPropertyData; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import javax.swing.*; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +abstract class AbstractShowPropertiesDiffAction extends AnAction implements DumbAware { + protected AbstractShowPropertiesDiffAction(String name) { + super(name); + } + + protected abstract DataKey getChangesKey(); + @Nullable + protected abstract SVNRevision getBeforeRevisionValue(final Change change, final SvnVcs17 vcs) throws SVNException; + @Nullable + protected abstract SVNRevision getAfterRevisionValue(final Change change, final SvnVcs17 vcs) throws SVNException; + + @Override + public void update(final AnActionEvent e) { + final DataContext dataContext = e.getDataContext(); + final Project project = PlatformDataKeys.PROJECT.getData(dataContext); + final Change[] changes = e.getData(getChangesKey()); + + final Presentation presentation = e.getPresentation(); + presentation.setVisible(VcsDataKeys.CHANGES.getData(dataContext) != null); + presentation.setEnabled(enabled(project, changes)); + } + + private boolean enabled(final Project project, final Change[] changes) { + final boolean noChange = (project == null) || (changes == null) || (changes.length != 1); + if (noChange) { + return false; + } else { + final Change change = changes[0]; + + final ContentRevision revision = (change.getBeforeRevision() != null) ? change.getBeforeRevision() : change.getAfterRevision(); + if ((revision == null) || (! (revision.getRevisionNumber() instanceof SvnRevisionNumber))) { + return false; + } + + return checkVcs(project, change); + } + } + + protected boolean checkVcs(final Project project, final Change change) { + final VirtualFile virtualFile = ChangesUtil.getFilePath(change).getVirtualFile(); + if (virtualFile == null) { + return false; + } + final AbstractVcs vcs = ChangesUtil.getVcsForFile(virtualFile, project); + return (vcs != null) && SvnVcs17.VCS_NAME.equals(vcs.getName()); + } + + public void actionPerformed(final AnActionEvent e) { + final DataContext dataContext = e.getDataContext(); + final Project project = PlatformDataKeys.PROJECT.getData(dataContext); + final Change[] changes = e.getData(getChangesKey()); + + if (! enabled(project, changes)) { + return; + } + + final Change change = changes[0]; + + final CalculateAndShow worker = new CalculateAndShow(project, change, e.getPresentation().getText()); + ProgressManager.getInstance().run(worker); + } + + private class CalculateAndShow extends Task.Backgroundable { + private final Change myChange; + private String myBeforeContent; + private String myAfterContent; + private SVNRevision myBeforeRevisionValue; + private SVNRevision myAfterRevision; + private SVNException myException; + private final String myErrorTitle; + + private CalculateAndShow(@Nullable final Project project, final Change change, final String errorTitle) { + super(project, SvnBundle.message("fetching.properties.contents.progress.title"), true, Backgroundable.DEAF); + myChange = change; + myErrorTitle = errorTitle; + } + + public void run(@NotNull final ProgressIndicator indicator) { + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final SVNWCClient client = vcs.createWCClient(); + + try { + myBeforeRevisionValue = getBeforeRevisionValue(myChange, vcs); + myAfterRevision = getAfterRevisionValue(myChange, vcs); + + myBeforeContent = getPropertyList(myChange.getBeforeRevision(), myBeforeRevisionValue, client); + indicator.checkCanceled(); + // gets exactly WORKING revision property + myAfterContent = getPropertyList(myChange.getAfterRevision(), myAfterRevision, client); + } + catch (SVNException exc) { + myException = exc; + } + + // since sometimes called from modal dialog (commit changes dialog) + SwingUtilities.invokeLater(new Runnable() { + public void run() { + if (myException != null) { + Messages.showErrorDialog(myException.getMessage(), myErrorTitle); + return; + } + if (myBeforeContent != null && myAfterContent != null && myBeforeRevisionValue != null && myAfterRevision != null) { + final SimpleDiffRequest diffRequest = new SimpleDiffRequest(myProject, getDiffWindowTitle(myChange)); + if (compareRevisions(myBeforeRevisionValue, myAfterRevision) >= 0) { + // before ahead + diffRequest.setContents(new SimpleContent(myAfterContent), new SimpleContent(myBeforeContent)); + diffRequest.setContentTitles(revisionToString(myAfterRevision), revisionToString(myBeforeRevisionValue)); + } else { + diffRequest.setContents(new SimpleContent(myBeforeContent), new SimpleContent(myAfterContent)); + diffRequest.setContentTitles(revisionToString(myBeforeRevisionValue), revisionToString(myAfterRevision)); + } + DiffManager.getInstance().getDiffTool().show(diffRequest); + } + } + }); + } + } + + private String getDiffWindowTitle(final Change change) { + if (change.isMoved() || change.isRenamed()) { + final FilePath beforeFilePath = ChangesUtil.getBeforePath(change); + final FilePath afterFilePath = ChangesUtil.getAfterPath(change); + + final String beforePath = beforeFilePath == null ? "" : beforeFilePath.getIOFile().getAbsolutePath(); + final String afterPath = afterFilePath == null ? "" : afterFilePath.getIOFile().getAbsolutePath(); + return SvnBundle.message("action.Subversion.properties.difference.diff.for.move.title", beforePath, afterPath); + } else { + return SvnBundle.message("action.Subversion.properties.difference.diff.title", ChangesUtil.getFilePath(change).getIOFile().getAbsolutePath()); + } + } + + private int compareRevisions(@NonNls final SVNRevision revision1, @NonNls final SVNRevision revision2) { + if (revision1.equals(revision2)) { + return 0; + } + // working(local) ahead of head + if (SVNRevision.WORKING.equals(revision1)) { + return 1; + } + if (SVNRevision.WORKING.equals(revision2)) { + return -1; + } + if (SVNRevision.HEAD.equals(revision1)) { + return 1; + } + if (SVNRevision.HEAD.equals(revision2)) { + return -1; + } + return revision1.getNumber() > revision2.getNumber() ? 1 : -1; + } + + private String revisionToString(final SVNRevision revision) { + if (revision == null) { + return "not exists"; + } + return revision.toString(); + } + + private final static String ourPropertiesDelimiter = "\n"; + + private String getPropertyList(final ContentRevision contentRevision, final SVNRevision revision, final SVNWCClient client) throws SVNException { + if (contentRevision == null) { + return ""; + } + + final StringBuilder sb = new StringBuilder(); + final List lines = new ArrayList(); + + final File ioFile = contentRevision.getFile().getIOFile(); + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (indicator != null) { + indicator.checkCanceled(); + indicator.setText(SvnBundle.message("show.properties.diff.progress.text.revision.information", revision.toString())); + } + + final ISVNPropertyHandler propertyHandler = new ISVNPropertyHandler() { + public void handleProperty(final File path, final SVNPropertyData property) throws SVNException { + if (indicator != null) { + indicator.checkCanceled(); + indicator.setText2(SvnBundle.message("show.properties.diff.progress.text2.property.information", property.getName())); + } + lines.add(property); + } + + public void handleProperty(final SVNURL url, final SVNPropertyData property) throws SVNException { + if (indicator != null) { + indicator.checkCanceled(); + indicator.setText2(SvnBundle.message("show.properties.diff.progress.text2.property.information", property.getName())); + } + lines.add(property); + } + + public void handleProperty(final long revision, final SVNPropertyData property) throws SVNException { + // revision properties here + } + }; + + if (contentRevision instanceof SvnRepositoryContentRevision) { + final SvnRepositoryContentRevision svnRevision = (SvnRepositoryContentRevision) contentRevision; + client.doGetProperty(SVNURL.parseURIEncoded(svnRevision.getFullPath()), null, revision, revision, SVNDepth.EMPTY, propertyHandler); + } else { + client.doGetProperty(ioFile, null, revision, revision, SVNDepth.EMPTY, propertyHandler, null); + } + + Collections.sort(lines, new Comparator() { + public int compare(final SVNPropertyData o1, final SVNPropertyData o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + for (SVNPropertyData line : lines) { + addPropertyPresentation(line, sb); + } + + return sb.toString(); + } + + private void addPropertyPresentation(final SVNPropertyData property, final StringBuilder sb) { + if (sb.length() != 0) { + sb.append(ourPropertiesDelimiter); + } + sb.append(property.getName()).append("=").append((property.getValue() == null) ? "" : SVNPropertyValue.getPropertyAsString(property.getValue())); + } + +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/AddAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/AddAction.java new file mode 100644 index 000000000000..bf2b9ec0e441 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/AddAction.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.actions; + +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnStatusUtil; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.checkin.SvnCheckinEnvironment17; +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.SVNEvent; +import org.tmatesoft.svn.core.wc.SVNEventAction; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +public class AddAction extends BasicAction { + static final Logger log = Logger.getInstance("org.jetbrains.idea.svn.action.AddAction"); + + protected String getActionName(AbstractVcs vcs) { + log.debug("enter: getActionName"); + return SvnBundle.message("action.name.add.files", vcs.getName()); + } + + protected boolean isEnabled(Project project, SvnVcs17 vcs, VirtualFile file) { + return SvnStatusUtil.fileCanBeAdded(project, file); + } + + protected boolean needsFiles() { + return true; + } + + protected void batchPerform(final Project project, SvnVcs17 activeVcs, VirtualFile[] files, DataContext context) + throws VcsException { + log.debug("enter: batchPerform"); + + SvnVcs17 vcs = SvnVcs17.getInstance(project); + SVNWCClient wcClient = vcs.createWCClient(); + wcClient.setEventHandler(new AddEventListener(project)); + + Collection exceptions = SvnCheckinEnvironment17.scheduleUnversionedFilesForAddition(wcClient, Arrays.asList(files), true); + if (! exceptions.isEmpty()) { + final Collection messages = new ArrayList(exceptions.size()); + for (SVNException exception : exceptions) { + messages.add(exception.getMessage()); + } + throw new VcsException(messages); + } + } + + protected boolean isBatchAction() { + log.debug("enter: isBatchAction"); + return true; + } + + protected void perform(Project project, SvnVcs17 activeVcs, VirtualFile file, DataContext context) + throws VcsException { + try { + SVNWCClient wcClient = activeVcs.createWCClient(); + wcClient.setEventHandler(new AddEventListener(project)); + wcClient.doAdd(new File(file.getPath()), false, false, true, true); + } + catch (SVNException e) { + VcsException ve = new VcsException(e); + ve.setVirtualFile(file); + throw ve; + } + } + + private static class AddEventListener implements ISVNEventHandler { + private final Project myProject; + + public AddEventListener(Project project) { + myProject = project; + } + + public void handleEvent(SVNEvent event, double progress) { + if (event.getAction() == SVNEventAction.ADD && event.getFile() != null) { + VirtualFile vfile = VirtualFileManager.getInstance() + .findFileByUrl("file://" + event.getFile().getAbsolutePath().replace(File.separatorChar, '/')); + if (vfile != null) { + VcsDirtyScopeManager.getInstance(myProject).fileDirty(vfile); + } + } + } + + public void checkCancelled() throws SVNCancelException { + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/AddToIgnoreListAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/AddToIgnoreListAction.java new file mode 100644 index 000000000000..4f31a6137522 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/AddToIgnoreListAction.java @@ -0,0 +1,88 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.ignore.IgnoreInfoGetter; +import org.jetbrains.idea.svn17.ignore.SvnPropertyService; + +public class AddToIgnoreListAction extends BasicAction { + private String myActionName; + private final boolean myUseCommonExtension; + private final IgnoreInfoGetter myInfoGetter; + + public AddToIgnoreListAction(final IgnoreInfoGetter infoGetter, final boolean useCommonExtension) { + myInfoGetter = infoGetter; + myUseCommonExtension = useCommonExtension; + } + + public void setActionText(final String name) { + myActionName = name; + } + + protected String getActionName(final AbstractVcs vcs) { + return SvnBundle.message("action.name.ignore.files"); + } + + public void update(final AnActionEvent e) { + final Presentation presentation = e.getPresentation(); + presentation.setVisible(true); + presentation.setEnabled(true); + + presentation.setText(myActionName); + presentation.setDescription(SvnBundle.message((myUseCommonExtension) ? "action.Subversion.Ignore.MatchExtension.description" : + "action.Subversion.Ignore.ExactMatch.description", myActionName)); + } + + @Override + protected void doVcsRefresh(Project project, VirtualFile file) { + final VcsDirtyScopeManager vcsDirtyScopeManager = VcsDirtyScopeManager.getInstance(project); + if (file != null && (file.getParent() != null)) { + vcsDirtyScopeManager.fileDirty(file.getParent()); + } + } + + protected boolean isEnabled(final Project project, final SvnVcs17 vcs, final VirtualFile file) { + return true; + } + + protected boolean needsFiles() { + return true; + } + + protected void perform(final Project project, final SvnVcs17 activeVcs, final VirtualFile file, final DataContext context) + throws VcsException { + + } + + protected void batchPerform(final Project project, final SvnVcs17 activeVcs, final VirtualFile[] file, final DataContext context) + throws VcsException { + SvnPropertyService.doAddToIgnoreProperty(activeVcs, project, myUseCommonExtension, file, myInfoGetter); + } + + protected boolean isBatchAction() { + return true; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/BasicAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/BasicAction.java new file mode 100644 index 000000000000..f83191b22364 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/BasicAction.java @@ -0,0 +1,233 @@ +/* + * 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.actions; + +import com.intellij.history.LocalHistory; +import com.intellij.history.LocalHistoryAction; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.*; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn17.SvnVcs17; + +import java.util.Arrays; +import java.util.List; + +public abstract class BasicAction extends AnAction implements DumbAware { + private static final Logger LOG = Logger.getInstance("org.jetbrains.idea.svn.actions.BasicAction"); + + public void actionPerformed(AnActionEvent event) { + if (LOG.isDebugEnabled()) { + LOG.debug("enter: actionPerformed(id='" + ActionManager.getInstance().getId(this) + "')"); + } + final DataContext dataContext = event.getDataContext(); + final Project project = PlatformDataKeys.PROJECT.getData(dataContext); + + final VirtualFile[] files = PlatformDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext); + if (LOG.isDebugEnabled() && files != null) { + LOG.debug("files='" + Arrays.asList(files) + "'"); + } + if ((files == null || files.length == 0) && needsFiles()) return; + + final SvnVcs17 vcs = SvnVcs17.getInstance(project); + if (!ProjectLevelVcsManager.getInstance(project).checkAllFilesAreUnder(vcs, files)) { + return; + } + + if (project != null) { + project.save(); + } + + final String actionName = getActionName(vcs); + + final AbstractVcsHelper helper = AbstractVcsHelper.getInstance(project); + LocalHistoryAction action = LocalHistoryAction.NULL; + if (actionName != null) { + action = LocalHistory.getInstance().startAction(actionName); + } + + try { + List exceptions = helper.runTransactionRunnable(vcs, new TransactionRunnable() { + public void run(List exceptions) { + VirtualFile badFile = null; + try { + if (isBatchAction()) { + batchExecute(project, vcs, files, dataContext, helper); + } + else { + for (int i = 0; files != null && i < files.length; i++) { + VirtualFile file = files[i]; + badFile = file; + execute(project, vcs, file, dataContext, helper); + } + } + } + catch (VcsException ex) { + ex.setVirtualFile(badFile); + exceptions.add(ex); + } + } + }, null); + + helper.showErrors(exceptions, actionName != null ? actionName : vcs.getName()); + } + finally { + action.finish(); + } + } + + public void update(AnActionEvent e) { + //LOG.debug("enter: update class:"+getClass().getName()); + super.update(e); + + Presentation presentation = e.getPresentation(); + final DataContext dataContext = e.getDataContext(); + + Project project = PlatformDataKeys.PROJECT.getData(dataContext); + if (project == null) { + presentation.setEnabled(false); + presentation.setVisible(false); + return; + } + + + if (!needsFiles()) { + presentation.setEnabled(true); + presentation.setVisible(true); + return; + } + + VirtualFile[] files = PlatformDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext); + if (files == null || files.length == 0) { + presentation.setEnabled(false); + presentation.setVisible(true); + return; + } + + SvnVcs17 vcs = SvnVcs17.getInstance(project); + if (!ProjectLevelVcsManager.getInstance(project).checkAllFilesAreUnder(vcs, files)) { + presentation.setEnabled(false); + presentation.setVisible(true); + return; + } + + boolean enabled = true; + if (!needsAllFiles()) { + enabled = false; + } + + LOG.debug(getClass().getName() + (enabled ? " needsAllFiles" : " needsSingleFile")); + for (int i = 0; i < files.length; i++) { + VirtualFile file = files[i]; + boolean fileEnabled = false; + try { + fileEnabled = isEnabled(project, vcs, file); + } + catch (Throwable t) { + LOG.debug(t); + + } + LOG.debug("file:" + file.getPath() + (fileEnabled ? " is enabled" : " is not enabled")); + if (needsAllFiles()) { + if (!fileEnabled) { + LOG.debug("now disabled"); + enabled = false; + break; + } + } + else { + if (fileEnabled) { + LOG.debug("now enabled"); + enabled = true; + } + } + } + + presentation.setEnabled(enabled); + presentation.setVisible(true); + } + + protected boolean needsAllFiles() { + return needsFiles(); + } + + protected void execute(Project project, + final SvnVcs17 activeVcs, + final VirtualFile file, + DataContext context, + AbstractVcsHelper helper) throws VcsException { + if (file.isDirectory()) { + perform(project, activeVcs, file, context); + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + file.refresh(false, true); + } + }); + + doVcsRefresh(project, file); + } + else { + perform(project, activeVcs, file, context); + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + file.refresh(false, true); + } + }); + doVcsRefresh(project, file); + } + } + + protected void doVcsRefresh(final Project project, final VirtualFile file) { + VcsDirtyScopeManager.getInstance(project).fileDirty(file); + } + + private void batchExecute(Project project, + final SvnVcs17 activeVcs, + final VirtualFile[] file, + DataContext context, + AbstractVcsHelper helper) throws VcsException { + batchPerform(project, activeVcs, file, context); + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + for (int i = 0; file != null && i < file.length; i++) { + file[i].refresh(false, true); + } + } + }); + + for (int i = 0; file != null && i < file.length; i++) { + doVcsRefresh(project, file[i]); + } + } + + protected abstract String getActionName(AbstractVcs vcs); + + protected abstract boolean isEnabled(Project project, SvnVcs17 vcs, VirtualFile file); + + protected abstract boolean needsFiles(); + + protected abstract void perform(Project project, final SvnVcs17 activeVcs, VirtualFile file, DataContext context) throws VcsException; + + protected abstract void batchPerform(Project project, final SvnVcs17 activeVcs, VirtualFile[] file, DataContext context) throws VcsException; + + protected abstract boolean isBatchAction(); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/BrowseRepositoryAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/BrowseRepositoryAction.java new file mode 100644 index 000000000000..215ba3b08d30 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/BrowseRepositoryAction.java @@ -0,0 +1,81 @@ +/* + * 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.actions; + +import com.intellij.ide.actions.ContextHelpAction; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowAnchor; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.ui.content.Content; +import com.intellij.ui.content.ContentFactory; +import org.jetbrains.idea.svn17.dialogs.RepositoryBrowserDialog; + +import javax.swing.*; +import java.awt.*; + +public class BrowseRepositoryAction extends AnAction implements DumbAware { + public static final String REPOSITORY_BROWSER_TOOLWINDOW = "SVN Repositories"; + + public void actionPerformed(AnActionEvent e) { + Project project = e.getData(PlatformDataKeys.PROJECT); + if (project == null) { + RepositoryBrowserDialog dialog = new RepositoryBrowserDialog(ProjectManager.getInstance().getDefaultProject()); + dialog.show(); + } + else { + ToolWindowManager manager = ToolWindowManager.getInstance(project); + ToolWindow w = manager.getToolWindow(REPOSITORY_BROWSER_TOOLWINDOW); + if (w == null) { + RepositoryToolWindowPanel component = new RepositoryToolWindowPanel(project); + w = manager.registerToolWindow(REPOSITORY_BROWSER_TOOLWINDOW, true, ToolWindowAnchor.BOTTOM, project, true); + final Content content = ContentFactory.SERVICE.getInstance().createContent(component, "", false); + Disposer.register(content, component); + w.getContentManager().addContent(content); + } + w.show(null); + w.activate(null); + } + } + + private static class RepositoryToolWindowPanel extends JPanel implements Disposable { + private final RepositoryBrowserDialog myDialog; + private final Project myProject; + + private RepositoryToolWindowPanel(final Project project) { + super(new BorderLayout()); + myProject = project; + + myDialog = new RepositoryBrowserDialog(project); + JComponent component = myDialog.createBrowserComponent(true); + + add(component, BorderLayout.CENTER); + add(myDialog.createToolbar(false, new ContextHelpAction("reference.svn.repository")), BorderLayout.WEST); + } + + public void dispose() { + myDialog.disposeRepositoryBrowser(); + ToolWindowManager.getInstance(myProject).unregisterToolWindow(BrowseRepositoryAction.REPOSITORY_BROWSER_TOOLWINDOW); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ChangeListsMergerFactory.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ChangeListsMergerFactory.java new file mode 100644 index 000000000000..e8abdc760332 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ChangeListsMergerFactory.java @@ -0,0 +1,52 @@ +/* + * 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.actions; + +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.integrate.IMerger; +import org.jetbrains.idea.svn17.integrate.Merger; +import org.jetbrains.idea.svn17.integrate.MergerFactory; +import org.jetbrains.idea.svn17.update.UpdateEventHandler; +import org.tmatesoft.svn.core.SVNURL; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class ChangeListsMergerFactory implements MergerFactory { + protected final List myChangeListsList; + + public ChangeListsMergerFactory(final List changeListsList) { + myChangeListsList = new ArrayList(changeListsList); + } + + public IMerger createMerger(final SvnVcs17 vcs, + final File target, + final UpdateEventHandler handler, + final SVNURL currentBranchUrl, + String branchName) { + return new Merger(vcs, myChangeListsList, target, handler, currentBranchUrl, branchName); + } + + public List getListsToMerge() { + return myChangeListsList; + } + + public boolean isMergeAll() { + return false; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ChangeSetMergerFactory.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ChangeSetMergerFactory.java new file mode 100644 index 000000000000..d60e1b425e19 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ChangeSetMergerFactory.java @@ -0,0 +1,56 @@ +/* + * 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.actions; + +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.integrate.IMerger; +import org.jetbrains.idea.svn17.integrate.MergerFactory; +import org.jetbrains.idea.svn17.integrate.PointMerger; +import org.jetbrains.idea.svn17.update.UpdateEventHandler; +import org.tmatesoft.svn.core.SVNURL; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ChangeSetMergerFactory implements MergerFactory { + private final CommittedChangeList mySelectedList; + private final List mySelectedChanges; + + public ChangeSetMergerFactory(final CommittedChangeList selectedList, final List selectedChanges) { + mySelectedList = selectedList; + mySelectedChanges = new ArrayList(selectedChanges); + } + + public IMerger createMerger(final SvnVcs17 vcs, + final File target, + final UpdateEventHandler handler, + final SVNURL currentBranchUrl, + String branchName) { + return new PointMerger(vcs, mySelectedList, target, handler, currentBranchUrl, mySelectedChanges, branchName); + } + + public List getListsToMerge() { + return Collections.singletonList(mySelectedList); + } + + public boolean isMergeAll() { + return false; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/CleanupAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/CleanupAction.java new file mode 100644 index 000000000000..937212d915b7 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/CleanupAction.java @@ -0,0 +1,67 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.AbstractVcsHelper; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnStatusUtil; +import org.jetbrains.idea.svn17.SvnVcs17; + +public class CleanupAction extends BasicAction { + protected String getActionName(AbstractVcs vcs) { + return SvnBundle.message("cleanup.action.name"); + } + + protected boolean needsAllFiles() { + return true; + } + + protected boolean isEnabled(Project project, SvnVcs17 vcs, VirtualFile file) { + return SvnStatusUtil.isUnderControl(project, file); + } + + protected boolean needsFiles() { + return true; + } + + @Override + protected void execute(final Project project, + final SvnVcs17 activeVcs, final VirtualFile file, final DataContext context, final AbstractVcsHelper helper) + throws VcsException { + perform(project, activeVcs, file, context); + } + + protected void perform(Project project, SvnVcs17 activeVcs, VirtualFile file, DataContext context) + throws VcsException { + new CleanupWorker(new VirtualFile[]{file}, project, "action.Subversion.cleanup.progress.title").execute(); + } + + protected void batchPerform(Project project, SvnVcs17 activeVcs, VirtualFile[] file, DataContext context) + throws VcsException { + throw new VcsException(SvnBundle.message("exception.text.cleanupaction.batchperform.not.implemented")); + } + + protected boolean isBatchAction() { + return false; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/CleanupProjectAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/CleanupProjectAction.java new file mode 100644 index 000000000000..664d5ac55994 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/CleanupProjectAction.java @@ -0,0 +1,47 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn17.SvnVcs17; + +/** + * @author yole + */ +public class CleanupProjectAction extends AnAction implements DumbAware { + public void actionPerformed(final AnActionEvent e) { + final Project project = e.getData(PlatformDataKeys.PROJECT); + final VirtualFile[] roots = ProjectLevelVcsManager.getInstance(project).getRootsUnderVcs(SvnVcs17.getInstance(project)); + new CleanupWorker(roots, project, "action.Subversion.cleanup.project.title").execute(); + } + + public void update(final AnActionEvent e) { + final Project project = e.getData(PlatformDataKeys.PROJECT); + e.getPresentation().setVisible(isEnabled(project)); + } + + private static boolean isEnabled(final Project project) { + if (project == null) return false; + return ProjectLevelVcsManager.getInstance(project).checkVcsIsActive(SvnVcs17.VCS_NAME); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/CleanupWorker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/CleanupWorker.java new file mode 100644 index 000000000000..0434b809e79d --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/CleanupWorker.java @@ -0,0 +1,120 @@ +/* + * 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.actions; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.PerformInBackgroundOption; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.AbstractVcsHelper; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.io.File; +import java.util.LinkedList; +import java.util.List; + +public class CleanupWorker { + private final VirtualFile[] myRoots; + private final Project myProject; + private final String myTitleKey; + + public CleanupWorker(final VirtualFile[] roots, final Project project, final String titleKey) { + myRoots = roots; + myProject = project; + myTitleKey = titleKey; + } + + public void execute() { + ApplicationManager.getApplication().saveAll(); + + final List> exceptions = new LinkedList>(); + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final SVNWCClient wcClient = vcs.createWCClient(); + + final Task.Backgroundable task = new Task.Backgroundable(myProject, SvnBundle.message(myTitleKey), false, PerformInBackgroundOption.DEAF) { + public void run(@NotNull final ProgressIndicator indicator) { + indicator.setIndeterminate(true); + VirtualFile currentRoot; + for (VirtualFile root : myRoots) { + currentRoot = root; + try { + final String path = root.getPath(); + indicator.setText(SvnBundle.message("action.Subversion.cleanup.progress.text", path)); + wcClient.doCleanup(new File(path)); + } + catch (SVNException ex) { + exceptions.add(new Pair(ex, currentRoot)); + } + } + } + + @Override + public void onCancel() { + onSuccess(); + } + + @Override + public void onSuccess() { + if (myProject.isDisposed()) { + return; + } + final VcsDirtyScopeManager manager = VcsDirtyScopeManager.getInstance(myProject); + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + if (myProject.isDisposed()) { + return; + } + for (final VirtualFile root : myRoots) { + root.refresh(false, true); + } + } + }); + } + }); + for (final VirtualFile root : myRoots) { + manager.fileDirty(root); + } + + if (! exceptions.isEmpty()) { + final List vcsExceptions = new LinkedList(); + for (Pair pair : exceptions) { + final SVNException exception = pair.first; + vcsExceptions.add(new VcsException(SvnBundle.message("action.Subversion.cleanup.error.message", + FileUtil.toSystemDependentName(pair.second.getPath()), + ((exception == null) ? "" : exception.getMessage())))); + } + final AbstractVcsHelper helper = AbstractVcsHelper.getInstance(myProject); + helper.showErrors(vcsExceptions, SvnBundle.message(myTitleKey)); + } + } + }; + + ProgressManager.getInstance().run(task); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/CompareWithBranchAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/CompareWithBranchAction.java new file mode 100644 index 000000000000..f25eec9aa088 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/CompareWithBranchAction.java @@ -0,0 +1,279 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.diff.DiffManager; +import com.intellij.openapi.diff.FileContent; +import com.intellij.openapi.diff.SimpleContent; +import com.intellij.openapi.diff.SimpleDiffRequest; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.AbstractVcsHelper; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vcs.FileStatusManager; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vfs.CharsetToolkit; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.WaitForProgressToShow; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.*; +import org.jetbrains.idea.svn17.branchConfig.SvnBranchConfigurationNew; +import org.jetbrains.idea.svn17.status.SvnDiffEditor; +import org.tmatesoft.svn.core.*; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; +import org.tmatesoft.svn.core.internal.wc.SVNCancellableEditor; +import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; +import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaInfo; +import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry; +import org.tmatesoft.svn.core.internal.wc.admin.SVNReporter; +import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess; +import org.tmatesoft.svn.core.io.SVNRepository; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNWCClient; +import org.tmatesoft.svn.util.SVNDebugLog; +import org.tmatesoft.svn.util.SVNLogType; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * @author yole + */ +public class CompareWithBranchAction extends AnAction implements DumbAware { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.actions.CompareWithBranchAction"); + + public void actionPerformed(AnActionEvent e) { + Project project = e.getData(PlatformDataKeys.PROJECT); + assert project != null; + final VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE); + + SelectBranchPopup.show(project, virtualFile, new SelectBranchPopup.BranchSelectedCallback() { + public void branchSelected(final Project project, final SvnBranchConfigurationNew configuration, final String url, final long revision) { + new CompareWithBranchOperation(project, virtualFile, configuration).compareWithBranch(url, revision); + } + }, SvnBundle.message("compare.with.branch.popup.title")); + } + + @Override + public void update(final AnActionEvent e) { + Project project = e.getData(PlatformDataKeys.PROJECT); + VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE); + e.getPresentation().setEnabled(isEnabled(project, virtualFile)); + } + + private static boolean isEnabled(final Project project, final VirtualFile virtualFile) { + if (project == null || virtualFile == null) { + return false; + } + final FileStatus fileStatus = FileStatusManager.getInstance(project).getStatus(virtualFile); + if (fileStatus == FileStatus.UNKNOWN || fileStatus == FileStatus.ADDED || fileStatus == FileStatus.IGNORED) { + return false; + } + return true; + } + + + private class CompareWithBranchOperation { + private final Project myProject; + private final VirtualFile myVirtualFile; + private final SvnBranchConfigurationNew myConfiguration; + + public CompareWithBranchOperation(final Project project, final VirtualFile virtualFile, final SvnBranchConfigurationNew config) { + myProject = project; + myVirtualFile = virtualFile; + myConfiguration = config; + } + + public void compareWithBranch(final String baseUrl, final long revision) { + if (myVirtualFile.isDirectory()) { + compareDirectoryWithBranch(baseUrl, revision); + } + else { + compareFileWithBranch(baseUrl, revision); + } + } + final StringBuilder titleBuilder = new StringBuilder(); + + public void compareDirectoryWithBranch(final String baseUrl, final long revision) { + final List changes = new ArrayList(); + ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + public void run() { + try { + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final SVNURL url = getURLInBranch(vcs, baseUrl); + if (url == null) return; + titleBuilder.append(SvnBundle.message("repository.browser.compare.title", + url.toString(), + FileUtil.toSystemDependentName(myVirtualFile.getPresentableUrl()))); + + + SVNWCAccess wcAccess = vcs.createWCAccess(); + SVNRepository repository = null; + SVNRepository repository2 = null; + try { + SVNAdminAreaInfo info = wcAccess.openAnchor(new File(myVirtualFile.getPath()), false, SVNWCAccess.INFINITE_DEPTH); + File anchorPath = info.getAnchor().getRoot(); + String target = "".equals(info.getTargetName()) ? null : info.getTargetName(); + + SVNEntry anchorEntry = info.getAnchor().getEntry("", false); + if (anchorEntry == null) { + SVNErrorMessage err = + SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND, "''{0}'' is not under version control", anchorPath); + SVNErrorManager.error(err, SVNLogType.WC); + } + else if (anchorEntry.getURL() == null) { + SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_MISSING_URL, "''{0}'' has no URL", anchorPath); + SVNErrorManager.error(err, SVNLogType.WC); + } + + SVNURL anchorURL = anchorEntry.getSVNURL(); + repository = vcs.createRepository(anchorURL.toString()); + SVNReporter reporter = new SVNReporter(info, info.getAnchor().getFile(info.getTargetName()), false, true, SVNDepth.INFINITY, + false, false, true, SVNDebugLog.getDefaultLog()); + long rev = repository.getLatestRevision(); + repository2 = vcs.createRepository((target == null) ? url.toString() : url.removePathTail().toString()); + SvnDiffEditor diffEditor = new SvnDiffEditor((target == null) ? myVirtualFile : myVirtualFile.getParent(), + repository2, rev, true); + repository.diff(url, rev, rev, target, true, true, false, reporter, + SVNCancellableEditor.newInstance(diffEditor, new SvnProgressCanceller(), null)); + changes.addAll(diffEditor.getChangesMap().values()); + } + finally { + wcAccess.close(); + if (repository != null) { + repository.closeSession(); + } + if (repository2 != null) { + repository2.closeSession(); + } + } + } + catch(SVNCancelException ex) { + changes.clear(); + } + catch (SVNException ex) { + reportException(ex, baseUrl); + } + } + }, SvnBundle.message("progress.computing.difference"), true, myProject); + if (!changes.isEmpty()) { + AbstractVcsHelper.getInstance(myProject).showWhatDiffersBrowser(null, changes, titleBuilder.toString()); + } + } + + public void compareFileWithBranch(final String baseUrl, final long revision) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final StringBuilder remoteTitleBuilder = new StringBuilder(); + final Ref success = new Ref(); + ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + public void run() { + try { + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (indicator != null) { + indicator.setIndeterminate(true); + } + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + SVNURL svnurl = getURLInBranch(vcs, baseUrl); + if (svnurl == null) { + reportNotFound(baseUrl); + return; + } + remoteTitleBuilder.append(svnurl.toString()); + SVNWCClient client = vcs.createWCClient(); + client.doGetFileContents(svnurl, SVNRevision.UNDEFINED, SVNRevision.HEAD, true, baos); + success.set(true); + } + catch (SVNException ex) { + reportException(ex, baseUrl); + } + } + }, SvnBundle.message("compare.with.branch.progress.loading.content"), true, myProject); + if (success.isNull()) { + return; + } + SimpleDiffRequest req = new SimpleDiffRequest(myProject, SvnBundle.message("compare.with.branch.diff.title")); + req.setContents(new SimpleContent(CharsetToolkit.bytesToString(baos.toByteArray(), myVirtualFile.getCharset())), + new FileContent(myProject, myVirtualFile)); + req.setContentTitles(remoteTitleBuilder.toString(), myVirtualFile.getPresentableUrl()); + DiffManager.getInstance().getDiffTool().show(req); + } + + @Nullable + private SVNURL getURLInBranch(final SvnVcs17 vcs, final String baseUrl) throws SVNException { + final SvnFileUrlMapping17 urlMapping = vcs.getSvnFileUrlMapping(); + final File file = new File(myVirtualFile.getPath()); + final SVNURL fileUrl = urlMapping.getUrlForFile(file); + if (fileUrl == null) { + return null; + } + + final String fileUrlString = fileUrl.toString(); + final RootUrlInfo rootMixed = urlMapping.getWcRootForUrl(fileUrlString); + if (rootMixed == null) { + return null; + } + + final SVNURL thisBranchForUrl = SvnUtil.getBranchForUrl(vcs, rootMixed.getVirtualFile(), fileUrlString); + if (thisBranchForUrl == null) { + return null; + } + + final String relativePath = SVNPathUtil.getRelativePath(thisBranchForUrl.toString(), fileUrlString); + return SVNURL.parseURIEncoded(SVNPathUtil.append(baseUrl, relativePath)); + } + + private void reportException(final SVNException ex, final String baseUrl) { + final SVNErrorCode errorCode = ex.getErrorMessage().getErrorCode(); + if (errorCode.equals(SVNErrorCode.RA_ILLEGAL_URL) || + errorCode.equals(SVNErrorCode.CLIENT_UNRELATED_RESOURCES) || + errorCode.equals(SVNErrorCode.RA_DAV_PATH_NOT_FOUND) || + errorCode.equals(SVNErrorCode.FS_NOT_FOUND)) { + reportNotFound(baseUrl); + } + else { + WaitForProgressToShow.runOrInvokeLaterAboveProgress(new Runnable() { + public void run() { + Messages.showMessageDialog(myProject, ex.getMessage(), + SvnBundle.message("compare.with.branch.error.title"), Messages.getErrorIcon()); + } + }, null, myProject); + LOG.info(ex); + } + } + + private void reportNotFound(final String baseUrl) { + WaitForProgressToShow.runOrInvokeLaterAboveProgress(new Runnable() { + public void run() { + Messages.showMessageDialog(myProject, + SvnBundle.message("compare.with.branch.location.error", myVirtualFile.getPresentableUrl(), baseUrl), + SvnBundle.message("compare.with.branch.error.title"), Messages.getErrorIcon()); + } + }, null, myProject); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ConfigureBranchesAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ConfigureBranchesAction.java new file mode 100644 index 000000000000..9da620a80ca9 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ConfigureBranchesAction.java @@ -0,0 +1,67 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.vcs.VcsDataKeys; +import com.intellij.openapi.vcs.changes.ChangeList; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.dialogs.BranchConfigurationDialog; +import org.jetbrains.idea.svn17.history.SvnChangeList; + +public class ConfigureBranchesAction extends AnAction implements DumbAware { + public void update(final AnActionEvent e) { + final Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext()); + final Presentation presentation = e.getPresentation(); + + if (project == null) { + presentation.setEnabled(false); + presentation.setVisible(false); + return; + } + + presentation.setText(SvnBundle.message("configure.branches.item")); + presentation.setDescription(SvnBundle.message("configure.branches.item")); + presentation.setIcon(IconLoader.getIcon("/icons/ConfigureBranches.png")); + + presentation.setVisible(true); + + final ChangeList[] cls = e.getData(VcsDataKeys.CHANGE_LISTS); + presentation.setEnabled((cls != null) && (cls.length > 0) && + (SvnVcs17.getInstance(project).getName().equals(((CommittedChangeList) cls[0]).getVcs().getName())) && + (((SvnChangeList) cls[0]).getRoot() != null)); + } + + public void actionPerformed(final AnActionEvent e) { + final Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext()); + final ChangeList[] cls = e.getData(VcsDataKeys.CHANGE_LISTS); + if ((cls == null) || (cls.length == 0) || + (! SvnVcs17.getInstance(project).getName().equals(((CommittedChangeList) cls[0]).getVcs().getName())) || + (((SvnChangeList) cls[0]).getRoot() == null)) { + return; + } + final SvnChangeList svnList = (SvnChangeList) cls[0]; + BranchConfigurationDialog.configureBranches(project, svnList.getRoot(), true); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/CopyAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/CopyAction.java new file mode 100644 index 000000000000..ab2a96c2033e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/CopyAction.java @@ -0,0 +1,190 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.BackgroundFromStartOption; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.WindowManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnStatusUtil; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.dialogs.CopyDialog; +import org.tmatesoft.svn.core.SVNCommitInfo; +import org.tmatesoft.svn.core.SVNErrorCode; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.*; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class CopyAction extends BasicAction { + protected String getActionName(AbstractVcs vcs) { + return SvnBundle.message("action.Subversion.Copy.text"); + } + + protected boolean needsAllFiles() { + return true; + } + + protected boolean isEnabled(Project project, SvnVcs17 vcs, VirtualFile file) { + if (file == null) { + return false; + } + return SvnStatusUtil.isUnderControl(project, file); + } + + protected boolean needsFiles() { + return true; + } + + protected void perform(final Project project, final SvnVcs17 activeVcs, VirtualFile file, DataContext context) + throws VcsException { + CopyDialog dialog = new CopyDialog(project, true, new File(file.getPath())); + dialog.show(); + if (dialog.isOK()) { + final String dstURL = dialog.getToURL(); + final SVNRevision revision = dialog.getRevision(); + final String comment = dialog.getComment(); + final Ref exception = new Ref(); + final boolean isSrcFile = dialog.isCopyFromWorkingCopy(); + final File srcFile = new File(dialog.getCopyFromPath()); + final SVNURL srcUrl; + final SVNURL dstSvnUrl; + final SVNURL parentUrl; + try { + final SVNWCClient wcClient = activeVcs.createWCClient(); + srcUrl = SVNURL.parseURIEncoded(dialog.getCopyFromUrl()); + dstSvnUrl = SVNURL.parseURIEncoded(dstURL); + parentUrl = dstSvnUrl.removePathTail(); + + if (!dirExists(project, parentUrl, wcClient)) { + int rc = Messages.showYesNoDialog(project, "The repository path '" + parentUrl + "' does not exist. Would you like to create it?", + "Branch or Tag", Messages.getQuestionIcon()); + if (rc == 1) { + return; + } + } + + } + catch (SVNException e) { + throw new VcsException(e); + } + + Runnable copyCommand = new Runnable() { + public void run() { + try { + ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + SVNCopyClient client = activeVcs.createCopyClient(); + if (progress != null) { + progress.setText(SvnBundle.message("progress.text.copy.to", dstURL)); + client.setEventHandler(new CopyEventHandler(progress)); + } + checkCreateDir(parentUrl, activeVcs, comment); + final SVNCopySource[] copySource = new SVNCopySource[] {isSrcFile ? new SVNCopySource(revision, revision, srcFile) : + new SVNCopySource(revision, revision, srcUrl)}; + SVNCommitInfo result = client.doCopy(copySource, dstSvnUrl, false, true, true, comment, null); + if (result != null && result != SVNCommitInfo.NULL) { + WindowManager.getInstance().getStatusBar(project) + .setInfo(SvnBundle.message("status.text.comitted.revision", result.getNewRevision())); + } + } + catch (Exception e) { + exception.set(e); + } + } + }; + ProgressManager.getInstance().runProcessWithProgressSynchronously(copyCommand, SvnBundle.message("progress.title.copy"), false, project); + if (!exception.isNull()) { + throw new VcsException(exception.get()); + } + } + } + + private static void checkCreateDir(SVNURL url, final SvnVcs17 activeVcs, final String comment) throws SVNException, VcsException { + final SVNURL baseUrl = url; + SVNWCClient client = activeVcs.createWCClient(); + List dirsToCreate = new ArrayList(); + while(!dirExists(activeVcs.getProject(), url, client)) { + dirsToCreate.add(0, url); + url = url.removePathTail(); + if (url.getPath().length() == 0) { + throw new VcsException("Invalid repository root path for " + baseUrl); + } + } + SVNCommitClient commitClient = activeVcs.createCommitClient(); + commitClient.doMkDir(dirsToCreate.toArray(new SVNURL[dirsToCreate.size()]), comment); + } + + private static boolean dirExists(Project project, final SVNURL url, final SVNWCClient client) throws SVNException { + final Ref excRef = new Ref(); + final Ref resultRef = new Ref(Boolean.TRUE); + + final Runnable taskImpl = new Runnable() { + public void run() { + try { + client.doInfo(url, SVNRevision.UNDEFINED, SVNRevision.HEAD); + } + catch (SVNException e) { + if (e.getErrorMessage().getErrorCode().equals(SVNErrorCode.RA_ILLEGAL_URL)) { + resultRef.set(Boolean.FALSE); + } + else { + excRef.set(e); + } + } + } + }; + + final Task.Backgroundable task = + new Task.Backgroundable(project, "Checking target folder", false, BackgroundFromStartOption.getInstance()) { + + public void run(@NotNull ProgressIndicator indicator) { + taskImpl.run(); + } + }; + if (ApplicationManager.getApplication().isDispatchThread()) { + ProgressManager.getInstance().run(task); + } else { + taskImpl.run(); + } + if (! excRef.isNull()) throw excRef.get(); + return resultRef.get(); + } + + protected void batchPerform(Project project, SvnVcs17 activeVcs, VirtualFile[] files, DataContext context) + throws VcsException { + } + + protected boolean isBatchAction() { + return false; + } + +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/CopyEventHandler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/CopyEventHandler.java new file mode 100644 index 000000000000..93b636323d3d --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/CopyEventHandler.java @@ -0,0 +1,56 @@ +/* + * 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.actions; + +import com.intellij.openapi.progress.ProgressIndicator; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnUtil; +import org.tmatesoft.svn.core.wc.ISVNEventHandler; +import org.tmatesoft.svn.core.wc.SVNEvent; +import org.tmatesoft.svn.core.wc.SVNEventAction; + +public class CopyEventHandler implements ISVNEventHandler { + private final ProgressIndicator myProgress; + + public CopyEventHandler(ProgressIndicator progress) { + myProgress = progress; + } + + public void handleEvent(SVNEvent event, double p) { + final String path = SvnUtil.getPathForProgress(event); + if (path == null) { + return; + } + if (event.getAction() == SVNEventAction.COMMIT_ADDED) { + myProgress.setText2(SvnBundle.message("progress.text2.adding", path)); + } + else if (event.getAction() == SVNEventAction.COMMIT_DELETED) { + myProgress.setText2(SvnBundle.message("progress.text2.deleting", path)); + } + else if (event.getAction() == SVNEventAction.COMMIT_MODIFIED) { + myProgress.setText2(SvnBundle.message("progress.text2.sending", path)); + } + else if (event.getAction() == SVNEventAction.COMMIT_REPLACED) { + myProgress.setText2(SvnBundle.message("progress.text2.replacing", path)); + } + else if (event.getAction() == SVNEventAction.COMMIT_DELTA_SENT) { + myProgress.setText2(SvnBundle.message("progress.text2.transmitting.delta", path)); + } + } + + public void checkCancelled() { + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ExclusiveBackgroundVcsAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ExclusiveBackgroundVcsAction.java new file mode 100644 index 000000000000..e24fd8aaebf4 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ExclusiveBackgroundVcsAction.java @@ -0,0 +1,45 @@ +/* + * 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.actions; + +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; + +public class ExclusiveBackgroundVcsAction { + private ExclusiveBackgroundVcsAction() { + } + + public static void run(final Project project, final Runnable action) { + final ProjectLevelVcsManager plVcsManager = ProjectLevelVcsManager.getInstance(project); + plVcsManager.startBackgroundVcsOperation(); + try { + action.run(); + } finally { + final Application application = ApplicationManager.getApplication(); + if (application.isDispatchThread()) { + application.executeOnPooledThread(new Runnable() { + public void run() { + plVcsManager.stopBackgroundVcsOperation(); + } + }); + } else { + plVcsManager.stopBackgroundVcsOperation(); + } + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/IgnoreActionGroup.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/IgnoreActionGroup.java new file mode 100644 index 000000000000..02cdce4810d4 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/IgnoreActionGroup.java @@ -0,0 +1,113 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.ignore.FileGroupInfo; +import org.jetbrains.idea.svn17.ignore.IgnoreGroupHelperAction; +import org.jetbrains.idea.svn17.ignore.IgnoreInfoGetter; +import org.jetbrains.idea.svn17.ignore.SvnPropertyService; + +import java.util.Map; +import java.util.Set; + +public class IgnoreActionGroup extends DefaultActionGroup implements DumbAware { + private final IgnoreGroupHelperAction myHelperAction; + private final IgnoreInfoGetterStub myGetterStub; + + private final RemoveFromIgnoreListAction myRemoveExactAction; + private final RemoveFromIgnoreListAction myRemoveExtensionAction; + private final AddToIgnoreListAction myAddExactAction; + private final AddToIgnoreListAction myAddExtensionAction; + + public IgnoreActionGroup() { + myHelperAction = new IgnoreGroupHelperAction(); + myGetterStub = new IgnoreInfoGetterStub(); + + myAddExactAction = new AddToIgnoreListAction(myGetterStub, false); + myAddExtensionAction = new AddToIgnoreListAction(myGetterStub, true); + myRemoveExactAction = new RemoveFromIgnoreListAction(false, myGetterStub); + myRemoveExtensionAction = new RemoveFromIgnoreListAction(true, myGetterStub); + } + + private static class IgnoreInfoGetterStub implements IgnoreInfoGetter { + private IgnoreInfoGetter myGetter; + + private void setDelegate(final IgnoreInfoGetter getter) { + myGetter = getter; + } + + public Map> getInfo(final boolean useCommonExtension) { + return myGetter.getInfo(useCommonExtension); + } + } + + public void update(final AnActionEvent e) { + final FileGroupInfo fileGroupInfo = new FileGroupInfo(); + + myHelperAction.setFileIterationListener(fileGroupInfo); + myHelperAction.update(e); + + myGetterStub.setDelegate(fileGroupInfo); + + if ((e.getPresentation().isEnabled())) { + removeAll(); + if (myHelperAction.allAreIgnored()) { + final DataContext dataContext = e.getDataContext(); + final Project project = PlatformDataKeys.PROJECT.getData(dataContext); + SvnVcs17 vcs = SvnVcs17.getInstance(project); + + final Ref filesOk = new Ref(Boolean.FALSE); + final Ref extensionOk = new Ref(Boolean.FALSE); + + // virtual files parameter is not used -> can pass null + SvnPropertyService.doCheckIgnoreProperty(vcs, project, null, fileGroupInfo, fileGroupInfo.getExtensionMask(), filesOk, extensionOk); + + if (Boolean.TRUE.equals(filesOk.get())) { + myRemoveExactAction.setActionText(fileGroupInfo.oneFileSelected() ? + fileGroupInfo.getFileName() : SvnBundle.message("action.Subversion.UndoIgnore.text")); + add(myRemoveExactAction); + } + + if (Boolean.TRUE.equals(extensionOk.get())) { + myRemoveExtensionAction.setActionText(fileGroupInfo.getExtensionMask()); + add(myRemoveExtensionAction); + } + + e.getPresentation().setText(SvnBundle.message("group.RevertIgnoreChoicesGroup.text")); + } else if (myHelperAction.allCanBeIgnored()) { + final String ignoreExactlyName = (fileGroupInfo.oneFileSelected()) ? + fileGroupInfo.getFileName() : SvnBundle.message("action.Subversion.Ignore.ExactMatch.text"); + myAddExactAction.setActionText(ignoreExactlyName); + add(myAddExactAction); + if (fileGroupInfo.sameExtension()) { + myAddExtensionAction.setActionText(fileGroupInfo.getExtensionMask()); + add(myAddExtensionAction); + } + e.getPresentation().setText(SvnBundle.message("group.IgnoreChoicesGroup.text")); + } + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ImportToRepositoryAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ImportToRepositoryAction.java new file mode 100644 index 000000000000..c282706c3aaf --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ImportToRepositoryAction.java @@ -0,0 +1,49 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import org.jetbrains.idea.svn17.dialogs.ImportDialog; + +/** + * @author alex + */ +public class ImportToRepositoryAction extends AnAction implements DumbAware { + @Override + public void update(final AnActionEvent e) { + super.update(e); + final Project project = e.getData(PlatformDataKeys.PROJECT); + final Presentation presentation = e.getPresentation(); + presentation.setEnabled(presentation.isEnabled() && + (project == null || (! ProjectLevelVcsManager.getInstance(project).isBackgroundVcsOperationRunning()))); + } + + public void actionPerformed(AnActionEvent e) { + Project project = e.getData(PlatformDataKeys.PROJECT); + if (project == null) { + project = ProjectManager.getInstance().getDefaultProject(); + } + ImportDialog dialog = new ImportDialog(project); + dialog.show(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/IntegrateChangeSetAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/IntegrateChangeSetAction.java new file mode 100644 index 000000000000..4731710af06b --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/IntegrateChangeSetAction.java @@ -0,0 +1,49 @@ +/* + * 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.actions; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn17.integrate.MergerFactory; +import org.jetbrains.idea.svn17.integrate.SelectedChangeSetChecker; +import org.jetbrains.idea.svn17.integrate.SelectedCommittedStuffChecker; + +public class IntegrateChangeSetAction extends AbstractIntegrateChangesAction { + public IntegrateChangeSetAction() { + super(true); + } + + @NotNull + protected MergerFactory createMergerFactory(SelectedChangeSetChecker checker) { + return new ChangeSetMergerFactory(checker.getSelectedLists().get(0), checker.getSelectedChanges()); + } + + @NotNull + protected SelectedChangeSetChecker createChecker() { + return new SelectedChangeSetChecker(); + } + + protected String getSelectedBranchUrl(SelectedCommittedStuffChecker checker) { + return null; + } + + protected String getSelectedBranchLocalPath(SelectedCommittedStuffChecker checker) { + return null; + } + + protected String getDialogTitle() { + return null; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/LockAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/LockAction.java new file mode 100644 index 000000000000..e43930cb3356 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/LockAction.java @@ -0,0 +1,71 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnStatusUtil; +import org.jetbrains.idea.svn17.SvnUtil; +import org.jetbrains.idea.svn17.SvnVcs17; + +import java.io.File; + +public class LockAction extends BasicAction { + protected String getActionName(AbstractVcs vcs) { + return SvnBundle.message("action.Subversion.Lock.description"); + } + + protected boolean needsAllFiles() { + return true; + } + + protected boolean isEnabled(Project project, SvnVcs17 vcs, VirtualFile file) { + if (file == null || file.isDirectory()) { + return false; + } + return SvnStatusUtil.isUnderControl(project, file) && (! SvnStatusUtil.isAdded(project, file)) && (! SvnStatusUtil.isExplicitlyLocked(project, file)); + } + + protected boolean needsFiles() { + return true; + } + + protected void perform(Project project, SvnVcs17 activeVcs, VirtualFile file, DataContext context) + throws VcsException { + batchPerform(project, activeVcs, new VirtualFile[]{file}, context); + } + + protected void batchPerform(Project project, SvnVcs17 activeVcs, VirtualFile[] files, DataContext context) + throws VcsException { + File[] ioFiles = new File[files.length]; + for (int i = 0; i < files.length; i++) { + VirtualFile virtualFile = files[i]; + ioFiles[i] = new File(virtualFile.getPath()); + } + SvnUtil.doLockFiles(project, activeVcs, ioFiles); + + } + + protected boolean isBatchAction() { + return true; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/MarkLocallyDeletedTreeConflictResolvedAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/MarkLocallyDeletedTreeConflictResolvedAction.java new file mode 100644 index 000000000000..315d9412a6d8 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/MarkLocallyDeletedTreeConflictResolvedAction.java @@ -0,0 +1,163 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.AbstractVcsHelper; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsDataKeys; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.BackgroundFromStartOption; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.LocallyDeletedChange; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vcs.changes.ui.ChangesListView; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn17.ConflictedSvnChange; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnLocallyDeletedChange; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.SVNConflictChoice; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.util.Collections; +import java.util.List; + +/** + * @author irengrig + * Date: 6/8/11 + * Time: 4:58 PM + */ +public class MarkLocallyDeletedTreeConflictResolvedAction extends AnAction { + public MarkLocallyDeletedTreeConflictResolvedAction() { + super(SvnBundle.message("action.mark.tree.conflict.resolved.text")); + } + + @Override + public void actionPerformed(AnActionEvent e) { + final MyLocallyDeletedChecker locallyDeletedChecker = new MyLocallyDeletedChecker(e); + if (! locallyDeletedChecker.isEnabled()) return; + + final String markText = SvnBundle.message("action.mark.tree.conflict.resolved.confirmation.title"); + final Project project = locallyDeletedChecker.getProject(); + final int result = Messages.showYesNoDialog(project, + SvnBundle.message("action.mark.tree.conflict.resolved.confirmation.text"), markText, + Messages.getQuestionIcon()); + if (result == DialogWrapper.OK_EXIT_CODE) { + final Ref exception = new Ref(); + ProgressManager + .getInstance().run(new Task.Backgroundable(project, markText, true, BackgroundFromStartOption.getInstance()) { + public void run(@NotNull ProgressIndicator indicator) { + resolveLocallyDeletedTextConflict(locallyDeletedChecker, exception); + } + }); + if (! exception.isNull()) { + AbstractVcsHelper.getInstance(project).showError(exception.get(), markText); + } + } + } + + @Override + public void update(AnActionEvent e) { + final MyLocallyDeletedChecker locallyDeletedChecker = new MyLocallyDeletedChecker(e); + e.getPresentation().setVisible(locallyDeletedChecker.isEnabled()); + e.getPresentation().setEnabled(locallyDeletedChecker.isEnabled()); + //e.getPresentation().setText(SvnBundle.message("action.mark.tree.conflict.resolved.text")); + } + + private void resolveLocallyDeletedTextConflict(MyLocallyDeletedChecker checker, Ref exception) { + final FilePath path = checker.getPath(); + resolve(checker.getProject(), exception, path); + VcsDirtyScopeManager.getInstance(checker.getProject()).filePathsDirty(Collections.singletonList(path), null); + } + + private void resolve(Project project, Ref exception, FilePath path) { + final SVNWCClient client = SvnVcs17.getInstance(project).createWCClient(); + try { + client.doResolve(path.getIOFile(), SVNDepth.EMPTY, false, false, true, SVNConflictChoice.MERGED); + } + catch (SVNException e1) { + exception.set(new VcsException(e1)); + } + } + + private static class MyLocallyDeletedChecker { + private final boolean myEnabled; + private final FilePath myPath; + private final Project myProject; + + public MyLocallyDeletedChecker(final AnActionEvent e) { + final DataContext dc = e.getDataContext(); + myProject = PlatformDataKeys.PROJECT.getData(dc); + if (myProject == null) { + myPath = null; + myEnabled = false; + return; + } + + final List missingFiles = e.getData(ChangesListView.LOCALLY_DELETED_CHANGES); + + if (missingFiles == null || missingFiles.size() != 1) { + final Change[] changes = e.getData(VcsDataKeys.CHANGES); + if (changes == null || changes.length != 1 || changes[0].getAfterRevision() != null) { + myPath = null; + myEnabled = false; + return; + } + myEnabled = changes[0] instanceof ConflictedSvnChange && ((ConflictedSvnChange) changes[0]).getConflictState().isTree(); + if (myEnabled) { + myPath = changes[0].getBeforeRevision().getFile(); + } else { + myPath = null; + } + return; + } + + final LocallyDeletedChange change = missingFiles.get(0); + myEnabled = change instanceof SvnLocallyDeletedChange && ((SvnLocallyDeletedChange) change).getConflictState().isTree(); + if (myEnabled) { + myPath = change.getPath(); + } + else { + myPath = null; + } + } + + public boolean isEnabled() { + return myEnabled; + } + + public FilePath getPath() { + return myPath; + } + + public Project getProject() { + return myProject; + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/MarkResolvedAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/MarkResolvedAction.java new file mode 100644 index 000000000000..a8fb65a03d36 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/MarkResolvedAction.java @@ -0,0 +1,135 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vcs.FileStatusManager; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnStatusUtil; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.dialogs.SelectFilesDialog; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.*; + +import java.io.File; +import java.util.Collection; +import java.util.TreeSet; + +public class MarkResolvedAction extends BasicAction { + protected String getActionName(AbstractVcs vcs) { + return SvnBundle.message("action.name.mark.resolved"); + } + + protected boolean needsAllFiles() { + return false; + } + + protected boolean isEnabled(Project project, @NotNull SvnVcs17 vcs, VirtualFile file) { + if (file.isDirectory()) { + return SvnStatusUtil.isUnderControl(project, file); + } + final FileStatus fStatus = FileStatusManager.getInstance(project).getStatus(file); + return FileStatus.MERGED_WITH_CONFLICTS.equals(fStatus) || FileStatus.MERGED_WITH_BOTH_CONFLICTS.equals(fStatus) || + FileStatus.MERGED_WITH_PROPERTY_CONFLICTS.equals(fStatus); + } + + protected boolean needsFiles() { + return true; + } + + protected void perform(Project project, SvnVcs17 activeVcs, VirtualFile file, DataContext context) + throws VcsException { + batchPerform(project, activeVcs, new VirtualFile[]{file}, context); + } + + protected void batchPerform(Project project, SvnVcs17 activeVcs, VirtualFile[] files, DataContext context) + throws VcsException { + SvnVcs17 vcs = SvnVcs17.getInstance(project); + ApplicationManager.getApplication().saveAll(); + Collection paths = collectResolvablePaths(vcs, files); + if (paths.isEmpty()) { + Messages.showInfoMessage(project, SvnBundle.message("message.text.no.conflicts.found"), SvnBundle.message("message.title.no.conflicts.found")); + return; + } + String[] pathsArray = ArrayUtil.toStringArray(paths); + SelectFilesDialog dialog = new SelectFilesDialog(project, SvnBundle.message("label.select.files.and.directories.to.mark.resolved"), + SvnBundle.message("dialog.title.mark.resolved"), + SvnBundle.message("action.name.mark.resolved"), pathsArray, "vcs.subversion.resolve" + ); + dialog.show(); + if (!dialog.isOK()) { + return; + } + pathsArray = dialog.getSelectedPaths(); + try { + SVNWCClient wcClient = vcs.createWCClient(); + for (String path : pathsArray) { + File ioFile = new File(path); + wcClient.doResolve(ioFile, false); + } + } + catch (SVNException e) { + throw new VcsException(e); + } + finally { + for (VirtualFile file : files) { + VcsDirtyScopeManager.getInstance(project).fileDirty(file); + file.refresh(true, false); + if (file.getParent() != null) { + file.getParent().refresh(true, false); + } + } + + } + } + + protected boolean isBatchAction() { + return true; + } + + private static Collection collectResolvablePaths(final SvnVcs17 vcs, VirtualFile[] files) { + final Collection target = new TreeSet(); + SVNStatusClient stClient = vcs.createStatusClient(); + for (VirtualFile file : files) { + try { + stClient.doStatus(new File(file.getPath()), true, false, false, false, new ISVNStatusHandler() { + public void handleStatus(SVNStatus status) { + if (status.getContentsStatus() == SVNStatusType.STATUS_CONFLICTED || + status.getPropertiesStatus() == SVNStatusType.STATUS_CONFLICTED) { + target.add(status.getFile().getAbsolutePath()); + } + } + }); + } + catch (SVNException e) { + // + } + } + return target; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/MarkTreeConflictResolvedAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/MarkTreeConflictResolvedAction.java new file mode 100644 index 000000000000..dfc87a6c2ad6 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/MarkTreeConflictResolvedAction.java @@ -0,0 +1,146 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.AbstractVcsHelper; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsDataKeys; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.BackgroundFromStartOption; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn17.ConflictedSvnChange; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.SVNConflictChoice; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class MarkTreeConflictResolvedAction extends AnAction implements DumbAware { + private static final String myText = SvnBundle.message("action.mark.tree.conflict.resolved.text"); + + public MarkTreeConflictResolvedAction() { + super(myText); + } + + @Override + public void update(AnActionEvent e) { + final MyChecker checker = new MyChecker(e); + e.getPresentation().setVisible(checker.isEnabled()); + e.getPresentation().setEnabled(checker.isEnabled()); + e.getPresentation().setText(myText); + } + + private static class MyChecker { + private final boolean myEnabled; + private final ConflictedSvnChange myChange; + private final Project myProject; + + public MyChecker(final AnActionEvent e) { + final DataContext dc = e.getDataContext(); + myProject = PlatformDataKeys.PROJECT.getData(dc); + final Change[] changes = VcsDataKeys.CHANGE_LEAD_SELECTION.getData(dc); + + if (myProject == null || changes == null || changes.length != 1) { + myEnabled = false; + myChange = null; + return; + } + + final Change change = changes[0]; + myEnabled = change instanceof ConflictedSvnChange && ((ConflictedSvnChange) change).getConflictState().isTree(); + if (myEnabled) { + myChange = (ConflictedSvnChange) change; + } + else { + myChange = null; + } + } + + public boolean isEnabled() { + return myEnabled; + } + + public ConflictedSvnChange getChange() { + return myChange; + } + + public Project getProject() { + return myProject; + } + } + + public void actionPerformed(AnActionEvent e) { + final MyChecker checker = new MyChecker(e); + if (! checker.isEnabled()) return; + + final String markText = SvnBundle.message("action.mark.tree.conflict.resolved.confirmation.title"); + final int result = Messages.showYesNoDialog(checker.getProject(), + SvnBundle.message("action.mark.tree.conflict.resolved.confirmation.text"), markText, + Messages.getQuestionIcon()); + if (result == DialogWrapper.OK_EXIT_CODE) { + final Ref exception = new Ref(); + ProgressManager.getInstance().run(new Task.Backgroundable(checker.getProject(), markText, true, BackgroundFromStartOption.getInstance()) { + public void run(@NotNull ProgressIndicator indicator) { + final ConflictedSvnChange change = checker.getChange(); + final FilePath path = change.getTreeConflictMarkHolder(); + final SVNWCClient client = SvnVcs17.getInstance(checker.getProject()).createWCClient(); + try { + client.doResolve(path.getIOFile(), SVNDepth.EMPTY, false, false, true, SVNConflictChoice.MERGED); + } + catch (SVNException e1) { + exception.set(new VcsException(e1)); + } + VcsDirtyScopeManager.getInstance(checker.getProject()).filePathsDirty(getDistinctFiles(change), null); + } + }); + if (! exception.isNull()) { + AbstractVcsHelper.getInstance(checker.getProject()).showError(exception.get(), markText); + } + } + } + + private Collection getDistinctFiles(final Change change) { + final List result = new ArrayList(2); + if (change.getBeforeRevision() != null) { + result.add(change.getBeforeRevision().getFile()); + } + if (change.getAfterRevision() != null) { + if (change.getBeforeRevision() == null || change.getBeforeRevision() != null && (change.isMoved() || change.isRenamed())) { + result.add(change.getAfterRevision().getFile()); + } + } + return result; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/RecordOnlyMergerFactory.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/RecordOnlyMergerFactory.java new file mode 100644 index 000000000000..9cbe7ce7f4d2 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/RecordOnlyMergerFactory.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.actions; + +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.integrate.IMerger; +import org.jetbrains.idea.svn17.integrate.Merger; +import org.jetbrains.idea.svn17.update.UpdateEventHandler; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNRevisionRange; + +import java.io.File; +import java.util.List; + +public class RecordOnlyMergerFactory extends ChangeListsMergerFactory { + private final boolean myUndo; + + public RecordOnlyMergerFactory(final List changeListsList, final boolean isUndo) { + super(changeListsList); + myUndo = isUndo; + } + + public IMerger createMerger(final SvnVcs17 vcs, + final File target, + final UpdateEventHandler handler, + final SVNURL currentBranchUrl, + String branchName) { + return new Merger(vcs, myChangeListsList, target, handler, currentBranchUrl, branchName) { + @Override + protected SVNRevisionRange createRange() { + if (myUndo) { + return new SVNRevisionRange(SVNRevision.create(myLatestProcessed.getNumber()), SVNRevision.create(myLatestProcessed.getNumber() - 1)); + } + return super.createRange(); + } + + @Override + protected boolean isRecordOnly() { + return true; + } + }; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/RelocateAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/RelocateAction.java new file mode 100644 index 000000000000..838112d48727 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/RelocateAction.java @@ -0,0 +1,94 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.WaitForProgressToShow; +import org.jetbrains.idea.svn17.SvnStatusUtil; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.dialogs.RelocateDialog; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.SVNInfo; +import org.tmatesoft.svn.core.wc.SVNUpdateClient; + +import java.io.File; + +/** + * @author yole + */ +public class RelocateAction extends BasicAction { + protected String getActionName(final AbstractVcs vcs) { + return "Relocate working copy to a different URL"; + } + + protected boolean isEnabled(final Project project, final SvnVcs17 vcs, final VirtualFile file) { + return SvnStatusUtil.isUnderControl(project, file); + } + + protected boolean needsFiles() { + return true; + } + + protected void perform(final Project project, final SvnVcs17 activeVcs, final VirtualFile file, DataContext context) throws VcsException { + SVNInfo info = activeVcs.getInfo(file); + assert info != null; + RelocateDialog dlg = new RelocateDialog(project, info.getURL()); + dlg.show(); + if (!dlg.isOK()) return; + final String beforeURL = dlg.getBeforeURL(); + final String afterURL = dlg.getAfterURL(); + if (beforeURL.equals(afterURL)) return; + ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + public void run() { + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (indicator != null) { + indicator.setIndeterminate(true); + } + final SVNUpdateClient client = activeVcs.createUpdateClient(); + try { + client.doRelocate(new File(file.getPath()), + SVNURL.parseURIEncoded(beforeURL), + SVNURL.parseURIEncoded(afterURL), + true); + VcsDirtyScopeManager.getInstance(project).markEverythingDirty(); + } + catch (final SVNException e) { + WaitForProgressToShow.runOrInvokeLaterAboveProgress(new Runnable() { + public void run() { + Messages.showErrorDialog(project, "Error relocating working copy: " + e.getMessage(), "Relocate Working Copy"); + } + }, null, project); + } + } + }, "Relocating Working Copy", false, project); + } + + protected void batchPerform(Project project, SvnVcs17 activeVcs, VirtualFile[] file, DataContext context) throws VcsException { + } + + protected boolean isBatchAction() { + return false; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/RemoveFromIgnoreListAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/RemoveFromIgnoreListAction.java new file mode 100644 index 000000000000..0705c1116e5a --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/RemoveFromIgnoreListAction.java @@ -0,0 +1,87 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.ignore.IgnoreInfoGetter; +import org.jetbrains.idea.svn17.ignore.SvnPropertyService; + +public class RemoveFromIgnoreListAction extends BasicAction { + private String myActionName; + private final boolean myUseCommonExtension; + private final IgnoreInfoGetter myInfoGetter; + + public RemoveFromIgnoreListAction(final boolean useCommonExtension, final IgnoreInfoGetter getter) { + myUseCommonExtension = useCommonExtension; + myInfoGetter = getter; + } + + public void setActionText(final String name) { + myActionName = name; + } + + protected String getActionName(final AbstractVcs vcs) { + return SvnBundle.message("action.name.undo.ignore.files"); + } + + public void update(final AnActionEvent e) { + final Presentation presentation = e.getPresentation(); + presentation.setVisible(true); + presentation.setEnabled(true); + + presentation.setText(myActionName); + presentation.setDescription(SvnBundle.message("action.Subversion.UndoIgnore.description")); + } + + protected boolean isEnabled(final Project project, final SvnVcs17 vcs, final VirtualFile file) { + return true; + } + + @Override + protected void doVcsRefresh(Project project, VirtualFile file) { + final VcsDirtyScopeManager vcsDirtyScopeManager = VcsDirtyScopeManager.getInstance(project); + if (file != null && (file.getParent() != null)) { + vcsDirtyScopeManager.fileDirty(file.getParent()); + } + } + + protected boolean needsFiles() { + return true; + } + + protected void perform(final Project project, final SvnVcs17 activeVcs, final VirtualFile file, final DataContext context) + throws VcsException { + + } + + protected void batchPerform(final Project project, final SvnVcs17 activeVcs, final VirtualFile[] file, final DataContext context) + throws VcsException { + SvnPropertyService.doRemoveFromIgnoreProperty(activeVcs, project, myUseCommonExtension, file, myInfoGetter); + } + + protected boolean isBatchAction() { + return true; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ResolveAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ResolveAction.java new file mode 100644 index 000000000000..d178c81539f8 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ResolveAction.java @@ -0,0 +1,102 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.*; +import com.intellij.openapi.vfs.ReadonlyStatusHandler; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Processor; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class ResolveAction extends BasicAction { + protected String getActionName(AbstractVcs vcs) { + return SvnBundle.message("action.name.resolve.conflict"); + } + + protected boolean needsAllFiles() { + return true; + } + + protected boolean isEnabled(Project project, SvnVcs17 vcs, VirtualFile file) { + if (file.isDirectory()) return true; + final FileStatus fStatus = FileStatusManager.getInstance(project).getStatus(file); + return FileStatus.MERGED_WITH_CONFLICTS.equals(fStatus) || FileStatus.MERGED_WITH_BOTH_CONFLICTS.equals(fStatus); + } + + protected boolean needsFiles() { + return true; + } + + protected void perform(Project project, SvnVcs17 activeVcs, VirtualFile file, DataContext context) throws VcsException { + batchPerform(project, activeVcs, new VirtualFile[]{file}, context); + } + + protected void batchPerform(final Project project, final SvnVcs17 activeVcs, final VirtualFile[] files, DataContext context) throws VcsException { + boolean hasDirs = false; + for(VirtualFile file: files) { + if (file.isDirectory()) { + hasDirs = true; + } + } + final List fileList = new ArrayList(); + if (!hasDirs) { + Collections.addAll(fileList, files); + } + else { + ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + public void run() { + for (VirtualFile file: files) { + if (file.isDirectory()) { + ProjectLevelVcsManager.getInstance(project).iterateVcsRoot(file, new Processor() { + public boolean process(final FilePath filePath) { + ProgressManager.checkCanceled(); + VirtualFile fileOrDir = filePath.getVirtualFile(); + if (fileOrDir != null && !fileOrDir.isDirectory() && isEnabled(project, activeVcs, fileOrDir) && !fileList.contains(fileOrDir)) { + fileList.add(fileOrDir); + } + return true; + } + }); + } + else { + if (!fileList.contains(file)) { + fileList.add(file); + } + } + } + } + }, SvnBundle.message("progress.searching.for.files.with.conflicts"), true, project); + } + final ReadonlyStatusHandler.OperationStatus status = ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(fileList); + fileList.removeAll(Arrays.asList(status.getReadonlyFiles())); + AbstractVcsHelper.getInstance(project).showMergeDialog(fileList, new SvnMergeProvider(project)); + } + + protected boolean isBatchAction() { + return true; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/SelectBranchPopup.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/SelectBranchPopup.java new file mode 100644 index 000000000000..a29a7de12857 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/SelectBranchPopup.java @@ -0,0 +1,308 @@ +/* + * 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.actions; + +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.ui.popup.*; +import com.intellij.openapi.ui.popup.util.BaseListPopupStep; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.components.JBList; +import com.intellij.util.continuation.ModalityIgnorantBackgroundableTask; +import com.intellij.util.text.DateFormatUtil; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.*; +import org.jetbrains.idea.svn17.branchConfig.InfoReliability; +import org.jetbrains.idea.svn17.branchConfig.NewRootBunch; +import org.jetbrains.idea.svn17.branchConfig.SvnBranchConfigManager; +import org.jetbrains.idea.svn17.branchConfig.SvnBranchConfigurationNew; +import org.jetbrains.idea.svn17.dialogs.BranchConfigurationDialog; +import org.jetbrains.idea.svn17.integrate.SvnBranchItem; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; + +import javax.swing.*; +import java.awt.*; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * @author yole + */ +public class SelectBranchPopup { + private final static String CONFIGURE_MESSAGE = SvnBundle.message("configure.branches.item"); + + private SelectBranchPopup() { + } + + public interface BranchSelectedCallback { + void branchSelected(Project project, SvnBranchConfigurationNew configuration, String url, long revision); + } + + public static void show(Project project, VirtualFile file, BranchSelectedCallback callback, final String title) { + show(project, file, callback, title, null); + } + + public static void show(Project project, VirtualFile file, BranchSelectedCallback callback, final String title, final Component component) { + final SvnFileUrlMapping17 urlMapping = SvnVcs17.getInstance(project).getSvnFileUrlMapping(); + final SVNURL svnurl = urlMapping.getUrlForFile(new File(file.getPath())); + if (svnurl == null) { + return; + } + final RootUrlInfo rootUrlInfo = urlMapping.getWcRootForUrl(svnurl.toString()); + if (rootUrlInfo == null) { + return; + } + + // not vcs root but wc root is ok + showForBranchRoot(project, rootUrlInfo.getVirtualFile(), callback, title, component); + } + + public static void showForBranchRoot(Project project, VirtualFile vcsRoot, BranchSelectedCallback callback, final String title) { + showForBranchRoot(project, vcsRoot, callback, title, null); + } + + public static void showForBranchRoot(Project project, VirtualFile vcsRoot, BranchSelectedCallback callback, final String title, + final Component component) { + final SvnBranchConfigurationNew configuration; + try { + configuration = SvnBranchConfigurationManager17.getInstance(project).get(vcsRoot); + } + catch (VcsException e1) { + Messages.showErrorDialog(project, SvnBundle.message("getting.branch.configuration.error", e1.getMessage()), title); + return; + } + + final List items = new ArrayList(); + if (configuration.getTrunkUrl() != null) { + items.add(configuration.getTrunkUrl()); + } + for (String url : configuration.getBranchUrls()) { + items.add(url); + } + items.add(CONFIGURE_MESSAGE); + + BranchBasesPopupStep step = new BranchBasesPopupStep(project, vcsRoot, configuration, callback, items, title, component); + final ListPopup listPopup = JBPopupFactory.getInstance().createListPopup(step); + step.showPopupAt(listPopup); + } + + private static class BranchBasesPopupStep extends BaseListPopupStep { + protected final Project myProject; + private final VirtualFile myVcsRoot; + private final SvnBranchConfigurationNew myConfiguration; + private final boolean myTopLevel; + private BranchSelectedCallback myCallback; + private final Component myComponent; + + private static final String REFRESH_MESSAGE = SvnBundle.message("refresh.branches.item"); + + protected BranchBasesPopupStep(final Project project, + final VirtualFile vcsRoot, + final SvnBranchConfigurationNew configuration, + boolean topLevel, + final BranchSelectedCallback callback, + Component component) { + myProject = project; + myVcsRoot = vcsRoot; + myConfiguration = configuration; + myTopLevel = topLevel; + myCallback = callback; + myComponent = component; + } + + public BranchBasesPopupStep(final Project project, final VirtualFile vcsRoot, + final SvnBranchConfigurationNew configuration, + final BranchSelectedCallback callback, + final List items, + final String title, + Component component) { + this(project, vcsRoot, configuration, true, callback, component); + init(title, items, null); + } + + @Override + public ListSeparator getSeparatorAbove(final String value) { + return (CONFIGURE_MESSAGE.equals(value)) || + (REFRESH_MESSAGE.equals(value)) ? new ListSeparator("") : null; + } + + @NotNull + @Override + public String getTextFor(final String value) { + int pos = value.lastIndexOf('/'); + if (pos < 0) { + return value; + } + if (myTopLevel && ((myConfiguration.getTrunkUrl() == null) || (! value.startsWith(myConfiguration.getTrunkUrl())))) { + return value.substring(pos+1) + "..."; + } + return value.substring(pos+1); + } + + @Override + public boolean hasSubstep(final String selectedValue) { + return false; + } + + @Override + public PopupStep onChosen(final String selectedValue, final boolean finalChoice) { + if (CONFIGURE_MESSAGE.equals(selectedValue)) { + return doFinalStep(new Runnable() { + public void run() { + BranchConfigurationDialog.configureBranches(myProject, myVcsRoot, true); + } + }); + } + else if (!myTopLevel || selectedValue.equals(myConfiguration.getTrunkUrl())) { + return doFinalStep(new Runnable() { + public void run() { + myCallback.branchSelected(myProject, myConfiguration, selectedValue, -1); + } + }); + } + else { + showBranchPopup(selectedValue); + } + return FINAL_CHOICE; + } + + @Nullable + private void loadBranches(final String selectedBranchesHolder, final Runnable runnable) { + final ProgressManager pm = ProgressManager.getInstance(); + pm.run(new ModalityIgnorantBackgroundableTask(myProject, SvnBundle.message("compare.with.branch.progress.loading.branches")) { + @Override + protected void doInAwtIfFail(Exception e) { + runnable.run(); + } + + @Override + protected void doInAwtIfCancel() { + runnable.run(); + } + + @Override + protected void doInAwtIfSuccess() { + runnable.run(); + } + + @Override + protected void runImpl(@NotNull ProgressIndicator indicator) { + final SvnBranchConfigManager manager = SvnBranchConfigurationManager17.getInstance(myProject).getSvnBranchConfigManager(); + new NewRootBunch.BranchesLoadRunnable(myProject, manager, selectedBranchesHolder, InfoReliability.setByUser, myVcsRoot, null, false).run(); + } + }); + } + + private void showBranchPopup(final String selectedValue) { + List branches = myConfiguration.getBranches(selectedValue); + if (branches == null) { + return; + } + + final Object[] items = new Object[branches.size() + 1]; + System.arraycopy(branches.toArray(), 0, items, 0, branches.size()); + items[items.length - 1] = REFRESH_MESSAGE; + + final JList branchList = new JBList(items); + branchList.setCellRenderer(new BranchRenderer()); + final JBPopup popup = JBPopupFactory.getInstance().createListPopupBuilder(branchList) + .setTitle(SVNPathUtil.tail(selectedValue)) + .setResizable(true) + //.setDimensionServiceKey("Svn.CompareWithBranchPopup") + .setItemChoosenCallback(new Runnable() { + public void run() { + if (REFRESH_MESSAGE.equals(branchList.getSelectedValue())) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + loadBranches(selectedValue, new Runnable() { + @Override + public void run() { + showBranchPopup(selectedValue); + } + }); + } + }); + return; + } + SvnBranchItem item = (SvnBranchItem)branchList.getSelectedValue(); + if (item != null) { + myCallback.branchSelected(myProject, myConfiguration, item.getUrl(), item.getRevision()); + } + } + }) + .createPopup(); + showPopupAt(popup); + } + + public void showPopupAt(final JBPopup listPopup) { + if (myComponent == null) { + listPopup.showCenteredInCurrentWindow(myProject); + } else { + listPopup.showInCenterOf(myComponent); + } + } + } + + private static class BranchRenderer extends JPanel implements ListCellRenderer { + private final JLabel myUrlLabel = new JLabel(); + private final JLabel myDateLabel = new JLabel(); + + public BranchRenderer() { + super(new BorderLayout()); + add(myUrlLabel, BorderLayout.WEST); + add(myDateLabel, BorderLayout.EAST); + myUrlLabel.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); + myDateLabel.setHorizontalAlignment(JLabel.RIGHT); + myDateLabel.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); + myDateLabel.setForeground(UIUtil.getInactiveTextColor()); + } + + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + if (isSelected || cellHasFocus) { + setBackground(UIUtil.getListSelectionBackground()); + final Color selectedForegroundColor = UIUtil.getListSelectionForeground(); + myUrlLabel.setForeground(selectedForegroundColor); + myDateLabel.setForeground(selectedForegroundColor); + setForeground(selectedForegroundColor); + } + else { + setBackground(UIUtil.getListBackground()); + final Color foregroundColor = UIUtil.getListForeground(); + myUrlLabel.setForeground(foregroundColor); + myDateLabel.setForeground(UIUtil.getInactiveTextColor()); + setForeground(foregroundColor); + } + if (value instanceof String) { + myUrlLabel.setText((String) value); + myDateLabel.setText(""); + } else { + SvnBranchItem item = (SvnBranchItem) value; + myUrlLabel.setText(SVNPathUtil.tail(item.getUrl())); + final long creationMillis = item.getCreationDateMillis(); + myDateLabel.setText((creationMillis > 0) ? DateFormatUtil.formatDate(creationMillis) : ""); + } + return this; + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/SetPropertyAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/SetPropertyAction.java new file mode 100644 index 000000000000..f0d2b66194d2 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/SetPropertyAction.java @@ -0,0 +1,99 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vcs.FileStatusManager; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.dialogs.SetPropertyDialog; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNPropertyValue; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.io.File; + +public class SetPropertyAction extends BasicAction { + protected String getActionName(AbstractVcs vcs) { + return SvnBundle.message("action.name.set.property"); + } + + protected boolean needsAllFiles() { + return true; + } + + protected boolean isEnabled(Project project, SvnVcs17 vcs, VirtualFile file) { + if (file == null || project == null || vcs == null) return false; + final FileStatus status = FileStatusManager.getInstance(project).getStatus(file); + return (! FileStatus.IGNORED.equals(status)) && (! FileStatus.UNKNOWN.equals(status)); + } + + protected boolean needsFiles() { + return true; + } + + protected void perform(Project project, SvnVcs17 activeVcs, VirtualFile file, DataContext context) + throws VcsException { + batchPerform(project, activeVcs, new VirtualFile[]{file}, context); + } + + protected void batchPerform(Project project, SvnVcs17 activeVcs, VirtualFile[] file, DataContext context) + throws VcsException { + File[] ioFiles = new File[file.length]; + for (int i = 0; i < ioFiles.length; i++) { + ioFiles[i] = new File(file[i].getPath()); + } + + SetPropertyDialog dialog = new SetPropertyDialog(project, ioFiles, null, true); + dialog.show(); + + if (dialog.isOK()) { + String name = dialog.getPropertyName(); + String value = dialog.getPropertyValue(); + boolean recursive = dialog.isRecursive(); + + SVNWCClient wcClient = activeVcs.createWCClient(); + for (int i = 0; i < ioFiles.length; i++) { + File ioFile = ioFiles[i]; + try { + wcClient.doSetProperty(ioFile, name, SVNPropertyValue.create(value), false, recursive, null); + } + catch (SVNException e) { + throw new VcsException(e); + } + } + for(int i = 0; i < file.length; i++) { + if (recursive && file[i].isDirectory()) { + VcsDirtyScopeManager.getInstance(project).dirDirtyRecursively(file[i], true); + } else { + VcsDirtyScopeManager.getInstance(project).fileDirty(file[i]); + } + } + } + } + + protected boolean isBatchAction() { + return true; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShareProjectAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShareProjectAction.java new file mode 100644 index 000000000000..35736577996e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShareProjectAction.java @@ -0,0 +1,254 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.application.ApplicationNamesInfo; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn17.*; +import org.jetbrains.idea.svn17.checkout.SvnCheckoutProvider; +import org.jetbrains.idea.svn17.dialogs.ShareDialog; +import org.tmatesoft.svn.core.SVNCommitInfo; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.io.File; + +public class ShareProjectAction extends BasicAction { + + protected String getActionName(AbstractVcs vcs) { + return SvnBundle.message("share.directory.action"); + } + + public void update(AnActionEvent e) { + Presentation presentation = e.getPresentation(); + final DataContext dataContext = e.getDataContext(); + + Project project = PlatformDataKeys.PROJECT.getData(dataContext); + if ((project == null) || (ProjectLevelVcsManager.getInstance(project).isBackgroundVcsOperationRunning())) { + presentation.setEnabled(false); + presentation.setVisible(false); + return; + } + + VirtualFile[] files = PlatformDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext); + if (files == null || files.length == 0) { + presentation.setEnabled(false); + presentation.setVisible(false); + return; + } + boolean enabled = false; + boolean visible = false; + if (files.length == 1 && files [0].isDirectory()) { + visible = true; + if (! SvnStatusUtil.isUnderControl(project, files[0])) { + enabled = true; + } + } + presentation.setEnabled(enabled); + presentation.setVisible(visible); + } + + protected boolean isEnabled(Project project, SvnVcs17 vcs, VirtualFile file) { + return false; + } + + protected boolean needsFiles() { + return true; + } + + public static boolean share(final Project project, final VirtualFile file) throws VcsException { + return performImpl(project, SvnVcs17.getInstance(project), file); + } + + protected void perform(final Project project, final SvnVcs17 activeVcs, final VirtualFile file, DataContext context) throws VcsException { + performImpl(project, activeVcs, file); + } + + private static boolean performImpl(final Project project, final SvnVcs17 activeVcs, final VirtualFile file) throws VcsException { + final ShareDialog shareDialog = new ShareDialog(project, file.getName()); + shareDialog.show(); + + final String parent = shareDialog.getSelectedURL(); + if (shareDialog.isOK() && parent != null) { + final Ref actionStarted = new Ref(Boolean.TRUE); + final SVNException[] error = new SVNException[1]; + + final ShareDialog.ShareTarget shareTarget = shareDialog.getShareTarget(); + final ProgressManager progressManager = ProgressManager.getInstance(); + + if (ShareDialog.ShareTarget.useSelected.equals(shareTarget)) { + final boolean folderEmpty = checkRemoteFolder(project, activeVcs, parent, progressManager); + + if (! folderEmpty) { + final int promptAnswer = + Messages.showYesNoDialog(project, "Remote folder \"" + parent + "\" is not empty.\nDo you want to continue sharing?", + "Share directory", Messages.getWarningIcon()); + if (DialogWrapper.OK_EXIT_CODE != promptAnswer) return false; + } + } + + ExclusiveBackgroundVcsAction.run(project, new Runnable() { + public void run() { + progressManager.runProcessWithProgressSynchronously(new Runnable() { + public void run() { + try { + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + + final File path = new File(file.getPath()); + if (! SvnCheckoutProvider.promptForWCFormatAndSelect(path, project)) { + // action cancelled + actionStarted.set(Boolean.FALSE); + return; + } + final SVNURL parenUrl = SVNURL.parseURIEncoded(parent); + final SVNURL checkoutUrl; + final SVNRevision revision; + + if (ShareDialog.ShareTarget.useSelected.equals(shareTarget)) { + checkoutUrl = parenUrl; + revision = SVNRevision.HEAD; + } else if (ShareDialog.ShareTarget.useProjectName.equals(shareTarget)) { + final Pair pair = createRemoteFolder(activeVcs, parenUrl, file.getName()); + revision = pair.getFirst(); + checkoutUrl = pair.getSecond(); + } else { + final Pair pair = createRemoteFolder(activeVcs, parenUrl, file.getName()); + final Pair trunkPair = createRemoteFolder(activeVcs, pair.getSecond(), "trunk"); + checkoutUrl = trunkPair.getSecond(); + revision = trunkPair.getFirst(); + + if (shareDialog.createStandardStructure()) { + createRemoteFolder(activeVcs, pair.getSecond(), "branches"); + createRemoteFolder(activeVcs, pair.getSecond(), "tags"); + } + } + + if (indicator != null) { + indicator.checkCanceled(); + indicator.setText(SvnBundle.message("share.directory.checkout.back.progress.text", checkoutUrl.toString())); + } + activeVcs.createUpdateClient().doCheckout(checkoutUrl, path, SVNRevision.UNDEFINED, revision, true); + SvnWorkingCopyFormatHolder.setPresetFormat(null); + + addRecursively(activeVcs, file); + } catch (SVNException e) { + error[0] = e; + } finally { + activeVcs.invokeRefreshSvnRoots(true); + SvnWorkingCopyFormatHolder.setPresetFormat(null); + } + } + }, SvnBundle.message("share.directory.title"), true, project); + } + }); + + if (Boolean.TRUE.equals(actionStarted.get())) { + if (error[0] != null) { + throw new VcsException(error[0].getMessage()); + } + Messages.showInfoMessage(project, SvnBundle.message("share.directory.info.message", file.getName()), + SvnBundle.message("share.directory.title")); + } + return true; + } + return false; + } + + private static boolean checkRemoteFolder(Project project, + final SvnVcs17 activeVcs, + final String parent, + ProgressManager progressManager) throws VcsException { + final SVNException[] exc = new SVNException[1]; + final boolean[] folderEmpty = new boolean[1]; + + progressManager.runProcessWithProgressSynchronously(new Runnable() { + @Override + public void run() { + try { + folderEmpty[0] = SvnUtil.remoteFolderIsEmpty(activeVcs, parent); + } + catch (SVNException e) { + exc[0] = e; + } + } + }, "Check remote folder contents", false, project); + if (exc[0] != null) { + throw new VcsException(exc[0]); + } + return folderEmpty[0]; + } + + private static Pair createRemoteFolder(final SvnVcs17 vcs, final SVNURL parent, final String folderName) throws SVNException { + SVNURL url = parent.appendPath(folderName, false); + final String urlText = url.toString(); + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (indicator != null) { + indicator.checkCanceled(); + indicator.setText(SvnBundle.message("share.directory.create.dir.progress.text", urlText)); + } + final SVNCommitInfo info = + vcs.createCommitClient().doMkDir(new SVNURL[]{url}, SvnBundle.message("share.directory.commit.message", folderName, + ApplicationNamesInfo.getInstance().getFullProductName())); + return new Pair(SVNRevision.create(info.getNewRevision()), url); + } + + @Override + protected void doVcsRefresh(final Project project, final VirtualFile file) { + VcsDirtyScopeManager.getInstance(project).dirDirtyRecursively(file); + } + + private static void addRecursively(final SvnVcs17 activeVcs, final VirtualFile file) throws SVNException { + final SVNWCClient wcClient = activeVcs.createWCClient(); + final SvnExcludingIgnoredOperation operation = new SvnExcludingIgnoredOperation(activeVcs.getProject(), new SvnExcludingIgnoredOperation.Operation() { + public void doOperation(final VirtualFile virtualFile) throws SVNException { + final File ioFile = new File(virtualFile.getPath()); + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (indicator != null) { + indicator.checkCanceled(); + indicator.setText(SvnBundle.message("share.or.import.add.progress.text", virtualFile.getPath())); + } + wcClient.doAdd(ioFile, true, false, false, SVNDepth.EMPTY, false, false); + } + }, SVNDepth.INFINITY); + + operation.execute(file); + } + + protected void batchPerform(Project project, final SvnVcs17 activeVcs, VirtualFile[] file, DataContext context) throws VcsException { + } + + protected boolean isBatchAction() { + return false; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShareWholeProject.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShareWholeProject.java new file mode 100644 index 000000000000..3c689863d637 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShareWholeProject.java @@ -0,0 +1,165 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.AbstractVcsHelper; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.VcsDirectoryMapping; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnStatusUtil; +import org.jetbrains.idea.svn17.SvnUtil; +import org.jetbrains.idea.svn17.SvnVcs17; + +import java.util.Arrays; +import java.util.List; + +public class ShareWholeProject extends AnAction implements DumbAware { + @Override + public void update(final AnActionEvent e) { + final MyChecker checker = new MyChecker(); + checker.execute(e); + + final Presentation presentation = e.getPresentation(); + presentation.setEnabled(checker.isEnabled()); + + presentation.setVisible(checker.isVisible()); + if (checker.isEnabled()) { + presentation.setText(SvnBundle.message("action.share.whole.project.text")); + } + } + + private static class MyChecker { + private boolean myEnabled; + private boolean myVisible; + private Project myProject; + private boolean myHadNoMappings; + + public void execute(final AnActionEvent e) { + final DataContext dataContext = e.getDataContext(); + myProject = PlatformDataKeys.PROJECT.getData(dataContext); + if (myProject == null || myProject.isDefault()) { + // remain false + return; + } + final VirtualFile baseDir = myProject.getBaseDir(); + if (baseDir == null) return; + + final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject); + final MyCheckResult result = checkMappings(baseDir, vcsManager); + + if (MyCheckResult.disable.equals(result)) return; + + myHadNoMappings = MyCheckResult.notMapped.equals(result); + if (MyCheckResult.notMapped.equals(result)) { + // no change list manager working + if(SvnUtil.seemsLikeVersionedDir(baseDir)) return; + } else if (SvnStatusUtil.isUnderControl(myProject, baseDir)) { + return; + } + + if ((! myHadNoMappings) && (! SvnVcs17.getInstance(myProject).getSvnFileUrlMapping().isEmpty())) { + // there are some versioned dirs under project dir + return; + } + + // visible: already checked above + myVisible = true; + myEnabled = (! vcsManager.isBackgroundVcsOperationRunning()); + } + + private static enum MyCheckResult { + disable, + notMapped, + rootToSvn + } + + private static MyCheckResult checkMappings(final VirtualFile baseDir, final ProjectLevelVcsManager vcsManager) { + final List mappings = vcsManager.getDirectoryMappings(); + + boolean notMapped = true; + boolean svnMappedToBase = false; + for (VcsDirectoryMapping mapping : mappings) { + final String vcs = mapping.getVcs(); + if (vcs != null && vcs.length() > 0) { + notMapped = false; + if (SvnVcs17.VCS_NAME.equals(vcs)) { + if (mapping.isDefaultMapping() || baseDir.getPath().equals(mapping.getDirectory())) { + svnMappedToBase = true; + break; + } + } + } + } + + return svnMappedToBase ? MyCheckResult.rootToSvn : + (notMapped ? MyCheckResult.notMapped : MyCheckResult.disable); + } + + public boolean isEnabled() { + return myEnabled; + } + + public boolean isVisible() { + return myVisible; + } + + public Project getProject() { + return myProject; + } + + public boolean isHadNoMappings() { + return myHadNoMappings; + } + } + + + public void actionPerformed(AnActionEvent e) { + final MyChecker checker = new MyChecker(); + checker.execute(e); + if (! checker.isEnabled()) return; + + final Project project = checker.getProject(); + final VirtualFile baseDir = project.getBaseDir(); + if (baseDir == null) return; + boolean success = false; + boolean excThrown = false; + try { + success = ShareProjectAction.share(project, baseDir); + } catch (VcsException exc) { + AbstractVcsHelper.getInstance(project).showError(exc, "Failed to Share Project"); + excThrown = true; + } finally { + // if success = false -> either action was cancelled or exception was thrown, so also check for exception + if (success || excThrown) { + baseDir.refresh(true, true, new Runnable() { + public void run() { + VcsDirtyScopeManager.getInstance(project).dirDirtyRecursively(project.getBaseDir()); + if (checker.isHadNoMappings() && SvnUtil.seemsLikeVersionedDir(baseDir)) { + final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(project); + vcsManager.setDirectoryMappings(Arrays.asList(new VcsDirectoryMapping("", SvnVcs17.VCS_NAME))); + } + } + }); + } + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShowAllSubmittedFilesAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShowAllSubmittedFilesAction.java new file mode 100644 index 000000000000..357f4f6c8f64 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShowAllSubmittedFilesAction.java @@ -0,0 +1,172 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream; +import com.intellij.openapi.vcs.AbstractVcsHelper; +import com.intellij.openapi.vcs.VcsDataKeys; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.history.VcsFileRevision; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.*; +import org.jetbrains.idea.svn17.history.SvnChangeList; +import org.jetbrains.idea.svn17.history.SvnFileRevision; +import org.jetbrains.idea.svn17.history.SvnRepositoryLocation; +import org.tmatesoft.svn.core.ISVNLogEntryHandler; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNLogEntry; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.SVNInfo; +import org.tmatesoft.svn.core.wc.SVNLogClient; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.io.File; +import java.io.PrintStream; + +public class ShowAllSubmittedFilesAction extends AnAction implements DumbAware { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.actions.ShowAllSubmittedFilesAction"); + + public ShowAllSubmittedFilesAction() { + super(SvnBundle.message("action.name.show.all.paths.affected"), null, IconLoader.getIcon("/icons/allRevisions.png")); + } + + public void update(AnActionEvent e) { + super.update(e); + final Project project = e.getData(PlatformDataKeys.PROJECT); + if (project == null) { + e.getPresentation().setEnabled(false); + return; + } + final VirtualFile revisionVirtualFile = e.getData(VcsDataKeys.VCS_VIRTUAL_FILE); + e.getPresentation().setEnabled((e.getData(VcsDataKeys.VCS_FILE_REVISION) != null) && (revisionVirtualFile != null)); + } + + public void actionPerformed(AnActionEvent e) { + final Project project = e.getData(PlatformDataKeys.PROJECT); + if (project == null) return; + final VcsFileRevision revision = e.getData(VcsDataKeys.VCS_FILE_REVISION); + final VirtualFile revisionVirtualFile = e.getData(VcsDataKeys.VCS_VIRTUAL_FILE); + if ((revision != null) && (revisionVirtualFile != null)) { + final SvnFileRevision svnRevision = ((SvnFileRevision)revision); + + showSubmittedFiles(project, svnRevision, revisionVirtualFile); + } + } + + public static void showSubmittedFiles(final Project project, final SvnFileRevision svnRevision, final VirtualFile file) { + final SvnChangeList changeList = loadRevisions(project, svnRevision, file); + + if (changeList != null) { + long revNumber = ((SvnRevisionNumber)svnRevision.getRevisionNumber()).getRevision().getNumber(); + AbstractVcsHelper.getInstance(project).showChangesListBrowser(changeList, getTitle(revNumber)); + } + } + + private static String getTitle(long revisionNumber) { + return SvnBundle.message("dialog.title.affected.paths", revisionNumber); + } + + @Nullable + public static SvnChangeList loadRevisions(final Project project, final SvnFileRevision svnRevision, @Nullable final VirtualFile file) { + return loadRevisions(project, svnRevision, file, true); + } + + @Nullable + public static SvnChangeList loadRevisions(final Project project, final SvnFileRevision svnRevision, @Nullable final VirtualFile file, boolean underProgress) { + final Ref result = new Ref(); + final SvnRevisionNumber number = ((SvnRevisionNumber)svnRevision.getRevisionNumber()); + + final SVNRevision targetRevision = ((SvnRevisionNumber)svnRevision.getRevisionNumber()).getRevision(); + final SvnVcs17 vcs = SvnVcs17.getInstance(project); + + try { + final Exception[] ex = new Exception[1]; + final String url = svnRevision.getURL(); + final SVNLogEntry[] logEntry = new SVNLogEntry[1]; + final SvnRepositoryLocation location = new SvnRepositoryLocation(url); + + final SVNLogClient client = vcs.createLogClient(); + final SVNURL repositoryUrl; + if ((file != null) && file.isInLocalFileSystem()) { + final SvnFileUrlMapping17 urlMapping = vcs.getSvnFileUrlMapping(); + final RootUrlInfo wcRoot = urlMapping.getWcRootForFilePath(new File(file.getPath())); + if (wcRoot == null) { + return null; + } + repositoryUrl = wcRoot.getRepositoryUrlUrl(); + } else { + final SVNInfo svnInfo = vcs.createWCClient().doInfo(SVNURL.parseURIEncoded(url), SVNRevision.HEAD, SVNRevision.HEAD); + repositoryUrl = svnInfo.getRepositoryRootURL(); + if (repositoryUrl == null) { + Messages.showErrorDialog(SvnBundle.message("message.text.cannot.load.version", number, "Cannot get repository url"), + SvnBundle.message("message.title.error.fetching.affected.paths")); + return null; + } + } + + final Runnable process = new Runnable() { + public void run() { + try { + + ProgressManager.getInstance().getProgressIndicator().setText(SvnBundle.message("progress.text.loading.log")); + client.doLog(repositoryUrl, null, targetRevision, targetRevision, targetRevision, false, true, 0, new ISVNLogEntryHandler() { + public void handleLogEntry(final SVNLogEntry currentLogEntry) throws SVNException { + logEntry[0] = currentLogEntry; + } + }); + if (logEntry[0] == null) { + throw new VcsException(SvnBundle.message("exception.text.cannot.load.version", number)); + } + + ProgressManager.getInstance().getProgressIndicator().setText(SvnBundle.message("progress.text.processing.changes")); + result.set(new SvnChangeList(vcs, location, logEntry[0], repositoryUrl.toString())); + } + catch (Exception e) { + ex[0] = e; + } + } + }; + if (underProgress) { + ProgressManager.getInstance().runProcessWithProgressSynchronously(process, getTitle(targetRevision.getNumber()), false, project); + } else { + process.run(); + } + if (ex[0] != null) throw ex[0]; + } + catch (Exception e1) { + final BufferExposingByteArrayOutputStream baos = new BufferExposingByteArrayOutputStream(); + e1.printStackTrace(new PrintStream(baos)); + LOG.info("For url: " + svnRevision.getURL() + "Exception: " + new String(baos.getInternalBuffer(), 0, baos.size())); + + Messages.showErrorDialog(SvnBundle.message("message.text.cannot.load.version", number, e1.getLocalizedMessage()), + SvnBundle.message("message.title.error.fetching.affected.paths")); + return null; + } + + return result.get(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShowPropertiesAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShowPropertiesAction.java new file mode 100644 index 000000000000..60abd725fefc --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShowPropertiesAction.java @@ -0,0 +1,87 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowAnchor; +import com.intellij.openapi.wm.ToolWindowManager; +import org.jetbrains.idea.svn17.SvnUtil; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.dialogs.PropertiesComponent; + +import java.io.File; + +public class ShowPropertiesAction extends BasicAction { + + protected String getActionName(AbstractVcs vcs) { + return "Show Properties"; + } + + protected boolean needsAllFiles() { + return false; + } + + protected boolean isEnabled(Project project, SvnVcs17 vcs, VirtualFile file) { + if (!file.isDirectory()) { + file = file.getParent(); + } + return file != null && file.getPath() != null && SvnUtil.isSvnVersioned(new File(file.getPath())); + } + + protected boolean needsFiles() { + return true; + } + + protected void perform(Project project, SvnVcs17 activeVcs, VirtualFile file, DataContext context) + throws VcsException { + batchPerform(project, activeVcs, new VirtualFile[]{file}, context); + } + + protected void batchPerform(Project project, final SvnVcs17 activeVcs, VirtualFile[] file, DataContext context) throws VcsException { + final File[] ioFiles = new File[file.length]; + for (int i = 0; i < ioFiles.length; i++) { + ioFiles[i] = new File(file[i].getPath()); + } + if (ioFiles.length > 0) { + ToolWindow w = ToolWindowManager.getInstance(project).getToolWindow(PropertiesComponent.ID); + PropertiesComponent component = null; + if (w == null) { + component = new PropertiesComponent(); + w = ToolWindowManager.getInstance(project).registerToolWindow(PropertiesComponent.ID, component, ToolWindowAnchor.BOTTOM); + } else { + component = ((PropertiesComponent) w.getContentManager().getContents()[0].getComponent()); + } + w.setTitle(ioFiles[0].getName()); + w.show(null); + final PropertiesComponent comp = component; + w.activate(new Runnable() { + public void run() { + comp.setFile(activeVcs, ioFiles[0]); + } + }); + } + + } + + protected boolean isBatchAction() { + return false; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShowPropertiesDiffAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShowPropertiesDiffAction.java new file mode 100644 index 000000000000..fd36b1d966b2 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShowPropertiesDiffAction.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.actions; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DataKey; +import com.intellij.openapi.vcs.VcsDataKeys; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ContentRevision; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnRevisionNumber; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.SVNRevision; + +public class ShowPropertiesDiffAction extends AbstractShowPropertiesDiffAction { + public ShowPropertiesDiffAction() { + super(SvnBundle.message("action.Subversion.properties.diff.name")); + } + + @Override + public void update(final AnActionEvent e) { + super.update(e); + //e.getPresentation().setText(SvnBundle.message("action.Subversion.properties.diff.name")); + } + + protected DataKey getChangesKey() { + return VcsDataKeys.CHANGE_LEAD_SELECTION; + } + + protected SVNRevision getBeforeRevisionValue(final Change change, final SvnVcs17 vcs) throws SVNException { + final ContentRevision beforeRevision = change.getBeforeRevision(); + if (beforeRevision != null) { + return ((SvnRevisionNumber) beforeRevision.getRevisionNumber()).getRevision(); + } else { + return SVNRevision.create(((SvnRevisionNumber) change.getAfterRevision().getRevisionNumber()).getRevision().getNumber() - 1); + } + } + + protected SVNRevision getAfterRevisionValue(final Change change, final SvnVcs17 vcs) throws SVNException { + final ContentRevision afterRevision = change.getAfterRevision(); + if (afterRevision != null) { + return ((SvnRevisionNumber) afterRevision.getRevisionNumber()).getRevision(); + } else { + return SVNRevision.create(((SvnRevisionNumber) change.getBeforeRevision().getRevisionNumber()).getRevision().getNumber() + 1); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShowPropertiesDiffWithLocalAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShowPropertiesDiffWithLocalAction.java new file mode 100644 index 000000000000..4871a7f3b706 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShowPropertiesDiffWithLocalAction.java @@ -0,0 +1,60 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DataKey; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.vcs.VcsDataKeys; +import com.intellij.openapi.vcs.changes.Change; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import javax.swing.*; + +public class ShowPropertiesDiffWithLocalAction extends AbstractShowPropertiesDiffAction { + private final Icon myIcon; + + public ShowPropertiesDiffWithLocalAction() { + super(SvnBundle.message("action.Subversion.properties.diff.with.local.name")); + myIcon = IconLoader.getIcon("/icons/PropertiesDiffWithLocal.png"); + } + + @Override + public void update(final AnActionEvent e) { + super.update(e); + + //e.getPresentation().setText(SvnBundle.message("action.Subversion.properties.diff.with.local.name")); + e.getPresentation().setIcon(myIcon); + } + + protected DataKey getChangesKey() { + return VcsDataKeys.CHANGE_LEAD_SELECTION; + } + + @Nullable + protected SVNRevision getBeforeRevisionValue(final Change change, final SvnVcs17 vcs) throws SVNException { + return SVNRevision.HEAD; + } + + @Nullable + protected SVNRevision getAfterRevisionValue(final Change change, final SvnVcs17 vcs) throws SVNException { + return SVNRevision.WORKING; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShowPropertiesDiffWithLocalAlienAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShowPropertiesDiffWithLocalAlienAction.java new file mode 100644 index 000000000000..3a2c63d3c005 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShowPropertiesDiffWithLocalAlienAction.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.actions; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ContentRevision; +import org.jetbrains.idea.svn17.SvnRevisionNumber; + +public class ShowPropertiesDiffWithLocalAlienAction extends ShowPropertiesDiffWithLocalAction { + @Override + protected boolean checkVcs(final Project project, final Change change) { + final ContentRevision contentRevision = change.getBeforeRevision(); + return (contentRevision != null) && (contentRevision.getRevisionNumber() instanceof SvnRevisionNumber); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShowSvnMapAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShowSvnMapAction.java new file mode 100644 index 000000000000..7de696bc98d8 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/ShowSvnMapAction.java @@ -0,0 +1,76 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowId; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.ui.content.Content; +import com.intellij.ui.content.ContentFactory; +import com.intellij.ui.content.ContentManager; +import com.intellij.util.ContentsUtil; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.dialogs.CopiesPanel; + +public class ShowSvnMapAction extends AnAction implements DumbAware { + public ShowSvnMapAction() { + super(SvnBundle.message("action.show.svn.map.text")); + } + + @Override + public void update(final AnActionEvent e) { + final Project project = e.getData(PlatformDataKeys.PROJECT); + + final Presentation presentation = e.getPresentation(); + presentation.setVisible(project != null); + presentation.setEnabled(project != null); + + presentation.setDescription(SvnBundle.message("action.show.svn.map.description")); + presentation.setIcon(IconLoader.getIcon("/icons/ShowWorkingCopies.png")); + } + + public void actionPerformed(final AnActionEvent e) { + final Project project = e.getData(PlatformDataKeys.PROJECT); + if (project == null) { + return; + } + + final CopiesPanel copiesPanel = new CopiesPanel(project); + ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.VCS); + final ContentManager contentManager = toolWindow.getContentManager(); + + Content content = ContentFactory.SERVICE.getInstance().createContent(copiesPanel.getComponent(), SvnBundle.message("dialog.show.svn.map.title"), true); + ContentsUtil.addOrReplaceContent(contentManager, content, true); + toolWindow.activate(new Runnable() { + public void run() { + IdeFocusManager.getInstance(project).requestFocus(copiesPanel.getPreferredFocusedComponent(), true); + } + }); + /*SwingUtilities.invokeLater(new Runnable() { + public void run() { + IdeFocusManager.getInstance(project).requestFocus(copiesPanel.getPrefferedFocusComponent(), true); + } + });*/ + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/SubversionGroup.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/SubversionGroup.java new file mode 100644 index 000000000000..4167b99d5f9e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/SubversionGroup.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.actions; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.actions.StandardVcsGroup; +import org.jetbrains.idea.svn17.SvnVcs17; + +public class SubversionGroup extends StandardVcsGroup { + public AbstractVcs getVcs(Project project) { + return SvnVcs17.getInstance(project); + } + + @Override + public String getVcsName(final Project project) { + return "svn"; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/SvnExcludingIgnoredOperation.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/SvnExcludingIgnoredOperation.java new file mode 100644 index 000000000000..0d57ba09bef6 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/SvnExcludingIgnoredOperation.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.actions; + +import com.intellij.lifecycle.PeriodicalTasksCloser; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.FileIndexFacade; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vfs.VirtualFile; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNException; + +public class SvnExcludingIgnoredOperation { + private final Operation myImportAction; + private final SVNDepth myDepth; + private final Filter myFilter; + + public SvnExcludingIgnoredOperation(final Project project, final Operation importAction, final SVNDepth depth) { + myImportAction = importAction; + myDepth = depth; + + myFilter = new Filter(project); + } + + public static class Filter { + private final Project myProject; + private final FileIndexFacade myIndex; + private final ChangeListManager myClManager; + + public Filter(final Project project) { + myProject = project; + + if (! project.isDefault()) { + myIndex = PeriodicalTasksCloser.getInstance().safeGetService(project, FileIndexFacade.class); + myClManager = ChangeListManager.getInstance(project); + } else { + myIndex = null; + myClManager = null; + } + } + + public boolean accept(final VirtualFile file) { + if (! myProject.isDefault()) { + if (myIndex.isExcludedFile(file)) { + return false; + } + if (myClManager.isIgnoredFile(file)) { + return false; + } + } + return true; + } + } + + private boolean operation(final VirtualFile file) throws SVNException { + if (! myFilter.accept(file)) return false; + + myImportAction.doOperation(file); + return true; + } + + private void executeDown(final VirtualFile file) throws SVNException { + if (! operation(file)) { + return; + } + + for (VirtualFile child : file.getChildren()) { + executeDown(child); + } + } + + public void execute(final VirtualFile file) throws SVNException { + if (SVNDepth.INFINITY.equals(myDepth)) { + executeDown(file); + return; + } + + if (! operation(file)) { + return; + } + + if (SVNDepth.EMPTY.equals(myDepth)) { + return; + } + + for (VirtualFile child : file.getChildren()) { + if (SVNDepth.FILES.equals(myDepth) && child.isDirectory()) { + continue; + } + operation(child); + } + } + + public interface Operation { + void doOperation(final VirtualFile file) throws SVNException; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/SvnMergeProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/SvnMergeProvider.java new file mode 100644 index 000000000000..c8fd30594ac8 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/SvnMergeProvider.java @@ -0,0 +1,139 @@ +/* + * 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.actions; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.VcsBundle; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.merge.MergeData; +import com.intellij.openapi.vcs.merge.MergeProvider; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.vcsUtil.VcsRunnable; +import com.intellij.vcsUtil.VcsUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn17.SvnRevisionNumber; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNProperty; +import org.tmatesoft.svn.core.SVNPropertyValue; +import org.tmatesoft.svn.core.wc.*; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; + +/** + * @author lesya + * @author yole + */ +public class SvnMergeProvider implements MergeProvider { + + private final Project myProject; + + public SvnMergeProvider(final Project project) { + myProject = project; + } + + @NotNull + public MergeData loadRevisions(final VirtualFile file) throws VcsException { + final MergeData data = new MergeData(); + VcsRunnable runnable = new VcsRunnable() { + public void run() throws VcsException { + SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + File oldFile = null; + File newFile = null; + File workingFile = null; + SVNWCClient client; + try { + client = vcs.createWCClient(); + SVNInfo info = client.doInfo(new File(file.getPath()), SVNRevision.UNDEFINED); + if (info != null) { + oldFile = info.getConflictOldFile(); + newFile = info.getConflictNewFile(); + workingFile = info.getConflictWrkFile(); + data.LAST_REVISION_NUMBER = new SvnRevisionNumber(info.getRevision()); + } + } + catch (SVNException e) { + throw new VcsException(e); + } + if (oldFile == null || newFile == null || workingFile == null) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + client.doGetFileContents(new File(file.getPath()), SVNRevision.UNDEFINED, SVNRevision.BASE, true, bos); + } + catch (SVNException e) { + // + } + data.ORIGINAL = bos.toByteArray(); + data.LAST = bos.toByteArray(); + data.CURRENT = readFile(new File(file.getPath())); + } + else { + data.ORIGINAL = readFile(oldFile); + data.LAST = readFile(newFile); + data.CURRENT = readFile(workingFile); + } + } + }; + VcsUtil.runVcsProcessWithProgress(runnable, VcsBundle.message("multiple.file.merge.loading.progress.title"), false, myProject); + + return data; + } + + private static byte[] readFile(File workingFile) throws VcsException { + try { + return FileUtil.loadFileBytes(workingFile); + } + catch (IOException e) { + throw new VcsException(e); + } + } + + public void conflictResolvedForFile(VirtualFile file) { + SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + try { + SVNWCClient client = vcs.createWCClient(); + client.doResolve(new File(file.getPath()), SVNDepth.EMPTY, true, false, SVNConflictChoice.MERGED); + } + catch (SVNException e) { + // + } + // the .mine/.r## files have been deleted + final VirtualFile parent = file.getParent(); + if (parent != null) { + parent.refresh(true, false); + } + } + + public boolean isBinary(final VirtualFile file) { + SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + try { + SVNWCClient client = vcs.createWCClient(); + File ioFile = new File(file.getPath()); + SVNPropertyData svnPropertyData = client.doGetProperty(ioFile, SVNProperty.MIME_TYPE, SVNRevision.UNDEFINED, SVNRevision.WORKING); + if (svnPropertyData != null && SVNProperty.isBinaryMimeType(SVNPropertyValue.getPropertyAsString(svnPropertyData.getValue()))) { + return true; + } + } + catch (SVNException e) { + // + } + return false; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/SvnQuickListContentProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/SvnQuickListContentProvider.java new file mode 100644 index 000000000000..53c5f0f9a7be --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/SvnQuickListContentProvider.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.actions; + +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.actions.VcsQuickListContentProvider; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnVcs17; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author Roman.Chernyatchik + */ +public class SvnQuickListContentProvider implements VcsQuickListContentProvider { + public List getVcsActions(@Nullable Project project, @Nullable AbstractVcs activeVcs, + @Nullable DataContext dataContext) { + + if (activeVcs == null || !SvnVcs17.VCS_NAME.equals(activeVcs.getName())) { + return null; + } + + final ActionManager manager = ActionManager.getInstance(); + final List actions = new ArrayList(); + add("Subversion.Copy", manager, actions); + add("Subversion.Clenaup", manager, actions); + return actions; + } + + public List getNotInVcsActions(@Nullable Project project, @Nullable DataContext dataContext) { + final AnAction action = ActionManager.getInstance().getAction("Subversion.ImportToSVNRepository"); + assert action != null; + return Collections.singletonList(action); + } + + public boolean replaceVcsActionsFor(@NotNull AbstractVcs activeVcs, @Nullable DataContext dataContext) { + return false; + } + + private void add(String actionName, ActionManager manager, List actions) { + final AnAction action = manager.getAction(actionName); + assert action != null; + actions.add(action); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/UnlockAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/UnlockAction.java new file mode 100644 index 000000000000..b1a78889d607 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/actions/UnlockAction.java @@ -0,0 +1,70 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnStatusUtil; +import org.jetbrains.idea.svn17.SvnUtil; +import org.jetbrains.idea.svn17.SvnVcs17; + +import java.io.File; + +public class UnlockAction extends BasicAction { + protected String getActionName(AbstractVcs vcs) { + return SvnBundle.message("action.Subversion.Unlock.description"); + } + + protected boolean needsAllFiles() { + return true; + } + + protected boolean isEnabled(Project project, SvnVcs17 vcs, VirtualFile file) { + if (file == null || file.isDirectory()) { + return false; + } + return SvnStatusUtil.isExplicitlyLocked(project, file); + } + + protected boolean needsFiles() { + return true; + } + + protected void perform(Project project, SvnVcs17 activeVcs, VirtualFile file, DataContext context) + throws VcsException { + batchPerform(project, activeVcs, new VirtualFile[]{file}, context); + } + + protected void batchPerform(Project project, SvnVcs17 activeVcs, VirtualFile[] files, DataContext context) + throws VcsException { + File[] ioFiles = new File[files.length]; + for (int i = 0; i < files.length; i++) { + VirtualFile virtualFile = files[i]; + ioFiles[i] = new File(virtualFile.getPath()); + } + SvnUtil.doUnlockFiles(project, activeVcs, ioFiles); + } + + protected boolean isBatchAction() { + return true; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/annotate/SvnAnnotationProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/annotate/SvnAnnotationProvider.java new file mode 100644 index 000000000000..89d99493acef --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/annotate/SvnAnnotationProvider.java @@ -0,0 +1,372 @@ +/* + * 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.annotate; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.fileEditor.impl.LoadTextUtil; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.vcs.FilePathImpl; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.annotate.*; +import com.intellij.openapi.vcs.history.VcsAbstractHistorySession; +import com.intellij.openapi.vcs.history.VcsFileRevision; +import com.intellij.openapi.vcs.history.VcsHistoryUtil; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.*; +import org.jetbrains.idea.svn17.history.SvnFileRevision; +import org.tmatesoft.svn.core.*; +import org.tmatesoft.svn.core.wc.*; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.*; + +public class SvnAnnotationProvider implements AnnotationProvider, VcsCacheableAnnotationProvider { + private static final Object MERGED_KEY = new Object(); + private final SvnVcs17 myVcs; + + public SvnAnnotationProvider(final SvnVcs17 vcs) { + myVcs = vcs; + } + + public FileAnnotation annotate(final VirtualFile file) throws VcsException { + final SvnRevisionNumber currentRevision = (SvnRevisionNumber)myVcs.getDiffProvider().getCurrentRevision(file); + if (currentRevision == null) { + throw new VcsException("Can not get current revision for file " + file.getPath()); + } + final SVNRevision svnRevision = currentRevision.getRevision(); + return annotate(file, new SvnFileRevision(myVcs, svnRevision, svnRevision, null, null, null, null, null, file.getCharset()), true); + } + + public FileAnnotation annotate(final VirtualFile file, final VcsFileRevision revision) throws VcsException { + return annotate(file, revision, false); + } + + private FileAnnotation annotate(final VirtualFile file, final VcsFileRevision revision, final boolean loadExternally) throws VcsException { + if (file.isDirectory()) { + throw new VcsException(SvnBundle.message("exception.text.cannot.annotate.directory")); + } + final FileAnnotation[] annotation = new FileAnnotation[1]; + final VcsException[] exception = new VcsException[1]; + + Runnable command = new Runnable() { + public void run() { + final ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + try { + final File ioFile = new File(file.getPath()).getAbsoluteFile(); + + final String contents; + if (loadExternally) { + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + myVcs.createWCClient().doGetFileContents(ioFile, SVNRevision.UNDEFINED, SVNRevision.BASE, true, buffer); + contents = LoadTextUtil.getTextByBinaryPresentation(buffer.toByteArray(), file, false).toString(); + } else { + final byte[] bytes = VcsHistoryUtil.loadRevisionContent(revision); + contents = LoadTextUtil.getTextByBinaryPresentation(bytes, file, false).toString(); + } + + final SvnFileAnnotation result = new SvnFileAnnotation(myVcs, file, contents, revision.getRevisionNumber()); + + SVNWCClient wcClient = myVcs.createWCClient(); + SVNInfo info = wcClient.doInfo(ioFile, SVNRevision.UNDEFINED); + if (info == null) { + exception[0] = new VcsException(new SVNException(SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "File ''{0}'' is not under version control", ioFile))); + return; + } + final String url = info.getURL() == null ? null : info.getURL().toString(); + + SVNLogClient client = myVcs.createLogClient(); + setLogClientOptions(client); + SVNRevision endRevision = ((SvnFileRevision) revision).getRevision(); + if (SVNRevision.WORKING.equals(endRevision)) { + endRevision = info.getRevision(); + } + if (progress != null) { + progress.setText(SvnBundle.message("progress.text.computing.annotation", file.getName())); + } + + // ignore mime type=true : IDEA-19562 + final ISVNAnnotateHandler annotateHandler = createAnnotationHandler(progress, result); + + final boolean calculateMergeinfo = SvnConfiguration17.getInstance(myVcs.getProject()).SHOW_MERGE_SOURCES_IN_ANNOTATE && + SvnUtil.checkRepositoryVersion15(myVcs, url); + final SVNRevision svnRevision = ((SvnRevisionNumber)revision.getRevisionNumber()).getRevision(); + + final MySteppedLogGetter logGetter = new MySteppedLogGetter(myVcs, ioFile, progress, client, endRevision, result, url, calculateMergeinfo, file.getCharset()); + logGetter.go(); + final LinkedList rp = logGetter.getRevisionPoints(); + + for (int i = 0; i < rp.size() - 1; i++) { + client.doAnnotate(ioFile, svnRevision, rp.get(i + 1), rp.get(i), true, calculateMergeinfo, annotateHandler, null); + } + + if (rp.get(1).getNumber() > 0) { + result.setFirstRevision(rp.get(1)); + } + annotation[0] = result; + } + catch (SVNException e) { + exception[0] = new VcsException(e); + } catch (IOException e) { + exception[0] = new VcsException(e); + } catch (VcsException e) { + exception[0] = e; + } + } + }; + if (ApplicationManager.getApplication().isDispatchThread()) { + ProgressManager.getInstance().runProcessWithProgressSynchronously(command, SvnBundle.message("action.text.annotate"), false, myVcs.getProject()); + } + else { + command.run(); + } + if (exception[0] != null) { + throw new VcsException(exception[0]); + } + return annotation[0]; + } + + private ISVNAnnotateHandler createAnnotationHandler(final ProgressIndicator progress, final SvnFileAnnotation result) { + return new ISVNAnnotateHandler() { + public void handleLine(Date date, long revision, String author, String line) { + if (progress != null) { + progress.checkCanceled(); + } + result.appendLineInfo(date, revision, author, null, -1, null); + } + + public void handleLine(final Date date, + final long revision, + final String author, + final String line, + final Date mergedDate, + final long mergedRevision, + final String mergedAuthor, + final String mergedPath, + final int lineNumber) throws SVNException { + if (progress != null) { + progress.checkCanceled(); + } + if (revision == -1) return; + if ((mergedDate != null) && (revision > mergedRevision)) { + // !!! merged date = date of merge, i.e. date -> date of original change etc. + result.setLineInfo(lineNumber, date, revision, author, mergedDate, mergedRevision, mergedAuthor); + } else { + result.setLineInfo(lineNumber, date, revision, author, null, -1, null); + } + } + + public boolean handleRevision(final Date date, final long revision, final String author, final File contents) + throws SVNException { + if (progress != null) { + progress.checkCanceled(); + } + return false; + } + + public void handleEOF() { + } + }; + } + + @Override + public VcsAnnotation createCacheable(FileAnnotation fileAnnotation) { + final SvnFileAnnotation svnFileAnnotation = (SvnFileAnnotation)fileAnnotation; + final AnnotationSourceSwitcher annotationSourceSwitcher = svnFileAnnotation.getAnnotationSourceSwitcher(); + if (annotationSourceSwitcher != null) { + annotationSourceSwitcher.switchTo(AnnotationSource.LOCAL); + } + final int size = svnFileAnnotation.getNumLines(); + + final VcsUsualLineAnnotationData lineAnnotationData = new VcsUsualLineAnnotationData(size); + for (int i = 0; i < size; i++) { + final VcsRevisionNumber revisionNumber = svnFileAnnotation.getLineRevisionNumber(i); + lineAnnotationData.put(i, revisionNumber); + } + + final VcsAnnotation vcsAnnotation = new VcsAnnotation(new FilePathImpl(svnFileAnnotation.getFile()), lineAnnotationData, + svnFileAnnotation.getFirstRevisionNumber()); + + if (annotationSourceSwitcher != null) { + final VcsRareLineAnnotationData merged = new VcsRareLineAnnotationData(size); + final Map addMap = new HashMap(); + + annotationSourceSwitcher.switchTo(AnnotationSource.MERGE); + for (int i = 0; i < size; i++) { + if (annotationSourceSwitcher.mergeSourceAvailable(i)) { + final VcsRevisionNumber number = svnFileAnnotation.getLineRevisionNumber(i); + if (number == null) continue; + merged.put(i, number); + addMap.put(number, svnFileAnnotation.getRevision(((SvnRevisionNumber) number).getRevision().getNumber())); + } + } + if (! merged.isEmpty()) { + vcsAnnotation.addAnnotation(MERGED_KEY, merged); + vcsAnnotation.addCachedOtherRevisions(addMap); + } + } + + return vcsAnnotation; + } + + @Nullable + @Override + public FileAnnotation restore(VcsAnnotation vcsAnnotation, + VcsAbstractHistorySession session, + String annotatedContent, + boolean forCurrentRevision, VcsRevisionNumber revisionNumber) { + final SvnFileAnnotation annotation = + new SvnFileAnnotation(myVcs, vcsAnnotation.getFilePath().getVirtualFile(), annotatedContent, revisionNumber); + final VcsLineAnnotationData basicAnnotation = vcsAnnotation.getBasicAnnotation(); + final VcsLineAnnotationData data = vcsAnnotation.getAdditionalAnnotations().get(MERGED_KEY); + final Map historyAsMap = session.getHistoryAsMap(); + final Map cachedOtherRevisions = vcsAnnotation.getCachedOtherRevisions(); + + for (int i = 0; i < basicAnnotation.getNumLines(); i++) { + final VcsRevisionNumber revision = basicAnnotation.getRevision(i); + final VcsRevisionNumber mergedData = data == null ? null : data.getRevision(i); + final VcsFileRevision fileRevision = historyAsMap.get(revision); + if (fileRevision == null) return null; + if (mergedData == null) { + annotation.setLineInfo(i, fileRevision.getRevisionDate(), ((SvnRevisionNumber) revision).getRevision().getNumber(), + fileRevision.getAuthor(), null, -1, null); + } else { + final VcsFileRevision mergedRevision = cachedOtherRevisions.get(mergedData); + if (mergedRevision == null) return null; + annotation.setLineInfo(i, fileRevision.getRevisionDate(), ((SvnRevisionNumber) revision).getRevision().getNumber(), + fileRevision.getAuthor(), mergedRevision.getRevisionDate(), + ((SvnRevisionNumber) mergedRevision.getRevisionNumber()).getRevision().getNumber(), mergedRevision.getAuthor()); + } + } + if (vcsAnnotation.getFirstRevision() != null) { + annotation.setFirstRevision(((SvnRevisionNumber) vcsAnnotation.getFirstRevision()).getRevision()); + } + for (VcsFileRevision revision : session.getRevisionList()) { + annotation.setRevision(((SvnRevisionNumber) revision.getRevisionNumber()).getRevision().getNumber(), (SvnFileRevision)revision); + } + return annotation; + } + + private static class MySteppedLogGetter { + private final LinkedList myRevisionPoints; + private final SvnVcs17 myVcs; + private final File myIoFile; + private final ProgressIndicator myProgress; + private final SVNLogClient myClient; + private final SVNRevision myEndRevision; + private final boolean myCalculateMergeinfo; + private final SvnFileAnnotation myResult; + private final String myUrl; + private final Charset myCharset; + + private MySteppedLogGetter(final SvnVcs17 vcs, final File ioFile, final ProgressIndicator progress, final SVNLogClient client, + final SVNRevision endRevision, + final SvnFileAnnotation result, + final String url, + final boolean calculateMergeinfo, + Charset charset) { + myVcs = vcs; + myIoFile = ioFile; + myProgress = progress; + myClient = client; + myEndRevision = endRevision; + myCalculateMergeinfo = calculateMergeinfo; + myResult = result; + myUrl = url; + myCharset = charset; + myRevisionPoints = new LinkedList(); + } + + public void go() throws SVNException { + final int maxAnnotateRevisions = SvnConfiguration17.getInstance(myVcs.getProject()).getMaxAnnotateRevisions(); + boolean longHistory = true; + if (maxAnnotateRevisions == -1) { + longHistory = false; + } else { + if (myEndRevision.getNumber() < maxAnnotateRevisions) { + longHistory = false; + } + } + + if (! longHistory) { + doLog(myCalculateMergeinfo, null, 0); + putDefaultBounds(); + } else { + doLog(false, null, 0); + final List fileRevisionList = myResult.getRevisions(); + if (fileRevisionList.size() < maxAnnotateRevisions) { + putDefaultBounds(); + if (myCalculateMergeinfo) { + doLog(true, null, 0); + } + return; + } + + myRevisionPoints.add(((SvnRevisionNumber) fileRevisionList.get(0).getRevisionNumber()).getRevision()); + final SVNRevision truncateTo = + ((SvnRevisionNumber)fileRevisionList.get(maxAnnotateRevisions - 1).getRevisionNumber()).getRevision(); + myRevisionPoints.add(truncateTo); + + // todo file history can be asked in parallel + if (myCalculateMergeinfo) { + doLog(true, truncateTo, maxAnnotateRevisions); + } + } + } + + private void putDefaultBounds() { + myRevisionPoints.add(myEndRevision); + myRevisionPoints.add(SVNRevision.create(0)); + } + + private void doLog(final boolean includeMerged, final SVNRevision truncateTo, final int max) throws SVNException { + myClient.doLog(new File[]{myIoFile}, myEndRevision, truncateTo == null ? SVNRevision.create(1L) : truncateTo, + SVNRevision.UNDEFINED, false, false, includeMerged, max, null, + new ISVNLogEntryHandler() { + public void handleLogEntry(SVNLogEntry logEntry) { + if (SVNRevision.UNDEFINED.getNumber() == logEntry.getRevision()) { + return; + } + + if (myProgress != null) { + myProgress.checkCanceled(); + myProgress.setText2(SvnBundle.message("progress.text2.revision.processed", logEntry.getRevision())); + } + myResult.setRevision(logEntry.getRevision(), new SvnFileRevision(myVcs, SVNRevision.UNDEFINED, logEntry, myUrl, "", myCharset)); + } + }); + } + + public LinkedList getRevisionPoints() { + return myRevisionPoints; + } + } + + public boolean isAnnotationValid( VcsFileRevision rev ){ + return true; + } + + private void setLogClientOptions(final SVNLogClient client) { + if (SvnConfiguration17.getInstance(myVcs.getProject()).IGNORE_SPACES_IN_ANNOTATE) { + client.setDiffOptions(new SVNDiffOptions(true, true, true)); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/annotate/SvnFileAnnotation.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/annotate/SvnFileAnnotation.java new file mode 100644 index 000000000000..c23957878aca --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/annotate/SvnFileAnnotation.java @@ -0,0 +1,414 @@ +/* + * 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.annotate; + +import com.intellij.openapi.vcs.annotate.*; +import com.intellij.openapi.vcs.history.VcsFileRevision; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.text.DateFormatUtil; +import com.intellij.xml.util.XmlStringUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.*; +import org.jetbrains.idea.svn17.history.SvnFileRevision; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.util.*; + +public class SvnFileAnnotation implements FileAnnotation { + private final String myContents; + private final VcsRevisionNumber myBaseRevision; + private final MyPartiallyCreatedInfos myInfos; + + private final SvnVcs17 myVcs; + private final VirtualFile myFile; + private final List myListeners = new ArrayList(); + private final Map myRevisionMap = new HashMap(); + + private final LineAnnotationAspect DATE_ASPECT = new SvnAnnotationAspect(SvnAnnotationAspect.DATE, true) { + public String getValue(int lineNumber) { + if (myInfos.size() <= lineNumber || lineNumber < 0) { + return ""; + } + else { + final LineInfo lineInfo = myInfos.get(lineNumber); + return (lineInfo == null) ? "" : DateFormatUtil.formatPrettyDate(lineInfo.getDate()); + } + } + }; + + private final LineAnnotationAspect REVISION_ASPECT = new SvnAnnotationAspect(SvnAnnotationAspect.REVISION, false) { + public String getValue(int lineNumber) { + if (myInfos.size() <= lineNumber || lineNumber < 0) { + return ""; + } + else { + final long revision = getRevision(lineNumber); + return (revision == -1) ? "" : String.valueOf(revision); + } + } + }; + + private final LineAnnotationAspect ORIGINAL_REVISION_ASPECT = new SvnAnnotationAspect(SvnBundle.message("annotation.original.revision"), false) { + @Override + public String getValue(int lineNumber) { + final long value = myInfos.originalRevision(lineNumber); + return (value == -1) ? "" : String.valueOf(value); + } + + @Override + protected long getRevision(int lineNum) { + return myInfos.originalRevision(lineNum); + } + + @Override + public String getTooltipText(int lineNumber) { + if (myInfos.size() <= lineNumber || lineNumber < 0) { + return ""; + } + final LineInfo info = myInfos.get(lineNumber); + if (info == null) return null; + SvnFileRevision svnRevision = myRevisionMap.get(info.getRevision()); + if (svnRevision != null) { + final String tooltip = "Revision " + info.getRevision() + ": " + svnRevision.getCommitMessage(); + return XmlStringUtil.escapeString(tooltip); + } + return ""; + } + }; + + private final LineAnnotationAspect AUTHOR_ASPECT = new SvnAnnotationAspect(SvnAnnotationAspect.AUTHOR, true) { + public String getValue(int lineNumber) { + if (myInfos.size() <= lineNumber || lineNumber < 0) { + return ""; + } + else { + final LineInfo lineInfo = myInfos.get(lineNumber); + return (lineInfo == null) ? "" : lineInfo.getAuthor(); + } + } + }; + private final SvnEntriesListener myListener = new SvnEntriesListener() { + public void onEntriesChanged(VirtualFile directory) { + if (directory != myFile.getParent()) return; + final VcsRevisionNumber currentRevision = myVcs.getDiffProvider().getCurrentRevision(myFile); + if (currentRevision != null && currentRevision.equals(myBaseRevision)) return; + + final AnnotationListener[] listeners = myListeners.toArray(new AnnotationListener[myListeners.size()]); + for (int i = 0; i < listeners.length; i++) { + listeners[i].onAnnotationChanged(); + } + } + + @Override + public void fileVersionProbablyChanged(VirtualFile file) { + if (myFile.equals(file)) { + final VcsRevisionNumber currentRevision = myVcs.getDiffProvider().getCurrentRevision(myFile); + if (currentRevision != null && currentRevision.equals(myBaseRevision)) return; + + final AnnotationListener[] listeners = myListeners.toArray(new AnnotationListener[myListeners.size()]); + for (int i = 0; i < listeners.length; i++) { + listeners[i].onAnnotationChanged(); + } + } + } + }; + private final SvnConfiguration17 myConfiguration; + private boolean myShowMergeSources; + // null if full annotation + private SvnRevisionNumber myFirstRevisionNumber; + + public void setRevision(final long revision, final SvnFileRevision svnRevision) { + myRevisionMap.put(revision, svnRevision); + } + + public void clearRevisions() { + myRevisionMap.clear(); + } + + public SvnFileRevision getRevision(final long revision) { + return myRevisionMap.get(revision); + } + + public void setFirstRevision(SVNRevision svnRevision) { + myFirstRevisionNumber = new SvnRevisionNumber(svnRevision); + } + + public SvnRevisionNumber getFirstRevisionNumber() { + return myFirstRevisionNumber; + } + + static class LineInfo { + private final Date myDate; + private final long myRevision; + private final String myAuthor; + + public LineInfo(final Date date, final long revision, final String author) { + myDate = date; + myRevision = revision; + myAuthor = author; + } + + public Date getDate() { + return myDate; + } + + public long getRevision() { + return myRevision; + } + + public String getAuthor() { + return myAuthor; + } + } + + public SvnFileAnnotation(final SvnVcs17 vcs, final VirtualFile file, final String contents, final VcsRevisionNumber baseRevision) { + myVcs = vcs; + myFile = file; + myContents = contents; + myBaseRevision = baseRevision; + myVcs.getSvnEntriesFileListener().addListener(myListener); + myConfiguration = SvnConfiguration17.getInstance(vcs.getProject()); + myShowMergeSources = myConfiguration.SHOW_MERGE_SOURCES_IN_ANNOTATE; + + myInfos = new MyPartiallyCreatedInfos(); + } + + public void addListener(AnnotationListener listener) { + myListeners.add(listener); + myConfiguration.addAnnotationListener(listener); + } + + public void removeListener(AnnotationListener listener) { + myListeners.remove(listener); + myConfiguration.removeAnnotationListener(listener); + } + + public void dispose() { + myVcs.getSvnEntriesFileListener().removeListener(myListener); + } + + public LineAnnotationAspect[] getAspects() { + return new LineAnnotationAspect[]{REVISION_ASPECT, DATE_ASPECT, AUTHOR_ASPECT}; + } + + public String getToolTip(final int lineNumber) { + if (myInfos.size() <= lineNumber || lineNumber < 0) { + return ""; + } + final LineInfo info = myInfos.get(lineNumber); + if (info == null) return ""; + SvnFileRevision svnRevision = myRevisionMap.get(info.getRevision()); + if (svnRevision != null) { + if (myInfos.getAnnotationSource(lineNumber).showMerged()) { + return "Merge source revision " + info.getRevision() + ": " + svnRevision.getCommitMessage(); + } else { + return "Revision " + info.getRevision() + ": " + svnRevision.getCommitMessage(); + } + } + return ""; + } + + public String getAnnotatedContent() { + return myContents; + } + + public void setLineInfo(final int lineNumber, final Date date, final long revision, final String author, + @Nullable final Date mergeDate, final long mergeRevision, @Nullable final String mergeAuthor) { + myInfos.appendNumberedLineInfo(lineNumber, date, revision, author, mergeDate, mergeRevision, mergeAuthor); + } + + public void appendLineInfo(final Date date, final long revision, final String author, + @Nullable final Date mergeDate, final long mergeRevision, @Nullable final String mergeAuthor) { + myInfos.appendNumberedLineInfo(date, revision, author, mergeDate, mergeRevision, mergeAuthor); + } + + @Nullable + public VcsRevisionNumber originalRevision(final int lineNumber) { + if (myInfos.size() <= lineNumber || lineNumber < 0) { + return null; + } + final SvnFileRevision revision = myRevisionMap.get(myInfos.originalRevision(lineNumber)); + return revision == null ? null : revision.getRevisionNumber(); + } + + public VcsRevisionNumber getLineRevisionNumber(final int lineNumber) { + if (myInfos.size() <= lineNumber || lineNumber < 0) { + return null; + } + final LineInfo info = myInfos.get(lineNumber); + if (info == null) return null; + final long revision = info.getRevision(); + if (revision >= 0) { + return new SvnRevisionNumber(SVNRevision.create(revision)); + } + return null; + } + + @Override + public Date getLineDate(int lineNumber) { + if (myInfos.size() <= lineNumber || lineNumber < 0) { + return null; + } + final LineInfo info = myInfos.get(lineNumber); + if (info == null) return null; + return info.getDate(); + } + + public List getRevisions() { + final List result = new ArrayList(myRevisionMap.values()); + Collections.sort(result, new Comparator() { + public int compare(final VcsFileRevision o1, final VcsFileRevision o2) { + return -1 * o1.getRevisionNumber().compareTo(o2.getRevisionNumber()); + } + }); + return result; + } + + public boolean revisionsNotEmpty() { + return ! myRevisionMap.isEmpty(); + } + + public VirtualFile getFile() { + return myFile; + } + + public int getNumLines() { + return myInfos.size(); + } + + @Nullable + public AnnotationSourceSwitcher getAnnotationSourceSwitcher() { + if (! myShowMergeSources) return null; + return new AnnotationSourceSwitcher() { + @NotNull + public AnnotationSource getAnnotationSource(int lineNumber) { + return myInfos.getAnnotationSource(lineNumber); + } + + public boolean mergeSourceAvailable(int lineNumber) { + return myInfos.mergeSourceAvailable(lineNumber); + } + + @NotNull + public LineAnnotationAspect getRevisionAspect() { + return ORIGINAL_REVISION_ASPECT; + } + + @NotNull + public AnnotationSource getDefaultSource() { + return AnnotationSource.getInstance(myShowMergeSources); + } + + public void switchTo(AnnotationSource source) { + myInfos.setShowMergeSource(source.showMerged()); + } + }; + } + + @Override + public int getLineCount() { + return getNumLines(); + } + + private abstract class SvnAnnotationAspect extends LineAnnotationAspectAdapter { + public SvnAnnotationAspect(String id, boolean showByDefault) { + super(id, showByDefault); + } + + protected long getRevision(final int lineNum) { + final LineInfo lineInfo = myInfos.get(lineNum); + return (lineInfo == null) ? -1 : lineInfo.getRevision(); + } + + @Override + protected void showAffectedPaths(int lineNum) { + if (lineNum >= 0 && lineNum < myInfos.size()) { + final long revision = getRevision(lineNum); + if (revision >= 0) { + ShowAllAffectedGenericAction.showSubmittedFiles(myVcs.getProject(), new SvnRevisionNumber(SVNRevision.create(revision)), + myFile, myVcs.getKeyInstanceMethod()); + } + } + } + } + + private static class MyPartiallyCreatedInfos { + private boolean myShowMergeSource; + private final Map myMappedLineInfo; + private final Map myMergeSourceInfos; + private int myMaxIdx; + + private MyPartiallyCreatedInfos() { + myMergeSourceInfos = new HashMap(); + myMappedLineInfo = new HashMap(); + myMaxIdx = 0; + } + + boolean isShowMergeSource() { + return myShowMergeSource; + } + + void setShowMergeSource(boolean showMergeSource) { + myShowMergeSource = showMergeSource; + } + + int size() { + return myMaxIdx + 1; + } + + void appendNumberedLineInfo(final Date date, final long revision, final String author, + @Nullable final Date mergeDate, final long mergeRevision, @Nullable final String mergeAuthor) { + appendNumberedLineInfo(myMaxIdx + 1, date, revision, author, mergeDate, mergeRevision, mergeAuthor); + } + + void appendNumberedLineInfo(final int lineNumber, final Date date, final long revision, final String author, + @Nullable final Date mergeDate, final long mergeRevision, @Nullable final String mergeAuthor) { + if (date == null) return; + if (myMappedLineInfo.get(lineNumber) != null) return; + myMaxIdx = (myMaxIdx < lineNumber) ? lineNumber : myMaxIdx; + myMappedLineInfo.put(lineNumber, new LineInfo(date, revision, author)); + if (mergeDate != null) { + myMergeSourceInfos.put(lineNumber, new LineInfo(mergeDate, mergeRevision, mergeAuthor)); + } + } + + LineInfo get(final int idx) { + if (myShowMergeSource) { + final LineInfo lineInfo = myMergeSourceInfos.get(idx); + if (lineInfo != null) { + return lineInfo; + } + } + return myMappedLineInfo.get(idx); + } + + AnnotationSource getAnnotationSource(final int line) { + return myShowMergeSource ? AnnotationSource.getInstance(myMergeSourceInfos.containsKey(line)) : AnnotationSource.LOCAL; + } + + public long originalRevision(final int line) { + if (line >= size()) return -1; + final LineInfo lineInfo = myMappedLineInfo.get(line); + return lineInfo == null ? -1 : lineInfo.getRevision(); + } + + public boolean mergeSourceAvailable(int lineNumber) { + return myMergeSourceInfos.containsKey(lineNumber); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/auth/ProviderType.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/auth/ProviderType.java new file mode 100644 index 000000000000..68e7616fd209 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/auth/ProviderType.java @@ -0,0 +1,23 @@ +/* + * 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.auth; + +public enum ProviderType { + dumb, + interactive, + persistent, + memory_cache +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/auth/SvnAuthenticationInteraction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/auth/SvnAuthenticationInteraction.java new file mode 100644 index 000000000000..568a72ff41d9 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/auth/SvnAuthenticationInteraction.java @@ -0,0 +1,29 @@ +/* + * 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.auth; + +import org.tmatesoft.svn.core.SVNURL; + +import java.io.File; + +public interface SvnAuthenticationInteraction { + void warnOnAuthStorageDisabled(final SVNURL url); + void warnOnPasswordStorageDisabled(final SVNURL url); + void warnOnSSLPassphraseStorageDisabled(final SVNURL url); + boolean promptForSSLPlaintextPassphraseSaving(final SVNURL url, String realm, File certificateFile, String certificateName); + boolean promptForPlaintextPasswordSaving(final SVNURL url, String realm); + boolean promptInAwt(); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/auth/SvnAuthenticationListener.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/auth/SvnAuthenticationListener.java new file mode 100644 index 000000000000..dc556ec452e1 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/auth/SvnAuthenticationListener.java @@ -0,0 +1,27 @@ +/* + * 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.auth; + +import org.tmatesoft.svn.core.SVNURL; + +import java.util.EventListener; + +public interface SvnAuthenticationListener extends EventListener { + void requested(final ProviderType type, final SVNURL url, String realm, String kind, boolean canceled); + void actualSaveWillBeTried(final ProviderType type, final SVNURL url, String realm, String kind); + void saveAttemptStarted(final ProviderType type, final SVNURL url, String realm, String kind); + void saveAttemptFinished(final ProviderType type, final SVNURL url, String realm, String kind); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/BranchesLoader.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/BranchesLoader.java new file mode 100644 index 000000000000..c0e3f8d80295 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/BranchesLoader.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.branchConfig; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import org.jetbrains.idea.svn17.SvnConfiguration17; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.integrate.SvnBranchItem; +import org.tmatesoft.svn.core.ISVNDirEntryHandler; +import org.tmatesoft.svn.core.SVNDirEntry; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; +import org.tmatesoft.svn.core.wc.SVNLogClient; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +public class BranchesLoader { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.branchConfig.BranchesLoader"); + + private BranchesLoader() { + } + + public static List loadBranches(final Project project, final String url, boolean passive) throws SVNException { + final List result = new LinkedList(); + + final SvnConfiguration17 configuration = SvnConfiguration17.getInstance(project); + final ISVNAuthenticationManager passiveManager = passive ? configuration.getPassiveAuthenticationManager() : configuration.getInteractiveManager( + SvnVcs17.getInstance(project)); + + final SVNLogClient logClient = new SVNLogClient(passiveManager, configuration.getOptions(project)); + final SVNURL branchesUrl = SVNURL.parseURIEncoded(url); + logClient.doList(branchesUrl, SVNRevision.UNDEFINED, SVNRevision.HEAD, false, false, new ISVNDirEntryHandler() { + public void handleDirEntry(final SVNDirEntry dirEntry) throws SVNException { + final SVNURL currentUrl = dirEntry.getURL(); + if (! branchesUrl.equals(currentUrl)) { + final String url = currentUrl.toString(); + // if have permissions + if (dirEntry.getDate() != null) { + result.add(new SvnBranchItem(url, dirEntry.getDate(), dirEntry.getRevision())); + } + } + } + }); + Collections.sort(result); + return result; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/DefaultConfigLoader.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/DefaultConfigLoader.java new file mode 100644 index 000000000000..123adfb16dae --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/DefaultConfigLoader.java @@ -0,0 +1,104 @@ +/* + * 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.branchConfig; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.integrate.SvnBranchItem; +import org.tmatesoft.svn.core.*; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; +import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry; +import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess; +import org.tmatesoft.svn.core.wc.SVNLogClient; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class DefaultConfigLoader { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.branchConfig.DefaultConfigLoader"); + @NonNls private static final String DEFAULT_TRUNK_NAME = "trunk"; + @NonNls private static final String DEFAULT_BRANCHES_NAME = "branches"; + @NonNls private static final String DEFAULT_TAGS_NAME = "tags"; + + private DefaultConfigLoader() { + } + + @Nullable + public static SvnBranchConfigurationNew loadDefaultConfiguration(final Project project, final VirtualFile vcsRoot) { + try { + SVNURL baseUrl = null; + final SVNWCAccess wcAccess = SVNWCAccess.newInstance(null); + File rootFile = new File(vcsRoot.getPath()); + wcAccess.open(rootFile, false, 0); + try { + SVNEntry entry = wcAccess.getEntry(rootFile, false); + if (entry != null) { + baseUrl = entry.getSVNURL(); + } + else { + LOG.info("Directory is not a working copy: " + vcsRoot.getPresentableUrl()); + return null; + } + } + finally { + wcAccess.close(); + } + + final SvnBranchConfigurationNew result = new SvnBranchConfigurationNew(); + result.setTrunkUrl(baseUrl.toString()); + while(true) { + final String s = SVNPathUtil.tail(baseUrl.getPath()); + if (s.equalsIgnoreCase(DEFAULT_TRUNK_NAME) || s.equalsIgnoreCase(DEFAULT_BRANCHES_NAME) || s.equalsIgnoreCase(DEFAULT_TAGS_NAME)) { + final SVNURL rootPath = baseUrl.removePathTail(); + SVNLogClient client = SvnVcs17.getInstance(project).createLogClient(); + client.doList(rootPath, SVNRevision.UNDEFINED, SVNRevision.HEAD, false, false, new ISVNDirEntryHandler() { + public void handleDirEntry(final SVNDirEntry dirEntry) throws SVNException { + if (("".equals(dirEntry.getRelativePath())) || (! SVNNodeKind.DIR.equals(dirEntry.getKind()))) { + // do not use itself or files + return; + } + + if (dirEntry.getName().toLowerCase().endsWith(DEFAULT_TRUNK_NAME)) { + result.setTrunkUrl(rootPath.appendPath(dirEntry.getName(), false).toString()); + } + else { + result.addBranches(rootPath.appendPath(dirEntry.getName(), false).toString(), + new InfoStorage>(new ArrayList(0), InfoReliability.defaultValues)); + } + } + }); + + break; + } + if (SVNPathUtil.removeTail(baseUrl.getPath()).length() == 0) { + break; + } + baseUrl = baseUrl.removePathTail(); + } + return result; + } + catch (SVNException e) { + LOG.info(e); + return null; + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/InfoReliability.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/InfoReliability.java new file mode 100644 index 000000000000..cc4573ba05de --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/InfoReliability.java @@ -0,0 +1,43 @@ +/* + * 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.branchConfig; + +public enum InfoReliability { + empty() { + @Override + protected final InfoReliability[] getOverriddenBy() { + return new InfoReliability[] {defaultValues, setByUser}; + }}, + defaultValues() { + @Override + protected final InfoReliability[] getOverriddenBy() { + return new InfoReliability[] {setByUser}; + }}, + setByUser() { + @Override + protected final InfoReliability[] getOverriddenBy() { + return new InfoReliability[] {setByUser}; + }}; + + protected abstract InfoReliability[] getOverriddenBy(); + + public boolean shouldOverride(final InfoReliability other) { + for (InfoReliability info : other.getOverriddenBy()) { + if (info.equals(this)) return true; + } + return false; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/InfoStorage.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/InfoStorage.java new file mode 100644 index 000000000000..e6209696904b --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/InfoStorage.java @@ -0,0 +1,47 @@ +/* + * 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.branchConfig; + +import com.intellij.util.PairConsumer; +import org.jetbrains.annotations.Nullable; + +public class InfoStorage { + public T myT; + public InfoReliability myInfoReliability; + + public InfoStorage(final T t, final InfoReliability infoReliability) { + myT = t; + myInfoReliability = infoReliability; + } + + public void accept(final InfoStorage infoStorage, @Nullable final PairConsumer callbackOnUpdate) { + if (infoStorage.myInfoReliability.shouldOverride(myInfoReliability)) { + if (callbackOnUpdate != null) { + callbackOnUpdate.consume(myT, infoStorage.myT); + } + myT = infoStorage.myT; + myInfoReliability = infoStorage.myInfoReliability; + } + } + + public T getValue() { + return myT; + } + + public InfoReliability getInfoReliability() { + return myInfoReliability; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/NewRootBunch.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/NewRootBunch.java new file mode 100644 index 000000000000..24f414ba2ae9 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/NewRootBunch.java @@ -0,0 +1,219 @@ +/* + * 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.branchConfig; + +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.CalledInBackground; +import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; +import com.intellij.util.PairConsumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.integrate.SvnBranchItem; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +// synch is here +public class NewRootBunch implements SvnBranchConfigManager { + private final static Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.branchConfig.NewRootBunch"); + private final Object myLock = new Object(); + private final Project myProject; + private final Map> myMap; + + public NewRootBunch(final Project project) { + myProject = project; + myMap = new HashMap>(); + } + + public void updateForRoot(@NotNull final VirtualFile root, @NotNull final InfoStorage config, + @Nullable final PairConsumer callbackOnUpdate) { + synchronized (myLock) { + final InfoStorage existing = myMap.get(root); + if (existing == null) { + myMap.put(root, config); + if (callbackOnUpdate != null) { + callbackOnUpdate.consume(null, config.getValue()); + } + } else { + existing.accept(config, callbackOnUpdate); + } + } + } + + public void updateBranches(@NotNull final VirtualFile root, @NotNull final String branchesParent, + @NotNull final InfoStorage> items) { + synchronized (myLock) { + final InfoStorage existing = myMap.get(root); + if (existing == null) { + LOG.info("cannot update branches, branches parent not found: " + branchesParent); + } else { + existing.getValue().updateBranch(branchesParent, items); + } + } + } + + @NotNull + public SvnBranchConfigurationNew getConfig(@NotNull final VirtualFile root) { + synchronized (myLock) { + final InfoStorage value = myMap.get(root); + final SvnBranchConfigurationNew result; + if (value == null) { + result = new SvnBranchConfigurationNew(); + myMap.put(root, new InfoStorage(result, InfoReliability.empty)); + ApplicationManager.getApplication().executeOnPooledThread(new DefaultBranchConfigInitializer(myProject, this, root)); + } else { + result = value.getValue(); + } + return result; + } + } + + public void reloadBranches(@NotNull final VirtualFile root, @NotNull final String branchParentUrl, + final Consumer> callback) { + ApplicationManager.getApplication().executeOnPooledThread(new BranchesLoadRunnable(myProject, this, branchParentUrl, + InfoReliability.setByUser, root, callback, true)); + } + + @Nullable + @CalledInBackground + public SVNURL getWorkingBranchWithReload(final SVNURL svnurl, final VirtualFile root) { + final Ref result = new Ref(); + try { + final SvnBranchConfigurationNew configuration = myMap.get(root).getValue(); + final String group = configuration.getGroupToLoadToReachUrl(svnurl); + final Runnable runnable = new Runnable() { + public void run() { + final SvnBranchConfigurationNew reloadedConfiguration = myMap.get(root).getValue(); + try { + result.set(reloadedConfiguration.getWorkingBranch(svnurl)); + } + catch (SVNException e) { + // + } + } + }; + + if (group == null) { + runnable.run(); + } else { + new BranchesLoadRunnable(myProject, this, group, InfoReliability.setByUser, root, + new Consumer>() { + public void consume(List svnBranchItems) { + runnable.run(); + } + }, true).run(); + } + } + catch (SVNException e) { + // + } + return result.get(); + } + + public static class BranchesLoadRunnable implements Runnable { + private final Project myProject; + private final SvnBranchConfigManager myBunch; + private final VirtualFile myRoot; + @Nullable + private final Consumer> myCallback; + private final String myUrl; + private final InfoReliability myInfoReliability; + private boolean myPassive; + + public BranchesLoadRunnable(final Project project, + final SvnBranchConfigManager bunch, + final String url, + final InfoReliability infoReliability, + final VirtualFile root, + @Nullable final Consumer> callback, + boolean passive) { + myProject = project; + myBunch = bunch; + myUrl = url; + myInfoReliability = infoReliability; + myRoot = root; + myCallback = callback; + myPassive = passive; + } + + public void run() { + boolean callbackCalled = false; + try { + final List items = BranchesLoader.loadBranches(myProject, myUrl, myPassive); + myBunch.updateBranches(myRoot, myUrl, new InfoStorage>(items, myInfoReliability)); + if (myCallback != null) { + myCallback.consume(items); + callbackCalled = true; + } + } + catch (SVNException e) { + // already logged inside + if (InfoReliability.setByUser.equals(myInfoReliability)) { + VcsBalloonProblemNotifier.showOverChangesView(myProject, "Branches load error: " + e.getMessage(), MessageType.ERROR); + } + } finally { + // callback must be called by contract + if (myCallback != null && (! callbackCalled)) { + myCallback.consume(null); + } + } + } + } + + private static class DefaultBranchConfigInitializer implements Runnable { + private final Project myProject; + private final SvnBranchConfigManager myBunch; + private final VirtualFile myRoot; + + private DefaultBranchConfigInitializer(final Project project, final SvnBranchConfigManager bunch, final VirtualFile root) { + myProject = project; + myRoot = root; + myBunch = bunch; + } + + public void run() { + final SvnBranchConfigurationNew result = DefaultConfigLoader.loadDefaultConfiguration(myProject, myRoot); + if (result != null) { + final Application application = ApplicationManager.getApplication(); + for (String url : result.getBranchUrls()) { + application.executeOnPooledThread(new BranchesLoadRunnable(myProject, myBunch, url, InfoReliability.defaultValues, myRoot, null, + true)); + } + myBunch.updateForRoot(myRoot, new InfoStorage(result, InfoReliability.defaultValues), null); + } + } + } + + public Map getMapCopy() { + synchronized (myLock) { + final Map result = new HashMap(); + for (VirtualFile vf : myMap.keySet()) { + result.put(vf, myMap.get(vf).getValue()); + } + return result; + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/SvnBranchConfigManager.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/SvnBranchConfigManager.java new file mode 100644 index 000000000000..1d3884c818b0 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/SvnBranchConfigManager.java @@ -0,0 +1,47 @@ +/* + * 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.branchConfig; + +import com.intellij.openapi.vcs.CalledInBackground; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; +import com.intellij.util.PairConsumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.integrate.SvnBranchItem; +import org.tmatesoft.svn.core.SVNURL; + +import java.util.List; +import java.util.Map; + +public interface SvnBranchConfigManager { + void updateForRoot(@NotNull VirtualFile root, @NotNull InfoStorage config, + @Nullable final PairConsumer callbackOnUpdate); + + void updateBranches(@NotNull VirtualFile root, @NotNull String branchesParent, + @NotNull InfoStorage> items); + + @NotNull + SvnBranchConfigurationNew getConfig(@NotNull VirtualFile root); + + void reloadBranches(@NotNull VirtualFile root, @NotNull String branchParentUrl, + Consumer> callback); + @Nullable + @CalledInBackground + SVNURL getWorkingBranchWithReload(final SVNURL svnurl, final VirtualFile root); + + Map getMapCopy(); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/SvnBranchConfigurationNew.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/SvnBranchConfigurationNew.java new file mode 100644 index 000000000000..fcd31e7d3c43 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/branchConfig/SvnBranchConfigurationNew.java @@ -0,0 +1,281 @@ +/* + * 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.branchConfig; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.ObjectsConvertor; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.containers.Convertor; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnUtil; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.integrate.SvnBranchItem; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; +import org.tmatesoft.svn.core.internal.util.SVNURLUtil; +import org.tmatesoft.svn.core.wc.SVNInfo; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.io.File; +import java.util.*; + +public class SvnBranchConfigurationNew { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.branchConfig.SvnBranchConfigurationNew"); + private String myTrunkUrl; + // need public for serialization + public Map>> myBranchMap; + private boolean myUserinfoInUrl; + + public SvnBranchConfigurationNew() { + myTrunkUrl = ""; + myBranchMap = new HashMap>>(); + } + + public boolean isUserinfoInUrl() { + return myUserinfoInUrl; + } + + public void setUserinfoInUrl(final boolean userinfoInUrl) { + myUserinfoInUrl = userinfoInUrl; + } + + public void setTrunkUrl(final String trunkUrl) { + myTrunkUrl = trunkUrl; + } + + public String getTrunkUrl() { + return myTrunkUrl; + } + + public List getBranchUrls() { + final ArrayList result = new ArrayList(myBranchMap.keySet()); + final List cutList = ObjectsConvertor.convert(result, new Convertor() { + @Override + public String convert(String s) { + return cutEndSlash(s); + } + }); + Collections.sort(cutList); + return cutList; + } + + public void addBranches(String branchParentName, final InfoStorage> items) { + branchParentName = ensureEndSlash(branchParentName); + InfoStorage> current = myBranchMap.get(branchParentName); + if (current != null) { + LOG.info("Branches list not added for : '" + branchParentName + "; this branch parent URL is already present."); + return; + } + myBranchMap.put(branchParentName, items); + } + + public static String ensureEndSlash(String name) { + return name.trim().endsWith("/") ? name : name + "/"; + } + + private static String cutEndSlash(String name) { + return name.endsWith("/") && name.length() > 0 ? name.substring(0, name.length() - 1) : name; + } + + public void updateBranch(String branchParentName, final InfoStorage> items) { + branchParentName = ensureEndSlash(branchParentName); + final InfoStorage> current = myBranchMap.get(branchParentName); + if (current == null) { + LOG.info("Branches list not updated for : '" + branchParentName + "; since config has changed."); + return; + } + current.accept(items, null); + } + + public Map>> getBranchMap() { + return myBranchMap; + } + + public List getBranches(String url) { + url = ensureEndSlash(url); + return myBranchMap.get(url).getValue(); + } + + public SvnBranchConfigurationNew copy() { + SvnBranchConfigurationNew result = new SvnBranchConfigurationNew(); + result.myUserinfoInUrl = myUserinfoInUrl; + result.myTrunkUrl = myTrunkUrl; + result.myBranchMap = new HashMap>>(); + for (Map.Entry>> entry : myBranchMap.entrySet()) { + final InfoStorage> infoStorage = entry.getValue(); + result.myBranchMap.put(entry.getKey(), new InfoStorage>( + new ArrayList(infoStorage.getValue()), infoStorage.getInfoReliability())); + } + return result; + } + + @Nullable + public String getBaseUrl(String url) { + if (myTrunkUrl != null) { + if (SVNPathUtil.isAncestor(myTrunkUrl, url)) { + return cutEndSlash(myTrunkUrl); + } + } + for(String branchUrl: myBranchMap.keySet()) { + if (SVNPathUtil.isAncestor(branchUrl, url)) { + String relativePath = SVNPathUtil.getRelativePath(branchUrl, url); + int secondSlash = relativePath.indexOf("/"); + return cutEndSlash(branchUrl + (secondSlash == -1 ? relativePath : relativePath.substring(0, secondSlash))); + } + } + return null; + } + + @Nullable + public String getBaseName(String url) { + String baseUrl = getBaseUrl(url); + if (baseUrl == null) { + return null; + } + int lastSlash = baseUrl.lastIndexOf("/"); + return lastSlash == -1 ? baseUrl : baseUrl.substring(lastSlash + 1); + } + + @Nullable + public String getRelativeUrl(String url) { + String baseUrl = getBaseUrl(url); + return baseUrl == null ? null : url.substring(baseUrl.length()); + } + + @Nullable + public SVNURL getWorkingBranch(final SVNURL someUrl) throws SVNException { + String baseUrl = getBaseUrl(someUrl.toString()); + return baseUrl == null ? null : SVNURL.parseURIEncoded(baseUrl); + } + + // todo not checked + // todo +- + @Nullable + public String getGroupToLoadToReachUrl(final SVNURL url) throws SVNException { + final BranchSearcher branchSearcher = new BranchSearcher(url); + for (String group : myBranchMap.keySet()) { + if (branchSearcher.accept(group)) { + return group; + } + } + return null; + } + + private void iterateUrls(final UrlListener listener) throws SVNException { + if (listener.accept(myTrunkUrl)) { + return; + } + + for (String branchUrl : myBranchMap.keySet()) { + // use more exact comparison first (paths longer) + final List children = myBranchMap.get(branchUrl).getValue(); + for (SvnBranchItem child : children) { + if (listener.accept(child.getUrl())) { + return; + } + } + + /*if (listener.accept(branchUrl)) { + return; + }*/ + } + } + + // to retrieve mappings between existing in the project working copies and their URLs + @Nullable + public Map getUrl2FileMappings(final Project project, final VirtualFile root) { + try { + final BranchRootSearcher searcher = new BranchRootSearcher(SvnVcs17.getInstance(project), root); + iterateUrls(searcher); + return searcher.getBranchesUnder(); + } catch (SVNException e) { + return null; + } + } + + public void removeBranch(String url) { + url = ensureEndSlash(url); + myBranchMap.remove(url); + } + + private static class BranchRootSearcher implements UrlListener { + private final VirtualFile myRoot; + private final SVNURL myRootUrl; + // url path to file path + private final Map myBranchesUnder; + + private BranchRootSearcher(final SvnVcs17 vcs, final VirtualFile root) throws SVNException { + myRoot = root; + myBranchesUnder = new HashMap(); + final SVNWCClient client = vcs.createWCClient(); + final SVNInfo info = client.doInfo(new File(myRoot.getPath()), SVNRevision.UNDEFINED); + myRootUrl = info.getURL(); + } + + public boolean accept(final String url) throws SVNException { + if (myRootUrl != null) { + final File baseDir = new File(myRoot.getPath()); + final String baseUrl = myRootUrl.getPath(); + + final SVNURL branchUrl = SVNURL.parseURIEncoded(url); + if (myRootUrl.equals(SVNURLUtil.getCommonURLAncestor(myRootUrl, branchUrl))) { + final File file = SvnUtil.fileFromUrl(baseDir, baseUrl, branchUrl.getPath()); + myBranchesUnder.put(url, file.getAbsolutePath()); + } + } + return false; // iterate everything + } + + public Map getBranchesUnder() { + return myBranchesUnder; + } + } + + private interface UrlListener { + boolean accept(final String url) throws SVNException; + } + + // todo not checked + private static class BranchSearcher implements UrlListener { + private final SVNURL mySomeUrl; + private SVNURL myResult; + + private BranchSearcher(final SVNURL someUrl) { + mySomeUrl = someUrl; + } + + public boolean accept(final String url) throws SVNException { + myResult = urlIsParent(url, mySomeUrl); + return myResult != null; + } + + public SVNURL getResult() { + return myResult; + } + } + + @Nullable + private static SVNURL urlIsParent(final String parentCandidate, final SVNURL child) throws SVNException { + final SVNURL parentUrl = SVNURL.parseURIEncoded(parentCandidate); + if(parentUrl.equals(SVNURLUtil.getCommonURLAncestor(parentUrl, child))) { + return parentUrl; + } + return null; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/checkin/SvnCheckinEnvironment17.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/checkin/SvnCheckinEnvironment17.java new file mode 100644 index 000000000000..82a665e7974a --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/checkin/SvnCheckinEnvironment17.java @@ -0,0 +1,399 @@ +/* + * 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.checkin; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.diff.impl.patch.formove.FilePathComparator; +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.ui.MessageType; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.CheckinProjectPanel; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangeList; +import com.intellij.openapi.vcs.changes.ChangesUtil; +import com.intellij.openapi.vcs.changes.ContentRevision; +import com.intellij.openapi.vcs.checkin.CheckinEnvironment; +import com.intellij.openapi.vcs.ui.Refreshable; +import com.intellij.openapi.vcs.ui.RefreshableOnComponent; +import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.util.FunctionUtil; +import com.intellij.util.NullableFunction; +import com.intellij.util.PairConsumer; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnConfiguration17; +import org.jetbrains.idea.svn17.SvnUtil; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNCancelException; +import org.tmatesoft.svn.core.SVNCommitInfo; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.*; + +import javax.swing.*; +import java.awt.*; +import java.io.File; +import java.util.*; +import java.util.List; + +public class SvnCheckinEnvironment17 implements CheckinEnvironment { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.checkin.SvnCheckinEnvironment17"); + private final SvnVcs17 mySvnVcs; + + public SvnCheckinEnvironment17(SvnVcs17 svnVcs) { + mySvnVcs = svnVcs; + } + + public RefreshableOnComponent createAdditionalOptionsPanel(CheckinProjectPanel panel, + PairConsumer additionalDataConsumer) { + return new KeepLocksComponent(panel); + } + + @Nullable + public String getDefaultMessageFor(FilePath[] filesToCheckin) { + return null; + } + + @Nullable + public String getHelpId() { + return null; + } + + + private List commitInt(List paths, final String comment, final boolean force, final boolean recursive) { + final List exception = new ArrayList(); + final Collection committables = getCommitables(paths); + + final SVNCommitClient committer = mySvnVcs.createCommitClient(); + + final ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + final Collection deletedFiles = new ArrayList(); + if (progress != null) { + committer.setEventHandler(new ISVNEventHandler() { + public void handleEvent(final SVNEvent event, double p) { + final String path = SvnUtil.getPathForProgress(event); + if (path == null) { + return; + } + if (event.getAction() == SVNEventAction.COMMIT_ADDED) { + progress.setText2(SvnBundle.message("progress.text2.adding", path)); + } + else if (event.getAction() == SVNEventAction.COMMIT_DELETED) { + @NonNls final String filePath = "file://" + event.getFile().getAbsolutePath().replace(File.separatorChar, '/'); + VirtualFile vf = ApplicationManager.getApplication().runReadAction(new Computable() { + @Nullable public VirtualFile compute() { + return VirtualFileManager.getInstance().findFileByUrl(filePath); + } + }); + if (vf != null) { + deletedFiles.add(vf); + } + progress.setText2(SvnBundle.message("progress.text2.deleting", path)); + } + else if (event.getAction() == SVNEventAction.COMMIT_MODIFIED) { + progress.setText2(SvnBundle.message("progress.text2.sending", path)); + } + else if (event.getAction() == SVNEventAction.COMMIT_REPLACED) { + progress.setText2(SvnBundle.message("progress.text2.replacing", path)); + } + else if (event.getAction() == SVNEventAction.COMMIT_DELTA_SENT) { + progress.setText2(SvnBundle.message("progress.text2.transmitting.delta", path)); + } + // do not need COMMIT_COMPLETED: same info is get another way + } + + public void checkCancelled() throws SVNCancelException { + try { + progress.checkCanceled(); + } + catch(ProcessCanceledException ex) { + throw new SVNCancelException(); + } + } + }); + } + + if (progress != null) { + doCommit(committables, progress, committer, comment, force, recursive, exception); + } + else if (ApplicationManager.getApplication().isDispatchThread()) { + ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + public void run() { + ProgressIndicator p = ProgressManager.getInstance().getProgressIndicator(); + doCommit(committables, p, committer, comment, force, recursive, exception); + } + }, SvnBundle.message("progress.title.commit"), false, mySvnVcs.getProject()); + } + else { + doCommit(committables, progress, committer, comment, force, recursive, exception); + } + + for(VirtualFile f : deletedFiles) { + f.putUserData(VirtualFile.REQUESTOR_MARKER, this); + } + return exception; + } + + private void doCommit(Collection committables, + ProgressIndicator progress, + SVNCommitClient committer, + String comment, + boolean force, + boolean recursive, + List exception) { + if (committables.isEmpty()) { + return; + } + File[] pathsToCommit = committables.toArray(new File[committables.size()]); + boolean keepLocks = SvnConfiguration17.getInstance(mySvnVcs.getProject()).isKeepLocks(); + SVNCommitPacket[] commitPackets = null; + SVNCommitInfo[] results; + try { + commitPackets = committer.doCollectCommitItems(pathsToCommit, keepLocks, force, recursive, true); + results = committer.doCommit(commitPackets, keepLocks, comment); + commitPackets = null; + } + catch (SVNException e) { + // exception on collecting commitables. + exception.add(new VcsException(e)); + LOG.info(e); + return; + } + finally { + if (commitPackets != null) { + for (int i = 0; i < commitPackets.length; i++) { + SVNCommitPacket commitPacket = commitPackets[i]; + try { + commitPacket.dispose(); + } + catch (SVNException e) { + // + } + } + } + } + final StringBuffer committedRevisions = new StringBuffer(); + for (SVNCommitInfo result : results) { + if (result.getErrorMessage() != null) { + exception.add(new VcsException(result.getErrorMessage().getFullMessage())); + } + else if (result != SVNCommitInfo.NULL && result.getNewRevision() > 0) { + if (committedRevisions.length() > 0) { + committedRevisions.append(", "); + } + committedRevisions.append(result.getNewRevision()); + } + } + if (committedRevisions.length() > 0) { + final Project project = mySvnVcs.getProject(); + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + new VcsBalloonProblemNotifier(project, SvnBundle.message("status.text.comitted.revision", committedRevisions), + MessageType.INFO).run(); + } + }, new Condition() { + @Override + public boolean value(Object o) { + return (! project.isOpen()) || project.isDisposed(); + } + }); + } + } + + private static class Adder { + private final Collection myResult = new ArrayList(); + private final Set myDuplicatesControlSet = new HashSet(); + + public void add(final File file) { + final String path = file.getAbsolutePath(); + if (! myDuplicatesControlSet.contains(path)) { + myResult.add(file); + myDuplicatesControlSet.add(path); + } + } + + public Collection getResult() { + return myResult; + } + } + + private Collection getCommitables(List paths) { + final Adder adder = new Adder(); + + SVNStatusClient statusClient = mySvnVcs.createStatusClient(); + for (File path : paths) { + File file = path.getAbsoluteFile(); + adder.add(file); + if (file.getParentFile() != null) { + addParents(statusClient, file.getParentFile(), adder); + } + } + return adder.getResult(); + } + + private static void addParents(SVNStatusClient statusClient, File file, final Adder adder) { + SVNStatus status; + try { + status = statusClient.doStatus(file, false); + } + catch (SVNException e) { + return; + } + if (status != null && + (SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_ADDED) || + SvnVcs17.svnStatusIs(status, SVNStatusType.STATUS_REPLACED))) { + // file should be added + adder.add(file); + file = file.getParentFile(); + if (file != null) { + addParents(statusClient, file, adder); + } + } + } + + private static List collectPaths(final List changes) { + // case sensitive.. + ArrayList result = new ArrayList(); + + final Set pathesSet = new HashSet(); + for (Change change : changes) { + final ContentRevision beforeRevision = change.getBeforeRevision(); + final ContentRevision afterRevision = change.getAfterRevision(); + if (beforeRevision != null) { + pathesSet.add(beforeRevision.getFile().getIOFile().getAbsolutePath()); + } + if (afterRevision != null) { + pathesSet.add(afterRevision.getFile().getIOFile().getAbsolutePath()); + } + } + + for (String s : pathesSet) { + result.add(new File(s)); + } + return result; + } + + public String getCheckinOperationName() { + return SvnBundle.message("checkin.operation.name"); + } + + public List commit(List changes, String preparedComment, @NotNull NullableFunction parametersHolder) { + return commitInt(collectPaths(changes), preparedComment, true, false); + } + + public List commit(List changes, String preparedComment) { + return commit(changes, preparedComment, FunctionUtil.nullConstant()); + } + + public List scheduleMissingFileForDeletion(List filePaths) { + List exceptions = new ArrayList(); + final SVNWCClient wcClient = mySvnVcs.createWCClient(); + + List files = ChangesUtil.filePathsToFiles(filePaths); + for (File file : files) { + try { + wcClient.doDelete(file, true, false); + } + catch (SVNException e) { + exceptions.add(new VcsException(e)); + } + } + + return exceptions; + } + + public List scheduleUnversionedFilesForAddition(List files) { + final List result = new ArrayList(); + final SVNWCClient wcClient = mySvnVcs.createWCClient(); + + final List exceptionList = scheduleUnversionedFilesForAddition(wcClient, files); + for (SVNException svnException : exceptionList) { + result.add(new VcsException(svnException)); + } + return result; + } + + public static List scheduleUnversionedFilesForAddition(SVNWCClient wcClient, List files) { + return scheduleUnversionedFilesForAddition(wcClient, files, false); + } + + public static List scheduleUnversionedFilesForAddition(SVNWCClient wcClient, List files, final boolean recursive) { + List exceptions = new ArrayList(); + + Collections.sort(files, FilePathComparator.getInstance()); + + for (VirtualFile file : files) { + try { + wcClient.doAdd(new File(FileUtil.toSystemDependentName(file.getPath())), true, false, true, recursive); + } + catch (SVNException e) { + exceptions.add(e); + } + } + + return exceptions; + } + + public boolean keepChangeListAfterCommit(ChangeList changeList) { + return false; + } + + private class KeepLocksComponent implements RefreshableOnComponent { + private final JCheckBox myKeepLocksBox; + private boolean myIsKeepLocks; + private final JPanel myPanel; + + public KeepLocksComponent(final Refreshable panel) { + + myPanel = new JPanel(new BorderLayout()); + myKeepLocksBox = new JCheckBox(SvnBundle.message("checkbox.chckin.keep.files.locked")); + myKeepLocksBox.setSelected(myIsKeepLocks); + + myPanel.add(myKeepLocksBox, BorderLayout.CENTER); + } + + public JComponent getComponent() { + return myPanel; + } + + public boolean isKeepLocks() { + return myKeepLocksBox != null && myKeepLocksBox.isSelected(); + } + + public void refresh() { + } + + public void saveState() { + mySvnVcs.getSvnConfiguration().setKeepLocks(isKeepLocks()); + } + + public void restoreState() { + myIsKeepLocks = mySvnVcs.getSvnConfiguration().isKeepLocks(); + } + } + +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/checkout/CheckoutEventHandler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/checkout/CheckoutEventHandler.java new file mode 100644 index 000000000000..651598f5bd19 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/checkout/CheckoutEventHandler.java @@ -0,0 +1,82 @@ +/* + * 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.checkout; + +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.wm.StatusBar; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnUtil; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNCancelException; +import org.tmatesoft.svn.core.SVNErrorCode; +import org.tmatesoft.svn.core.SVNErrorMessage; +import org.tmatesoft.svn.core.wc.ISVNEventHandler; +import org.tmatesoft.svn.core.wc.SVNEvent; +import org.tmatesoft.svn.core.wc.SVNEventAction; + +public class CheckoutEventHandler implements ISVNEventHandler { + private final ProgressIndicator myIndicator; + private int myExternalsCount; + private final SvnVcs17 myVCS; + private final boolean myIsExport; + private int myCnt; + + public CheckoutEventHandler(SvnVcs17 vcs, boolean isExport, ProgressIndicator indicator) { + myIndicator = indicator; + myVCS = vcs; + myExternalsCount = 1; + myIsExport = isExport; + myCnt = 0; + } + + public void handleEvent(SVNEvent event, double progress) { + final String path = SvnUtil.getPathForProgress(event); + if (path == null) { + return; + } + if (event.getAction() == SVNEventAction.UPDATE_EXTERNAL) { + myExternalsCount++; + myIndicator.setText(SvnBundle.message("progress.text2.fetching.external.location", event.getFile().getAbsolutePath())); + myIndicator.setText2(""); + } + else if (event.getAction() == SVNEventAction.UPDATE_ADD) { + myIndicator.setText2(SvnBundle.message(myIsExport ? "progress.text2.exported" : "progress.text2.checked.out", event.getFile().getName(), myCnt)); + ++ myCnt; + } + else if (event.getAction() == SVNEventAction.UPDATE_COMPLETED) { + myExternalsCount--; + myIndicator.setText2(SvnBundle.message(myIsExport ? "progress.text2.exported.revision" : "progress.text2.checked.out.revision", event.getRevision())); + if (myExternalsCount == 0 && event.getRevision() >= 0 && myVCS != null) { + myExternalsCount = 1; + Project project = myVCS.getProject(); + if (project != null) { + StatusBar.Info.set(SvnBundle.message(myIsExport ? "progress.text2.exported.revision" : "status.text.checked.out.revision", event.getRevision()), project); + } + } + } else if (event.getAction() == SVNEventAction.COMMIT_ADDED) { + myIndicator.setText2(SvnBundle.message("progress.text2.adding", path)); + } else if (event.getAction() == SVNEventAction.COMMIT_DELTA_SENT) { + myIndicator.setText2(SvnBundle.message("progress.text2.transmitting.delta", path)); + } + } + + public void checkCancelled() throws SVNCancelException { + if (myIndicator.isCanceled()) { + throw new SVNCancelException(SVNErrorMessage.create(SVNErrorCode.CANCELLED, "Operation cancelled")); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/checkout/SvnCheckoutProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/checkout/SvnCheckoutProvider.java new file mode 100644 index 000000000000..7d06723f79da --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/checkout/SvnCheckoutProvider.java @@ -0,0 +1,281 @@ +/* + * 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.checkout; + +import com.intellij.lifecycle.PeriodicalTasksCloser; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.FileIndexFacade; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.CheckoutProvider; +import com.intellij.openapi.vcs.VcsConfiguration; +import com.intellij.openapi.vcs.ex.ProjectLevelVcsManagerEx; +import com.intellij.openapi.vcs.update.RefreshVFsSynchronously; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.*; +import org.jetbrains.idea.svn17.actions.ExclusiveBackgroundVcsAction; +import org.jetbrains.idea.svn17.actions.SvnExcludingIgnoredOperation; +import org.jetbrains.idea.svn17.dialogs.CheckoutDialog; +import org.tmatesoft.svn.core.SVNCancelException; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration; +import org.tmatesoft.svn.core.wc.*; + +import javax.swing.*; +import java.io.File; + +public class SvnCheckoutProvider implements CheckoutProvider { + + public void doCheckout(@NotNull final Project project, Listener listener) { + CheckoutDialog dialog = new CheckoutDialog(project, listener); + dialog.show(); + } + + public static void doCheckout(final Project project, final File target, final String url, final SVNRevision revision, + final SVNDepth depth, final boolean ignoreExternals, @Nullable final Listener listener) { + if (! target.exists()) { + target.mkdirs(); + } + + final String selectedFormat = promptForWCopyFormat(target, project); + if (selectedFormat == null) { + // cancelled + return; + } + + checkout(project, target, url, revision, depth, ignoreExternals, listener, WorkingCopyFormat.getInstance(selectedFormat)); + } + + public static void checkout(final Project project, + final File target, + final String url, + final SVNRevision revision, + final SVNDepth depth, + final boolean ignoreExternals, + final Listener listener, final WorkingCopyFormat selectedFormat) { + final Ref checkoutSuccessful = new Ref(); + final SVNException[] exception = new SVNException[1]; + final Task.Backgroundable checkoutBackgroundTask = new Task.Backgroundable(project, + SvnBundle.message("message.title.check.out"), true, VcsConfiguration.getInstance(project).getCheckoutOption()) { + public void run(@NotNull final ProgressIndicator indicator) { + SvnWorkingCopyFormatHolder.setPresetFormat(selectedFormat); + + final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); + final SVNUpdateClient client = SvnVcs17.getInstance(project).createUpdateClient(); + if (! WorkingCopyFormat.ONE_DOT_SEVEN.equals(selectedFormat)) { + client.getOperationsFactory().setPrimaryWcGeneration(SvnWcGeneration.V16); + } + client.setEventHandler(new CheckoutEventHandler(SvnVcs17.getInstance(project), false, progressIndicator)); + client.setIgnoreExternals(ignoreExternals); + try { + progressIndicator.setText(SvnBundle.message("progress.text.checking.out", target.getAbsolutePath())); + client.doCheckout(SVNURL.parseURIEncoded(url), target, SVNRevision.UNDEFINED, revision, depth, true); + progressIndicator.checkCanceled(); + checkoutSuccessful.set(Boolean.TRUE); + } + catch (SVNCancelException ignore) { + } + catch (SVNException e) { + exception[0] = e; + } + finally { + client.setIgnoreExternals(false); + client.setEventHandler(null); + SvnWorkingCopyFormatHolder.setPresetFormat(null); + } + } + + public void onCancel() { + onSuccess(); + } + + public void onSuccess() { + if (exception[0] != null) { + Messages.showErrorDialog(SvnBundle.message("message.text.cannot.checkout", exception[0].getMessage()), SvnBundle.message("message.title.check.out")); + } + + final VirtualFile vf = RefreshVFsSynchronously.findCreatedFile(target); + if (vf != null) { + vf.refresh(true, true, new Runnable() { + public void run() { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + notifyListener(); + } + }); + } + }); + } + else { + notifyListener(); + } + } + + private void notifyListener() { + notifyRootManagerIfUnderProject(project, target); + if (listener != null) { + if (!checkoutSuccessful.isNull()) { + listener.directoryCheckedOut(target); + } + listener.checkoutCompleted(); + } + } + }; + ProgressManager.getInstance().run(checkoutBackgroundTask); + } + + private static void notifyRootManagerIfUnderProject(final Project project, final File directory) { + if (project.isDefault()) return; + final ProjectLevelVcsManagerEx plVcsManager = ProjectLevelVcsManagerEx.getInstanceEx(project); + final SvnVcs17 vcs = (SvnVcs17) plVcsManager.findVcsByName(SvnVcs17.VCS_NAME); + + final VirtualFile[] files = vcs.getSvnFileUrlMapping().getNotFilteredRoots(); + for (VirtualFile file : files) { + if (FileUtil.isAncestor(new File(file.getPath()), directory, false)) { + // todo: should be done like auto detection + plVcsManager.fireDirectoryMappingsChanged(); + return; + } + } + } + + public static boolean promptForWCFormatAndSelect(final File target, final Project project) { + final String result = promptForWCopyFormat(target, project); + if (result != null) { + SvnWorkingCopyFormatHolder.setPresetFormat(WorkingCopyFormat.getInstance(result)); + } + return result != null; + } + + @Nullable + private static String promptForWCopyFormat(final File target, final Project project) { + String formatMode = null; + final Ref wasOk = new Ref(); + while ((formatMode == null) && (! Boolean.FALSE.equals(wasOk.get()))) { + formatMode = SvnFormatSelector.showUpgradeDialog(target, project, true, SvnConfiguration17.UPGRADE_AUTO_17, wasOk); + } + return Boolean.TRUE.equals(wasOk.get()) ? formatMode : null; + } + + public static void doExport(final Project project, final File target, final String url, final SVNDepth depth, + final boolean ignoreExternals, final boolean force, final String eolStyle) { + try { + final SVNException[] exception = new SVNException[1]; + final SVNUpdateClient client = SvnVcs17.getInstance(project).createUpdateClient(); + + ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + public void run() { + ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); + client.setEventHandler(new CheckoutEventHandler(SvnVcs17.getInstance(project), true, progressIndicator)); + client.setIgnoreExternals(ignoreExternals); + try { + progressIndicator.setText(SvnBundle.message("progress.text.export", target.getAbsolutePath())); + client.doExport(SVNURL.parseURIEncoded(url), target, SVNRevision.UNDEFINED, SVNRevision.HEAD, eolStyle, force, depth); + } + catch (SVNException e) { + exception[0] = e; + } + finally { + client.setIgnoreExternals(false); + client.setEventHandler(null); + } + } + }, SvnBundle.message("message.title.export"), true, project); + if (exception[0] != null) { + throw exception[0]; + } + } + catch (SVNException e1) { + Messages.showErrorDialog(SvnBundle.message("message.text.cannot.export", e1.getMessage()), SvnBundle.message("message.title.export")); + } + } + + public static void doImport(final Project project, final File target, final SVNURL url, final SVNDepth depth, + final boolean includeIgnored, final String message) { + final Ref errorMessage = new Ref(); + final SVNCommitClient client = SvnVcs17.getInstance(project).createCommitClient(); + final String targetPath = FileUtil.toSystemIndependentName(target.getAbsolutePath()); + + ExclusiveBackgroundVcsAction.run(project, new Runnable() { + public void run() { + ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + public void run() { + ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); + client.setEventHandler(new CheckoutEventHandler(SvnVcs17.getInstance(project), true, progressIndicator)); + try { + progressIndicator.setText(SvnBundle.message("progress.text.import", target.getAbsolutePath())); + + final VirtualFile targetVf = SvnUtil.getVirtualFile(targetPath); + if (targetVf == null) { + errorMessage.set("Can not find file: " + targetPath); + } else if (project.isDefault() || !PeriodicalTasksCloser.getInstance().safeGetService(project, FileIndexFacade.class) + .isInContent(targetVf)) { + // do not pay attention to ignored/excluded settings + client.doImport(target, url, message, null, !includeIgnored, false, depth); + } else { + client.setCommitHandler(new MyFilter(LocalFileSystem.getInstance(), new SvnExcludingIgnoredOperation.Filter(project))); + client.doImport(target, url, message, null, !includeIgnored, false, depth); + } + } + catch (SVNException e) { + errorMessage.set(e.getMessage()); + } + finally { + client.setIgnoreExternals(false); + client.setEventHandler(null); + } + } + }, SvnBundle.message("message.title.import"), true, project); + } + }); + + if (! errorMessage.isNull()) { + Messages.showErrorDialog(SvnBundle.message("message.text.cannot.import", errorMessage.get()), SvnBundle.message("message.title.import")); + } + } + + private static class MyFilter extends DefaultSVNCommitHandler implements ISVNFileFilter { + private final LocalFileSystem myLfs; + private final SvnExcludingIgnoredOperation.Filter myFilter; + + private MyFilter(LocalFileSystem lfs, SvnExcludingIgnoredOperation.Filter filter) { + myLfs = lfs; + myFilter = filter; + } + + public boolean accept(final File file) throws SVNException { + final VirtualFile vf = myLfs.findFileByIoFile(file); + return vf != null && myFilter.accept(vf); + } + } + + public String getVcsName() { + return "_Subversion"; + } + +} + + diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/AmbiguousPatternsFinder.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/AmbiguousPatternsFinder.java new file mode 100644 index 000000000000..104500038760 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/AmbiguousPatternsFinder.java @@ -0,0 +1,76 @@ +/* + * 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.config; + +import com.intellij.openapi.util.Ref; +import org.jetbrains.idea.svn17.SvnBundle; + +import java.util.*; + +public class AmbiguousPatternsFinder { + private final Map> urls2groups; + + private final static int ourMessageLen = 30; + + public AmbiguousPatternsFinder() { + urls2groups = new HashMap>(); + } + + public void acceptUrls(final String groupName, final List urls) { + for (String url : urls) { + final Set set; + if (urls2groups.containsKey(url)) { + set = urls2groups.get(url); + } else { + set = new HashSet(); + urls2groups.put(url, set); + } + set.add(groupName); + } + } + + public boolean isValid(final Ref errorMessageRef) { + StringBuilder sb = null; + for (Map.Entry> entry : urls2groups.entrySet()) { + if (entry.getValue().size() > 1) { + if (sb == null) { + sb = new StringBuilder(); + } else { + if (sb.length() > ourMessageLen) { + sb.append("..."); + break; + } + sb.append("; "); + } + StringBuilder innerBuilder = null; + for (String groupName : entry.getValue()) { + if (innerBuilder == null) { + innerBuilder = new StringBuilder(); + } else { + innerBuilder.append(", "); + } + innerBuilder.append(groupName); + } + sb.append(SvnBundle.message("dialog.edit.http.proxies.settings.error.ambiguous.group.patterns.to.text", + entry.getKey(), innerBuilder.toString())); + } + } + if (sb != null) { + errorMessageRef.set(SvnBundle.message("dialog.edit.http.proxies.settings.error.ambiguous.group.patterns.text", sb.toString())); + } + return sb == null; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/CompositeRunnable.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/CompositeRunnable.java new file mode 100644 index 000000000000..cb939f6fd641 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/CompositeRunnable.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.config; + +import org.jetbrains.annotations.NotNull; + +public class CompositeRunnable implements Runnable { + private final Runnable[] myRunnables; + + public CompositeRunnable(@NotNull Runnable... runnables) { + myRunnables = runnables; + } + + public void run() { + for (Runnable runnable : myRunnables) { + runnable.run(); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/ConfigureProxiesListener.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/ConfigureProxiesListener.java new file mode 100644 index 000000000000..3608321fcbe0 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/ConfigureProxiesListener.java @@ -0,0 +1,34 @@ +/* + * 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.config; + +import com.intellij.openapi.project.Project; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class ConfigureProxiesListener implements ActionListener { + private final Project myProject; + + public ConfigureProxiesListener(final Project project) { + myProject = project; + } + + public void actionPerformed(final ActionEvent e) { + final SvnConfigureProxiesDialog dialog = new SvnConfigureProxiesDialog(myProject); + dialog.show(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/ConfigureProxiesOptionsPanel.form b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/ConfigureProxiesOptionsPanel.form new file mode 100644 index 000000000000..35db4bedfd98 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/ConfigureProxiesOptionsPanel.form @@ -0,0 +1,297 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/ConfigureProxiesOptionsPanel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/ConfigureProxiesOptionsPanel.java new file mode 100644 index 000000000000..8ccae22d4ff8 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/ConfigureProxiesOptionsPanel.java @@ -0,0 +1,262 @@ +/* + * 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.config; + +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.ui.InsertPathAction; +import org.jetbrains.idea.svn17.SvnBundle; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.text.JTextComponent; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ConfigureProxiesOptionsPanel implements RepositoryUrlsListener { + private JPanel myMainPanel; + private JTextField myServerField; + private JTextField myUserField; + private JTextField myPortField; + private JPasswordField myPasswordField; + private JTextArea myUrlPatterns; + private JTextArea myExceptions; + private JTextField myTimeoutField; + private JCheckBox myTrustDefaultCAsCheckBox; + private JPasswordField myClientCertificatePasswordField; + private JButton myTestConnectionButton; + private JTextField myPathToCertificatesField; + private TextFieldWithBrowseButton myClientCertificatePathField; + private JList myRepositoriesList; + private boolean myIsDefault; + + private final Runnable myValidator; + // listens for patterns+exceptions changes on lost focus + private PatternsListener myPatternsListener; + + private final Map myComponent2Key; + private final Map myKey2Component; + + private final TestConnectionPerformer myTestConnectionPerformer; + + /** + * called on after repositories list had been recalculated by {@link org.jetbrains.idea.svn17.config.PatternsListener} + * + * @see org.jetbrains.idea.svn17.config.RepositoryUrlsListener#onListChanged(java.util.List) + */ + public void onListChanged(final List urls) { + final String value = (String) myRepositoriesList.getSelectedValue(); + myRepositoriesList.removeAll(); + myRepositoriesList.setListData(urls.toArray()); + // for keeping selection + if (value != null) { + final ListModel model = myRepositoriesList.getModel(); + for (int i = 0; i < model.getSize(); i++) { + final String element = (String) model.getElementAt(i); + if (value.equals(element)) { + myRepositoriesList.setSelectedIndex(i); + } + } + } + // in order to check for groups ambiguity + maybe highlight another error + myValidator.run(); + } + + public List getRepositories() { + final ListModel model = myRepositoriesList.getModel(); + final List result = new ArrayList(model.getSize()); + for (int i = 0; i < model.getSize(); i++) { + result.add((String) model.getElementAt(i)); + } + return result; + } + + public JPanel getMainPanel() { + return myMainPanel; + } + + public void setPatternsListener(final PatternsListener listener) { + myPatternsListener = listener; + } + + public ConfigureProxiesOptionsPanel(final Runnable validator, final TestConnectionPerformer testConnectionPerformer) { + myValidator = validator; + myTestConnectionPerformer = testConnectionPerformer; + + myComponent2Key = new HashMap(); + myKey2Component = new HashMap(); + fillMappings(); + + initNumericValidation(); + initBrowseActions(); + initRepositories(); + putPatternsListener(); + + myTestConnectionButton.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + final String value = (String) myRepositoriesList.getSelectedValue(); + if ((value != null) && (myTestConnectionPerformer.enabled())) { + myTestConnectionPerformer.execute(value); + } + } + }); + } + + private void putPatternsListener() { + final UrlsSetter urlsSetter = new UrlsSetter(); + myUrlPatterns.addFocusListener(urlsSetter); + myExceptions.addFocusListener(urlsSetter); + } + + private void initBrowseActions() { + InsertPathAction.addTo(myPathToCertificatesField, FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor()); + myClientCertificatePathField.addBrowseFolderListener( + SvnBundle.message("dialog.edit.http.proxies.settings.dialog.select.ssl.client.certificate.path.title"), + null, null, new FileChooserDescriptor(true, false, false, false, false, false)); + } + + private void initNumericValidation() { + final NumericFieldsValidator numericFocusListener = new NumericFieldsValidator(); + myPortField.addFocusListener(numericFocusListener); + myTimeoutField.addFocusListener(numericFocusListener); + } + + private void initRepositories() { + final ListSelectionModel selectionModel = new DefaultListSelectionModel(); + selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + myRepositoriesList.setSelectionModel(selectionModel); + myRepositoriesList.addListSelectionListener(new ListSelectionListener() { + public void valueChanged(final ListSelectionEvent e) { + myTestConnectionButton.setEnabled(myTestConnectionPerformer.enabled() && (myRepositoriesList.getSelectedValue() != null)); + } + }); + } + + private void fillMappings() { + addToKeyMappings(myServerField, SvnServerFileKeys.SERVER); + addToKeyMappings(myUserField, SvnServerFileKeys.USER); + addToKeyMappings(myPortField, SvnServerFileKeys.PORT); + addToKeyMappings(myPasswordField, SvnServerFileKeys.PASSWORD); + addToKeyMappings(myExceptions, SvnServerFileKeys.EXCEPTIONS); + addToKeyMappings(myTimeoutField, SvnServerFileKeys.TIMEOUT); + addToKeyMappings(myTrustDefaultCAsCheckBox, SvnServerFileKeys.SSL_TRUST_DEFAULT_CA); + addToKeyMappings(myClientCertificatePasswordField, SvnServerFileKeys.SSL_CLIENT_CERT_PASSWORD); + addToKeyMappings(myPathToCertificatesField, SvnServerFileKeys.SSL_AUTHORITY_FILES); + addToKeyMappings(myClientCertificatePathField, SvnServerFileKeys.SSL_CLIENT_CERT_FILE); + } + + private void addToKeyMappings(final JComponent component, final String key) { + myComponent2Key.put(component, key); + myKey2Component.put(key, component); + } + + private class UrlsSetter implements FocusListener { + public void focusGained(final FocusEvent e) { + } + + public void focusLost(final FocusEvent e) { + repositoryUrlsRecalculation(); + } + } + + private void repositoryUrlsRecalculation() { + if (! myIsDefault) { + myPatternsListener.onChange(myUrlPatterns.getText(), myExceptions.getText()); + } + } + + public void setIsValid(final boolean valid) { + myTestConnectionButton.setEnabled(valid && (myRepositoriesList.getSelectedValue() != null)); + } + + private class NumericFieldsValidator implements FocusListener { + public void focusGained(final FocusEvent e) { + } + public void focusLost(final FocusEvent e) { + myValidator.run(); + } + } + + public void setStringProperties(final Map properties) { + for (Map.Entry entry : properties.entrySet()) { + final JComponent component = myKey2Component.get(entry.getKey()); + if (component != null) { + JTextComponent textComponent = null; + if (component instanceof JTextComponent) { + textComponent = (JTextComponent) component; + } else if (component instanceof TextFieldWithBrowseButton) { + textComponent = ((TextFieldWithBrowseButton) component).getTextField(); + } + if (textComponent != null) { + textComponent.setText(entry.getValue()); + textComponent.selectAll(); + } + component.setToolTipText(entry.getKey()); + } + } + + myTrustDefaultCAsCheckBox.setSelected(booleanPropertySelected(properties.get(myComponent2Key.get(myTrustDefaultCAsCheckBox)))); + repositoryUrlsRecalculation(); + } + + public void copyStringProperties(Map map) { + for (Map.Entry entry : myKey2Component.entrySet()) { + final JComponent component = entry.getValue(); + String value = null; + if (component instanceof JTextComponent) { + value = ((JTextComponent) component).getText(); + } else if (component instanceof TextFieldWithBrowseButton) { + value = ((TextFieldWithBrowseButton) component).getTextField().getText(); + } else if (component instanceof JCheckBox) { + value = ((JCheckBox) component).isSelected() ? "yes" : "no"; + } + + if ((value != null) && ((! "".equals(value)) || (map.containsKey(entry.getKey())))) { + map.put(entry.getKey(), value); + } + } + } + + private static boolean booleanPropertySelected(final String value) { + return value != null && SvnServerFileKeys.YES_OPTIONS.contains(value.toLowerCase()); + } + + public boolean isDefault() { + return myIsDefault; + } + + public void setIsDefaultGroup(final boolean value) { + myIsDefault = value; + myUrlPatterns.setEditable(! myIsDefault); + } + + public String getPatterns() { + return myUrlPatterns.getText(); + } + + public void setPatterns(final String value) { + myUrlPatterns.setText(value); + repositoryUrlsRecalculation(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/DefaultProxyGroup.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/DefaultProxyGroup.java new file mode 100644 index 000000000000..055df2f66af2 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/DefaultProxyGroup.java @@ -0,0 +1,34 @@ +/* + * 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.config; + +import java.util.Map; + +public class DefaultProxyGroup extends ProxyGroup { + public static final String DEFAULT_GROUP_NAME = "global"; + + public DefaultProxyGroup(final Map properties) { + super(DEFAULT_GROUP_NAME, "", properties); + } + + public boolean isDefault() { + return true; + } + + public ProxyGroup copy() { + return new DefaultProxyGroup(createPropertiesCopy()); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/DefaultRepositoryUrlFilter.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/DefaultRepositoryUrlFilter.java new file mode 100644 index 000000000000..b7770c0f0199 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/DefaultRepositoryUrlFilter.java @@ -0,0 +1,37 @@ +/* + * 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.config; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class DefaultRepositoryUrlFilter { + private final RepositoryUrlsListener myListener; + private final SvnConfigureProxiesComponent myComponent; + + public DefaultRepositoryUrlFilter(final SvnConfigureProxiesComponent component, final RepositoryUrlsListener listener) { + myComponent = component; + myListener = listener; + } + + public void execute(final Collection urls) { + final List filtered = myComponent.getGlobalGroupRepositories(urls); + Collections.sort(filtered); + + myListener.onListChanged(filtered); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/GroupConfigurable.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/GroupConfigurable.java new file mode 100644 index 000000000000..bbc4c25406d0 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/GroupConfigurable.java @@ -0,0 +1,138 @@ +/* + * 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.config; + +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.ui.NamedConfigurable; +import com.intellij.openapi.util.Ref; +import org.jetbrains.idea.svn17.SvnBundle; + +import javax.swing.*; +import java.util.List; + +public class GroupConfigurable extends NamedConfigurable { + private final ProxyGroup myProxyGroup; + private final ConfigureProxiesOptionsPanel myPanel; + private boolean myIsInitialized; + + public GroupConfigurable(final ProxyGroup proxyGroup, final Runnable treeUpdater, final ConfigureProxiesOptionsPanel panel) { + super(! proxyGroup.isDefault(), treeUpdater); + myProxyGroup = proxyGroup; + myPanel = panel; + } + + public List getRepositories() { + return myPanel.getRepositories(); + } + + public void setDisplayName(final String name) { + myProxyGroup.setName(name); + } + + public ProxyGroup getEditableObject() { + return myProxyGroup; + } + + public String getBannerSlogan() { + return myProxyGroup.getName(); + } + + public JComponent createOptionsPanel() { + return myPanel.getMainPanel(); + } + + public String getDisplayName() { + return myProxyGroup.getName(); + } + + public Icon getIcon() { + return null; + } + + public String getHelpTopic() { + return null; + } + + public boolean isModified() { + // not used + return false; + } + + public void applyImpl() { + if (myIsInitialized) { + myProxyGroup.setPatterns(myPanel.getPatterns()); + myPanel.copyStringProperties(myProxyGroup.getProperties()); + } + } + + public void apply() throws ConfigurationException { + if (myIsInitialized) { + applyImpl(); + } + final Ref errorMessageRef = new Ref(); + if (! validate(errorMessageRef)) { + throw new ConfigurationException(errorMessageRef.get()); + } + } + + public boolean validate(final Ref errorMessageRef) { + if (! checkNumericFieldValue(myProxyGroup.getPort())) { + errorMessageRef.set(SvnBundle.message("dialog.edit.http.proxies.settings.port.must.be.number.error", myProxyGroup.getName())); + return false; + } + + if (! checkNumericFieldValue(myProxyGroup.getTimeout())) { + errorMessageRef.set(SvnBundle.message("dialog.edit.http.proxies.settings.timeout.must.be.number.error", myProxyGroup.getName())); + return false; + } + + return true; + } + + public void setIsValid(final boolean valid) { + myPanel.setIsValid(valid); + } + + private boolean checkNumericFieldValue(final String value) { + if (value == null) { + return true; + } + try { + final String portString = value.trim(); + if (portString.length() > 0) { + Integer.valueOf(portString); + } + } catch (NumberFormatException e) { + return false; + } + return true; + } + + public void reset() { + try { + myPanel.setStringProperties(myProxyGroup.getProperties()); + myPanel.setPatterns(myProxyGroup.getPatterns()); + myPanel.setIsDefaultGroup(myProxyGroup.isDefault()); + myIsInitialized = true; + } catch (NumberFormatException e) { + // never + } + } + + public void disposeUIResources() { + + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/GroupsValidator.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/GroupsValidator.java new file mode 100644 index 000000000000..0c5c0cb11e07 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/GroupsValidator.java @@ -0,0 +1,57 @@ +/* + * 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.config; + +import java.util.ArrayList; +import java.util.List; + +public class GroupsValidator implements Runnable { + private final List myComponents; + private final ValidationListener myListener; + private boolean myStopped; + + public GroupsValidator(final ValidationListener listener) { + myComponents = new ArrayList(); + myListener = listener; + } + + public void add(final SvnConfigureProxiesComponent component) { + myComponents.add(component); + } + + public void run() { + if (myStopped) { + return; + } + myListener.onSuccess(); // clear status of previous validation + + boolean valid = true; + for (SvnConfigureProxiesComponent component : myComponents) { + if(! component.validate(myListener)) { + valid = false; + break; + } + } + + for (SvnConfigureProxiesComponent component : myComponents) { + component.setIsValid(valid); + } + } + + public void stop() { + myStopped = true; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/PatternsListener.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/PatternsListener.java new file mode 100644 index 000000000000..e227972ffdea --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/PatternsListener.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.config; + +public interface PatternsListener { + void onChange(String patterns, String exceptions); + + class Empty implements PatternsListener { + public static final Empty instance = new Empty(); + public void onChange(final String patterns, final String exceptions) { + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/ProxyGroup.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/ProxyGroup.java new file mode 100644 index 000000000000..f077c65176ce --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/ProxyGroup.java @@ -0,0 +1,77 @@ +/* + * 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.config; + +import java.util.HashMap; +import java.util.Map; + +public class ProxyGroup { + private String myGroupName; + private String myPattern; + + // no sence in keeping 'named' referencies to properties - they are just put through into a file; + // svnkit has its internal named referencies + private final Map myProperties; + + public ProxyGroup(final String groupName, final String patterns, final Map properties) { + myGroupName = groupName; + myPattern = patterns; + myProperties = new HashMap(); + myProperties.putAll(properties); + } + + public Map getProperties() { + return myProperties; + } + + public boolean isDefault() { + return false; + } + + public void setName(final String value) { + myGroupName = value; + } + + public String getName() { + return myGroupName; + } + + public String getPort() { + return myProperties.get(SvnServerFileKeys.PORT); + } + + public String getTimeout() { + return myProperties.get(SvnServerFileKeys.TIMEOUT); + } + + public String getPatterns() { + return myPattern; + } + + public void setPatterns(final String value) { + myPattern = value; + } + + protected Map createPropertiesCopy() { + final Map copyProperties = new HashMap(); + copyProperties.putAll(myProperties); + return copyProperties; + } + + public ProxyGroup copy() { + return new ProxyGroup(myGroupName, myPattern, createPropertiesCopy()); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/RepositoryUrlFilter.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/RepositoryUrlFilter.java new file mode 100644 index 000000000000..5935e02797cb --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/RepositoryUrlFilter.java @@ -0,0 +1,52 @@ +/* + * 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.config; + +import org.jetbrains.idea.svn17.SvnApplicationSettings17; +import org.jetbrains.idea.svn17.SvnAuthenticationManager; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class RepositoryUrlFilter implements PatternsListener { + private final RepositoryUrlsListener myListener; + private final DefaultRepositoryUrlFilter myDefaultFilter; + + public RepositoryUrlFilter(final RepositoryUrlsListener listener, final SvnConfigureProxiesComponent component, + final RepositoryUrlsListener defaultGroupListener) { + myListener = listener; + myDefaultFilter = new DefaultRepositoryUrlFilter(component, defaultGroupListener); + } + + public void onChange(final String patterns, final String exceptions) { + final Collection urls = SvnApplicationSettings17.getInstance().getCheckoutURLs(); + final List result = new ArrayList(); + + for (String url : urls) { + if (SvnAuthenticationManager.checkHostGroup(url, patterns, exceptions)) { + result.add(url); + } + } + + Collections.sort(result); + + myListener.onListChanged(result); + + myDefaultFilter.execute(urls); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/RepositoryUrlsListener.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/RepositoryUrlsListener.java new file mode 100644 index 000000000000..364d361f3c0e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/RepositoryUrlsListener.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.config; + +import java.util.List; + +public interface RepositoryUrlsListener { + void onListChanged(final List urls); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/SvnConfigureProxiesComponent.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/SvnConfigureProxiesComponent.java new file mode 100644 index 000000000000..a002af674144 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/SvnConfigureProxiesComponent.java @@ -0,0 +1,311 @@ +/* + * 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.config; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonShortcuts; +import com.intellij.openapi.actionSystem.CustomShortcutSet; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.ui.MasterDetailsComponent; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Ref; +import com.intellij.util.PlatformIcons; +import com.intellij.util.ui.tree.TreeUtil; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnServerFileManager; + +import javax.swing.*; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import java.awt.event.KeyEvent; +import java.util.*; + +public class SvnConfigureProxiesComponent extends MasterDetailsComponent { + private final SvnServerFileManager myManager; + + private final CompositeRunnable myTreeUpdaterValidator; + private final Runnable myValidator; + private JComponent myComponent; + private final TestConnectionPerformer myTestConnectionPerformer; + private ConfigureProxiesOptionsPanel myDefaultGroupPanel; + + public SvnConfigureProxiesComponent(final SvnServerFileManager manager, final GroupsValidator validator, final TestConnectionPerformer testConnectionPerformer) { + myTestConnectionPerformer = testConnectionPerformer; + myValidator = validator; + myTreeUpdaterValidator = new CompositeRunnable(TREE_UPDATER, myValidator); + initTree(); + myManager = manager; + fillTree(); + validator.add(this); + } + + public JComponent createComponent() { + if (myComponent == null) { + myComponent = super.createComponent(); + } + return myComponent; + } + + protected void processRemovedItems() { + // not used + } + + protected boolean wasObjectStored(final Object editableObject) { + return false; + } + + public String getDisplayName() { + return "HTTP proxies configuration"; + } + + public Icon getIcon() { + return null; + } + + public String getHelpTopic() { + return null; + } + + private String getNewName() { + return "Unnamed"; + } + + private void addGroup(final ProxyGroup template) { + final ProxyGroup group; + if (template == null) { + group = new ProxyGroup(getNewName(), "", Collections.emptyMap()); + } else { + group = new ProxyGroup(getNewName(), template.getPatterns(), template.getProperties()); + } + + addNode(createNodeForObject(group), myRoot); + selectNodeInTree(group); + } + + public List getGlobalGroupRepositories(final Collection all) { + // i.e. all-[all used] + final List result = new LinkedList(all); + + for (int i = 0; i < myRoot.getChildCount(); i++) { + final MyNode node = (MyNode) myRoot.getChildAt(i); + final GroupConfigurable groupConfigurable = (GroupConfigurable) node.getConfigurable(); + if (! groupConfigurable.getEditableObject().isDefault()) { + result.removeAll(groupConfigurable.getRepositories()); + } + } + + return result; + } + + public boolean validate(final ValidationListener listener) { + final Ref errorMessageRef = new Ref(); + final Set checkSet = new HashSet(); + final AmbiguousPatternsFinder ambiguousPatternsFinder = new AmbiguousPatternsFinder(); + + for (int i = 0; i < myRoot.getChildCount(); i++) { + final MyNode node = (MyNode) myRoot.getChildAt(i); + final GroupConfigurable groupConfigurable = (GroupConfigurable) node.getConfigurable(); + final String groupName = groupConfigurable.getEditableObject().getName(); + + if (checkSet.contains(groupName)) { + listener.onError(SvnBundle.message("dialog.edit.http.proxies.settings.error.same.group.names.text", groupName), myComponent, true); + return false; + } + checkSet.add(groupName); + } + + for (int i = 0; i < myRoot.getChildCount(); i++) { + final MyNode node = (MyNode) myRoot.getChildAt(i); + final GroupConfigurable groupConfigurable = (GroupConfigurable) node.getConfigurable(); + groupConfigurable.applyImpl(); + if(! groupConfigurable.validate(errorMessageRef)) { + listener.onError(errorMessageRef.get(), myComponent, false); + return false; + } + + if (! groupConfigurable.getEditableObject().isDefault()) { + final String groupName = groupConfigurable.getEditableObject().getName(); + final List urls = groupConfigurable.getRepositories(); + ambiguousPatternsFinder.acceptUrls(groupName, urls); + } + } + + if(! ambiguousPatternsFinder.isValid(errorMessageRef)) { + listener.onError(errorMessageRef.get(), myComponent, false); + return false; + } + return true; + } + + protected ArrayList createActions(final boolean fromPopup) { + ArrayList result = new ArrayList(); + result.add(new AnAction("Add", "Add", PlatformIcons.ADD_ICON) { + { + registerCustomShortcutSet(CommonShortcuts.INSERT, myTree); + } + public void actionPerformed(AnActionEvent event) { + addGroup(null); + } + + + }); + result.add(new MyDeleteAction(forAll(new Condition(){ + public boolean value(final Object o) { + if (o instanceof MyNode) { + final MyNode node = (MyNode) o; + if (node.getConfigurable() instanceof GroupConfigurable) { + final ProxyGroup group = ((GroupConfigurable) node.getConfigurable()).getEditableObject(); + return ! group.isDefault(); + } + } + return false; + } + })) { + public void actionPerformed(final AnActionEvent e) { + final TreePath path = myTree.getSelectionPath(); + final MyNode node = (MyNode)path.getLastPathComponent(); + final MyNode parentNode = (MyNode) node.getParent(); + int idx = parentNode.getIndex(node); + + super.actionPerformed(e); + + idx = (idx == parentNode.getChildCount()) ? idx - 1 : idx; + if (parentNode.getChildCount() > 0) { + final TreePath newSelectedPath = new TreePath(parentNode.getPath()).pathByAddingChild(parentNode.getChildAt(idx)); + myTree.setSelectionPath(newSelectedPath); + } + } + }); + + result.add(new AnAction("Copy", "Copy", PlatformIcons.COPY_ICON) { + { + registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_D, KeyEvent.CTRL_MASK)), myTree); + } + public void actionPerformed(AnActionEvent event) { + // apply - for update of editable object + try { + getSelectedConfugurable().apply(); + } catch (ConfigurationException e) { + // suppress & wait for OK + } + final ProxyGroup selectedGroup = (ProxyGroup) getSelectedObject(); + if (selectedGroup != null) { + addGroup(selectedGroup); + } + } + + public void update(AnActionEvent event) { + super.update(event); + event.getPresentation().setEnabled(getSelectedObject() != null); + } + }); + return result; + } + + public void apply() throws ConfigurationException { + final List groups = new ArrayList(myRoot.getChildCount()); + + for (int i = 0; i < myRoot.getChildCount(); i++) { + final MyNode node = (MyNode) myRoot.getChildAt(i); + final GroupConfigurable groupConfigurable = (GroupConfigurable) node.getConfigurable(); + groupConfigurable.apply(); + groups.add(groupConfigurable.getEditableObject()); + } + + myManager.updateUserServerFile(groups); + } + + public void reset() { + super.reset(); + + for (int i = 0; i < myRoot.getChildCount(); i++) { + final MyNode node = (MyNode) myRoot.getChildAt(i); + final GroupConfigurable groupConfigurable = (GroupConfigurable) node.getConfigurable(); + groupConfigurable.reset(); + } + } + + private MyNode createNodeForObject(final ProxyGroup group) { + final ConfigureProxiesOptionsPanel panel = new ConfigureProxiesOptionsPanel(myValidator, myTestConnectionPerformer); + final GroupConfigurable gc = new GroupConfigurable(group, myTreeUpdaterValidator, panel); + if (group.isDefault()) { + myDefaultGroupPanel = panel; + } + + // first added node must be global /default group. since url-fillers for other groups forwards their updates to global group + assert myDefaultGroupPanel != null; + panel.setPatternsListener((group.isDefault()) ? PatternsListener.Empty.instance : + new RepositoryUrlFilter(panel, this, myDefaultGroupPanel)); + + return new MyNode(gc, group.isDefault()); + } + + private void fillTree() { + myRoot.removeAllChildren(); + + DefaultProxyGroup defaultProxyGroup = myManager.getDefaultGroup(); + defaultProxyGroup = (defaultProxyGroup == null) ? new DefaultProxyGroup(Collections.emptyMap()) : defaultProxyGroup; + final Map userGroups = myManager.getGroups(); + + myRoot.add(createNodeForObject(defaultProxyGroup)); + for (Map.Entry entry : userGroups.entrySet()) { + myRoot.add(createNodeForObject(entry.getValue())); + } + + TreeUtil.sort(myRoot, GroupNodesComparator.getInstance()); + ((DefaultTreeModel) myTree.getModel()).reload(myRoot); + } + + @Override + protected Comparator getNodeComparator() { + return GroupNodesComparator.getInstance(); + } + + private static class GroupNodesComparator implements Comparator { + private final static GroupNodesComparator instance = new GroupNodesComparator(); + + private static GroupNodesComparator getInstance() { + return instance; + } + + public int compare(final MyNode node1, final MyNode node2) { + if ((node1.getConfigurable() instanceof GroupConfigurable) && (node2.getConfigurable() instanceof GroupConfigurable)) { + final ProxyGroup group1 = ((GroupConfigurable) node1.getConfigurable()).getEditableObject(); + final ProxyGroup group2 = ((GroupConfigurable) node2.getConfigurable()).getEditableObject(); + if (group1.isDefault()) { + return -1; + } + if (group2.isDefault()) { + return 1; + } + } + + return node1.getDisplayName().compareToIgnoreCase(node2.getDisplayName()); + } + } + + /** + * total component made invalid => for test connection to be forbidden when for ex. ambiguous settings given + */ + public void setIsValid(final boolean valid) { + for (int i = 0; i < myRoot.getChildCount(); i++) { + final MyNode node = (MyNode) myRoot.getChildAt(i); + final GroupConfigurable groupConfigurable = (GroupConfigurable) node.getConfigurable(); + groupConfigurable.setIsValid(valid); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/SvnConfigureProxiesDialog.form b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/SvnConfigureProxiesDialog.form new file mode 100644 index 000000000000..7736108df38e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/SvnConfigureProxiesDialog.form @@ -0,0 +1,14 @@ + +
+ + + + + + + + + + + +
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/SvnConfigureProxiesDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/SvnConfigureProxiesDialog.java new file mode 100644 index 000000000000..64c6e598665d --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/SvnConfigureProxiesDialog.java @@ -0,0 +1,182 @@ +/* + * 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.config; + +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Ref; +import com.intellij.ui.components.JBTabbedPane; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnConfiguration17; +import org.jetbrains.idea.svn17.SvnServerFileManager; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.io.SVNRepository; + +import javax.swing.*; +import java.awt.*; + +public class SvnConfigureProxiesDialog extends DialogWrapper implements ValidationListener, TestConnectionPerformer { + private final SvnConfigureProxiesComponent mySystemTab; + private final SvnConfigureProxiesComponent myUserTab; + private JPanel myPanel; + private JTabbedPane myTabbedPane; + private final GroupsValidator myValidator; + private final Project myProject; + + public SvnConfigureProxiesDialog(final Project project) { + super(project, true); + valid = true; + myProject = project; + + setTitle(SvnBundle.message("dialog.title.edit.http.proxies.settings")); + + final Ref systemManager = new Ref(); + final Ref userManager = new Ref(); + + SvnConfiguration17.getInstance(project).getServerFilesManagers(systemManager, userManager); + + myValidator = new GroupsValidator(this); + mySystemTab = new SvnConfigureProxiesComponent(systemManager.get(), myValidator, this); + myUserTab = new SvnConfigureProxiesComponent(userManager.get(), myValidator, this); + + init(); + + mySystemTab.reset(); + myUserTab.reset(); + myValidator.run(); + } + + public void onError(final String text, final JComponent component, final boolean forbidSave) { + myTabbedPane.setSelectedComponent(component); + String prefixString = ""; + for (int i = 0; i < myTabbedPane.getComponentCount(); i++) { + final Component currentComponent = myTabbedPane.getComponentAt(i); + // compare referencies - same objects + if (currentComponent == component) { + prefixString = myTabbedPane.getTitleAt(i) + ": "; + } + } + setOKActionEnabled(! forbidSave); + setInvalid(prefixString + text); + } + + public void onSuccess() { + if (isVisible()) { + setOKActionEnabled(true); + setInvalid(null); + } + } + + public boolean shouldCloseOnCross() { + return true; + } + + private boolean applyToTab(final SvnConfigureProxiesComponent component) { + try { + component.apply(); + } catch (ConfigurationException e) { + myTabbedPane.setSelectedComponent(component.createComponent()); + setInvalid(e.getMessage()); + return false; + } + return true; + } + + public void doCancelAction() { + myValidator.stop(); + super.doCancelAction(); + } + + private boolean applyImpl() { + if (! applyToTab(myUserTab)) { + return false; + } + if (! applyToTab(mySystemTab)) { + return false; + } + return true; + } + + protected void doOKAction() { + if (getOKAction().isEnabled()) { + if(! applyImpl()) { + return; + } + myValidator.stop(); + close(OK_EXIT_CODE); + } + } + + public void execute(final String url) { + Messages.showInfoMessage(myProject, SvnBundle.message("dialog.edit.http.proxies.settings.test.connection.settings.will.be.stored.text"), + SvnBundle.message("dialog.edit.http.proxies.settings.test.connection.settings.will.be.stored.title")); + if(! applyImpl()) { + return; + } + final Ref excRef = new Ref(); + final ProgressManager pm = ProgressManager.getInstance(); + pm.runProcessWithProgressSynchronously(new Runnable() { + public void run() { + final ProgressIndicator pi = pm.getProgressIndicator(); + if (pi != null) { + pi.setText("Connecting to " + url); + } + SVNRepository repository = null; + try { + repository = SvnVcs17.getInstance(myProject).createRepository(url); + repository.testConnection(); + } catch (SVNException exc) { + excRef.set(exc); + } finally { + if (repository != null) { + repository.closeSession(); + } + } + } + }, "Test connection", true, myProject); + if (! excRef.isNull()) { + Messages.showErrorDialog(myProject, excRef.get().getMessage(), + SvnBundle.message("dialog.edit.http.proxies.settings.test.connection.error.title")); + } else { + Messages.showInfoMessage(myProject, SvnBundle.message("dialog.edit.http.proxies.settings.test.connection.succes.text"), + SvnBundle.message("dialog.edit.http.proxies.settings.test.connection.succes.title")); + } + } + + private boolean valid; + + public void setInvalid(final String text) { + valid = (text == null) || ("".equals(text.trim())); + setErrorText(text); + } + + public boolean enabled() { + return valid; + } + + protected JComponent createCenterPanel() { + myTabbedPane = new JBTabbedPane(); + myTabbedPane.add(myUserTab.createComponent(), SvnBundle.message("dialog.edit.http.proxies.settings.tab.edit.user.file.title")); + myTabbedPane.add(mySystemTab.createComponent(), SvnBundle.message("dialog.edit.http.proxies.settings.tab.edit.system.file.title")); + myPanel.add(myTabbedPane, BorderLayout.NORTH); + return myPanel; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/SvnServerFileKeys.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/SvnServerFileKeys.java new file mode 100644 index 000000000000..dad9a3b301a4 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/SvnServerFileKeys.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.config; + +import java.util.Arrays; +import java.util.List; + +public interface SvnServerFileKeys { + String EXCEPTIONS = "http-proxy-exceptions"; + String SERVER = "http-proxy-host"; + String PORT = "http-proxy-port"; + String USER = "http-proxy-username"; + String PASSWORD = "http-proxy-password"; + String TIMEOUT = "http-timeout"; + + // not used by svnkit jet + String COMPRESSION = "http-compression"; + String LIBRARY = "http-library"; + String AUTH_TYPES = "http-auth-types"; + String NEON_DEBUG_MASK = "neon-debug-mask"; + + String SSL_AUTHORITY_FILES = "ssl-authority-files"; + String SSL_TRUST_DEFAULT_CA = "ssl-trust-default-ca"; + String SSL_CLIENT_CERT_FILE = "ssl-client-cert-file"; + String SSL_CLIENT_CERT_PASSWORD = "ssl-client-cert-password"; + + List YES_OPTIONS = Arrays.asList(new String[]{"yes", "on", "true", "1"}); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/TestConnectionPerformer.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/TestConnectionPerformer.java new file mode 100644 index 000000000000..32b4180fb73d --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/TestConnectionPerformer.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.config; + +public interface TestConnectionPerformer { + void execute(final String url); + boolean enabled(); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/ValidationListener.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/ValidationListener.java new file mode 100644 index 000000000000..e0af722df163 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/config/ValidationListener.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.config; + +import javax.swing.*; + +public interface ValidationListener { + void onError(final String text, final JComponent component, final boolean forbidSave); + void onSuccess(); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/AddRepositoryLocationDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/AddRepositoryLocationDialog.java new file mode 100644 index 000000000000..ee840bf862a8 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/AddRepositoryLocationDialog.java @@ -0,0 +1,152 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.MultiLineLabelUI; +import com.intellij.util.ArrayUtil; +import org.jetbrains.idea.svn17.SvnBundle; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class AddRepositoryLocationDialog extends DialogWrapper { + private final List myPreviousLocations; + private JComboBox myCombo; + private String mySelected; + private JTextField myComboField; + + public AddRepositoryLocationDialog(final Project project, final List values) { + super(project, true); + myPreviousLocations = new ArrayList(values); + Collections.sort(myPreviousLocations); + + setTitle(getTitle()); + init(); + myComboField.setText(initText()); + } + + @Override + public String getTitle() { + return SvnBundle.message("repository.browser.add.location.title"); + } + + protected String initText() { + return "http://"; + } + + @Override + public JComponent getPreferredFocusedComponent() { + return myCombo; + } + + protected JComponent createCenterPanel() { + final JLabel selectText = new JLabel(SvnBundle.message("repository.browser.add.location.prompt")); + selectText.setUI(new MultiLineLabelUI()); + + final JPanel mainPanel = new JPanel(new GridBagLayout()); + final GridBagConstraints gb = + new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0); + + mainPanel.add(selectText, gb); + + ++ gb.gridy; + + myCombo = new JComboBox(ArrayUtil.toObjectArray(myPreviousLocations)); + myCombo.setEditable(true); + myCombo.setMinimumSize(new Dimension(250, 20)); + gb.fill = GridBagConstraints.HORIZONTAL; + mainPanel.add(myCombo, gb); + gb.fill = GridBagConstraints.NONE; + + myComboField = (JTextField)myCombo.getEditor().getEditorComponent(); + myComboField.addInputMethodListener(new InputMethodListener() { + public void inputMethodTextChanged(final InputMethodEvent event) { + validateMe(); + } + + public void caretPositionChanged(final InputMethodEvent event) { + validateMe(); + } + }); + myComboField.addKeyListener(new KeyListener() { + public void keyTyped(final KeyEvent e) { + validateMe(); + } + + public void keyPressed(final KeyEvent e) { + validateMe(); + } + + public void keyReleased(final KeyEvent e) { + validateMe(); + } + }); + + myCombo.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + validateMe(); + } + }); + validateMe(); + + final JPanel wrapper = new JPanel(new GridBagLayout()); + wrapper.add(mainPanel, new GridBagConstraints(0,0,1,1,1,1,GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, + new Insets(0,0,0,0), 0,0)); + wrapper.setPreferredSize(new Dimension(400, 70)); + return wrapper; + } + + private void validateMe() { + final String inputString = myComboField.getText(); + setOKActionEnabled(urlValid(inputString)); + } + + private boolean urlValid(final String inputString) { + if (inputString == null) { + return false; + } + try { + final SVNURL svnurl = SVNURL.parseURIDecoded(inputString.trim()); + return svnurl != null; + } catch (SVNException e) { + // + } + return false; + } + + @Override + protected void doOKAction() { + mySelected = myComboField.getText().trim(); + super.doOKAction(); + } + + @Override + protected Action[] createActions() { + return new Action[]{getOKAction(), getCancelAction()}; + } + + public String getSelected() { + return mySelected; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/BasePageEngine.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/BasePageEngine.java new file mode 100644 index 000000000000..c0191e044777 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/BasePageEngine.java @@ -0,0 +1,55 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.vcs.CollectionSplitter; + +import java.util.List; + +public class BasePageEngine implements PageEngine> { + private List> mySplitData; + private int myIdx; + + public BasePageEngine(final List data, final int pageSize) { + final CollectionSplitter splitter = new CollectionSplitter(pageSize); + mySplitData = splitter.split(data); + myIdx = 0; + } + + public List getCurrent() { + return mySplitData.get(myIdx); + } + + public boolean hasNext() { + return myIdx < (mySplitData.size() - 1); + } + + public boolean hasPrevious() { + return myIdx > 0; + } + + public List next() { + if (! hasNext()) return null; + ++ myIdx; + return mySplitData.get(myIdx); + } + + public List previous() { + if (! hasPrevious()) return null; + -- myIdx; + return mySplitData.get(myIdx); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/BranchConfigurationDialog.form b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/BranchConfigurationDialog.form new file mode 100644 index 000000000000..18a7f8f80d19 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/BranchConfigurationDialog.form @@ -0,0 +1,71 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/BranchConfigurationDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/BranchConfigurationDialog.java new file mode 100644 index 000000000000..87c24f5c5000 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/BranchConfigurationDialog.java @@ -0,0 +1,226 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.DocumentAdapter; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.*; +import org.jetbrains.idea.svn17.branchConfig.InfoReliability; +import org.jetbrains.idea.svn17.branchConfig.InfoStorage; +import org.jetbrains.idea.svn17.branchConfig.SvnBranchConfigManager; +import org.jetbrains.idea.svn17.branchConfig.SvnBranchConfigurationNew; +import org.jetbrains.idea.svn17.integrate.SvnBranchItem; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * @author yole + */ +public class BranchConfigurationDialog extends DialogWrapper { + private JPanel myTopPanel; + private TextFieldWithBrowseButton myTrunkLocationTextField; + private JList myLocationList; + private JButton myAddButton; + private JButton myRemoveButton; + private final SvnBranchConfigManager mySvnBranchConfigManager; + private final VirtualFile myRoot; + + public BranchConfigurationDialog(final Project project, final SvnBranchConfigurationNew configuration, final String rootUrl, + final VirtualFile root) { + super(project, true); + myRoot = root; + init(); + setTitle(SvnBundle.message("configure.branches.title")); + + final String trunkUrl = configuration.getTrunkUrl(); + if (trunkUrl == null || trunkUrl.trim().length() == 0) { + configuration.setTrunkUrl(rootUrl); + } + + mySvnBranchConfigManager = SvnBranchConfigurationManager17.getInstance(project).getSvnBranchConfigManager(); + + myTrunkLocationTextField.setText(configuration.getTrunkUrl()); + myTrunkLocationTextField.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + final String selectedUrl = SelectLocationDialog.selectLocation(project, myTrunkLocationTextField.getText()); + if (selectedUrl != null) { + myTrunkLocationTextField.setText(selectedUrl); + } + } + }); + + final TrunkUrlValidator trunkUrlValidator = new TrunkUrlValidator(rootUrl, configuration); + myTrunkLocationTextField.getTextField().getDocument().addDocumentListener(trunkUrlValidator); + trunkUrlValidator.textChanged(null); + + final MyListModel listModel = new MyListModel(configuration); + myLocationList.setModel(listModel); + myAddButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + final String selectedUrl = SelectLocationDialog.selectLocation(project, rootUrl); + if (selectedUrl != null) { + if (!configuration.getBranchUrls().contains(selectedUrl)) { + configuration.addBranches(selectedUrl, new InfoStorage>(new ArrayList(), InfoReliability.empty)); + mySvnBranchConfigManager.reloadBranches(myRoot, selectedUrl, null); + listModel.fireItemAdded(); + myLocationList.setSelectedIndex(listModel.getSize()-1); + } + } + } + }); + myRemoveButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + int selIndex = myLocationList.getSelectedIndex(); + Object[] selection = myLocationList.getSelectedValues(); + for(Object urlObj: selection) { + String url = (String) urlObj; + int index = configuration.getBranchUrls().indexOf(url); + configuration.removeBranch(url); + listModel.fireItemRemoved(index); + } + if (listModel.getSize() > 0) { + if (selIndex >= listModel.getSize()) + selIndex = listModel.getSize()-1; + myLocationList.setSelectedIndex(selIndex); + } + } + }); + } + + private class TrunkUrlValidator extends DocumentAdapter { + private final String myRootUrl; + private final String myRootUrlPrefix; + private final SvnBranchConfigurationNew myConfiguration; + + private TrunkUrlValidator(final String rootUrl, final SvnBranchConfigurationNew configuration) { + myRootUrl = rootUrl; + myRootUrlPrefix = rootUrl + "/"; + myConfiguration = configuration; + } + + protected void textChanged(final DocumentEvent e) { + final String currentValue = myTrunkLocationTextField.getText(); + final boolean valueOk = (currentValue != null) && (currentValue.equals(myRootUrl) || currentValue.startsWith(myRootUrlPrefix)); + final boolean prefixOk = (currentValue != null) && (currentValue.startsWith(myRootUrlPrefix)) && + (currentValue.length() > myRootUrlPrefix.length()); + + myTrunkLocationTextField.getButton().setEnabled(valueOk); + setOKActionEnabled(prefixOk); + if (prefixOk) { + myConfiguration.setTrunkUrl(currentValue.endsWith("/") ? currentValue.substring(0, currentValue.length() - 1) : currentValue); + } + setErrorText(prefixOk ? null : SvnBundle.message("configure.branches.error.wrong.url")); + } + } + + @Nullable + protected JComponent createCenterPanel() { + return myTopPanel; + } + + @Override + @NonNls + protected String getDimensionServiceKey() { + return "Subversion.BranchConfigurationDialog"; + } + + public static void configureBranches(final Project project, final VirtualFile file) { + configureBranches(project, file, false); + } + + public static void configureBranches(final Project project, final VirtualFile file, final boolean isRoot) { + final VirtualFile vcsRoot = (isRoot) ? file : ProjectLevelVcsManager.getInstance(project).getVcsRootFor(file); + if (vcsRoot == null) { + return; + } + + final VirtualFile directory = SvnUtil.correctRoot(project, file); + if (directory == null) { + return; + } + final RootUrlInfo wcRoot = SvnVcs17.getInstance(project).getSvnFileUrlMapping().getWcRootForFilePath(new File(directory.getPath())); + if (wcRoot == null) { + return; + } + final String rootUrl = wcRoot.getRepositoryUrl(); + if (rootUrl == null) { + Messages.showErrorDialog(project, SvnBundle.message("configure.branches.error.no.connection.title"), + SvnBundle.message("configure.branches.title")); + return; + } + + SvnBranchConfigurationNew configuration; + try { + configuration = SvnBranchConfigurationManager17.getInstance(project).get(vcsRoot); + } + catch (VcsException ex) { + Messages.showErrorDialog(project, "Error loading branch configuration: " + ex.getMessage(), + SvnBundle.message("configure.branches.title")); + return; + } + + final SvnBranchConfigurationNew clonedConfiguration = configuration.copy(); + BranchConfigurationDialog dlg = new BranchConfigurationDialog(project, clonedConfiguration, rootUrl, vcsRoot); + dlg.show(); + if (dlg.isOK()) { + SvnBranchConfigurationManager17.getInstance(project).setConfiguration(vcsRoot, clonedConfiguration); + } + } + + private static class MyListModel extends AbstractListModel { + private final SvnBranchConfigurationNew myConfiguration; + private List myBranchUrls; + + public MyListModel(final SvnBranchConfigurationNew configuration) { + myConfiguration = configuration; + myBranchUrls = myConfiguration.getBranchUrls(); + } + + public int getSize() { + return myBranchUrls.size(); + } + + public Object getElementAt(final int index) { + return myBranchUrls.get(index); + } + + public void fireItemAdded() { + final int index = myConfiguration.getBranchUrls().size() - 1; + myBranchUrls = myConfiguration.getBranchUrls(); + super.fireIntervalAdded(this, index, index); + } + + public void fireItemRemoved(final int index) { + myBranchUrls = myConfiguration.getBranchUrls(); + super.fireIntervalRemoved(this, index, index); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/BranchMerger.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/BranchMerger.java new file mode 100644 index 000000000000..571456ad2d17 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/BranchMerger.java @@ -0,0 +1,109 @@ +/* + * 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.dialogs; + +import com.intellij.util.Consumer; +import org.jetbrains.idea.svn17.SvnConfiguration17; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.integrate.IMerger; +import org.jetbrains.idea.svn17.update.UpdateEventHandler; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.io.SVNRepository; +import org.tmatesoft.svn.core.wc.SVNDiffClient; +import org.tmatesoft.svn.core.wc.SVNDiffOptions; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.io.File; + +public class BranchMerger implements IMerger { + private final SvnVcs17 myVcs; + private final String myTargetPath; + private final SVNURL mySourceUrl; + private final SVNURL myTargetUrl; + private final UpdateEventHandler myHandler; + private final boolean myReintegrate; + private final String myBranchName; + private final long mySourceCopyRevision; + private boolean myAtStart; + private long mySourceLatestRevision; + + public BranchMerger(final SvnVcs17 vcs, + final SVNURL sourceUrl, + final SVNURL targetUrl, final String targetPath, + final UpdateEventHandler handler, + final boolean isReintegrate, final String branchName, final long sourceCopyRevision) { + myVcs = vcs; + myTargetPath = targetPath; + mySourceUrl = sourceUrl; + myTargetUrl = targetUrl; + myHandler = handler; + myReintegrate = isReintegrate; + myBranchName = branchName; + mySourceCopyRevision = sourceCopyRevision; + myAtStart = true; + SVNRepository repository = null; + try { + repository = myVcs.createRepository(mySourceUrl); + mySourceLatestRevision = repository.getLatestRevision(); + } + catch (SVNException e) { + mySourceLatestRevision = SVNRevision.HEAD.getNumber(); + } finally { + if (repository != null) { + repository.closeSession(); + } + } + } + + public String getComment() { + return "Merge all from " + myBranchName + " at " + mySourceLatestRevision +(myReintegrate ? " (reintegration)" : ""); + } + + public boolean hasNext() { + return myAtStart; + } + + public void mergeNext() throws SVNException { + myAtStart = false; + final SVNDiffClient dc = myVcs.createDiffClient(); + dc.setEventHandler(myHandler); + final SvnConfiguration17 svnConfig = SvnConfiguration17.getInstance(myVcs.getProject()); + dc.setMergeOptions(new SVNDiffOptions(svnConfig.IGNORE_SPACES_IN_MERGE, svnConfig.IGNORE_SPACES_IN_MERGE, + svnConfig.IGNORE_SPACES_IN_MERGE)); + + if (myReintegrate) { + dc.doMergeReIntegrate(mySourceUrl, SVNRevision.UNDEFINED, new File(myTargetPath), false); + } else { + dc.doMerge(mySourceUrl, SVNRevision.create(mySourceCopyRevision), mySourceUrl, SVNRevision.create(mySourceLatestRevision), + new File(myTargetPath), SVNDepth.INFINITY, true, true, false, false); + } + } + + public void getInfo(Consumer holder, boolean getLatest) { + } + + public File getMergeInfoHolder() { + return new File(myTargetPath); + } + + public void afterProcessing() { + } + + public void getSkipped(Consumer holder) { + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ByKeySelectedSearcher.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ByKeySelectedSearcher.java new file mode 100644 index 000000000000..28851656062d --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ByKeySelectedSearcher.java @@ -0,0 +1,53 @@ +/* + * 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.dialogs; + +import org.jetbrains.annotations.Nullable; + +import javax.swing.tree.TreeNode; +import java.util.List; + +abstract class ByKeySelectedSearcher { + private final String myKey; + protected final List myChildren; + + protected ByKeySelectedSearcher(final String key, final List myChildren) { + myKey = key; + this.myChildren = myChildren; + } + + protected abstract IterationResultHolder doChecks(final TreeNode node); + + @Nullable + public TreeNode getNextSelectedByKey() { + if (myKey != null) { + for (final TreeNode node : myChildren) { + final IterationResultHolder checkResult = doChecks(node); + if (IterationResultHolder.RESULT.equals(checkResult)) { + return node; + } + else if (IterationResultHolder.SKIP.equals(checkResult)) { + continue; + } + + if (myKey.compareTo(node.toString()) <= 0) { + return node; + } + } + } + return ((myChildren == null) || myChildren.isEmpty()) ? null : myChildren.get(myChildren.size() - 1); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ChangeFormatDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ChangeFormatDialog.java new file mode 100644 index 000000000000..ab04b9734da2 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ChangeFormatDialog.java @@ -0,0 +1,89 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.ui.MultiLineLabelUI; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; + +import javax.swing.*; +import java.awt.*; +import java.io.File; + +public class ChangeFormatDialog extends UpgradeFormatDialog { + private final boolean myWcRootIsAbove; + + public ChangeFormatDialog(final Project project, final File path, final boolean canBeParent, final boolean wcRootIsAbove) { + super(project, path, canBeParent, false); + myWcRootIsAbove = wcRootIsAbove; + setTitle(SvnBundle.message("action.change.wcopy.format.task.title")); + init(); + } + + @Override + protected Action[] createActions() { + return new Action[]{getOKAction(),getCancelAction()}; + } + + @Nullable + @Override + protected JPanel getBottomAuxiliaryPanel() { + if (! myWcRootIsAbove) { + return null; + } + final JPanel result = new JPanel(new GridBagLayout()); + + GridBagConstraints gb = new GridBagConstraints(); + + gb.insets = new Insets(2, 2, 2, 2); + gb.weightx = 1; + gb.weighty = 0; + gb.gridwidth = 2; + gb.gridheight = 1; + gb.gridx = 0; + gb.gridy = 0; + gb.anchor = GridBagConstraints.WEST; + gb.fill = GridBagConstraints.HORIZONTAL; + + final JLabel iconLabel = new JLabel(Messages.getWarningIcon()); + result.add(iconLabel, gb); + ++ gb.gridx; + + JLabel warningLabel = new JLabel(SvnBundle.message("label.working.copy.root.outside.text")); + warningLabel.setFont(warningLabel.getFont().deriveFont(Font.BOLD)); + warningLabel.setUI(new MultiLineLabelUI()); + result.add(warningLabel); + + return result; + } + + @Override + protected String getTopMessage(final String label) { + return SvnBundle.message("label.configure.change.label", myPath.getAbsolutePath()); + } + + @Override + protected String getMiddlePartOfResourceKey(final boolean adminPathIsDirectory) { + return "change"; + } + + @Override + protected boolean showHints() { + return false; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/CheckoutDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/CheckoutDialog.java new file mode 100644 index 000000000000..f9072e59195f --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/CheckoutDialog.java @@ -0,0 +1,60 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.CheckoutProvider; +import org.jetbrains.idea.svn17.SvnBundle; + +import javax.swing.*; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; + +public class CheckoutDialog extends RepositoryBrowserDialog { + private final CheckoutProvider.Listener myListener; + + public CheckoutDialog(Project project, final CheckoutProvider.Listener listener) { + super(project, false, null); + myListener = listener; + } + + public void init() { + super.init(); + setTitle(SvnBundle.message("checkout.dialog.title")); + setOKButtonText(SvnBundle.message("checkout.dialog.button")); + getRepositoryBrowser().addChangeListener(new TreeSelectionListener() { + public void valueChanged(TreeSelectionEvent e) { + if (getOKAction() != null) { + getOKAction().setEnabled(getRepositoryBrowser().getSelectedURL() != null); + } + } + }); + getOKAction().setEnabled(getRepositoryBrowser().getSelectedURL() != null); + } + + protected Action[] createActions() { + return new Action[] {getOKAction(), getCancelAction(), getHelpAction()}; + } + protected String getHelpId() { + return "vcs.subversion.browseSVN"; + } + + protected void doOKAction() { + final RepositoryTreeNode selectedNode = getSelectedNode(); + close(OK_EXIT_CODE); + doCheckout(myListener, selectedNode); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/CopiesPanel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/CopiesPanel.java new file mode 100644 index 000000000000..7b91bfc0935b --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/CopiesPanel.java @@ -0,0 +1,459 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.vcs.ObjectsConvertor; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.ui.DottedBorder; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.components.labels.LinkLabel; +import com.intellij.ui.components.labels.LinkListener; +import com.intellij.util.containers.Convertor; +import com.intellij.util.io.EqualityPolicy; +import com.intellij.util.messages.MessageBusConnection; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.idea.svn17.NestedCopyType; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.WorkingCopyFormat; +import org.jetbrains.idea.svn17.actions.SelectBranchPopup; +import org.jetbrains.idea.svn17.branchConfig.SvnBranchConfigurationNew; +import org.jetbrains.idea.svn17.checkout.SvnCheckoutProvider; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import javax.swing.*; +import javax.swing.border.Border; +import java.awt.*; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.io.File; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class CopiesPanel { + private final Project myProject; + private MessageBusConnection myConnection; + private SvnVcs17 myVcs; + private JPanel myPanel; + private JComponent myHolder; + private LinkLabel myRefreshLabel; + // updated only on AWT + private List> myCurrentInfoList; + private int myTextHeight; + + public CopiesPanel(final Project project) { + myProject = project; + myConnection = myProject.getMessageBus().connect(myProject); + myVcs = SvnVcs17.getInstance(myProject); + myCurrentInfoList = null; + + final Runnable focus = new Runnable() { + public void run() { + IdeFocusManager.getInstance(myProject).requestFocus(myRefreshLabel, true); + } + }; + final Runnable refreshView = new Runnable() { + public void run() { + final List infoList = myVcs.getAllWcInfos(); + if (myCurrentInfoList != null) { + final List> newList = + ObjectsConvertor.convert(infoList, new Convertor>() { + public OverrideEqualsWrapper convert(WCInfo o) { + return new OverrideEqualsWrapper(InfoEqualityPolicy.getInstance(), o); + } + }, ObjectsConvertor.NOT_NULL); + + if (Comparing.haveEqualElements(newList, myCurrentInfoList)) { + myRefreshLabel.setEnabled(true); + return; + } + myCurrentInfoList = newList; + } + Collections.sort(infoList, WCComparator.getInstance()); + updateList(infoList); + myRefreshLabel.setEnabled(true); + SwingUtilities.invokeLater(focus); + } + }; + myConnection.subscribe(SvnVcs17.ROOTS_RELOADED, new Runnable() { + public void run() { + ApplicationManager.getApplication().invokeLater(refreshView, ModalityState.NON_MODAL); + } + }); + + final JPanel holderPanel = new JPanel(new BorderLayout()); + FontMetrics fm = holderPanel.getFontMetrics(holderPanel.getFont()); + myTextHeight = (int)(fm.getHeight() * 1.3); + myPanel = new JPanel(new GridBagLayout()); + final JPanel panel = new JPanel(new BorderLayout()); + panel.add(myPanel, BorderLayout.NORTH); + holderPanel.add(panel, BorderLayout.WEST); + myRefreshLabel = new MyLinkLabel(myTextHeight, "Refresh", new LinkListener() { + public void linkSelected(LinkLabel aSource, Object aLinkData) { + if (myRefreshLabel.isEnabled()) { + myVcs.invokeRefreshSvnRoots(true); + myRefreshLabel.setEnabled(false); + } + } + }); + myHolder = ScrollPaneFactory.createScrollPane(holderPanel); + myHolder.setBorder(null); + setFocusableForLinks(myRefreshLabel); + refreshView.run(); + initView(); + } + + public JComponent getPreferredFocusedComponent() { + return myRefreshLabel; + } + + private JTextField createField(final String text) { + final JTextField field = new JTextField(text) { + public Dimension getPreferredSize() { + Dimension preferredSize = super.getPreferredSize(); + return new Dimension(preferredSize.width, myTextHeight); + } + }; + field.setBackground(UIUtil.getPanelBackground()); + field.setEditable(false); + final Border lineBorder = BorderFactory.createLineBorder(UIUtil.getPanelBackground()); + final DottedBorder dotted = new DottedBorder(UIUtil.getActiveTextColor()); + field.setBorder(lineBorder); + //field.setFocusable(false); + field.setHorizontalAlignment(JTextField.RIGHT); + field.setCaretPosition(0); + field.addFocusListener(new FocusAdapter() { + public void focusGained(FocusEvent e) { + field.setBorder(dotted); + } + + public void focusLost(FocusEvent e) { + field.setBorder(lineBorder); + } + }); + return field; + } + + private void updateList(final List infoList) { + myPanel.removeAll(); + final Insets nullIndent = new Insets(1, 3, 1, 0); + final GridBagConstraints gb = + new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(2, 2, 0, 0), 0, 0); + gb.insets.left = 4; + myPanel.add(myRefreshLabel, gb); + gb.insets.left = 1; + + final LocalFileSystem lfs = LocalFileSystem.getInstance(); + final Insets topIndent = new Insets(10, 3, 0, 0); + for (final WCInfo wcInfo : infoList) { + final JPanel copyPanel = new JPanel(new GridBagLayout()); + + final GridBagConstraints gb1 = + new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, nullIndent, 0, 0); + gb1.insets.top = 1; + gb1.gridwidth = 3; + + gb.insets = topIndent; + gb.fill = GridBagConstraints.HORIZONTAL; + ++ gb.gridy; + + final JPanel contForCopy = new JPanel(new BorderLayout()); + contForCopy.add(copyPanel, BorderLayout.WEST); + myPanel.add(contForCopy, gb); + + final JTextField path = createField(wcInfo.getPath()); + copyPanel.add(path, gb1); + path.setFont(path.getFont().deriveFont(Font.BOLD)); + + gb1.insets = nullIndent; + gb1.insets.top = 5; + ++ gb1.gridy; + final JTextField url = createField("URL: " + wcInfo.getRootUrl()); + copyPanel.add(url, gb1); + + ++ gb1.gridy; + gb1.insets.top = 1; + final JTextField format = createField("Format: " + wcInfo.getFormat().getName()); + copyPanel.add(format, gb1); + + if (! WorkingCopyFormat.ONE_DOT_SEVEN.equals(wcInfo.getFormat())) { + gb1.gridx = 2; + final LinkLabel changeFormatLabel = new MyLinkLabel(myTextHeight, "Change", new LinkListener() { + public void linkSelected(LinkLabel aSource, Object aLinkData) { + changeFormat(wcInfo); + } + }); + copyPanel.add(changeFormatLabel, gb1); + setFocusableForLinks(changeFormatLabel); + } + gb1.gridx = 0; + + ++ gb1.gridy; + final JTextField depth = createField("Depth: " + wcInfo.getStickyDepth().getName()); + copyPanel.add(depth, gb1); + + if (! SVNDepth.INFINITY.equals(wcInfo.getStickyDepth())) { + gb1.gridx = 2; + final LinkLabel fixDepthLabel = new MyLinkLabel(myTextHeight, "Make infinity", new LinkListener() { + public void linkSelected(LinkLabel aSource, Object aLinkData) { + final int result = + Messages.showOkCancelDialog(myVcs.getProject(), "You are going to checkout into '" + wcInfo.getPath() + "' with 'infinity' depth.\n" + + "This will update your working copy to HEAD revision as well.", + "Set working copy infinity depth", + Messages.getWarningIcon()); + if (result == 0) { + // update of view will be triggered by roots changed event + SvnCheckoutProvider.checkout(myVcs.getProject(), new File(wcInfo.getPath()), wcInfo.getRootUrl(), SVNRevision.HEAD, + SVNDepth.INFINITY, false, null, wcInfo.getFormat()); + } + } + }); + copyPanel.add(fixDepthLabel, gb1); + setFocusableForLinks(fixDepthLabel); + gb1.gridx = 0; + } + + final NestedCopyType type = wcInfo.getType(); + if (NestedCopyType.external.equals(type) || NestedCopyType.switched.equals(type)) { + ++ gb1.gridy; + final JTextField whetherNested = createField(type.getName() + " copy"); + copyPanel.add(whetherNested, gb1); + whetherNested.setFont(whetherNested.getFont().deriveFont(Font.ITALIC)); + } + if (wcInfo.isIsWcRoot()) { + ++ gb1.gridy; + final JTextField whetherRoot = createField("Working copy root"); + copyPanel.add(whetherRoot, gb1); + whetherRoot.setFont(whetherRoot.getFont().deriveFont(Font.ITALIC)); + } + + gb1.gridwidth = 1; + gb1.insets.top = 5; + ++ gb1.gridy; + + final VirtualFile vf = lfs.refreshAndFindFileByIoFile(new File(wcInfo.getPath())); + final VirtualFile root = (vf == null) ? wcInfo.getVcsRoot() : vf; + final LinkLabel configureBranchesLabel = new MyLinkLabel(myTextHeight, "Configure Branches", new LinkListener() { + public void linkSelected(LinkLabel aSource, Object aLinkData) { + BranchConfigurationDialog.configureBranches(myProject, root, true); + } + }); + if (root == null) { + configureBranchesLabel.setEnabled(false); //+- + } + copyPanel.add(configureBranchesLabel, gb1); + setFocusableForLinks(configureBranchesLabel); + + ++ gb1.gridy; + final LinkLabel mergeLabel = new MyLinkLabel(myTextHeight, "Merge from...", null); + mergeLabel.setListener(new LinkListener() { + public void linkSelected(LinkLabel aSource, Object aLinkData) { + mergeFrom(wcInfo, root, mergeLabel); + } + }, null); + if (root == null) { + mergeLabel.setEnabled(false); //+- + } + final Font font = mergeLabel.getFont(); + mergeLabel.setFont(font.deriveFont(Font.BOLD)); + mergeLabel.setForeground(mergeLabel.getForeground().darker()); + copyPanel.add(mergeLabel, gb1); + setFocusableForLinks(mergeLabel); + } + + myPanel.revalidate(); + myPanel.repaint(); + } + + private void mergeFrom(final WCInfo wcInfo, final VirtualFile root, final LinkLabel mergeLabel) { + SelectBranchPopup.showForBranchRoot(myProject, root, new SelectBranchPopup.BranchSelectedCallback() { + public void branchSelected(Project project, SvnBranchConfigurationNew configuration, String url, long revision) { + new QuickMerge(project, url, wcInfo, SVNPathUtil.tail(url), root).execute(); + } + }, "Select branch", mergeLabel); + } + + private void setFocusableForLinks(final LinkLabel label) { + final Border border = new DottedBorder(new Insets(1,2,1,1), Color.black); + label.setFocusable(true); + label.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + super.focusGained(e); + label.setBorder(border); + } + + @Override + public void focusLost(FocusEvent e) { + super.focusLost(e); + label.setBorder(null); + } + }); + label.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (KeyEvent.VK_ENTER == e.getKeyCode()) { + label.doClick(); + } + } + }); + } + + private void changeFormat(final WCInfo wcInfo) { + ChangeFormatDialog dialog = new ChangeFormatDialog(myProject, new File(wcInfo.getPath()), false, ! wcInfo.isIsWcRoot()); + dialog.setData(true, wcInfo.getFormat().getOption()); + dialog.show(); + if (! dialog.isOK()) { + return; + } + final String newMode = dialog.getUpgradeMode(); + if (! wcInfo.getFormat().getOption().equals(newMode)) { + final WorkingCopyFormat newFormat = WorkingCopyFormat.getInstance(newMode); + final Task.Backgroundable task = new SvnFormatWorker(myProject, newFormat, wcInfo) { + @Override + public void onSuccess() { + super.onSuccess(); + myRefreshLabel.doClick(); + } + }; + ProgressManager.getInstance().run(task); + } + } + + private void initView() { + myRefreshLabel.doClick(); + } + + public JComponent getComponent() { + return myHolder; + } + + public static class OverrideEqualsWrapper { + private final EqualityPolicy myPolicy; + private final T myT; + + public OverrideEqualsWrapper(EqualityPolicy policy, T t) { + myPolicy = policy; + myT = t; + } + + public T getT() { + return myT; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final OverrideEqualsWrapper that = (OverrideEqualsWrapper) o; + + return myPolicy.isEqual(myT, that.getT()); + } + + @Override + public int hashCode() { + return myPolicy.getHashCode(myT); + } + } + + private static class InfoEqualityPolicy implements EqualityPolicy { + private final static InfoEqualityPolicy ourInstance = new InfoEqualityPolicy(); + + public static InfoEqualityPolicy getInstance() { + return ourInstance; + } + + private static class HashCodeBuilder { + private int myCode; + + private HashCodeBuilder() { + myCode = 0; + } + + public void append(final Object o) { + myCode = 31 * myCode + (o != null ? o.hashCode() : 0); + } + + public int getCode() { + return myCode; + } + } + + public int getHashCode(WCInfo value) { + final HashCodeBuilder builder = new HashCodeBuilder(); + builder.append(value.getPath()); + builder.append(value.getUrl()); + builder.append(value.getFormat()); + builder.append(value.getType()); + builder.append(value.getStickyDepth()); + + return builder.getCode(); + } + + public boolean isEqual(WCInfo val1, WCInfo val2) { + if (val1 == val2) return true; + if (val1 == null || val2 == null || val1.getClass() != val2.getClass()) return false; + + if (! Comparing.equal(val1.getFormat(), val2.getFormat())) return false; + if (! Comparing.equal(val1.getPath(), val2.getPath())) return false; + if (! Comparing.equal(val1.getStickyDepth(), val2.getStickyDepth())) return false; + if (! Comparing.equal(val1.getType(), val2.getType())) return false; + if (! Comparing.equal(val1.getUrl(), val2.getUrl())) return false; + + return true; + } + } + + private static class WCComparator implements Comparator { + private final static WCComparator ourComparator = new WCComparator(); + + public static WCComparator getInstance() { + return ourComparator; + } + + public int compare(WCInfo o1, WCInfo o2) { + return o1.getPath().compareTo(o2.getPath()); + } + } + + private static class MyLinkLabel extends LinkLabel { + private final int myHeight; + + public MyLinkLabel(final int height, final String text, final LinkListener linkListener) { + super(text, null, linkListener); + myHeight = height; + } + + @Override + public Dimension getPreferredSize() { + final Dimension preferredSize = super.getPreferredSize(); + return new Dimension(preferredSize.width, myHeight); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/CopyDialog.form b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/CopyDialog.form new file mode 100644 index 000000000000..8af700083a8f --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/CopyDialog.form @@ -0,0 +1,209 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/CopyDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/CopyDialog.java new file mode 100644 index 000000000000..8926062f5843 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/CopyDialog.java @@ -0,0 +1,361 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.help.HelpManager; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.ComboboxWithBrowseButton; +import com.intellij.ui.DocumentAdapter; +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.idea.svn17.SvnBranchConfigurationManager17; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.branchConfig.SvnBranchConfigurationNew; +import org.jetbrains.idea.svn17.update.SvnRevisionPanel; +import org.tmatesoft.svn.core.SVNException; +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.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; + +/** + * Created by IntelliJ IDEA. + * User: alex + * Date: 05.07.2005 + * Time: 23:35:12 + */ +public class CopyDialog extends DialogWrapper { + private static final Logger LOG = Logger.getInstance("org.jetbrains.idea.svn.dialogs.CopyDialog"); + + private final File mySrcFile; + private String mySrcURL; + private final Project myProject; + private String myURL; + + private TextFieldWithBrowseButton myToURLText; + + private JTextArea myCommentText; + private JPanel myTopPanel; + private JRadioButton myWorkingCopyRadioButton; + private JRadioButton myRepositoryRadioButton; + private TextFieldWithBrowseButton myWorkingCopyField; + private TextFieldWithBrowseButton myRepositoryField; + private SvnRevisionPanel myRevisionPanel; + private ComboboxWithBrowseButton myBranchTagBaseComboBox; + private JTextField myBranchTextField; + private JRadioButton myBranchOrTagRadioButton; + private JRadioButton myAnyLocationRadioButton; + private JButton myProjectButton; + private JLabel myErrorLabel; + + @NonNls private static final String HELP_ID = "vcs.subversion.branch"; + private SvnBranchConfigurationNew myBranchConfiguration; + private final VirtualFile mySrcVirtualFile; + + public CopyDialog(final Project project, boolean canBeParent, File file) { + super(project, canBeParent); + mySrcFile = file; + myProject = project; + setResizable(true); + setTitle(SvnBundle.message("dialog.title.branch")); + getHelpAction().setEnabled(true); + myProjectButton.setIcon(IconLoader.getIcon("/nodes/ideaProject.png")); + myBranchTagBaseComboBox.setPreferredSize(new Dimension(myBranchTagBaseComboBox.getPreferredSize().width, + myWorkingCopyField.getPreferredSize().height)); + + myWorkingCopyField.addBrowseFolderListener("Select Working Copy Location", "Select Location to Copy From:", + project, FileChooserDescriptorFactory.createSingleFolderDescriptor()); + myWorkingCopyField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { + protected void textChanged(final DocumentEvent e) { + updateControls(); + } + }); + myRepositoryField.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + String url = SelectLocationDialog.selectLocation(project, myRepositoryField.getText()); + if (url != null) { + myRepositoryField.setText(url); + } + } + }); + myRepositoryField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { + protected void textChanged(final DocumentEvent e) { + updateToURL(); + } + }); + myToURLText.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + String url = myToURLText.getText(); + String dstName = SVNPathUtil.tail(mySrcURL); + dstName = SVNEncodingUtil.uriDecode(dstName); + url = SelectLocationDialog.selectCopyDestination(myProject, SVNPathUtil.removeTail(url), + SvnBundle.message("label.copy.select.location.dialog.copy.as"), dstName, false); + if (url != null) { + myToURLText.setText(url); + } + } + }); + + VirtualFile srcVirtualFile = LocalFileSystem.getInstance().findFileByIoFile(file); + srcVirtualFile = ProjectLevelVcsManager.getInstance(project).getVcsRootFor(srcVirtualFile); + this.mySrcVirtualFile = srcVirtualFile; + + myRevisionPanel.setRoot(mySrcVirtualFile); + myRevisionPanel.setProject(myProject); + myRevisionPanel.setUrlProvider(new SvnRevisionPanel.UrlProvider() { + public String getUrl() { + return mySrcURL; + } + }); + updateBranchTagBases(); + + myRevisionPanel.addChangeListener(new ChangeListener() { + public void stateChanged(final ChangeEvent e) { + getOKAction().setEnabled(isOKActionEnabled()); + } + }); + + init(); + ActionListener listener = new ActionListener() { + public void actionPerformed(ActionEvent e) { + updateControls(); + } + }; + myWorkingCopyRadioButton.addActionListener(listener); + myRepositoryRadioButton.addActionListener(listener); + myBranchOrTagRadioButton.addActionListener(listener); + myAnyLocationRadioButton.addActionListener(listener); + updateControls(); + myBranchTextField.getDocument().addDocumentListener(new DocumentAdapter() { + protected void textChanged(final DocumentEvent e) { + updateToURL(); + } + }); + updateToURL(); + myProjectButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + myRepositoryField.setText(myBranchConfiguration.getBaseUrl(mySrcURL)); + } + }); + myBranchTagBaseComboBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + BranchConfigurationDialog.configureBranches(project, mySrcVirtualFile); + updateBranchTagBases(); + updateControls(); + } + }); + myBranchTagBaseComboBox.getComboBox().addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + updateToURL(); + updateControls(); + } + }); + } + + private void updateBranchTagBases() { + try { + myBranchConfiguration = SvnBranchConfigurationManager17.getInstance(myProject).get(mySrcVirtualFile); + final String[] strings = ArrayUtil.toStringArray(myBranchConfiguration.getBranchUrls()); + myBranchTagBaseComboBox.getComboBox().setModel(new DefaultComboBoxModel(strings)); + } + catch (VcsException e) { + LOG.info(e); + myBranchTagBaseComboBox.setEnabled(false); + } + } + + private void updateToURL() { + if (myBranchConfiguration == null) { + return; + } + String relativeUrl; + if (myWorkingCopyRadioButton.isSelected()) { + relativeUrl = myBranchConfiguration.getRelativeUrl(mySrcURL); + } + else { + relativeUrl = myBranchConfiguration.getRelativeUrl(myRepositoryField.getText()); + } + + final Object selectedBranch = myBranchTagBaseComboBox.getComboBox().getSelectedItem(); + if (relativeUrl != null && selectedBranch != null) { + myToURLText.setText(selectedBranch.toString() + "/" + myBranchTextField.getText() + relativeUrl); + } + } + + private void updateControls() { + myWorkingCopyField.setEnabled(myWorkingCopyRadioButton.isSelected()); + myRepositoryField.setEnabled(myRepositoryRadioButton.isSelected()); + myRevisionPanel.setEnabled(myRepositoryRadioButton.isSelected()); + myProjectButton.setEnabled(myRepositoryRadioButton.isSelected()); + + myBranchTagBaseComboBox.setEnabled(myBranchOrTagRadioButton.isSelected()); + myBranchTextField.setEnabled(myBranchOrTagRadioButton.isSelected()); + myToURLText.setEnabled(myAnyLocationRadioButton.isSelected()); + + getOKAction().setEnabled(isOKActionEnabled()); + } + + protected Action[] createActions() { + return new Action[]{getOKAction(), getCancelAction(), getHelpAction()}; + } + + protected void doHelpAction() { + HelpManager.getInstance().invokeHelp(HELP_ID); + } + + protected void init() { + super.init(); + SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + String revStr = ""; + try { + SVNWCClient client = vcs.createWCClient(); + SVNInfo info = client.doInfo(mySrcFile, SVNRevision.UNDEFINED); + if (info != null) { + mySrcURL = info.getURL() == null ? null : info.getURL().toString(); + revStr = String.valueOf(info.getRevision()); + myURL = mySrcURL; + } + } + catch (SVNException e) { + // + } + if (myURL == null) { + return; + } + myWorkingCopyField.setText(mySrcFile.toString()); + myRepositoryField.setText(mySrcURL); + myToURLText.setText(myURL); + myRevisionPanel.setRevisionText(revStr); + updateControls(); + + myWorkingCopyRadioButton.setSelected(true); + } + + public String getComment() { + return myCommentText.getText(); + } + + public SVNRevision getRevision() { + if (myWorkingCopyRadioButton.isSelected()) { + return SVNRevision.WORKING; + } + else { + try { + return myRevisionPanel.getRevision(); + } + catch (ConfigurationException e) { + return SVNRevision.UNDEFINED; + } + } + } + + public String getToURL() { + return myToURLText.getText(); + } + + protected JComponent createCenterPanel() { + return myTopPanel; + } + + public JComponent getPreferredFocusedComponent() { + return myToURLText; + } + + public boolean shouldCloseOnCross() { + return true; + } + + protected String getDimensionServiceKey() { + return "svn.copyDialog"; + } + + public boolean isOKActionEnabled() { + myErrorLabel.setText(" "); + if (myURL == null) { + return false; + } + if (myBranchOrTagRadioButton.isSelected() && myBranchTagBaseComboBox.getComboBox().getSelectedItem() == null) { + myErrorLabel.setText(SvnBundle.message("create.branch.no.base.location.error")); + return false; + } + String url = myToURLText.getText(); + if (url != null && url.trim().length() > 0) { + if (myRepositoryRadioButton.isSelected()) { + SVNRevision revision; + try { + revision = myRevisionPanel.getRevision(); + } + catch (ConfigurationException e) { + revision = SVNRevision.UNDEFINED; + } + if (!revision.isValid() || revision.isLocal()) { + myErrorLabel.setText(SvnBundle.message("create.branch.invalid.revision.error", myRevisionPanel.getRevisionText())); + return false; + } + return true; + } + else if (myWorkingCopyRadioButton.isSelected()) { + try { + SVNWCClient client = SvnVcs17.getInstance(myProject).createWCClient(); + SVNInfo info = client.doInfo(mySrcFile, SVNRevision.UNDEFINED); + mySrcURL = info != null && info.getURL() != null ? info.getURL().toString() : null; + } + catch (SVNException e) { + mySrcURL = null; + } + if (mySrcURL == null) { + myErrorLabel.setText(SvnBundle.message("create.branch.no.working.copy.error", myWorkingCopyField.getText())); + return false; + } + return true; + } + } + return false; + } + + public boolean isCopyFromWorkingCopy() { + return myWorkingCopyRadioButton.isSelected(); + } + + public String getCopyFromPath() { + return myWorkingCopyField.getText(); + } + + public String getCopyFromUrl() { + return myRepositoryField.getText(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/FactsCalculator.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/FactsCalculator.java new file mode 100644 index 000000000000..5e73ed692690 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/FactsCalculator.java @@ -0,0 +1,69 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.CalledInAwt; +import com.intellij.util.Consumer; +import com.intellij.util.ValueHolder; +import com.intellij.util.containers.Convertor; +import com.intellij.util.continuation.TaskDescriptor; + +// cache. persistent. by request +public class FactsCalculator { + private final Project myProject; + private final String myTaskTitle; + private final ValueHolder myCache; + private final Convertor myLive; + + public FactsCalculator(Project project, String taskTitle, ValueHolder cache, Convertor live) { + myProject = project; + myTaskTitle = taskTitle; + myCache = cache; + myLive = live; + } + + @CalledInAwt + public void get(final In in, final Consumer resultConsumer) { + createRunOrContinuation(in, resultConsumer).execute(); + } + + private RunOrContinuation createRunOrContinuation(final In in, final Consumer resultConsumer) { + return new RunOrContinuation(myProject, myTaskTitle) { + @Override + protected Out calculate() { + return myCache.getValue(in); + } + @Override + protected Out calculateLong() { + final Out result = myLive.convert(in); + if (result != null) { + myCache.setValue(result, in); + } + return result; + } + @Override + protected void processResult(Out out) { + resultConsumer.consume(out); + } + }; + } + + @CalledInAwt + public TaskDescriptor getTask(final In in, final Consumer resultConsumer) { + return createRunOrContinuation(in, resultConsumer).getTask(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/FileByKeySelectedSearcher.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/FileByKeySelectedSearcher.java new file mode 100644 index 000000000000..50664dc56b74 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/FileByKeySelectedSearcher.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.dialogs; + +import javax.swing.tree.TreeNode; +import java.util.List; + +class FileByKeySelectedSearcher extends ByKeySelectedSearcher { + FileByKeySelectedSearcher(final String key, final List myChildren) { + super(key, myChildren); + } + + protected IterationResultHolder doChecks(final TreeNode node) { + if (! (node instanceof RepositoryTreeNode)) { + return IterationResultHolder.RESULT; + } + if (! node.isLeaf()) { + // folder. folders come first + return IterationResultHolder.SKIP; + } + return IterationResultHolder.DO_NOTHING; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/FolderByKeySelectedSearcher.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/FolderByKeySelectedSearcher.java new file mode 100644 index 000000000000..934b60b18590 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/FolderByKeySelectedSearcher.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.dialogs; + +import javax.swing.tree.TreeNode; +import java.util.List; + +class FolderByKeySelectedSearcher extends ByKeySelectedSearcher { + FolderByKeySelectedSearcher(final String key, final List myChildren) { + super(key, myChildren); + } + + protected IterationResultHolder doChecks(final TreeNode node) { + if ((! (node instanceof RepositoryTreeNode)) || (node.isLeaf())) { + return IterationResultHolder.RESULT; + } + return IterationResultHolder.DO_NOTHING; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ImportDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ImportDialog.java new file mode 100644 index 000000000000..a71e944c449f --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ImportDialog.java @@ -0,0 +1,72 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.help.HelpManager; +import com.intellij.openapi.project.Project; +import org.jetbrains.idea.svn17.SvnBundle; + +import javax.swing.*; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; + +public class ImportDialog extends RepositoryBrowserDialog { + public ImportDialog(Project project) { + super(project); + } + + public void init() { + super.init(); + setTitle(SvnBundle.message("import.dialog.title")); + setOKButtonText(SvnBundle.message("import.dialog.button")); + getRepositoryBrowser().addChangeListener(new TreeSelectionListener() { + public void valueChanged(TreeSelectionEvent e) { + if (getOKAction() != null) { + updateOKAction(); + } + } + }); + updateOKAction(); + } + + protected Action[] createActions() { + return new Action[] {getOKAction(), getCancelAction(), getHelpAction()}; + } + @Override + protected String getHelpId() { + return "vcs.subversion.import"; + } + + private void updateOKAction() { + getOKAction().setEnabled(getRepositoryBrowser().getSelectedURL() != null); + } + + protected void doOKAction() { + if(doImport()) { + super.doOKAction(); + } + } + + @Override + protected void doHelpAction() { + HelpManager.getInstance().invokeHelp("vcs.subversion.import"); + } + + @Override + protected boolean showImportAction() { + return false; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/IntersectingLocalChangesPanel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/IntersectingLocalChangesPanel.java new file mode 100644 index 000000000000..4df97d0f3c7d --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/IntersectingLocalChangesPanel.java @@ -0,0 +1,145 @@ +/* + * 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.dialogs; + +import com.intellij.ide.actions.EditSourceAction; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.MultiLineLabelUI; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.changes.ui.ChangesBrowserNode; +import com.intellij.openapi.vcs.changes.ui.ChangesBrowserNodeRenderer; +import com.intellij.openapi.vcs.changes.ui.TreeModelBuilder; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowId; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.pom.Navigatable; +import com.intellij.ui.content.Content; +import com.intellij.ui.content.ContentFactory; +import com.intellij.ui.content.ContentManager; +import com.intellij.util.ContentsUtil; +import com.intellij.util.EditSourceOnDoubleClickHandler; +import com.intellij.util.EditSourceOnEnterKeyHandler; +import com.intellij.util.ui.UIUtil; + +import javax.swing.*; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +public class IntersectingLocalChangesPanel { + private final JPanel myPanel; + private final String myText; + private final List myFilesToShow; + private final Project myProject; + private JTree myJTree; + + public IntersectingLocalChangesPanel(final Project project, final List filesToShow, String text) { + myProject = project; + myText = text; + myPanel = new MyPanel(new BorderLayout()); + myFilesToShow = filesToShow; + initUI(); + } + + private class MyPanel extends JPanel implements TypeSafeDataProvider { + private MyPanel(LayoutManager layout) { + super(layout); + } + + public void calcData(DataKey key, DataSink sink) { + if (PlatformDataKeys.NAVIGATABLE_ARRAY.equals(key)) { + final TreePath[] treePaths = myJTree.getSelectionModel().getSelectionPaths(); + final List navigatables = new ArrayList(treePaths.length); + for (TreePath treePath : treePaths) { + final List filePaths = ((ChangesBrowserNode)treePath.getLastPathComponent()).getAllFilePathsUnder(); + for (FilePath filePath : filePaths) { + if (filePath.getVirtualFile() != null) { + navigatables.add(new OpenFileDescriptor(myProject, filePath.getVirtualFile())); + } + } + } + sink.put(key, navigatables.toArray(new Navigatable[navigatables.size()])); + } + } + } + + public JPanel getPanel() { + return myPanel; + } + + private void initUI() { + final DefaultMutableTreeNode root = new DefaultMutableTreeNode(); + + myJTree = new JTree(root); + myJTree.setRootVisible(false); + myJTree.setShowsRootHandles(false); + myJTree.setCellRenderer(new ChangesBrowserNodeRenderer(myProject, true, false)); + + TreeModelBuilder builder = new TreeModelBuilder(myProject, true); + final DefaultTreeModel treeModel = builder.buildModelFromFilePaths(myFilesToShow); + myJTree.setModel(treeModel); + + myJTree.expandPath(new TreePath(root.getPath())); + + final JLabel label = new JLabel(myText) { + @Override + public Dimension getPreferredSize() { + final Dimension superValue = super.getPreferredSize(); + return new Dimension((int) superValue.getWidth(), (int) (superValue.getHeight() * 1.7)); + } + }; + label.setUI(new MultiLineLabelUI()); + label.setBackground(UIUtil.getTextFieldBackground()); + label.setVerticalTextPosition(JLabel.TOP); + myPanel.setBackground(UIUtil.getTextFieldBackground()); + myPanel.add(label, BorderLayout.NORTH); + myPanel.add(myJTree, BorderLayout.CENTER); + + EditSourceOnDoubleClickHandler.install(myJTree); + EditSourceOnEnterKeyHandler.install(myJTree); + + final EditSourceAction editSourceAction = new EditSourceAction(); + editSourceAction.registerCustomShortcutSet(CommonShortcuts.getEditSource(), myPanel); + } + + private Component getPrefferedFocusComponent() { + return myJTree; + } + + public static void showInVersionControlToolWindow(final Project project, final String title, final List filesToShow, + final String prompt) { + final IntersectingLocalChangesPanel component = new IntersectingLocalChangesPanel(project, filesToShow, prompt); + + ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.VCS); + final ContentManager contentManager = toolWindow.getContentManager(); + + Content content = ContentFactory + .SERVICE.getInstance().createContent(component.getPanel(), title, true); + ContentsUtil.addContent(contentManager, content, true); + toolWindow.activate(new Runnable() { + public void run() { + IdeFocusManager.getInstance(project).requestFocus(component.getPrefferedFocusComponent(), true); + } + }); + + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/IterationResultHolder.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/IterationResultHolder.java new file mode 100644 index 000000000000..8db5ff795634 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/IterationResultHolder.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.dialogs; + +enum IterationResultHolder { + DO_NOTHING, + SKIP, + RESULT +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/LockDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/LockDialog.java new file mode 100644 index 000000000000..90b42a6c314e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/LockDialog.java @@ -0,0 +1,132 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.help.HelpManager; +import com.intellij.openapi.project.Project; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.util.ui.OptionsDialog; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; + +import javax.swing.*; +import java.awt.*; + +/** + * @author alex + */ +public class LockDialog extends OptionsDialog { + private JTextArea myLockTextArea; + private JCheckBox myForceCheckBox; + + @NonNls private static final String HELP_ID = "reference.VCS.subversion.lockFile"; + + public LockDialog(Project project, boolean canBeParent, boolean multiple) { + super(project, canBeParent); + setTitle(multiple ? SvnBundle.message("dialog.title.lock.files") : SvnBundle.message("dialog.title.lock.file")); + setResizable(true); + + getHelpAction().setEnabled(true); + init(); + + } + + protected Action[] createActions() { + return new Action[]{getOKAction(), getCancelAction(), getHelpAction()}; + } + + protected void doHelpAction() { + HelpManager.getInstance().invokeHelp(HELP_ID); + } + + public boolean shouldCloseOnCross() { + return true; + } + + public String getComment() { + return myLockTextArea.getText(); + } + + public boolean isForce() { + return myForceCheckBox.isSelected(); + } + + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(new GridBagLayout()); + + GridBagConstraints gc = new GridBagConstraints(); + gc.insets = new Insets(2, 2, 2, 2); + gc.gridwidth = 1; + gc.gridheight = 1; + gc.gridx = 0; + gc.gridy = 0; + gc.anchor = GridBagConstraints.WEST; + gc.fill = GridBagConstraints.NONE; + gc.weightx = 0; + gc.weighty = 0; + + JLabel commentLabel = new JLabel(SvnBundle.message("label.lock.comment")); + panel.add(commentLabel, gc); + + gc.gridy += 1; + gc.weightx = 1; + gc.weighty = 1; + gc.fill = GridBagConstraints.BOTH; + + myLockTextArea = new JTextArea(7, 25); + JScrollPane scrollPane = + ScrollPaneFactory.createScrollPane(myLockTextArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + scrollPane.setMinimumSize(scrollPane.getPreferredSize()); + panel.add(scrollPane, gc); + commentLabel.setLabelFor(myLockTextArea); + + gc.gridy += 1; + gc.weightx = 0; + gc.weighty = 0; + gc.fill = GridBagConstraints.NONE; + + myForceCheckBox = new JCheckBox(SvnBundle.message("label.locl.steal.existing")); + panel.add(myForceCheckBox, gc); + + gc.gridy += 1; + gc.weightx = 1; + gc.fill = GridBagConstraints.HORIZONTAL; + panel.add(new JSeparator(), gc); + + return panel; + } + + protected String getDimensionServiceKey() { + return "svn.lockDialog"; + } + + public JComponent getPreferredFocusedComponent() { + return myLockTextArea; + } + + protected boolean isToBeShown() { + return true; + } + + protected void setToBeShown(final boolean value, final boolean onOk) { + SvnVcs17.getInstance(myProject).getCheckoutOptions().setValue(value); + } + + protected boolean shouldSaveOptionsOnCancel() { + return false; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/MergeFromAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/MergeFromAction.java new file mode 100644 index 000000000000..61f1ed3121db --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/MergeFromAction.java @@ -0,0 +1,73 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.ui.content.Content; +import com.intellij.ui.content.ContentManager; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; + +public class MergeFromAction extends DumbAwareAction { + public MergeFromAction() { + super("Merge from...", "One-click merge for feature branches", null); + } + + @Override + public void actionPerformed(AnActionEvent e) { + if (! isEnabled(e)) return; + final DataContext dc = e.getDataContext(); + final Project project = PlatformDataKeys.PROJECT.getData(dc); + if (project == null) return; + final ToolWindowManager manager = ToolWindowManager.getInstance(project); + if (manager != null) { + final ToolWindow window = manager.getToolWindow(ChangesViewContentManager.TOOLWINDOW_ID); + if (window != null) { + window.show(null); + final ContentManager cm = window.getContentManager(); + final Content content = cm.findContent(SvnBundle.message("dialog.show.svn.map.title")); + if (content != null) { + cm.setSelectedContent(content, true); + } + } + } + } + + @Override + public void update(AnActionEvent e) { + final DataContext dc = e.getDataContext(); + final Project project = PlatformDataKeys.PROJECT.getData(dc); + if (project == null || project.isDefault()) return; + e.getPresentation().setVisible(isEnabled(e)); + } + + private boolean isEnabled(AnActionEvent e) { + final DataContext dc = e.getDataContext(); + final Project project = PlatformDataKeys.PROJECT.getData(dc); + if (project == null || project.isDefault()) return false; + final VirtualFile[] files = ProjectLevelVcsManager.getInstance(project).getRootsUnderVcs(SvnVcs17.getInstance(project)); + return files != null && files.length > 0; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/PageEngine.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/PageEngine.java new file mode 100644 index 000000000000..d1f55d38290b --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/PageEngine.java @@ -0,0 +1,24 @@ +/* + * 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.dialogs; + +public interface PageEngine { + boolean hasNext(); + boolean hasPrevious(); + T getCurrent(); + T next(); + T previous(); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/PagedListWithActions.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/PagedListWithActions.java new file mode 100644 index 000000000000..82f02549a259 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/PagedListWithActions.java @@ -0,0 +1,109 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.util.IconLoader; +import com.intellij.ui.ScrollPaneFactory; + +import javax.swing.*; +import java.awt.*; +import java.util.List; + +public class PagedListWithActions { + private final JPanel myPanel; + private final PageEngine> myEngine; + private final InnerComponentManager myComponentManager; + private final AnAction[] myOtherActions; + + public PagedListWithActions(PageEngine> engine, + final InnerComponentManager componentManager, + final AnAction... otherActions) { + myEngine = engine; + myComponentManager = componentManager; + myOtherActions = otherActions; + myPanel = new JPanel(new BorderLayout()); + createView(); + } + + private void createView() { + myComponentManager.setData(myEngine.getCurrent()); + //myList.setListData(ArrayUtil.toObjectArray(myEngine.getCurrent())); + myPanel.add(ActionManager.getInstance().createActionToolbar("merge all", createListActions(), true).getComponent(), BorderLayout.NORTH); + final JScrollPane scroll = ScrollPaneFactory + .createScrollPane(myComponentManager.getComponent(), JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, + JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + myPanel.add(scroll, BorderLayout.CENTER); + } + + public JComponent getComponent() { + return myPanel; + } + + private ActionGroup createListActions() { + final DefaultActionGroup group = new DefaultActionGroup(); + group.add(new MyPrevious()); + group.add(new MyNext()); + group.addAll(myOtherActions); + return group; + } + + private class MyNext extends AnAction { + private MyNext() { + super("Next Page", "Next Page", IconLoader.getIcon("/actions/nextfile.png")); + } + + @Override + public void actionPerformed(AnActionEvent e) { + final List data = myEngine.next(); + myComponentManager.setData(data); + myComponentManager.refresh(); + } + + @Override + public void update(AnActionEvent e) { + super.update(e); + e.getPresentation().setEnabled(myEngine.hasNext()); + e.getPresentation().setVisible(myEngine.hasNext()); + } + } + + private class MyPrevious extends AnAction { + private MyPrevious() { + super("Previous Page", "Previous Page", IconLoader.getIcon("/actions/prevfile.png")); + } + + @Override + public void actionPerformed(AnActionEvent e) { + final List data = myEngine.previous(); + myComponentManager.setData(data); + myComponentManager.refresh(); + } + + @Override + public void update(AnActionEvent e) { + super.update(e); + e.getPresentation().setEnabled(myEngine.hasPrevious()); + e.getPresentation().setVisible(myEngine.hasPrevious()); + } + } + + public interface InnerComponentManager { + Component getComponent(); + void setData(List list); + void refresh(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/PropertiesComponent.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/PropertiesComponent.java new file mode 100644 index 000000000000..83acfaffbd42 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/PropertiesComponent.java @@ -0,0 +1,418 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.keymap.KeymapManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.ui.PopupHandler; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.table.JBTable; +import com.intellij.util.containers.HashMap; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNProperty; +import org.tmatesoft.svn.core.SVNPropertyValue; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.ISVNPropertyHandler; +import org.tmatesoft.svn.core.wc.SVNPropertyData; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.DefaultTableModel; +import java.awt.*; +import java.io.File; +import java.util.Map; +import java.util.TreeMap; + +/** + * Created by IntelliJ IDEA. + * User: alex + * Date: Jun 20, 2006 + * Time: 4:39:46 PM + */ +public class PropertiesComponent extends JPanel { + public static final String ID = "SVN Properties"; + private JTable myTable; + private JTextArea myTextArea; + private boolean myIsFollowSelection; + private File myFile; + private SvnVcs17 myVcs; + private JSplitPane mySplitPane; + private static final String CONTEXT_ID = "context"; + private final CloseAction myCloseAction = new CloseAction(); + private final RefreshAction myRefreshAction = new RefreshAction(); + private ActionGroup myPopupActionGroup; + + public PropertiesComponent() { + // register toolwindow and add listener to the selection. + myIsFollowSelection = true; + init(); + } + + public void init() { + setLayout(new BorderLayout()); + myTable = new JBTable(); + myTextArea = new JTextArea(0, 0); + myTextArea.setEditable(false); + JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTable); + mySplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, scrollPane, ScrollPaneFactory.createScrollPane(myTextArea)); + add(mySplitPane, BorderLayout.CENTER); + add(createToolbar(), BorderLayout.WEST); + final DefaultTableModel model = new DefaultTableModel(createTableModel(new HashMap()), new Object[]{"Name", "Value"}) { + public boolean isCellEditable(final int row, final int column) { + return false; + } + }; + myTable.setModel(model); + myTable.setShowVerticalLines(true); + myTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + myTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + public void valueChanged(ListSelectionEvent e) { + int index = myTable.getSelectedRow(); + if (index >= 0) { + Object value = myTable.getValueAt(index, 1); + if (value instanceof String) { + myTextArea.setText(((String) value)); + } else { + myTextArea.setText(""); + } + } else { + myTextArea.setText(""); + } + } + }); + myPopupActionGroup = createPopup(); + PopupHandler.installPopupHandler(myTable, myPopupActionGroup, ActionPlaces.UNKNOWN, ActionManager.getInstance()); + PopupHandler.installPopupHandler(scrollPane, myPopupActionGroup, ActionPlaces.UNKNOWN, ActionManager.getInstance()); + final Shortcut[] shortcuts = KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_CLOSE_ACTIVE_TAB); + myCloseAction.registerCustomShortcutSet(new CustomShortcutSet(shortcuts), this); + myRefreshAction.registerCustomShortcutSet(CommonShortcuts.getRerun(), this); + } + + public void setFile(SvnVcs17 vcs, File file) { + final Map props = new TreeMap(); + boolean firstTime = myFile == null; + if (file != null) { + myFile = file; + myVcs = vcs; + try { + vcs.createWCClient().doGetProperty(file, null, SVNRevision.UNDEFINED, SVNRevision.WORKING, false, new ISVNPropertyHandler() { + public void handleProperty(File path, SVNPropertyData property) throws SVNException { + final SVNPropertyValue value = property.getValue(); + if (value != null) { + props.put(property.getName(), value.getString()); + } + } + public void handleProperty(SVNURL url, SVNPropertyData property) throws SVNException { + } + public void handleProperty(long revision, SVNPropertyData property) throws SVNException { + } + }); + } catch (SVNException e) { + props.clear(); + } + } + DefaultTableModel model = (DefaultTableModel) myTable.getModel(); + model.setDataVector(createTableModel(props), new Object[] {"Name", "Value"}); + + myTable.getColumnModel().setColumnSelectionAllowed(false); + myTable.getColumnModel().getColumn(1).setCellRenderer(new DefaultTableCellRenderer() { + protected void setValue(Object value) { + if (value != null) { + if (value.toString().indexOf('\r') >= 0) { + value = value.toString().substring(0, value.toString().indexOf('\r')) + " [...]"; + } + if (value.toString().indexOf('\n') >= 0) { + value = value.toString().substring(0, value.toString().indexOf('\n')) + " [...]"; + } + } + super.setValue(value); + } + }); + if (firstTime) { + mySplitPane.setDividerLocation(.5); + } + if (myTable.getRowCount() > 0) { + myTable.getSelectionModel().setSelectionInterval(0, 0); + } + } + + private static Object[][] createTableModel(Map model) { + Object[][] result = new Object[model.size()][2]; + int index = 0; + for (final String name : model.keySet()) { + String value = model.get(name); + if (value == null) { + value = ""; + } + result[index][0] = name; + result[index][1] = value; + index++; + } + return result; + } + + private JComponent createToolbar() { + DefaultActionGroup group = new DefaultActionGroup(); + group.add(new AddPropertyAction()); + group.add(new EditPropertyAction()); + group.add(new DeletePropertyAction()); + group.addSeparator(); + group.add(new SetKeywordsAction()); + group.addSeparator(); + group.add(new FollowSelectionAction()); + group.add(myRefreshAction); + group.add(myCloseAction); + return ActionManager.getInstance().createActionToolbar("", group, false).getComponent(); + } + + private DefaultActionGroup createPopup() { + DefaultActionGroup group = new DefaultActionGroup(); + group.add(new AddPropertyAction()); + group.add(new EditPropertyAction()); + group.add(new DeletePropertyAction()); + group.addSeparator(); + group.add(new SetKeywordsAction()); + group.addSeparator(); + group.add(myRefreshAction); + return group; + } + + private String getSelectedPropertyName() { + int row = myTable.getSelectedRow(); + if (row < 0) { + return null; + } + return (String) myTable.getValueAt(row, 0); + } + + private void updateFileStatus(boolean recursive) { + if (myFile != null && myVcs != null) { + String url = "file://" + myFile.getPath().replace(File.separatorChar, '/'); + VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(url); + if (file != null) { + if (recursive && file.isDirectory()) { + VcsDirtyScopeManager.getInstance(myVcs.getProject()).dirDirtyRecursively(file, true); + } else { + VcsDirtyScopeManager.getInstance(myVcs.getProject()).fileDirty(file); + } + } + } + } + + private static class CloseAction extends AnAction { + + public void update(AnActionEvent e) { + e.getPresentation().setText("Close"); + e.getPresentation().setDescription("Close this tool window"); + e.getPresentation().setIcon(IconLoader.getIcon("/actions/cancel.png")); + } + + public void actionPerformed(AnActionEvent e) { + Project p = e.getData(PlatformDataKeys.PROJECT); + ToolWindowManager.getInstance(p).unregisterToolWindow(ID); + } + } + + private class RefreshAction extends AnAction { + public void update(AnActionEvent e) { + e.getPresentation().setText("Refresh"); + e.getPresentation().setDescription("Reload properties"); + e.getPresentation().setIcon(IconLoader.getIcon("/actions/sync.png")); + e.getPresentation().setEnabled(myFile != null); + } + + public void actionPerformed(AnActionEvent e) { + setFile(myVcs, myFile); + updateFileStatus(false); + } + } + + private class SetKeywordsAction extends AnAction { + + public void update(AnActionEvent e) { + e.getPresentation().setText("Edit Keywords"); + e.getPresentation().setDescription("Manage svn:keywords property"); + if (!CONTEXT_ID.equals(e.getPlace())) { + e.getPresentation().setIcon(IconLoader.getIcon("/actions/properties.png")); + } + e.getPresentation().setEnabled(myFile != null && myFile.isFile()); + } + + public void actionPerformed(AnActionEvent e) { + Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext()); + SVNWCClient wcClient = myVcs.createWCClient(); + SVNPropertyData propValue = null; + try { + propValue = wcClient.doGetProperty(myFile, SVNProperty.KEYWORDS, SVNRevision.UNDEFINED, SVNRevision.WORKING); + } catch (SVNException e1) { + // show error message + } + + SetKeywordsDialog dialog = new SetKeywordsDialog(project, + propValue != null ? SVNPropertyValue.getPropertyAsString(propValue.getValue()) : null); + dialog.show(); + if (dialog.isOK()) { + String value = dialog.getKeywords(); + try { + wcClient.doSetProperty(myFile, SVNProperty.KEYWORDS, SVNPropertyValue.create(value), false, false, null); + } + catch (SVNException err) { + // show error message + } + } + setFile(myVcs, myFile); + updateFileStatus(false); + } + } + + private class DeletePropertyAction extends AnAction { + public void update(AnActionEvent e) { + e.getPresentation().setText("Delete Property"); + e.getPresentation().setDescription("Delete selected property"); + if (!CONTEXT_ID.equals(e.getPlace())) { + e.getPresentation().setIcon(IconLoader.getIcon("/general/remove.png")); + } + e.getPresentation().setEnabled(myFile != null && getSelectedPropertyName() != null); + } + + public void actionPerformed(AnActionEvent e) { + try { + myVcs.createWCClient().doSetProperty(myFile, getSelectedPropertyName(), null, true, false, null); + } catch (SVNException error) { + // show error message. + } + setFile(myVcs, myFile); + updateFileStatus(false); + } + } + + private class AddPropertyAction extends AnAction { + + public void update(AnActionEvent e) { + e.getPresentation().setText("Add Property"); + e.getPresentation().setDescription("Add new property"); + if (!CONTEXT_ID.equals(e.getPlace())) { + e.getPresentation().setIcon(IconLoader.getIcon("/general/add.png")); + } + e.getPresentation().setEnabled(myFile != null); + } + + public void actionPerformed(AnActionEvent e) { + Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext()); + SetPropertyDialog dialog = new SetPropertyDialog(project, new File[] {myFile}, null, + myFile.isDirectory()); + dialog.show(); + boolean recursive = false; + if (dialog.isOK()) { + String name = dialog.getPropertyName(); + String value = dialog.getPropertyValue(); + recursive = dialog.isRecursive(); + SVNWCClient wcClient = myVcs.createWCClient(); + try { + wcClient.doSetProperty(myFile, name, SVNPropertyValue.create(value), false, recursive, null); + } + catch (SVNException err) { + // show error message + } + } + setFile(myVcs, myFile); + updateFileStatus(recursive); + } + } + + private class EditPropertyAction extends AnAction { + public void update(AnActionEvent e) { + e.getPresentation().setText("Edit Property"); + e.getPresentation().setDescription("Edit selected property value"); + if (!CONTEXT_ID.equals(e.getPlace())) { + e.getPresentation().setIcon(IconLoader.getIcon("/actions/editSource.png")); + } + e.getPresentation().setEnabled(myFile != null && getSelectedPropertyName() != null); + } + + public void actionPerformed(AnActionEvent e) { + Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext()); + SetPropertyDialog dialog = new SetPropertyDialog(project, new File[] {myFile}, getSelectedPropertyName(), myFile.isDirectory()); + dialog.show(); + boolean recursive = false; + if (dialog.isOK()) { + String name = dialog.getPropertyName(); + String value = dialog.getPropertyValue(); + recursive = dialog.isRecursive(); + SVNWCClient wcClient = myVcs.createWCClient(); + try { + wcClient.doSetProperty(myFile, name, SVNPropertyValue.create(value), false, recursive, null); + } + catch (SVNException err) { + // show error message + } + } + setFile(myVcs, myFile); + updateFileStatus(recursive); + } + } + + private class FollowSelectionAction extends ToggleAction { + + public boolean isSelected(AnActionEvent e) { + return myIsFollowSelection; + } + public void setSelected(AnActionEvent e, boolean state) { + if (state && !myIsFollowSelection) { + updateSelection(e); + } + myIsFollowSelection = state; + } + + public void update(final AnActionEvent e) { + super.update(e); + e.getPresentation().setIcon(IconLoader.getIcon("/general/autoscrollFromSource.png")); + e.getPresentation().setText("Follow Selection"); + e.getPresentation().setDescription("Follow Selection"); + // change file + if (myIsFollowSelection) { + updateSelection(e); + } + } + + private void updateSelection(AnActionEvent e) { + if (myVcs == null) { + return; + } + VirtualFile vf = PlatformDataKeys.VIRTUAL_FILE.getData(e.getDataContext()); + if (vf != null) { + File f = new File(vf.getPath()); + if (!f.equals(myFile)) { + setFile(myVcs, f); + Project p = PlatformDataKeys.PROJECT.getData(e.getDataContext()); + ToolWindowManager.getInstance(p).getToolWindow(ID).setTitle(f.getName()); + } + + } + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/QuickMerge.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/QuickMerge.java new file mode 100644 index 000000000000..e1a54255e534 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/QuickMerge.java @@ -0,0 +1,723 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.RunBackgroundable; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.*; +import com.intellij.openapi.vcs.changes.*; +import com.intellij.openapi.vcs.changes.shelf.ShelveChangesManager; +import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; +import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.newvfs.RefreshSessionImpl; +import com.intellij.util.Consumer; +import com.intellij.util.FilePathByPathComparator; +import com.intellij.util.PairConsumer; +import com.intellij.util.containers.MultiMap; +import com.intellij.util.continuation.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.NestedCopyType; +import org.jetbrains.idea.svn17.SvnBranchConfigurationManager17; +import org.jetbrains.idea.svn17.SvnUtil; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.actions.ChangeListsMergerFactory; +import org.jetbrains.idea.svn17.history.SvnChangeList; +import org.jetbrains.idea.svn17.history.SvnCommittedChangesProvider; +import org.jetbrains.idea.svn17.history.SvnRepositoryLocation; +import org.jetbrains.idea.svn17.history.TreeStructureNode; +import org.jetbrains.idea.svn17.integrate.*; +import org.jetbrains.idea.svn17.mergeinfo.MergeChecker; +import org.jetbrains.idea.svn17.mergeinfo.OneShotMergeInfoHelper; +import org.jetbrains.idea.svn17.mergeinfo.SvnMergeInfoCache17; +import org.jetbrains.idea.svn17.update.UpdateEventHandler; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNLogEntry; +import org.tmatesoft.svn.core.SVNLogEntryPath; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +public class QuickMerge { + private final Project myProject; + private final String myBranchName; + private final VirtualFile myRoot; + private WCInfo myWcInfo; + private String mySourceUrl; + private SvnVcs17 myVcs; + private final String myTitle; + private final Continuation myContinuation; + + public QuickMerge(Project project, String sourceUrl, WCInfo wcInfo, final String branchName, final VirtualFile root) { + myProject = project; + myBranchName = branchName; + myRoot = root; + myVcs = SvnVcs17.getInstance(project); + mySourceUrl = sourceUrl; + myWcInfo = wcInfo; + myTitle = "Merge from " + myBranchName; + + myContinuation = Continuation.createFragmented(myProject, true); + } + + private class SourceUrlCorrection extends TaskDescriptor { + private SourceUrlCorrection() { + super("Checking branch", Where.POOLED); + } + + @Override + public void run(ContinuationContext continuationContext) { + final SVNURL branch = SvnBranchConfigurationManager17.getInstance(myProject).getSvnBranchConfigManager().getWorkingBranchWithReload(myWcInfo.getUrl(), myRoot); + if (branch != null && (! myWcInfo.getUrl().equals(branch))) { + final String branchString = branch.toString(); + if (SVNPathUtil.isAncestor(branchString, myWcInfo.getRootUrl())) { + final String subPath = SVNPathUtil.getRelativePath(branchString, myWcInfo.getRootUrl()); + mySourceUrl = SVNPathUtil.append(mySourceUrl, subPath); + } + } + } + } + + private boolean prompt(final String question) { + return Messages.showOkCancelDialog(myProject, question, myTitle, Messages.getQuestionIcon()) == 0; + } + + private class MyInitChecks extends TaskDescriptor { + private MyInitChecks() { + super("initial checks", Where.AWT); + } + + @Override + public void run(ContinuationContext continuationContext) { + if (SVNPathUtil.isAncestor(mySourceUrl, myWcInfo.getRootUrl()) || SVNPathUtil.isAncestor(myWcInfo.getRootUrl(), mySourceUrl)) { + finishWithError(continuationContext, "Cannot merge from self", true); + return; + } + + if (! checkForSwitchedRoots()) { + continuationContext.cancelEverything(); + } + } + } + + private class CheckRepositorySupportsMergeinfo extends TaskDescriptor { + private CheckRepositorySupportsMergeinfo() { + super("Checking repository capabilities", Where.POOLED); + } + + @Override + public void run(ContinuationContext context) { + try { + final List tasks = new LinkedList(); + final boolean supportsMergeinfo = myWcInfo.getFormat().supportsMergeInfo() && + SvnUtil.doesRepositorySupportMergeinfo(myVcs, SVNURL.parseURIEncoded(mySourceUrl)); + if (! supportsMergeinfo) { + insertMergeAll(tasks); + } else { + tasks.add(new MergeAllOrSelectedChooser()); + } + context.next(tasks); + } + catch (SVNException e) { + finishWithError(context, e.getMessage(), true); + } + } + } + + @CalledInAwt + public void execute() { + FileDocumentManager.getInstance().saveAllDocuments(); + + final List tasks = new LinkedList(); + tasks.add(new MyInitChecks()); + tasks.add(new SourceUrlCorrection()); + tasks.add(new CheckRepositorySupportsMergeinfo()); + + myContinuation.run(tasks); + } + + private class MergeAllOrSelectedChooser extends TaskDescriptor { + private MergeAllOrSelectedChooser() { + super("merge source selector", Where.AWT); + } + + @Override + public void run(final ContinuationContext context) { + final int result = Messages.showYesNoCancelDialog(myProject, "Merge all?", myTitle, + "Merge &all", "&Select revisions to merge", "Cancel", Messages.getQuestionIcon()); + if (result == 2) return; + if (result == 0) { + insertMergeAll(context); + return; + } + + final MergeCalculator calculator; + try { + calculator = new MergeCalculator(myWcInfo, mySourceUrl, myBranchName); + } + catch (SVNException e) { + finishWithError(context, e.getMessage(), true); + return; + } + context.next(myVcs.getSvnBranchPointsCalculator().getFirstCopyPointTask(myWcInfo.getRepositoryRoot(), myWcInfo.getRootUrl(), mySourceUrl, calculator), + calculator); + } + } + + private void insertMergeAll(final ContinuationContext context) { + final List queue = new LinkedList(); + insertMergeAll(queue); + context.next(queue); + } + + private boolean checkForSwitchedRoots() { + final List infoList = myVcs.getAllWcInfos(); + boolean switchedFound = false; + for (WCInfo wcInfo : infoList) { + if (FileUtil.isAncestor(new File(myWcInfo.getPath()), new File(wcInfo.getPath()), true) + && NestedCopyType.switched.equals(wcInfo.getType())) { + switchedFound = true; + break; + } + } + if (switchedFound) { + return prompt("There are some switched paths in the working copy. Do you want to continue?"); + } + return true; + } + + @CalledInAny + private void finishWithError(final ContinuationContext context, final String message, final List exceptions) { + context.cancelEverything(); + context.next(new TaskDescriptor(message, Where.AWT) { + @Override + public void run(ContinuationContext context) { + AbstractVcsHelper.getInstance(myProject).showErrors(exceptions, message); + } + }); + } + + // todo can be a very base class! + @CalledInAny + private void finishWithError(final ContinuationContext context, final String message, final boolean isError) { + context.cancelEverything(); + context.next(new TaskDescriptor(message, Where.AWT) { + @Override + public void run(ContinuationContext context) { + VcsBalloonProblemNotifier.showOverChangesView(myProject, message, isError ? MessageType.ERROR : MessageType.WARNING); + } + }); + } + + private void insertMergeAll(final List queue) { + queue.add(new LocalChangesPrompt(true, null, null)); + final MergeAllWithBranchCopyPoint mergeAllExecutor = new MergeAllWithBranchCopyPoint(); + queue.add(myVcs.getSvnBranchPointsCalculator().getFirstCopyPointTask(myWcInfo.getRepositoryRoot(), mySourceUrl, myWcInfo.getRootUrl(), mergeAllExecutor)); + queue.add(mergeAllExecutor); + } + + private class MergeAllWithBranchCopyPoint extends TaskDescriptor implements Consumer> { + private SvnBranchPointsCalculator.WrapperInvertor myData; + + private MergeAllWithBranchCopyPoint() { + super("merge all", Where.AWT); + } + + public void consume(SvnBranchPointsCalculator.WrapperInvertor data) { + myData = data; + } + + @Override + public void run(ContinuationContext context) { + if (myData == null) { + finishWithError(context, "Merge start wasn't found", true); + return; + } + final boolean reintegrate = myData.isInvertedSense(); + if (reintegrate && (! prompt("You are going to reintegrate changes.

This will make branch '" + mySourceUrl + + "' no longer usable for further work." + + "
It will not be able to correctly absorb new trunk (" + myData.inverted().getTarget() + + ") changes,
nor can this branch be properly reintegrated to trunk again.

Are you sure?"))) { + context.cancelEverything(); + return; + } + final MergerFactory mergerFactory = createBranchMergerFactory(reintegrate); + + final String title = "Merging all from " + myBranchName + (reintegrate ? " (reintegrate)" : ""); + context.next(new MergeTask(mergerFactory, title)); + } + + private MergerFactory createBranchMergerFactory(final boolean reintegrate) { + return new MergerFactory() { + public IMerger createMerger(SvnVcs17 vcs, File target, UpdateEventHandler handler, SVNURL currentBranchUrl, String branchName) { + return new BranchMerger(vcs, currentBranchUrl, myWcInfo.getUrl(), myWcInfo.getPath(), handler, reintegrate, myBranchName, + reintegrate ? myData.getWrapped().getTargetRevision() : myData.getWrapped().getSourceRevision()); + } + @Nullable + public List getListsToMerge() { + return null; + } + public boolean isMergeAll() { + return true; + } + }; + } + } + + private class MergeTask extends TaskDescriptor { + private final MergerFactory myFactory; + + // awt since only a wrapper + private MergeTask(final MergerFactory factory, final String mergeTitle) { + super(mergeTitle, Where.AWT); + myFactory = factory; + } + + @Override + public void run(ContinuationContext context) { + final SVNURL sourceUrlUrl; + try { + sourceUrlUrl = SVNURL.parseURIEncoded(mySourceUrl); + } catch (SVNException e) { + finishWithError(context, "Cannot merge: " + e.getMessage(), true); + return; + } + + context.next(new TaskDescriptor(getName(), Where.POOLED) { + @Override + public void run(ContinuationContext context) { + final SvnIntegrateChangesTask task = new SvnIntegrateChangesTask(SvnVcs17.getInstance(myProject), + new WorkingCopyInfo(myWcInfo.getPath(), true), myFactory, sourceUrlUrl, getName(), false, myBranchName); + RunBackgroundable.run(task); + } + }); + createChangelist(context); + } + + private void createChangelist(final ContinuationPause context) { + final ChangeListManager listManager = ChangeListManager.getInstance(myProject); + String name = myTitle; + int i = 1; + boolean updateDefaultList = false; + while (true) { + final LocalChangeList changeList = listManager.findChangeList(name); + if (changeList == null) { + final LocalChangeList newList = listManager.addChangeList(name, null); + listManager.setDefaultChangeList(newList); + updateDefaultList = true; + break; + } + if (changeList.getChanges().isEmpty()) { + if (! changeList.isDefault()) { + listManager.setDefaultChangeList(changeList); + updateDefaultList = true; + } + break; + } + name = myTitle + " (" + i + ")"; + ++ i; + } + if (updateDefaultList) { + context.suspend(); + listManager.invokeAfterUpdate(new Runnable() { + public void run() { + context.ping(); + } + }, InvokeAfterUpdateMode.BACKGROUND_NOT_CANCELLABLE_NOT_AWT, "", ModalityState.NON_MODAL); + } + } + } + + private class MergeCalculator extends TaskDescriptor implements Consumer> { + private final static String ourOneShotStrategy = "svn.quickmerge.oneShotStrategy"; + private final WCInfo myWcInfo; + private final String mySourceUrl; + private final String myBranchName; + private SvnBranchPointsCalculator.WrapperInvertor myCopyData; + private boolean myIsReintegrate; + + private final List myNotMerged; + private String myMergeTitle; + private final MergeChecker myMergeChecker; + + public void consume(SvnBranchPointsCalculator.WrapperInvertor branchCopyDataWrapperInvertor) { + myCopyData = branchCopyDataWrapperInvertor; + } + + private MergeCalculator(WCInfo wcInfo, String sourceUrl, String branchName) throws SVNException { + super("Calculating not merged revisions", Where.POOLED); + myWcInfo = wcInfo; + mySourceUrl = sourceUrl; + myBranchName = branchName; + myNotMerged = new LinkedList(); + myMergeTitle = "Merge from " + branchName; +// if (Boolean.TRUE.equals(Boolean.getBoolean(ourOneShotStrategy))) { + myMergeChecker = new OneShotMergeInfoHelper(myProject, myWcInfo, mySourceUrl); + ((OneShotMergeInfoHelper) myMergeChecker).prepare(); +/* } else { + myMergeChecker = new BranchInfo.MyMergeCheckerWrapper(myWcInfo.getPath(), new BranchInfo(myVcs, myWcInfo.getRepositoryRoot(), + myWcInfo.getRootUrl(), mySourceUrl, + mySourceUrl, myVcs.createWCClient())); + }*/ + } + + //"Calculating not merged revisions" + @Override + public void run(ContinuationContext context) { + if (myCopyData == null) { + finishWithError(context, "Merge start wasn't found", true); + return; + } + + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + myIsReintegrate = myCopyData.isInvertedSense(); + if (!myWcInfo.getFormat().supportsMergeInfo()) return; + final SvnBranchPointsCalculator.BranchCopyData data = myCopyData.getTrue(); + final long sourceLatest = data.getTargetRevision(); + + final SvnCommittedChangesProvider committedChangesProvider = (SvnCommittedChangesProvider)myVcs.getCommittedChangesProvider(); + final ChangeBrowserSettings settings = new ChangeBrowserSettings(); + settings.CHANGE_AFTER = Long.toString(sourceLatest); + settings.USE_CHANGE_AFTER_FILTER = true; + + String local = SVNPathUtil.getRelativePath(myWcInfo.getRepositoryRoot(), myWcInfo.getRootUrl()); + final String relativeLocal = (local.startsWith("/") ? local : "/" + local); + + final LinkedList>> list = + new LinkedList>>(); + try { + committedChangesProvider.getCommittedChangesWithMergedRevisons(settings, new SvnRepositoryLocation(mySourceUrl), 0, + new PairConsumer>() { + public void consume(SvnChangeList svnList, TreeStructureNode tree) { + indicator.checkCanceled(); + if (sourceLatest >= svnList.getNumber()) return; + list.add(new Pair>(svnList, tree)); + } + }); + } + catch (VcsException e) { + finishWithError(context, "Checking revisions for merge fault", Collections.singletonList(e)); + } + + indicator.setText("Checking merge information..."); + // to do not go into file system while asking something on the net + for (Pair> pair : list) { + final SvnChangeList svnList = pair.getFirst(); + final SvnMergeInfoCache17.MergeCheckResult checkResult = myMergeChecker.checkList(svnList); + indicator.setText2("Processing revision " + svnList.getNumber()); + + if (SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED.equals(checkResult)) { + // additionally check for being 'local' + final List> children = pair.getSecond().getChildren(); + boolean localChange = false; + for (TreeStructureNode child : children) { + if (isLocalRevisionMergeIteration(child, relativeLocal, indicator)) { + localChange = true; + break; + } + } + + if (! localChange) { + myNotMerged.add(svnList); + } + } + } + + if (myNotMerged.isEmpty()) { + finishWithError(context, "Everything is up-to-date", false); + return; + } + context.next(new ShowRevisionSelector(myCopyData)); + } + + private boolean isLocalRevisionMergeIteration(final TreeStructureNode tree, + final String localURL, + ProgressIndicator indicator) { + final LinkedList> queue = new LinkedList>(); + queue.addLast(tree); + + while (! queue.isEmpty()) { + final TreeStructureNode element = queue.removeFirst(); + indicator.checkCanceled(); + + final Map map = element.getMe().getChangedPaths(); + for (Object o : map.values()) { + final SVNLogEntryPath path = (SVNLogEntryPath) o; + if (SVNPathUtil.isAncestor(localURL, path.getPath())) { + return true; + } + break; // do not check all. first should match or fail + } + queue.addAll(element.getChildren()); + } + return false; + } + + private class ShowRevisionSelector extends TaskDescriptor { + private final SvnBranchPointsCalculator.WrapperInvertor myCopyPoint; + + private ShowRevisionSelector(SvnBranchPointsCalculator.WrapperInvertor copyPoint) { + super("show revisions to merge", Where.AWT); + myCopyPoint = copyPoint; + } + + @Override + public void run(ContinuationContext context) { + final ToBeMergedDialog dialog = new ToBeMergedDialog(myProject, myNotMerged, myMergeTitle, myMergeChecker); + dialog.show(); + if (dialog.getExitCode() == DialogWrapper.CANCEL_EXIT_CODE) { + context.cancelEverything(); + return; + } + if (dialog.getExitCode() == ToBeMergedDialog.MERGE_ALL_CODE) { + insertMergeAll(context); + } else { + final List lists = dialog.getSelected(); + if (lists.isEmpty()) return; + final MergerFactory factory = new ChangeListsMergerFactory(lists) { + @Override + public IMerger createMerger(SvnVcs17 vcs, File target, UpdateEventHandler handler, SVNURL currentBranchUrl, String branchName) { + return new GroupMerger(vcs, lists, target, handler, currentBranchUrl, branchName, false, false, false); + } + }; + context.next(new LocalChangesPrompt(false, lists, myCopyPoint), new MergeTask(factory, myMergeTitle)); + } + } + } + } + + private class LocalChangesPrompt extends TaskDescriptor { + private final boolean myMergeAll; + @Nullable private final List myLists; + private final SvnBranchPointsCalculator.WrapperInvertor myCopyPoint; + + private LocalChangesPrompt(final boolean mergeAll, + @Nullable final List lists, + @Nullable SvnBranchPointsCalculator.WrapperInvertor copyPoint) { + super("local changes intersection check", Where.AWT); + myMergeAll = mergeAll; + myLists = lists; + myCopyPoint = copyPoint; + } + + @Nullable + private File getLocalPath(final String relativeToRepoPath) { + // from source if not inverted + final String pathToCheck = SVNPathUtil.append(myWcInfo.getRepositoryRoot(), relativeToRepoPath); + final SvnBranchPointsCalculator.BranchCopyData wrapped = myCopyPoint.getWrapped(); + final String relativeInSource = + SVNPathUtil.getRelativePath(myCopyPoint.isInvertedSense() ? wrapped.getSource() : wrapped.getTarget(), pathToCheck); + if (StringUtil.isEmptyOrSpaces(relativeInSource)) return null; + final File local = new File(myWcInfo.getPath(), relativeInSource); + return local; + } + + @Override + public void run(ContinuationContext context) { + final String message; + final Intersection intersection; + final ChangeListManager listManager = ChangeListManager.getInstance(myProject); + final List localChangeLists = listManager.getChangeListsCopy(); + + if (myMergeAll) { + intersection = getMergeAllIntersection(localChangeLists); + message = "There are local changes that can potentially intersect with merge changes.\nDo you want to continue?"; + } else { + intersection = checkIntersection(myLists, localChangeLists); + message = "There are local changes that will intersect with merge changes.\nDo you want to continue?"; + } + if (intersection == null || intersection.getChangesSubset().isEmpty()) return; + + final LocalChangesAction action; + if (! myMergeAll) { + final LocalChangesAction[] possibleResults = {LocalChangesAction.shelve, LocalChangesAction.inspect, + LocalChangesAction.continueMerge, LocalChangesAction.cancel}; + final int result = Messages.showDialog(message, myTitle, + new String[]{"Shelve local changes", "Inspect changes", "Continue merge", "Cancel"}, + 0, Messages.getQuestionIcon()); + action = possibleResults[result]; + } else { + final LocalChangesAction[] possibleResults = {LocalChangesAction.shelve, LocalChangesAction.continueMerge, LocalChangesAction.cancel}; + final int result = Messages.showDialog(message, myTitle, + new String[]{"Shelve local changes", "Continue merge", "Cancel"}, + 0, Messages.getQuestionIcon()); + action = possibleResults[result]; + } + switch (action) { + // shelve + case shelve: + context.next(new ShelveLocalChanges(intersection)); + return; + // cancel + case cancel: + context.cancelEverything(); + return; + // continue + case continueMerge: + return; + // inspect + case inspect: + final Collection changes = (Collection) intersection.getChangesSubset().values(); + final List paths = ChangesUtil.getPaths(changes); + Collections.sort(paths, FilePathByPathComparator.getInstance()); + // todo rework message + IntersectingLocalChangesPanel.showInVersionControlToolWindow(myProject, myTitle + ", local changes intersection", + paths, "The following file(s) have local changes that will intersect with merge changes:"); + context.cancelEverything(); + return; + default: + } + } + + @Nullable + private Intersection checkIntersection(@Nullable final List lists, List localChangeLists) { + if (lists == null || lists.isEmpty()) { + return null; + } + final Set mergePaths = new HashSet(); + for (CommittedChangeList list : lists) { + final SvnChangeList svnList = (SvnChangeList)list; + final List paths = new ArrayList(svnList.getAddedPaths()); + paths.addAll(svnList.getChangedPaths()); + paths.addAll(svnList.getDeletedPaths()); + for (String path : paths) { + final File localPath = getLocalPath(path); + if (localPath != null) { + mergePaths.add(new FilePathImpl(localPath, false)); + } + } + } + + final Intersection intersection = new Intersection(); + for (LocalChangeList localChangeList : localChangeLists) { + final Collection localChanges = localChangeList.getChanges(); + + for (Change localChange : localChanges) { + final FilePath before = localChange.getBeforeRevision() == null ? null : localChange.getBeforeRevision().getFile(); + final FilePath after = localChange.getAfterRevision() == null ? null : localChange.getAfterRevision().getFile(); + + if ((before != null && mergePaths.contains(before)) || (after != null && mergePaths.contains(after))) { + intersection.add(localChangeList.getName(), localChangeList.getComment(), localChange); + } + } + } + return intersection; + } + } + + private enum LocalChangesAction { + cancel, + continueMerge, + shelve, + inspect + } + + private class ShelveLocalChanges extends TaskDescriptor { + private final Intersection myIntersection; + + private ShelveLocalChanges(final Intersection intersection) { + super("Shelving local changes before merge", Where.POOLED); + myIntersection = intersection; + } + + @Override + public void run(final ContinuationContext context) { + final MultiMap map = myIntersection.getChangesSubset(); + + final RefreshSessionImpl session = new RefreshSessionImpl(true, false, new Runnable() { + public void run() { + context.ping(); + } + }); + + for (String name : map.keySet()) { + try { + final Collection changes = map.get(name); + ApplicationManager.getApplication().invokeAndWait(new Runnable() { + @Override + public void run() { + FileDocumentManager.getInstance().saveAllDocuments(); + } + }, ModalityState.NON_MODAL); + ShelveChangesManager.getInstance(myProject).shelveChanges(changes, myIntersection.getComment(name) + " (auto shelve before merge)", + true); + session.addAllFiles(ChangesUtil.getFilesFromChanges(changes)); + } + catch (IOException e) { + finishWithError(context, e.getMessage(), true); + } + catch (VcsException e) { + finishWithError(context, e.getMessage(), true); + } + } + // first suspend to guarantee stop->then start back sequence + context.suspend(); + session.launch(); + } + } + + private Intersection getMergeAllIntersection(List localChangeLists) { + final Intersection intersection = new Intersection(); + + for (LocalChangeList localChangeList : localChangeLists) { + final Collection localChanges = localChangeList.getChanges(); + for (Change localChange : localChanges) { + intersection.add(localChangeList.getName(), localChangeList.getComment(), localChange); + } + } + return intersection; + } + + private static class Intersection { + private final Map myLists; + private final MultiMap myChangesSubset; + + private Intersection() { + myLists = new HashMap(); + myChangesSubset = new MultiMap(); + } + + public void add(@NotNull final String listName, @Nullable final String comment, final Change change) { + myChangesSubset.putValue(listName, change); + final String commentToPut = comment == null ? listName : comment; + myLists.put(listName, commentToPut); + } + + public String getComment(final String listName) { + return myLists.get(listName); + } + + public MultiMap getChangesSubset() { + return myChangesSubset; + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RelocateDialog.form b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RelocateDialog.form new file mode 100644 index 000000000000..1f0489164a7f --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RelocateDialog.form @@ -0,0 +1,52 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RelocateDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RelocateDialog.java new file mode 100644 index 000000000000..c3dea81755fe --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RelocateDialog.java @@ -0,0 +1,53 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import org.jetbrains.annotations.Nullable; +import org.tmatesoft.svn.core.SVNURL; + +import javax.swing.*; + +/** + * @author yole + */ +public class RelocateDialog extends DialogWrapper { + private JPanel myMainPanel; + private JTextField myFromURLTextField; + private JTextField myToURLTextField; + + public RelocateDialog(Project project, final SVNURL url) { + super(project, false); + init(); + setTitle("Relocate Working Copy"); + myFromURLTextField.setText(url.toString()); + myToURLTextField.setText(url.toString()); + } + + @Nullable + protected JComponent createCenterPanel() { + return myMainPanel; + } + + public String getBeforeURL() { + return myFromURLTextField.getText(); + } + + public String getAfterURL() { + return myToURLTextField.getText(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RepositoryBrowserComponent.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RepositoryBrowserComponent.java new file mode 100644 index 000000000000..6804ef15e949 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RepositoryBrowserComponent.java @@ -0,0 +1,265 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.DataProvider; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.vcs.vfs.VcsFileSystem; +import com.intellij.openapi.vcs.vfs.VcsVirtualFile; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.treeStructure.Tree; +import com.intellij.util.EditSourceOnDoubleClickHandler; +import com.intellij.util.NotNullFunction; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.dialogs.browserCache.Expander; +import org.jetbrains.idea.svn17.history.SvnFileRevision; +import org.tmatesoft.svn.core.SVNDirEntry; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNNodeKind; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import javax.swing.*; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; +import java.awt.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; + +/** + * @author alex + */ +public class RepositoryBrowserComponent extends JPanel implements Disposable, DataProvider { + + private JTree myRepositoryTree; + private final SvnVcs17 myVCS; + + public RepositoryBrowserComponent(@NotNull SvnVcs17 vcs) { + myVCS = vcs; + createComponent(); + } + + public JTree getRepositoryTree() { + return myRepositoryTree; + } + + public void setRepositoryURLs(SVNURL[] urls, final boolean showFiles) { + RepositoryTreeModel model = new RepositoryTreeModel(myVCS, showFiles, this); + model.setRoots(urls); + Disposer.register(this, model); + myRepositoryTree.setModel(model); + } + + public void setRepositoryURL(SVNURL url, boolean showFiles, final NotNullFunction defaultExpanderFactory) { + RepositoryTreeModel model = new RepositoryTreeModel(myVCS, showFiles, this); + + model.setDefaultExpanderFactory(defaultExpanderFactory); + + model.setSingleRoot(url); + Disposer.register(this, model); + myRepositoryTree.setModel(model); + myRepositoryTree.setRootVisible(true); + myRepositoryTree.setSelectionRow(0); + } + + public void setRepositoryURL(SVNURL url, boolean showFiles) { + RepositoryTreeModel model = new RepositoryTreeModel(myVCS, showFiles, this); + model.setSingleRoot(url); + Disposer.register(this, model); + myRepositoryTree.setModel(model); + myRepositoryTree.setRootVisible(true); + myRepositoryTree.setSelectionRow(0); + } + + public void expandNode(@NotNull final TreeNode treeNode) { + final TreeNode[] pathToNode = ((RepositoryTreeModel) myRepositoryTree.getModel()).getPathToRoot(treeNode); + + if ((pathToNode != null) && (pathToNode.length > 0)) { + final TreePath treePath = new TreePath(pathToNode); + myRepositoryTree.expandPath(treePath); + } + } + + public Collection getExpandedSubTree(@NotNull final TreeNode treeNode) { + final TreeNode[] pathToNode = ((RepositoryTreeModel) myRepositoryTree.getModel()).getPathToRoot(treeNode); + + final Enumeration expanded = myRepositoryTree.getExpandedDescendants(new TreePath(pathToNode)); + + final List result = new ArrayList(); + if (expanded != null) { + while (expanded.hasMoreElements()) { + final TreePath treePath = expanded.nextElement(); + result.add((TreeNode) treePath.getLastPathComponent()); + } + } + return result; + } + + public boolean isExpanded(@NotNull final TreeNode treeNode) { + final TreeNode[] pathToNode = ((RepositoryTreeModel) myRepositoryTree.getModel()).getPathToRoot(treeNode); + + return (pathToNode != null) && (pathToNode.length > 0) && myRepositoryTree.isExpanded(new TreePath(pathToNode)); + } + + public void addURL(String url) { + try { + ((RepositoryTreeModel) myRepositoryTree.getModel()).addRoot(SVNURL.parseURIEncoded(url)); + } catch (SVNException e) { + // + } + } + + public void removeURL(String url) { + try { + ((RepositoryTreeModel) myRepositoryTree.getModel()).removeRoot(SVNURL.parseURIEncoded(url)); + } catch (SVNException e) { + // + } + } + + @Nullable + public SVNDirEntry getSelectedEntry() { + TreePath selection = myRepositoryTree.getSelectionPath(); + if (selection == null) { + return null; + } + Object element = selection.getLastPathComponent(); + if (element instanceof RepositoryTreeNode) { + RepositoryTreeNode node = (RepositoryTreeNode) element; + return node.getSVNDirEntry(); + } + return null; + } + + @Nullable + public String getSelectedURL() { + SVNURL selectedUrl = getSelectedSVNURL(); + return selectedUrl == null ? null : selectedUrl.toString(); + } + + @Nullable + public SVNURL getSelectedSVNURL() { + TreePath selection = myRepositoryTree.getSelectionPath(); + if (selection == null) { + return null; + } + Object element = selection.getLastPathComponent(); + if (element instanceof RepositoryTreeNode) { + RepositoryTreeNode node = (RepositoryTreeNode) element; + return node.getURL(); + } + return null; + } + + public void addChangeListener(TreeSelectionListener l) { + myRepositoryTree.addTreeSelectionListener(l); + } + + public void removeChangeListener(TreeSelectionListener l) { + myRepositoryTree.removeTreeSelectionListener(l); + } + + public Component getPreferredFocusedComponent() { + return myRepositoryTree; + } + + private void createComponent() { + setLayout(new BorderLayout()); + myRepositoryTree = new Tree(); + myRepositoryTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + myRepositoryTree.setRootVisible(false); + myRepositoryTree.setShowsRootHandles(true); + JScrollPane scrollPane = + ScrollPaneFactory.createScrollPane(myRepositoryTree, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); + add(scrollPane, BorderLayout.CENTER); + myRepositoryTree.setCellRenderer(new SvnRepositoryTreeCellRenderer()); + + EditSourceOnDoubleClickHandler.install(myRepositoryTree); + } + + @Nullable + public RepositoryTreeNode getSelectedNode() { + TreePath selection = myRepositoryTree.getSelectionPath(); + if (selection != null && selection.getLastPathComponent() instanceof RepositoryTreeNode) { + return (RepositoryTreeNode) selection.getLastPathComponent(); + } + return null; + } + + public void setSelectedNode(@NotNull final TreeNode node) { + final TreeNode[] pathNodes = ((RepositoryTreeModel) myRepositoryTree.getModel()).getPathToRoot(node); + myRepositoryTree.setSelectionPath(new TreePath(pathNodes)); + } + + @Nullable + public VirtualFile getSelectedVcsFile() { + final RepositoryTreeNode node = getSelectedNode(); + if (node == null) return null; + + SVNDirEntry entry = node.getSVNDirEntry(); + if (entry == null || entry.getKind() != SVNNodeKind.FILE) { + return null; + } + + String name = entry.getName(); + FileTypeManager manager = FileTypeManager.getInstance(); + + if (entry.getName().lastIndexOf('.') > 0 && !manager.getFileTypeByFileName(name).isBinary()) { + SVNURL url = node.getURL(); + final SvnFileRevision revision = new SvnFileRevision(myVCS, SVNRevision.UNDEFINED, SVNRevision.HEAD, url.toString(), + entry.getAuthor(), entry.getDate(), null, null, null); + + return new VcsVirtualFile(node.getSVNDirEntry().getName(), revision, VcsFileSystem.getInstance()); + } else { + return null; + } + } + + @Nullable + public Object getData(@NonNls String dataId) { + if (PlatformDataKeys.NAVIGATABLE.is(dataId)) { + final Project project = myVCS.getProject(); + if (project == null || project.isDefault()) { + return null; + } + final VirtualFile vcsFile = getSelectedVcsFile(); + return vcsFile != null ? new OpenFileDescriptor(project, vcsFile) : null; + } else if (PlatformDataKeys.PROJECT.is(dataId)) { + return myVCS.getProject(); + } + return null; + } + + public void dispose() { + } + + public void setLazyLoadingExpander(final NotNullFunction expanderFactory) { + ((RepositoryTreeModel) myRepositoryTree.getModel()).setDefaultExpanderFactory(expanderFactory); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RepositoryBrowserDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RepositoryBrowserDialog.java new file mode 100644 index 000000000000..904253dd9188 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RepositoryBrowserDialog.java @@ -0,0 +1,1237 @@ +/* + * 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.dialogs; + +import com.intellij.CommonBundle; +import com.intellij.ide.CommonActionsManager; +import com.intellij.ide.TreeExpander; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.fileChooser.FileChooser; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.ide.CopyPasteManager; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.vcs.*; +import com.intellij.openapi.vcs.actions.VcsContextFactory; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangesUtil; +import com.intellij.openapi.vcs.changes.ContentRevision; +import com.intellij.openapi.vcs.changes.ui.ChangeListViewerDialog; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.util.NotNullFunction; +import com.intellij.util.WaitForProgressToShow; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.*; +import org.jetbrains.idea.svn17.actions.BrowseRepositoryAction; +import org.jetbrains.idea.svn17.checkout.SvnCheckoutProvider; +import org.jetbrains.idea.svn17.dialogs.browser.*; +import org.jetbrains.idea.svn17.dialogs.browserCache.Expander; +import org.jetbrains.idea.svn17.dialogs.browserCache.KeepingExpandedExpander; +import org.jetbrains.idea.svn17.dialogs.browserCache.SyntheticWorker; +import org.jetbrains.idea.svn17.history.SvnHistoryProvider; +import org.jetbrains.idea.svn17.history.SvnRepositoryLocation; +import org.jetbrains.idea.svn17.status.SvnDiffEditor; +import org.tmatesoft.svn.core.*; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; +import org.tmatesoft.svn.core.internal.wc.SVNCancellableEditor; +import org.tmatesoft.svn.core.io.ISVNEditor; +import org.tmatesoft.svn.core.io.ISVNReporter; +import org.tmatesoft.svn.core.io.ISVNReporterBaton; +import org.tmatesoft.svn.core.io.SVNRepository; +import org.tmatesoft.svn.core.wc.SVNCommitClient; +import org.tmatesoft.svn.core.wc.SVNCopyClient; +import org.tmatesoft.svn.core.wc.SVNCopySource; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import javax.swing.*; +import javax.swing.tree.TreeNode; +import java.awt.*; +import java.awt.datatransfer.StringSelection; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +public class RepositoryBrowserDialog extends DialogWrapper { + + private final Project myProject; + private final SvnVcs17 myVCS; + private RepositoryBrowserComponent myRepositoryBrowser; + + @NonNls public static final String COPY_OF_PREFIX = "CopyOf"; + @NonNls public static final String NEW_FOLDER_POSTFIX = "NewFolder"; + + private final DeleteAction myDeleteAction = new DeleteAction(); + private AnAction copyUrlAction; + private AnAction mkDirAction; + + private final boolean myShowFiles; + + @NonNls private static final String PLACE_TOOLBAR = "RepositoryBrowser.Toolbar"; + @NonNls private static final String PLACE_MENU = "RepositoryBrowser.Menu"; + private final String myRepositoriesLabelText; + protected JLabel myRepositoriesLabel; + + public RepositoryBrowserDialog(Project project) { + this(project, true, null); + } + + public RepositoryBrowserDialog(Project project, final boolean showFiles, @Nullable final String repositoriesLabelText) { + super(project, true); + myRepositoriesLabelText = repositoriesLabelText == null ? "Repositories:" : repositoriesLabelText; + myShowFiles = showFiles; + myProject = project; + myVCS = SvnVcs17.getInstance(project); + setTitle("SVN Repository Browser"); + setResizable(true); + setOKButtonText(CommonBundle.getCloseButtonText()); + getHelpAction().setEnabled(true); + Disposer.register(project, getDisposable()); + init(); + } + + protected String getHelpId() { + return "reference.svn.repository"; + } + + protected Action[] createActions() { + return new Action[] {getOKAction(), getHelpAction()}; + } + + protected String getDimensionServiceKey() { + return "svn.repositoryBrowser"; + } + + protected boolean showImportAction() { + return true; + } + + public JComponent createToolbar(final boolean horizontal, final AnAction... additionalActions) { + DefaultActionGroup group = new DefaultActionGroup(); + group.add(new AddLocationAction()); + group.add(new EditLocationAction()); + group.add(new DiscardLocationAction()); + group.add(new DetailsAction()); + group.addSeparator(); + final RefreshAction refreshAction = new RefreshAction(); + refreshAction.registerCustomShortcutSet(CommonShortcuts.getRerun(), getRepositoryBrowser()); + group.add(refreshAction); + + myDeleteAction.registerCustomShortcutSet(CommonShortcuts.DELETE, getRepositoryBrowser()); + copyUrlAction = new CopyUrlAction(); + copyUrlAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, + KeyEvent.CTRL_MASK|KeyEvent.CTRL_DOWN_MASK| + KeyEvent.ALT_MASK|KeyEvent.ALT_DOWN_MASK)), getRepositoryBrowser()); + mkDirAction = new MkDirAction(); + mkDirAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, + KeyEvent.ALT_MASK|KeyEvent.ALT_DOWN_MASK)), getRepositoryBrowser()); + + AnAction action = CommonActionsManager.getInstance().createCollapseAllAction(new TreeExpander() { + public void expandAll() { + } + + public boolean canExpand() { + return false; + } + + public void collapseAll() { + JTree tree = getRepositoryBrowser().getRepositoryTree(); + int row = tree.getRowCount() - 1; + while (row >= 0) { + tree.collapseRow(row); + row--; + } + } + + public boolean canCollapse() { + return true; + } + }, getRepositoryBrowser()); + group.add(action); + + if ((additionalActions != null) || (! horizontal)) { + group.addSeparator(); + } + if (additionalActions != null) { + for (AnAction anAction : additionalActions) { + group.add(anAction); + } + } + if (! horizontal) { + group.add(new CloseToolWindowAction()); + } + return ActionManager.getInstance().createActionToolbar(PLACE_TOOLBAR, group, horizontal).getComponent(); + } + + protected JPopupMenu createPopup(boolean toolWindow) { + DefaultActionGroup group = new DefaultActionGroup(); + DefaultActionGroup newGroup = new DefaultActionGroup("_New", true); + newGroup.add(new AddLocationAction()); + newGroup.add(new MkDirAction()); + group.add(newGroup); + group.addSeparator(); + if (toolWindow) { + group.add(new OpenAction()); + group.add(new HistoryAction()); + } + group.add(new CheckoutAction()); + group.add(new DiffAction()); + group.add(new BrowseChangesAction()); + group.addSeparator(); + group.add(new ImportAction()); + group.add(new ExportAction()); + group.addSeparator(); + group.add(new CopyOrMoveAction("Branch or Tag...", "copy.dialog.title", false)); + group.add(new CopyOrMoveAction("_Move or Rename...", "move.dialog.title", true)); + group.add(myDeleteAction); + group.add(copyUrlAction); + group.addSeparator(); + group.add(new RefreshAction()); + group.add(new EditLocationAction()); + group.add(new DiscardLocationAction()); + ActionPopupMenu menu = ActionManager.getInstance().createActionPopupMenu(PLACE_MENU, group); + return menu.getComponent(); + } + + public JComponent createCenterPanel() { + JPanel parentPanel = new JPanel(new BorderLayout()); + JPanel top = new JPanel(); + final BoxLayout layout = new BoxLayout(top, BoxLayout.X_AXIS); + top.setLayout(layout); + + myRepositoriesLabel = new JLabel(myRepositoriesLabelText); + top.add(myRepositoriesLabel); + top.add(createToolbar(true, null)); + parentPanel.add(top, BorderLayout.NORTH); + + JComponent panel = createBrowserComponent(false); + parentPanel.add(panel, BorderLayout.CENTER); + + return parentPanel; + } + + public JComponent createBrowserComponent(final boolean toolWindow) { + JPanel panel = new JPanel(); + panel.setLayout(new GridBagLayout()); + + GridBagConstraints gc = new GridBagConstraints(); + gc.gridx = 0; + gc.gridwidth = 1; + gc.gridy = 0; + gc.gridheight = 1; + + gc.gridx = 0; + gc.gridwidth = 2; + gc.gridy += 1; + gc.gridheight = 1; + gc.weightx = 1; + gc.weighty = 1; + gc.fill = GridBagConstraints.BOTH; + gc.anchor = GridBagConstraints.WEST; + + panel.add(getRepositoryBrowser(), gc); + + gc.gridy += 1; + gc.weighty = 0; + gc.fill = GridBagConstraints.HORIZONTAL; + + panel.add(new JLabel(), gc); + + Collection urls = SvnApplicationSettings17.getInstance().getCheckoutURLs(); + ArrayList svnURLs = new ArrayList(); + for (final String url : urls) { + try { + svnURLs.add(SVNURL.parseURIEncoded(url)); + } + catch (SVNException e) { + // + } + } + getRepositoryBrowser().setRepositoryURLs(svnURLs.toArray(new SVNURL[svnURLs.size()]), myShowFiles); + getRepositoryBrowser().getRepositoryTree().addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + showPopup(e); + } + public void mousePressed(MouseEvent e) { + showPopup(e); + } + public void mouseReleased(MouseEvent e) { + showPopup(e); + } + + private void showPopup(MouseEvent e) { + if (e.isPopupTrigger()) { + JTree tree = getRepositoryBrowser().getRepositoryTree(); + int row = tree.getRowForLocation(e.getX(), e.getY()); + if (row >= 0) { + tree.setSelectionRow(row); + } + JPopupMenu popupMenu = createPopup(toolWindow); + if (popupMenu != null) { + popupMenu.show(e.getComponent(), e.getX(), e.getY()); + } + } + } + }); + return panel; + } + + protected RepositoryBrowserComponent getRepositoryBrowser() { + if (myRepositoryBrowser == null) { + myRepositoryBrowser = new RepositoryBrowserComponent(SvnVcs17.getInstance(myProject)); + myRepositoryBrowser.setPreferredSize(new Dimension(300, 300)); + } + return myRepositoryBrowser; + } + + public void disposeRepositoryBrowser() { + if (myRepositoryBrowser != null) { + Disposer.dispose(myRepositoryBrowser); + myRepositoryBrowser = null; + } + } + + protected void dispose() { + super.dispose(); + disposeRepositoryBrowser(); + } + + public JComponent getPreferredFocusedComponent() { + return (JComponent) getRepositoryBrowser().getPreferredFocusedComponent(); + } + + public boolean shouldCloseOnCross() { + return true; + } + + public boolean isOKActionEnabled() { + return true; + } + + public String getSelectedURL() { + return getRepositoryBrowser().getSelectedURL(); + } + + @Nullable + protected RepositoryTreeNode getSelectedNode() { + return getRepositoryBrowser().getSelectedNode(); + } + + protected class HistoryAction extends AnAction { + public void update(AnActionEvent e) { + e.getPresentation().setText(SvnBundle.message("repository.browser.history.action")); + e.getPresentation().setDescription(SvnBundle.message("repository.browser.history.action")); + final RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode(); + e.getPresentation().setEnabled(node != null && node.getURL() != null && !myProject.isDefault()); + } + + public void actionPerformed(AnActionEvent e) { + final RepositoryTreeNode node = getSelectedNode(); + if (node == null) { + return; + } + boolean isDirectory = node.getUserObject() instanceof SVNURL || + (node.getSVNDirEntry() != null && node.getSVNDirEntry().getKind() == SVNNodeKind.DIR); + AbstractVcsHelper.getInstance(myProject).showFileHistory(new SvnHistoryProvider(myVCS), + VcsContextFactory.SERVICE.getInstance().createFilePathOnNonLocal(node.getURL().toString(), isDirectory), + myVCS, node.getURL().toString()); + node.reload(false); + } + } + + protected class RefreshAction extends AnAction { + public void update(AnActionEvent e) { + e.getPresentation().setText(SvnBundle.message("action.name.refresh")); + e.getPresentation().setDescription(SvnBundle.message("repository.browser.refresh.action")); + e.getPresentation().setIcon(IconLoader.getIcon("/actions/sync.png")); + e.getPresentation().setEnabled(getRepositoryBrowser().getSelectedNode() != null); + } + + public void actionPerformed(AnActionEvent e) { + final RepositoryTreeNode selectedNode = getSelectedNode(); + if (selectedNode != null) { + selectedNode.reload(true); + } + } + } + + protected class AddLocationAction extends AnAction { + + public AddLocationAction() { + super(SvnBundle.message("repository.browser.add.location.menu.item")); + } + + public void update(AnActionEvent e) { + if (e.getPlace().equals(PLACE_TOOLBAR)) { + e.getPresentation().setDescription(SvnBundle.message("repository.browser.add.location.action")); + e.getPresentation().setText(SvnBundle.message("repository.browser.add.location.action")); + e.getPresentation().setIcon(IconLoader.getIcon("/general/add.png")); + } + } + + public void actionPerformed(AnActionEvent e) { + final SvnApplicationSettings17 settings = SvnApplicationSettings17.getInstance(); + final AddRepositoryLocationDialog dialog = new AddRepositoryLocationDialog(myProject, settings.getTypedUrlsListCopy()); + dialog.show(); + if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) { + final String url = dialog.getSelected(); + if (url != null && url.length() > 0) { + settings.addTypedUrl(url); + settings.addCheckoutURL(url); + getRepositoryBrowser().addURL(url); + } + } + } + } + + protected class EditLocationAction extends AnAction { + public EditLocationAction() { + super(SvnBundle.message("repository.browser.edit.location.menu.item")); + } + + public void update(AnActionEvent e) { + RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode(); + if (e.getPlace().equals(PLACE_TOOLBAR)) { + e.getPresentation().setDescription(SvnBundle.message("repository.browser.edit.location.menu.item")); + e.getPresentation().setText(SvnBundle.message("repository.browser.edit.location.menu.item")); + e.getPresentation().setIcon(IconLoader.getIcon("/actions/editSource.png")); + } + e.getPresentation().setEnabled(node != null && node.getParent() instanceof RepositoryTreeRootNode); + } + + public void actionPerformed(AnActionEvent e) { + RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode(); + if (node == null || (! (node.getParent() instanceof RepositoryTreeRootNode))) { + return; + } + final String oldUrl = node.getURL().toString(); + final SvnApplicationSettings17 settings = SvnApplicationSettings17.getInstance(); + final AddRepositoryLocationDialog dialog = new AddRepositoryLocationDialog(myProject, settings.getTypedUrlsListCopy()) { + @Override + protected String initText() { + return oldUrl; + } + + @Override + public String getTitle() { + return SvnBundle.message("repository.browser.edit.location.dialog.title"); + } + }; + dialog.show(); + if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) { + final String url = dialog.getSelected(); + if (url != null && url.length() > 0) { + settings.addTypedUrl(url); + settings.removeCheckoutURL(oldUrl); + settings.addCheckoutURL(url); + final RepositoryBrowserComponent browser = getRepositoryBrowser(); + browser.removeURL(oldUrl); + browser.addURL(url); + } + } + } + } + + protected class DiscardLocationAction extends AnAction { + public void update(AnActionEvent e) { + RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode(); + e.getPresentation().setText(SvnBundle.message("repository.browser.discard.location.action"), true); + e.getPresentation().setIcon(IconLoader.getIcon("/general/remove.png")); + e.getPresentation().setEnabled(node != null && node.getParent() instanceof RepositoryTreeRootNode); + } + + public void actionPerformed(AnActionEvent e) { + RepositoryTreeNode node = getSelectedNode(); + if (node == null) { + return; + } + SVNURL url = node.getURL(); + if (url != null) { + int rc = Messages.showYesNoDialog(myProject, SvnBundle.message("repository.browser.discard.location.prompt", url.toString()), + SvnBundle.message("repository.browser.discard.location.title"), Messages.getQuestionIcon()); + if (rc != 0) { + return; + } + SvnApplicationSettings17.getInstance().removeCheckoutURL(url.toString()); + getRepositoryBrowser().removeURL(url.toString()); + } + } + } + + protected class MkDirAction extends AnAction { + public void update(AnActionEvent e) { + RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode(); + e.getPresentation().setText(SvnBundle.message("repository.browser.new.folder.action"), true); + if (node != null) { + SVNDirEntry entry = node.getSVNDirEntry(); + e.getPresentation().setEnabled(entry == null || entry.getKind() == SVNNodeKind.DIR); + } else { + e.getPresentation().setEnabled(false); + } + } + + public void actionPerformed(AnActionEvent e) { + // show dialog for comment and folder name, then create folder + // then refresh selected node. + final RepositoryTreeNode node = getSelectedNode(); + if (node == null) { + return; + } + MkdirOptionsDialog dialog = new MkdirOptionsDialog(myProject, node.getURL()); + dialog.show(); + VcsConfiguration.getInstance(myProject).saveCommitMessage(dialog.getCommitMessage()); + if (dialog.isOK()) { + SVNURL url = dialog.getURL(); + String message = dialog.getCommitMessage(); + doMkdir(url, message); + + final SVNURL repositoryUrl = (node.getSVNDirEntry() == null) ? node.getURL() : node.getSVNDirEntry().getRepositoryRoot(); + final SyntheticWorker worker = new SyntheticWorker(node.getURL()); + worker.addSyntheticChildToSelf(url, repositoryUrl, dialog.getName(), true); + + node.reload(false); + } + } + } + + protected class DiffAction extends AnAction { + public void update(AnActionEvent e) { + RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode(); + e.getPresentation().setText("Compare With...", true); + if (node != null) { + SVNDirEntry entry = node.getSVNDirEntry(); + e.getPresentation().setEnabled(entry == null || entry.getKind() == SVNNodeKind.DIR); + } else { + e.getPresentation().setEnabled(false); + } + } + + public void actionPerformed(AnActionEvent e) { + // show dialog for comment and folder name, then create folder + // then refresh selected node. + SVNURL root; + RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode(); + if (node == null) { + return; + } + while (node.getSVNDirEntry() != null) { + node = (RepositoryTreeNode) node.getParent(); + } + root = node.getURL(); + final RepositoryTreeNode selectedNode = getSelectedNode(); + if (selectedNode == null) { + return; + } + SVNURL sourceURL = selectedNode.getURL(); + DiffOptionsDialog dialog = new DiffOptionsDialog(myProject, root, sourceURL); + dialog.show(); + if (dialog.isOK()) { + SVNURL targetURL = dialog.getTargetURL(); + if (dialog.isReverseDiff()) { + targetURL = sourceURL; + sourceURL = dialog.getTargetURL(); + } + + final SVNURL sURL = sourceURL; + final SVNURL tURL = targetURL; + + Runnable command; + boolean cancelable; + if (dialog.isUnifiedDiff()) { + final File targetFile = dialog.getTargetFile(); + command = new Runnable() { + public void run() { + targetFile.getParentFile().mkdirs(); + doUnifiedDiff(targetFile, sURL, tURL); + } + }; + cancelable = false; + } else { + command = new Runnable() { + public void run() { + try { + doGraphicalDiff(sURL, tURL); + } + catch(SVNCancelException ex) { + // ignore + } + catch (final SVNException e1) { + WaitForProgressToShow.runOrInvokeLaterAboveProgress(new Runnable() { + public void run() { + Messages.showErrorDialog(myProject, e1.getErrorMessage().getFullMessage(), "Error"); + } + }, null, myProject); + } + } + }; + cancelable = true; + } + ProgressManager.getInstance().runProcessWithProgressSynchronously(command, SvnBundle.message("progress.computing.difference"), + cancelable, myProject); + } + } + } + + protected class CopyOrMoveAction extends AnAction { + private final String myActionName; + private final String myDialogTitleKey; + private final boolean myMove; + + public CopyOrMoveAction(final String actionName, final String dialogTitleKey, final boolean move) { + myActionName = actionName; + myDialogTitleKey = dialogTitleKey; + myMove = move; + } + + public void update(AnActionEvent e) { + e.getPresentation().setText(myActionName); + RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode(); + e.getPresentation().setEnabled(node != null && node.getSVNDirEntry() != null); + } + + public void actionPerformed(final AnActionEvent e) { + final RepositoryTreeNode node = getSelectedNode(); + if (node == null) { + return; + } + RepositoryTreeNode rootNode = node; + while (! rootNode.isRepositoryRoot()) { + rootNode = (RepositoryTreeNode) rootNode.getParent(); + } + + CopyOptionsDialog dialog = new CopyOptionsDialog(SvnBundle.message(myDialogTitleKey), myProject, rootNode, node, ! myMove); + dialog.show(); + VcsConfiguration.getInstance(myProject).saveCommitMessage(dialog.getCommitMessage()); + if (dialog.isOK()) { + SVNURL dst = dialog.getTargetURL(); + SVNURL src = dialog.getSourceURL(); + final String path = src.getPath(); + final int folder = path.replace('\\', '/').lastIndexOf('/'); + if (folder != -1) { + final String lastFolder = path.substring(folder + 1, path.length()); + if (myMove && "trunk".equalsIgnoreCase(lastFolder)) { + final int result = + Messages.showOkCancelDialog(myProject, "You are about to move folder named '" + lastFolder + + "'. Are you sure?", SvnBundle.message(myDialogTitleKey), Messages.getWarningIcon()); + if (DialogWrapper.CANCEL_EXIT_CODE == result) return; + } + } + String message = dialog.getCommitMessage(); + doCopy(src, dst, myMove, message); + + final CopyMoveReloadHelper sourceReloader = myMove ? new MoveSourceReloader(node) : CopyMoveReloadHelper.EMPTY; + final TargetReloader destinationReloader = new TargetReloader(dialog, node, rootNode, myRepositoryBrowser); + + sourceReloader.doSynthetic(); + destinationReloader.doSynthetic(); + if ((! myMove) || (! Comparing.equal(sourceReloader.parent(), destinationReloader.parent()))) { + destinationReloader.doRefresh(); + } + sourceReloader.doRefresh(); + } + } + } + + private static class TargetReloader implements CopyMoveReloadHelper { + private final RepositoryTreeNode myDialogParent; + private final SVNURL myDst; + private final RepositoryTreeNode mySourceNode; + private final RepositoryTreeNode myRoot; + private final RepositoryBrowserComponent myBrowserComponent; + private final String myNewName; + + private TargetReloader(final CopyOptionsDialog dialog, final RepositoryTreeNode node, + final RepositoryTreeNode root, final RepositoryBrowserComponent browserComponent) { + myDialogParent = dialog.getTargetParentNode(); + myDst = dialog.getTargetURL(); + mySourceNode = node; + myRoot = root; + myBrowserComponent = browserComponent; + myNewName = dialog.getName(); + } + + public void doRefresh() { + final TreeNode[] oldPath = myDialogParent.getSelfPath(); + final TreeNode[] correctedPath = new TreeNode[oldPath.length + 1]; + System.arraycopy(oldPath, 0, correctedPath, 1, oldPath.length); + + myRoot.reload(new OpeningExpander(oldPath, myBrowserComponent, myDialogParent), false); + } + + public void doSynthetic() { + final SyntheticWorker parentWorker = new SyntheticWorker(myDialogParent.getURL()); + parentWorker.addSyntheticChildToSelf(myDst, myRoot.getURL(), myNewName, ! mySourceNode.isLeaf()); + parentWorker.copyTreeToSelf(mySourceNode); + } + + public SVNURL parent() { + return myDialogParent.getURL(); + } + } + + private static class MoveSourceReloader implements CopyMoveReloadHelper { + private final RepositoryTreeNode mySource; + private final RepositoryTreeNode myParent; + + private MoveSourceReloader(final RepositoryTreeNode source) { + mySource = source; + myParent = (RepositoryTreeNode) source.getParent(); + } + + public void doRefresh() { + myParent.reload(false); + } + + public void doSynthetic() { + final SyntheticWorker worker = new SyntheticWorker(mySource.getURL()); + worker.removeSelf(); + } + + public SVNURL parent() { + return myParent.getURL(); + } + } + + private interface CopyMoveReloadHelper { + void doRefresh(); + void doSynthetic(); + @Nullable + SVNURL parent(); + + CopyMoveReloadHelper EMPTY = new CopyMoveReloadHelper() { + public void doRefresh() { + } + public void doSynthetic() { + } + @Nullable + public SVNURL parent() { + return null; + } + }; + } + + protected class CopyUrlAction extends AnAction { + public void update(AnActionEvent e) { + e.getPresentation().setText("Copy URL..."); + RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode(); + e.getPresentation().setEnabled(node != null); + } + + public void actionPerformed(final AnActionEvent e) { + final RepositoryTreeNode treeNode = getRepositoryBrowser().getSelectedNode(); + if (treeNode != null) { + final String url = treeNode.getURL().toString(); + CopyPasteManager.getInstance().setContents(new StringSelection(url)); + } + } + } + + protected class DeleteAction extends AnAction { + + public void update(AnActionEvent e) { + e.getPresentation().setText("_Delete..."); + RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode(); + e.getPresentation().setEnabled(node != null && node.getSVNDirEntry() != null); + } + + public void actionPerformed(AnActionEvent e) { + DeleteOptionsDialog dialog = new DeleteOptionsDialog(myProject); + RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode(); + dialog.show(); + VcsConfiguration.getInstance(myProject).saveCommitMessage(dialog.getCommitMessage()); + if (dialog.isOK()) { + SVNURL url = node.getURL(); + String message = dialog.getCommitMessage(); + final boolean successful = doDelete(url, message); + + if (successful) { + final SyntheticWorker worker = new SyntheticWorker(url); + worker.removeSelf(); + final RepositoryTreeNode parentNode = (RepositoryTreeNode) node.getParent(); + parentNode.reload(new KeepingExpandedExpander(myRepositoryBrowser, new AfterDeletionSelectionInstaller(node)), false); + } + } + } + } + + private class AfterDeletionSelectionInstaller implements Expander { + private final RepositoryTreeNode myParentNode; + private final String myDeletedNodeName; + private final boolean myIsFolder; + + private AfterDeletionSelectionInstaller(final RepositoryTreeNode deletedNode) { + myParentNode = (RepositoryTreeNode) deletedNode.getParent(); + myDeletedNodeName = deletedNode.toString(); + myIsFolder = ! deletedNode.isLeaf(); + } + + public void onBeforeRefresh(final RepositoryTreeNode node) { + } + + public void onAfterRefresh(final RepositoryTreeNode node) { + TreeNode nodeToSelect = myParentNode.getNextChildByKey(myDeletedNodeName, myIsFolder); + nodeToSelect = (nodeToSelect == null) ? myParentNode : nodeToSelect; + getRepositoryBrowser().setSelectedNode(nodeToSelect); + } + } + + protected class ImportAction extends AnAction { + public void update(AnActionEvent e) { + e.getPresentation().setVisible(showImportAction()); + e.getPresentation().setText(SvnBundle.message("repository.browser.import.action")); + RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode(); + final boolean running = ProjectLevelVcsManager.getInstance(myProject).isBackgroundVcsOperationRunning(); + if (node != null) { + SVNDirEntry entry = node.getSVNDirEntry(); + e.getPresentation().setEnabled((entry == null || entry.getKind() == SVNNodeKind.DIR) && (! running)); + } else { + e.getPresentation().setEnabled(false); + } + } + + public void actionPerformed(AnActionEvent e) { + // get directory, then import. + doImport(); + } + } + + protected class ExportAction extends AnAction { + public void update(AnActionEvent e) { + e.getPresentation().setText("_Export..."); + e.getPresentation().setEnabled(getRepositoryBrowser().getSelectedNode() != null); + } + public void actionPerformed(AnActionEvent e) { + final RepositoryTreeNode selectedNode = getSelectedNode(); + if (selectedNode == null) { + return; + } + SVNURL url = selectedNode.getURL(); + final File dir = selectFile("Destination directory", "Select export destination directory"); + if (dir == null) { + return; + } + Project p = e.getData(PlatformDataKeys.PROJECT); + ExportOptionsDialog dialog = new ExportOptionsDialog(p, url, dir); + dialog.show(); + if (dialog.isOK()) { + SvnCheckoutProvider.doExport(myProject, dir, url.toString(), dialog.getDepth(), + dialog.isIgnoreExternals(), dialog.isForce(), dialog.getEOLStyle()); + } + } + } + protected class CheckoutAction extends AnAction { + public void update(AnActionEvent e) { + e.getPresentation().setText("_Checkout...", true); + RepositoryTreeNode node = getRepositoryBrowser().getSelectedNode(); + if (node != null) { + SVNDirEntry entry = node.getSVNDirEntry(); + e.getPresentation().setEnabled(entry == null || entry.getKind() == SVNNodeKind.DIR); + } else { + e.getPresentation().setEnabled(false); + } + } + public void actionPerformed(AnActionEvent e) { + final RepositoryTreeNode selectedNode = getSelectedNode(); + if (! ModalityState.NON_MODAL.equals(ModalityState.current())) { + doCancelAction(); + } + doCheckout(ProjectLevelVcsManager.getInstance(myProject).getCompositeCheckoutListener(), selectedNode); + } + } + + protected class BrowseChangesAction extends AnAction { + public BrowseChangesAction() { + super(SvnBundle.message("repository.browser.browse.changes.action"), + SvnBundle.message("repository.browser.browse.changes.description"), null); + } + + public void actionPerformed(AnActionEvent e) { + RepositoryTreeNode node = getSelectedNode(); + if (node == null) { + return; + } + SVNURL url = node.getURL(); + AbstractVcsHelper.getInstance(myProject).showChangesBrowser(myVCS.getCommittedChangesProvider(), + new SvnRepositoryLocation(url.toString()), + "Changes in " + url.toString(), null); + } + + public void update(final AnActionEvent e) { + e.getPresentation().setEnabled(getRepositoryBrowser().getSelectedNode() != null); + } + } + + protected class OpenAction extends AnAction { + public void update(AnActionEvent e) { + e.getPresentation().setEnabled(false); + if (myVCS == null) { + return; + } + e.getPresentation().setText("_Open", true); + e.getPresentation().setEnabled(getRepositoryBrowser().getSelectedVcsFile() != null); + } + public void actionPerformed(AnActionEvent e) { + VirtualFile vcsVF = getRepositoryBrowser().getSelectedVcsFile(); + if (vcsVF != null) { + FileEditorManager.getInstance(myVCS.getProject()).openFile(vcsVF, true); + } + } + } + + protected class DetailsAction extends ToggleAction { + + private boolean myIsSelected; + + public void update(final AnActionEvent e) { + e.getPresentation().setDescription(SvnBundle.message("repository.browser.details.action")); + e.getPresentation().setText(SvnBundle.message("repository.browser.details.action")); + e.getPresentation().setIcon(IconLoader.getIcon("/actions/annotate.png")); + super.update(e); + } + + public boolean isSelected(AnActionEvent e) { + return myIsSelected; + } + + public void setSelected(AnActionEvent e, boolean state) { + myIsSelected = state; + SvnRepositoryTreeCellRenderer r = new SvnRepositoryTreeCellRenderer(); + r.setShowDetails(state); + getRepositoryBrowser().getRepositoryTree().setCellRenderer(r); + } + } + + @Nullable + private File selectFile(String title, String description) { + FileChooserDescriptor fcd = FileChooserDescriptorFactory.createSingleFolderDescriptor(); + fcd.setShowFileSystemRoots(true); + fcd.setTitle(title); + fcd.setDescription(description); + fcd.setHideIgnored(false); + VirtualFile file = FileChooser.chooseFile(myProject, fcd); + if (file == null) { + return null; + } + final String path = file.getPath(); + if (path.endsWith(":")) { // workaround for VFS oddities with drive root (IDEADEV-20870) + return new File(path + "/"); + } + return new File(path); + } + + protected void doMkdir(final SVNURL url, final String comment) { + final SVNException[] exception = new SVNException[1]; + Runnable command = new Runnable() { + public void run() { + ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + if (progress != null) { + progress.setText(SvnBundle.message("progress.text.browser.creating", url.toString())); + } + SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + try { + SVNCommitClient committer = vcs.createCommitClient(); + committer.doMkDir(new SVNURL[] {url}, comment); + } + catch (SVNException e) { + exception[0] = e; + } + } + }; + ProgressManager.getInstance().runProcessWithProgressSynchronously(command, SvnBundle.message("progress.text.create.remote.folder"), false, myProject); + if (exception[0] != null) { + Messages.showErrorDialog(exception[0].getMessage(), SvnBundle.message("message.text.error")); + } + } + private boolean doDelete(final SVNURL url, final String comment) { + final SVNException[] exception = new SVNException[1]; + Runnable command = new Runnable() { + public void run() { + ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + if (progress != null) { + progress.setText(SvnBundle.message("progres.text.deleting", url.toString())); + } + SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + try { + SVNCommitClient committer = vcs.createCommitClient(); + committer.doDelete(new SVNURL[]{url}, comment); + } + catch (SVNException e) { + exception[0] = e; + } + } + }; + ProgressManager.getInstance().runProcessWithProgressSynchronously(command, SvnBundle.message("progress.title.browser.delete"), false, myProject); + if (exception[0] != null) { + Messages.showErrorDialog(exception[0].getMessage(), SvnBundle.message("message.text.error")); + } + return exception[0] == null; + } + + private void doCopy(final SVNURL src, final SVNURL dst, final boolean move, final String comment) { + final SVNException[] exception = new SVNException[1]; + Runnable command = new Runnable() { + public void run() { + ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + if (progress != null) { + progress.setText((move ? SvnBundle.message("progress.text.browser.moving", src) : SvnBundle.message("progress.text.browser.copying", src))); + progress.setText2(SvnBundle.message("progress.text.browser.remote.destination", dst)); + } + SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + try { + SVNCopyClient committer = vcs.createCopyClient(); + final SVNCopySource[] copySource = new SVNCopySource[] {new SVNCopySource(SVNRevision.HEAD, SVNRevision.HEAD, src)}; + committer.doCopy(copySource, dst, move, true, true, comment, null); + } + catch (SVNException e) { + exception[0] = e; + } + } + }; + String progressTitle = move ? SvnBundle.message("progress.title.browser.move") : SvnBundle.message("progress.title.browser.copy"); + ProgressManager.getInstance().runProcessWithProgressSynchronously(command, progressTitle, false, myProject); + if (exception[0] != null) { + Messages.showErrorDialog(exception[0].getMessage(), SvnBundle.message("message.text.error")); + } + } + + protected void doCheckout(@Nullable final CheckoutProvider.Listener listener, final RepositoryTreeNode selectedNode) { + if (selectedNode == null) { + return; + } + SVNURL url = selectedNode.getURL(); + + final String relativePath; + if (selectedNode.isRepositoryRoot()) { + relativePath = ""; + } else { + final SVNDirEntry dirEntry = selectedNode.getSVNDirEntry(); + if (dirEntry == null) { + return; + } + if (dirEntry.getRepositoryRoot() != null) { + relativePath = SVNPathUtil.getRelativePath(dirEntry.getRepositoryRoot().toString(), url.toString()); + } else { + relativePath = dirEntry.getRelativePath(); + } + } + + File dir = selectFile(SvnBundle.message("svn.checkout.destination.directory.title"), + SvnBundle.message("svn.checkout.destination.directory.description")); + if (dir == null) { + return; + } + + Project p = myProject; + CheckoutOptionsDialog dialog = new CheckoutOptionsDialog(p, url, dir, SvnUtil.getVirtualFile(dir.getAbsolutePath()), relativePath); + dialog.show(); + dir = dialog.getTarget(); + if (dialog.isOK() && dir != null) { + final SVNRevision revision; + try { + revision = dialog.getRevision(); + } catch (ConfigurationException e) { + Messages.showErrorDialog(SvnBundle.message("message.text.cannot.checkout", e.getMessage()), SvnBundle.message("message.title.check.out")); + return; + } + + SvnCheckoutProvider.doCheckout(myProject, dir, url.toString(), revision, dialog.getDepth(), dialog.isIgnoreExternals(), listener); + } + } + + /** + * @return true only if import was called + */ + protected boolean doImport() { + File dir = selectFile("Import Directory", "Select directory to import into repository"); + if (dir == null) { + return false; + } + + final RepositoryTreeNode selectedNode = getSelectedNode(); + if (selectedNode == null) { + return false; + } + SVNURL url = selectedNode.getURL(); + ImportOptionsDialog dialog = new ImportOptionsDialog(myProject, url, dir); + dialog.show(); + VcsConfiguration.getInstance(myProject).saveCommitMessage(dialog.getCommitMessage()); + if (dialog.isOK()) { + File src = dialog.getTarget(); + boolean ignored = dialog.isIncludeIgnored(); + String message = dialog.getCommitMessage(); + SvnCheckoutProvider.doImport(myProject, src, url, dialog.getDepth(), ignored, message); + selectedNode.reload(false); + } + return dialog.isOK(); + } + + private void doUnifiedDiff(File targetFile, SVNURL sourceURL, SVNURL targetURL) { + OutputStream os = null; + try { + os = new BufferedOutputStream(new FileOutputStream(targetFile)); + myVCS.createDiffClient().doDiff(sourceURL, SVNRevision.HEAD, targetURL, SVNRevision.HEAD, true, false, os); + } catch (IOException e1) { + // + } catch (SVNException e1) { + // + } finally { + if (os != null) { + try { + os.close(); + } catch (IOException e1) { + // + } + } + } + } + + private void doGraphicalDiff(SVNURL sourceURL, SVNURL targetURL) throws SVNException { + SVNRepository sourceRepository = myVCS.createRepository(sourceURL.toString()); + sourceRepository.setCanceller(new SvnProgressCanceller()); + SvnDiffEditor diffEditor; + final long rev; + SVNRepository targetRepository = null; + try { + rev = sourceRepository.getLatestRevision(); + // generate Map of path->Change + targetRepository = myVCS.createRepository(targetURL.toString()); + diffEditor = new SvnDiffEditor(sourceRepository, targetRepository, -1, false); + final ISVNEditor cancellableEditor = SVNCancellableEditor.newInstance(diffEditor, new SvnProgressCanceller(), null); + sourceRepository.diff(targetURL, rev, rev, null, true, true, false, new ISVNReporterBaton() { + public void report(ISVNReporter reporter) throws SVNException { + reporter.setPath("", null, rev, false); + reporter.finishReport(); + } + }, cancellableEditor); + } + finally { + sourceRepository.closeSession(); + if (targetRepository != null) { + targetRepository.closeSession(); + } + } + final String sourceTitle = SVNPathUtil.tail(sourceURL.toString()); + final String targetTitle = SVNPathUtil.tail(targetURL.toString()); + showDiffEditorResults(diffEditor.getChangesMap(), sourceTitle, targetTitle, sourceURL, targetURL, rev); + } + + private void showDiffEditorResults(final Map changes, String sourceTitle, String targetTitle, + final SVNURL sourceUrl, final SVNURL targetUrl, final long revision) { + if (changes.isEmpty()) { + // display no changes dialog. + final String text = SvnBundle.message("repository.browser.compare.no.difference.message", sourceTitle, targetTitle); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + Messages.showInfoMessage(myProject, text, SvnBundle.message("repository.browser.compare.no.difference.title")); + } + }); + return; + } + final Collection changesList = changes.values(); + /*final Collection changesListConverted = new ArrayList(changesList.size()); + for (Change change : changesList) { + final FilePath path = ChangesUtil.getFilePath(change); + final Change newChange = new Change( + new UrlContentRevision(change.getBeforeRevision(), + FilePathImpl.createNonLocal(SVNPathUtil.append(sourceUrl.toString(), path.getName()), path.isDirectory()), revision), + new UrlContentRevision(change.getAfterRevision(), + FilePathImpl.createNonLocal(SVNPathUtil.append(targetUrl.toString(), path.getName()), path.isDirectory()), revision)); + changesListConverted.add(newChange); + }*/ + + final String title = SvnBundle.message("repository.browser.compare.title", sourceTitle, targetTitle); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + final ChangeListViewerDialog dlg = new ChangeListViewerDialog(myRepositoryBrowser, myProject, changesList, true); + dlg.setTitle(title); + dlg.setConvertor(new NotNullFunction() { + @NotNull + public Change fun(final Change change) { + final FilePath path = ChangesUtil.getFilePath(change); + + return new Change(new UrlContentRevision(change.getBeforeRevision(), + FilePathImpl.createNonLocal(SVNPathUtil.append(sourceUrl.toString(), path.getPath()), path.isDirectory()), revision), + new UrlContentRevision(change.getAfterRevision(), + FilePathImpl.createNonLocal(SVNPathUtil.append(targetUrl.toString(), path.getPath()), path.isDirectory()), revision)); + } + }); + dlg.show(); + } + }); + } + + private class CloseToolWindowAction extends AnAction { + public void actionPerformed(AnActionEvent e) { + disposeRepositoryBrowser(); + Project p = e.getData(PlatformDataKeys.PROJECT); + ToolWindowManager.getInstance(p).unregisterToolWindow(BrowseRepositoryAction.REPOSITORY_BROWSER_TOOLWINDOW); + + } + + public void update(AnActionEvent e) { + e.getPresentation().setText("Close"); + e.getPresentation().setDescription("Close this tool window"); + e.getPresentation().setIcon(IconLoader.getIcon("/actions/cancel.png")); + } + } + + public void setDefaultExpander(final NotNullFunction expanderFactory) { + myRepositoryBrowser.setLazyLoadingExpander(expanderFactory); + } + + private static class UrlContentRevision implements ContentRevision { + private final ContentRevision myContentRevision; + private final FilePath myPath; + private final SvnRevisionNumber myNumber; + + private UrlContentRevision(final ContentRevision contentRevision, final FilePath path, final long revision) { + myContentRevision = contentRevision; + myPath = path; + myNumber = new SvnRevisionNumber(SVNRevision.create(revision)); + } + + public String getContent() throws VcsException { + return (myContentRevision == null) ? "" : myContentRevision.getContent(); + } + + @NotNull + public FilePath getFile() { + return myPath; + } + + @NotNull + public VcsRevisionNumber getRevisionNumber() { + return myNumber; + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RepositoryTreeModel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RepositoryTreeModel.java new file mode 100644 index 000000000000..f0449406fe1a --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RepositoryTreeModel.java @@ -0,0 +1,191 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.util.Disposer; +import com.intellij.util.NotNullFunction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.dialogs.browserCache.*; +import org.tmatesoft.svn.core.SVNURL; + +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeNode; +import java.util.Enumeration; +import java.util.List; + +public class RepositoryTreeModel extends DefaultTreeModel implements Disposable { + private boolean myIsDisposed; + private final SvnVcs17 myVCS; + private boolean myIsShowFiles; + + private final Loader17 myCacheLoader; + private final RepositoryBrowserComponent myBrowser; + private NotNullFunction myDefaultExpanderFactory; + + public RepositoryTreeModel(@NotNull SvnVcs17 vcs, boolean showFiles, final RepositoryBrowserComponent browser) { + super(null); + myVCS = vcs; + myIsShowFiles = showFiles; + myBrowser = browser; + + myCacheLoader = CacheLoader17.getInstance(); + + myDefaultExpanderFactory = new KeepingExpandedExpander.Factory(); + } + + public boolean isShowFiles() { + return myIsShowFiles; + } + + public void setShowFiles(boolean showFiles) { + myIsShowFiles = showFiles; + } + + public void setRoots(SVNURL[] urls) { + final RepositoryTreeRootNode rootNode = new RepositoryTreeRootNode(this, urls); + Disposer.register(this, rootNode); + setRoot(rootNode); + } + + public void setSingleRoot(SVNURL url) { + final RepositoryTreeNode rootNode = new RepositoryTreeNode(this, null, url, url); + Disposer.register(this, rootNode); + setRoot(rootNode); + } + + private boolean hasRoot(SVNURL url) { + if (getRoot()instanceof RepositoryTreeNode) { + return ((RepositoryTreeNode) getRoot()).getUserObject().equals(url); + } + RepositoryTreeRootNode root = (RepositoryTreeRootNode) getRoot(); + for (int i = 0; i < root.getChildCount(); i++) { + RepositoryTreeNode node = (RepositoryTreeNode) root.getChildAt(i); + if (node.getUserObject().equals(url)) { + return true; + } + } + return false; + } + + public TreeNode[] getPathToSubRoot(final TreeNode node) { + final TreeNode[] path = getPathToRoot(node); + final TreeNode[] result = new TreeNode[path.length - 1]; + System.arraycopy(path, 1, result, 0, path.length - 1); + return result; + } + + public void addRoot(SVNURL url) { + if (!hasRoot(url)) { + ((RepositoryTreeRootNode) getRoot()).addRoot(url); + } + } + + public void removeRoot(SVNURL url) { + RepositoryTreeRootNode root = (RepositoryTreeRootNode) getRoot(); + for (int i = 0; i < root.getChildCount(); i++) { + RepositoryTreeNode node = (RepositoryTreeNode) root.getChildAt(i); + if (node.getUserObject().equals(url)) { + root.remove(node); + } + } + } + + public SvnVcs17 getVCS() { + return myVCS; + } + + public void dispose() { + myIsDisposed = true; + } + + public boolean isDisposed() { + return myIsDisposed; + } + + public Loader17 getCacheLoader() { + return myCacheLoader; + } + + public Expander getLazyLoadingExpander() { + return myDefaultExpanderFactory.fun(myBrowser); + } + + public Expander getSelectionKeepingExpander() { + return new KeepingSelectionExpander(myBrowser); + } + + public void setDefaultExpanderFactory(final NotNullFunction defaultExpanderFactory) { + myDefaultExpanderFactory = defaultExpanderFactory; + } + + @Nullable + public RepositoryTreeNode findByUrl(final RepositoryTreeNode oldNode) { + if (oldNode.getParent() == null) { + return oldNode; + } + + TreeNode[] oldPath = getPathToRoot(oldNode); + if (! (oldPath[0] instanceof RepositoryTreeNode)) { + final TreeNode[] result = new TreeNode[oldPath.length - 1]; + System.arraycopy(oldPath, 1, result, 0, oldPath.length - 1); + oldPath = result; + } + + TreeNode root = (TreeNode) getRoot(); + if (! (root instanceof RepositoryTreeNode)) { + final Enumeration children = root.children(); + root = null; + while (children.hasMoreElements()) { + TreeNode node = (TreeNode) children.nextElement(); + if ((node instanceof RepositoryTreeNode) && (((RepositoryTreeNode) node).getURL().equals(((RepositoryTreeNode) oldPath[0]).getURL()))) { + root = node; + break; + } + } + } else { + if ((root == null) || (! ((RepositoryTreeNode) root).getURL().equals(((RepositoryTreeNode) oldPath[0]).getURL()))) { + return null; + } + } + if (root == null) { + return null; + } + + for (int i = 1; i < oldPath.length; i++) { + final TreeNode treeNode = oldPath[i]; + if (root == null) { + return null; + } + root = getChild((RepositoryTreeNode) root, ((RepositoryTreeNode) treeNode).getURL()); + } + + return (RepositoryTreeNode) root; + } + + @Nullable + private RepositoryTreeNode getChild(final RepositoryTreeNode node, final SVNURL url) { + final List children = node.getAlreadyLoadedChildren(); + for (RepositoryTreeNode child : children) { + if (child.getURL().equals(url)) { + return child; + } + } + return null; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RepositoryTreeNode.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RepositoryTreeNode.java new file mode 100644 index 000000000000..b4b9ae468c03 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RepositoryTreeNode.java @@ -0,0 +1,295 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.util.NotNullFunction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.dialogs.browserCache.Expander; +import org.jetbrains.idea.svn17.dialogs.browserCache.NodeLoadState; +import org.tmatesoft.svn.core.SVNDirEntry; +import org.tmatesoft.svn.core.SVNErrorMessage; +import org.tmatesoft.svn.core.SVNNodeKind; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; + +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +public class RepositoryTreeNode implements TreeNode, Disposable { + + private TreeNode myParentNode; + private List myChildren; + private final RepositoryTreeModel myModel; + private final SVNURL myURL; + private final Object myUserObject; + + private final NodeLoadState myLoadState; + private NodeLoadState myChildrenLoadState; + + public RepositoryTreeNode(RepositoryTreeModel model, TreeNode parentNode, + @NotNull SVNURL url, Object userObject, final NodeLoadState state) { + myParentNode = parentNode; + + myURL = url; + myModel = model; + myUserObject = userObject; + + myLoadState = state; + myChildrenLoadState = NodeLoadState.EMPTY; + } + + public RepositoryTreeNode(RepositoryTreeModel model, TreeNode parentNode, @NotNull SVNURL url, Object userObject) { + // created outside: only roots + this(model, parentNode, url, userObject, NodeLoadState.REFRESHED); + } + + public Object getUserObject() { + return myUserObject; + } + + public int getChildCount() { + return getChildren().size(); + } + + public Enumeration children() { + return Collections.enumeration(getChildren()); + } + + public TreeNode getChildAt(int childIndex) { + return (TreeNode) getChildren().get(childIndex); + } + + public int getIndex(TreeNode node) { + return getChildren().indexOf(node); + } + + public boolean getAllowsChildren() { + return !isLeaf(); + } + + public boolean isLeaf() { + return myUserObject instanceof SVNDirEntry ? ((SVNDirEntry) myUserObject).getKind() == SVNNodeKind.FILE : false; + } + + public TreeNode getParent() { + return myParentNode; + } + + public void reload(final boolean removeCurrentChildren) { + // todo lazyLoading as explicit: keeping... + reload(removeCurrentChildren ? myModel.getSelectionKeepingExpander() : myModel.getLazyLoadingExpander(), removeCurrentChildren); + } + + @Nullable + public TreeNode getNextChildByKey(final String key, final boolean isFolder) { + final ByKeySelectedSearcher searcher = (isFolder) ? new FolderByKeySelectedSearcher(key, myChildren) : + new FileByKeySelectedSearcher(key, myChildren); + return searcher.getNextSelectedByKey(); + } + + public String toString() { + if (myParentNode instanceof RepositoryTreeRootNode) { + return myURL.toString(); + } + return SVNPathUtil.tail(myURL.getPath()); + } + + public void reload(final Expander expander, final boolean removeCurrentChildren) { + ApplicationManager.getApplication().assertIsDispatchThread(); + + if (removeCurrentChildren || (myChildren == null)) { + initChildren(); + } + + myModel.getCacheLoader().load(this, expander); + } + + private void initChildren() { + myChildren = new ArrayList(); + myChildren.add(new DefaultMutableTreeNode("Loading")); + myChildrenLoadState = NodeLoadState.LOADING; + } + + private List getChildren() { + ApplicationManager.getApplication().assertIsDispatchThread(); + + if (myChildren == null) { + initChildren(); + myModel.getCacheLoader().load(this, myModel.getLazyLoadingExpander()); + } + return myChildren; + } + + public SVNURL getURL() { + return myURL; + } + + @Nullable + public SVNDirEntry getSVNDirEntry() { + if (myUserObject instanceof SVNDirEntry) { + return (SVNDirEntry) myUserObject; + } + return null; + } + + public void dispose() { + } + + public TreeNode[] getSelfPath() { + return myModel.getPathToRoot(this); + } + + public boolean isRepositoryRoot() { + return ! (myUserObject instanceof SVNDirEntry); + } + + @NotNull + public List getAllAlreadyLoadedChildren() { + if (myChildren != null) { + final List result = new ArrayList(myChildren.size()); + for (TreeNode child : myChildren) { + result.add(child); + } + return result; + } + return Collections.emptyList(); + } + + @NotNull + public List getAlreadyLoadedChildren() { + if (myChildren != null) { + final List result = new ArrayList(myChildren.size()); + for (TreeNode child : myChildren) { + if (child instanceof RepositoryTreeNode) { + result.add((RepositoryTreeNode) child); + } + } + return result; + } + return Collections.emptyList(); + } + + public boolean isDisposed() { + return myModel.isDisposed(); + } + + public void setChildren(final List children, final NodeLoadState state) { + final List nodes = new ArrayList(); + for (final SVNDirEntry entry : children) { + if (!myModel.isShowFiles() && entry.getKind() != SVNNodeKind.DIR) { + continue; + } + nodes.add(new RepositoryTreeNode(myModel, this, entry.getURL(), entry, state)); + } + + myChildrenLoadState = state; + myChildren.clear(); + myChildren.addAll(nodes); + + myModel.reload(this); + } + + public void setParentNode(final TreeNode parentNode) { + myParentNode = parentNode; + } + + public void setAlienChildren(final List children, final NodeLoadState oldState) { + if (myChildren == null) { + myChildren = new ArrayList(); + } else { + myChildren.clear(); + } + + for (TreeNode child : children) { + if (child instanceof RepositoryTreeNode) { + ((RepositoryTreeNode) child).setParentNode(this); + myChildren.add(child); + myChildrenLoadState = oldState; + } else if (child instanceof DefaultMutableTreeNode) { + myChildren.add(new DefaultMutableTreeNode(((DefaultMutableTreeNode) child).getUserObject())); + myChildrenLoadState = oldState; + } + } + + myModel.reload(this); + } + + public void setErrorNode(final SVNErrorMessage text, final NodeLoadState state) { + if (myChildren == null) { + myChildren = new ArrayList(); + } + myChildren.clear(); + myChildren.add(new DefaultMutableTreeNode(text)); + + myChildrenLoadState = NodeLoadState.ERROR; + myModel.reload(this); + } + + public SvnVcs17 getVcs() { + return myModel.getVCS(); + } + + public boolean isCached() { + return NodeLoadState.CACHED.equals(myLoadState); + } + + @Nullable + public RepositoryTreeNode getNodeWithSamePathUnderModelRoot() { + return myModel.findByUrl(this); + } + + public RepositoryTreeModel getModel() { + return myModel; + } + + public NodeLoadState getChildrenLoadState() { + return myChildrenLoadState; + } + + public void doOnSubtree(final NotNullFunction function) { + final SubTreeWalker walker = new SubTreeWalker(this, function); + walker.execute(); + } + + private static class SubTreeWalker { + private final RepositoryTreeNode myNode; + private final NotNullFunction myFunction; + + private SubTreeWalker(final RepositoryTreeNode node, final NotNullFunction function) { + myNode = node; + myFunction = function; + } + + public void execute() { + executeImpl(myNode); + } + + private void executeImpl(final RepositoryTreeNode node) { + myFunction.fun(node); + for (RepositoryTreeNode child : node.getAlreadyLoadedChildren()) { + myFunction.fun(child); + } + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RepositoryTreeRootNode.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RepositoryTreeRootNode.java new file mode 100644 index 000000000000..da6add1d8b36 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RepositoryTreeRootNode.java @@ -0,0 +1,94 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.util.Disposer; +import org.tmatesoft.svn.core.SVNURL; + +import javax.swing.tree.TreeNode; +import java.text.Collator; +import java.util.*; + +public class RepositoryTreeRootNode implements TreeNode, Disposable { + private final List myChildren; + private final RepositoryTreeModel myModel; + + public RepositoryTreeRootNode(RepositoryTreeModel model, SVNURL[] urls) { + myChildren = new ArrayList(); + myModel = model; + + for (SVNURL url : urls) { + RepositoryTreeNode rootNode = new RepositoryTreeNode(model, this, url, url); + Disposer.register(this, rootNode); + myChildren.add(rootNode); + } + Collections.sort(myChildren, new Comparator() { + public int compare(TreeNode o1, TreeNode o2) { + return Collator.getInstance().compare(o1.toString(), o2.toString()); + } + }); + } + + public void addRoot(SVNURL url) { + RepositoryTreeNode rootNode = new RepositoryTreeNode(myModel, this, url, url); + Disposer.register(this, rootNode); + myChildren.add(rootNode); + Collections.sort(myChildren, new Comparator() { + public int compare(TreeNode o1, TreeNode o2) { + return Collator.getInstance().compare(o1.toString(), o2.toString()); + } + }); + myModel.nodesWereInserted(this, new int[]{myChildren.indexOf(rootNode)}); + } + + public void remove(TreeNode node) { + int index = getIndex(node); + myChildren.remove(node); + myModel.nodesWereRemoved(this, new int[]{index}, new Object[]{node}); + } + + public Enumeration children() { + return Collections.enumeration(myChildren); + } + + public boolean getAllowsChildren() { + return true; + } + + public TreeNode getChildAt(int childIndex) { + return myChildren.get(childIndex); + } + + public int getChildCount() { + return myChildren.size(); + } + + public int getIndex(TreeNode node) { + return myChildren.indexOf(node); + } + + public TreeNode getParent() { + return null; + } + + public boolean isLeaf() { + return false; + } + + public void dispose() { + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RunOrContinuation.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RunOrContinuation.java new file mode 100644 index 000000000000..48509ceceb6c --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/RunOrContinuation.java @@ -0,0 +1,108 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.CalledInAwt; +import com.intellij.openapi.vcs.CalledInBackground; +import com.intellij.util.continuation.Continuation; +import com.intellij.util.continuation.ContinuationContext; +import com.intellij.util.continuation.TaskDescriptor; +import com.intellij.util.continuation.Where; +import org.jetbrains.annotations.Nullable; + +public abstract class RunOrContinuation { + protected final Project myProject; + private final String myTaskTitle; + private boolean myWasCanceled; + + protected RunOrContinuation(final Project project, final String taskTitle) { + myProject = project; + myTaskTitle = taskTitle; + } + + @Nullable + @CalledInAwt + protected abstract T calculate(); + @Nullable + @CalledInBackground + protected abstract T calculateLong(); + @CalledInAwt + protected abstract void processResult(final T t); + + protected void cancel() { + myWasCanceled = true; + } + + @CalledInAwt + public TaskDescriptor getTask() { + final Ref refT = new Ref(); + + final TaskDescriptor pooled = new TaskDescriptor(myTaskTitle, Where.POOLED) { + @Override + public void run(ContinuationContext context) { + refT.set(calculateLong()); + if (! myWasCanceled) { + context.next(new TaskDescriptor("final part", Where.AWT) { + @Override + public void run(ContinuationContext context) { + processResult(refT.get()); + } + }); + } + } + }; + + return new TaskDescriptor("short part", Where.AWT) { + @Override + public void run(ContinuationContext context) { + refT.set(calculate()); + if ((! myWasCanceled) && (! refT.isNull())) { + processResult(refT.get()); + return; + } + context.next(pooled); + } + }; + } + + @CalledInAwt + public void execute() { + Continuation.createFragmented(myProject, true).run(getTask()); + } + + /*@CalledInAwt + public void execute() { + final Ref refT = new Ref(); + refT.set(calculate()); + if ((! myWasCanceled) && (! refT.isNull())) { + processResult(refT.get()); + return; + } + ProgressManager.getInstance().run(new Task.Backgroundable(myProject, myTaskTitle, true, BackgroundFromStartOption.getInstance()) { + public void run(@NotNull ProgressIndicator indicator) { + refT.set(calculateLong()); + } + @Override + public void onSuccess() { + if (! myWasCanceled) { + processResult(refT.get()); + } + } + }); + } */ +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SSHCredentialsDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SSHCredentialsDialog.java new file mode 100644 index 000000000000..f2069ff62284 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SSHCredentialsDialog.java @@ -0,0 +1,435 @@ +/* + * 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.dialogs; + +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.fileChooser.FileChooser; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.help.HelpManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.util.Consumer; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.idea.svn17.SvnBundle; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; + +/** + * @author alex + */ +public class SSHCredentialsDialog extends DialogWrapper implements ActionListener, DocumentListener { + private boolean myAllowSave; + private String myUserName; + + private String myRealm; + private JTextField myUserNameText; + private JCheckBox myAllowSaveCheckBox; + private JPasswordField myPasswordText; + private JPasswordField myPassphraseText; + private TextFieldWithBrowseButton myKeyFileText; + private JRadioButton myPasswordButton; + private JRadioButton myKeyButton; + private JLabel myPasswordLabel; + private JLabel myKeyFileLabel; + private JLabel myPortLabel; + private JTextField myPortField; + private JLabel myPassphraseLabel; + private final Project myProject; + + @NonNls private static final String HELP_ID = "vcs.subversion.authentication"; + @NonNls private static final String USER_HOME_PROPERTY = "user.home"; + + protected SSHCredentialsDialog(Project project, String realm, String userName, boolean allowSave, final int port) { + super(project, true); + myProject = project; + myRealm = realm; + myUserName = userName; + myAllowSave = allowSave; + setResizable(true); + getHelpAction().setEnabled(true); + init(); + myPortField.setText("" + port); + } + + protected void doHelpAction() { + HelpManager.getInstance().invokeHelp(HELP_ID); + } + + protected Action[] createActions() { + return new Action[]{getOKAction(), getCancelAction(), getHelpAction()}; + } + + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(); + panel.setLayout(new GridBagLayout()); + + GridBagConstraints gb = new GridBagConstraints(); + + // top label. + gb.insets = new Insets(2, 2, 2, 2); + gb.weightx = 1; + gb.weighty = 0; + gb.gridwidth = 3; + gb.gridheight = 1; + gb.gridx = 0; + gb.gridy = 0; + gb.anchor = GridBagConstraints.WEST; + gb.fill = GridBagConstraints.HORIZONTAL; + + JLabel label = new JLabel(SvnBundle.message("label.ssh.authentication.realm", myRealm)); + panel.add(label, gb); + + // user name + gb.gridy += 1; + gb.gridwidth = 1; + gb.weightx = 0; + gb.fill = GridBagConstraints.NONE; + + label = new JLabel(SvnBundle.message("label.ssh.user.name")); + panel.add(label, gb); + + // user name field + gb.gridx = 1; + gb.gridwidth = 2; + gb.weightx = 1; + gb.fill = GridBagConstraints.HORIZONTAL; + + myUserNameText = new JTextField(); + panel.add(myUserNameText, gb); + label.setLabelFor(myUserNameText); + + if (myUserName != null) { + myUserNameText.setText(myUserName); + } + myUserNameText.selectAll(); + myUserNameText.getDocument().addDocumentListener(this); + + gb.gridy += 1; + gb.weightx = 0; + gb.gridx = 0; + gb.fill = GridBagConstraints.NONE; + gb.gridwidth = 3; + // password type + myPasswordButton = new JRadioButton(SvnBundle.message("radio.ssh.authentication.with.password")); + panel.add(myPasswordButton, gb); + + gb.gridy += 1; + gb.weightx = 0; + gb.gridx = 0; + gb.gridwidth = 1; + gb.fill = GridBagConstraints.NONE; + gb.gridwidth = 1; + + myPasswordLabel = new JLabel(SvnBundle.message("label.ssh.password")); + panel.add(myPasswordLabel, gb); + + // passworde field + gb.gridx = 1; + gb.weightx = 1; + gb.gridwidth = 2; + gb.fill = GridBagConstraints.HORIZONTAL; + + myPasswordText = new JPasswordField(); + panel.add(myPasswordText, gb); + myPasswordLabel.setLabelFor(myPasswordText); + + gb.gridy += 1; + gb.weightx = 0; + gb.gridx = 0; + gb.fill = GridBagConstraints.NONE; + gb.gridwidth = 3; + myKeyButton = new JRadioButton(SvnBundle.message("radio.ssh.authentication.private.key")); + panel.add(myKeyButton, gb); + + // key file. + gb.gridy += 1; + gb.weightx = 0; + gb.gridx = 0; + gb.gridwidth = 1; + gb.fill = GridBagConstraints.NONE; + gb.gridwidth = 1; + + myKeyFileLabel = new JLabel(SvnBundle.message("label.ssh.key.file")); + panel.add(myKeyFileLabel, gb); + + // key field + gb.gridx = 1; + gb.weightx = 1; + gb.gridwidth = 2; + gb.fill = GridBagConstraints.HORIZONTAL; + + myKeyFileText = new TextFieldWithBrowseButton(this); + myKeyFileText.setEditable(false); + panel.add(myKeyFileText, gb); + myKeyFileLabel.setLabelFor(myKeyFileText); + + gb.gridy += 1; + gb.weightx = 0; + gb.gridx = 0; + gb.gridwidth = 1; + gb.fill = GridBagConstraints.NONE; + gb.gridwidth = 1; + + myPassphraseLabel = new JLabel(SvnBundle.message("label.ssh.passphrase")); + panel.add(myPassphraseLabel, gb); + + // key field + gb.gridx = 1; + gb.weightx = 1; + gb.gridwidth = 2; + gb.fill = GridBagConstraints.HORIZONTAL; + + myPassphraseText = new JPasswordField(30); + panel.add(myPassphraseText, gb); + myPassphraseText.getDocument().addDocumentListener(this); + + myPassphraseLabel.setLabelFor(myPassphraseText); + + // key file. + gb.gridy += 1; + gb.weightx = 0; + gb.gridx = 0; + gb.gridwidth = 1; + gb.fill = GridBagConstraints.NONE; + gb.gridwidth = 1; + + myPortLabel = new JLabel(SvnBundle.message("label.ssh.port")); + panel.add(myPortLabel, gb); + + // key field + gb.gridx = 1; + gb.weightx = 0; + gb.gridwidth = 2; + gb.fill = GridBagConstraints.NONE; + + myPortField = new JTextField(); + myPortField.setColumns(6); + panel.add(myPortField, gb); + myPortLabel.setLabelFor(myPortField); + myPortField.setText("22"); + myPortField.setMinimumSize(myPortField.getPreferredSize()); + myPortField.getDocument().addDocumentListener(this); + + ButtonGroup group = new ButtonGroup(); + group.add(myPasswordButton); + group.add(myKeyButton); + group.setSelected(myPasswordButton.getModel(), true); + group.setSelected(myPasswordButton.getModel(), false); + + gb.gridy += 1; + gb.gridx = 0; + gb.gridwidth = 3; + gb.weightx = 1; + gb.anchor = GridBagConstraints.WEST; + gb.fill = GridBagConstraints.HORIZONTAL; + myAllowSaveCheckBox = new JCheckBox(SvnBundle.message("checkbox.ssh.keep.for.current.session")); + panel.add(myAllowSaveCheckBox, gb); + if (! myAllowSave) { + gb.gridy += 1; + final JLabel cannotSaveLabel = new JLabel(SvnBundle.message("svn.cannot.save.credentials.store-auth-creds")); + cannotSaveLabel.setForeground(UIUtil.getInactiveTextColor()); + panel.add(cannotSaveLabel, gb); + } + gb.gridy += 1; + panel.add(new JSeparator(), gb); + + gb.gridy += 1; + gb.weighty = 1; + panel.add(new JLabel(), gb); + + myAllowSaveCheckBox.setSelected(false); + myAllowSaveCheckBox.setEnabled(myAllowSave); + + myKeyButton.addActionListener(this); + myPasswordButton.addActionListener(this); + + updateFields(); + updateOKButton(); + + return panel; + } + + public JComponent getPreferredFocusedComponent() { + return myUserNameText; + } + + public boolean shouldCloseOnCross() { + return true; + } + + protected String getDimensionServiceKey() { + return "svn.sshPasswordDialog"; + } + + public boolean isOKActionEnabled() { + boolean ok = myUserNameText != null && myUserNameText.getText().trim().length() > 0; + if (ok) { + if (myPasswordButton.isSelected()) { + ok = myPasswordText != null && myPasswordText.getPassword() != null; + } + else if (myKeyButton.isSelected()) { + ok = myKeyFileText != null && myKeyFileText.getText().trim().length() > 0; + } + if (ok) { + String portNumber = myPortField.getText(); + try { + int port = Integer.parseInt(portNumber); + ok = port > 0; + } catch (NumberFormatException nfe) { + ok = false; + } + } + } + return ok; + } + + public String getUserName() { + return isOK() && myUserNameText != null ? myUserNameText.getText() : null; + } + + public String getKeyFile() { + if (myKeyFileText.isEnabled()) { + return myKeyFileText.getText(); + } + return null; + } + + public int getPortNumber() { + String portNumber = myPortField.getText(); + int port = 22; + try { + port = Integer.parseInt(portNumber); + } catch (NumberFormatException nfe) { + port = 22; + } + if (port <= 0) { + port = 22; + } + return port; + } + + public String getPassphrase() { + if (myPassphraseText == null || !myPassphraseText.isEnabled()) { + return null; + } + if (isOK()) { + char[] pwd = myPassphraseText.getPassword(); + if (pwd != null) { + return new String(pwd); + } + } + return null; + } + + public String getPassword() { + if (myPasswordText != null && !myPasswordText.isEnabled()) { + return null; + } + if (isOK() && myPasswordText != null) { + char[] pwd = myPasswordText.getPassword(); + if (pwd != null) { + return new String(pwd); + } + } + return null; + } + + public boolean isSaveAllowed() { + return isOK() && myAllowSave && myAllowSaveCheckBox != null && myAllowSaveCheckBox.isSelected(); + } + + public void actionPerformed(ActionEvent e) { + if (e.getSource() == myPasswordButton || e.getSource() == myKeyButton) { + updateFields(); + updateOKButton(); + } + else { + final String[] path = {myKeyFileText.getText()}; + VirtualFile file; + if (path[0] != null && path[0].trim().length() > 0) { + path[0] = "file://" + path[0].replace(File.separatorChar, '/'); + file = VirtualFileManager.getInstance().findFileByUrl(path[0]); + } + else { + path[0] = "file://" + System.getProperty(USER_HOME_PROPERTY) + "/.ssh/identity"; + path[0] = path[0].replace(File.separatorChar, '/'); + file = VirtualFileManager.getInstance().findFileByUrl(path[0]); + if (file == null || !file.exists()) { + path[0] = "file://" + System.getProperty(USER_HOME_PROPERTY) + "/.ssh"; + file = VirtualFileManager.getInstance().findFileByUrl(path[0]); + } + } + FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor(); + + descriptor.setShowFileSystemRoots(true); + descriptor.setTitle(SvnBundle.message("dialog.title.openssh.v2.private.key")); + descriptor.setDescription(SvnBundle.message("dialog.description.openssh.v2.private.key")); + descriptor.setHideIgnored(false); + + final String oldValue = PropertiesComponent.getInstance().getValue("FileChooser.showHiddens"); + PropertiesComponent.getInstance().setValue("FileChooser.showHiddens", Boolean.TRUE.toString()); + + FileChooser.chooseFilesWithSlideEffect(descriptor, myProject, file, new Consumer() { + @Override + public void consume(VirtualFile[] files) { + PropertiesComponent.getInstance().setValue("FileChooser.showHiddens", oldValue); + if (files != null && files.length == 1) { + path[0] = files[0].getPath().replace('/', File.separatorChar); + myKeyFileText.setText(path[0]); + } + updateOKButton(); + } + }); + } + } + + private void updateOKButton() { + getOKAction().setEnabled(isOKActionEnabled()); + } + + private void updateFields() { + myPasswordText.setEnabled(myPasswordButton.isSelected()); + myPasswordLabel.setEnabled(myPasswordButton.isSelected()); + + myKeyFileText.setEnabled(myKeyButton.isSelected()); + myKeyFileLabel.setEnabled(myKeyButton.isSelected()); + myPassphraseLabel.setEnabled(myKeyButton.isSelected()); + myPassphraseText.setEnabled(myKeyButton.isSelected()); + } + + public void insertUpdate(DocumentEvent e) { + updateOKButton(); + } + + public void removeUpdate(DocumentEvent e) { + updateOKButton(); + } + + public void changedUpdate(DocumentEvent e) { + updateOKButton(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SSLCredentialsDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SSLCredentialsDialog.java new file mode 100644 index 000000000000..bc0bd7601996 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SSLCredentialsDialog.java @@ -0,0 +1,127 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.idea.svn17.SvnBundle; + +import javax.swing.*; +import java.awt.*; + +public class SSLCredentialsDialog extends DialogWrapper { + private JPanel myPanel; + private TextFieldWithBrowseButton myCertificatePath; + private JPasswordField myCertificatePassword; + private JCheckBox mySaveAuth; + + public SSLCredentialsDialog(final Project project, final String authRealm, final boolean authSaveAllowed) { + super(project, true); + setResizable(true); + initUI(authRealm, authSaveAllowed); + init(); + } + + protected String getDimensionServiceKey() { + return "svn.sslCredentialsDialog"; + } + + protected Action[] createActions() { + return new Action[]{getOKAction(), getCancelAction()}; + } + + private void initUI(final String authRealm, final boolean authSaveAllowed) { + myPanel = new JPanel(new GridBagLayout()); + final GridBagConstraints gb = + new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(1, 1, 1, 1), 0, 0); + gb.fill = GridBagConstraints.HORIZONTAL; + + gb.gridwidth = 2; + gb.weightx = 0; + JLabel label = new JLabel(SvnBundle.message("label.auth.authentication.realm", authRealm)); + myPanel.add(label, gb); + + ++ gb.gridy; + gb.gridwidth = 1; + gb.weightx = 0; + final JLabel certificatePath = new JLabel(SvnBundle.message("label.ssl.certificate.path")); + myPanel.add(certificatePath, gb); + + myCertificatePath = new TextFieldWithBrowseButton(); + + myCertificatePath.addBrowseFolderListener( + SvnBundle.message("dialog.edit.http.proxies.settings.dialog.select.ssl.client.certificate.path.title"), + null, null, FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor()); + + gb.weightx = 1; + ++ gb.gridx; + myPanel.add(myCertificatePath, gb); + + gb.gridx = 0; + gb.weightx = 0; + ++ gb.gridy; + final JLabel certificatePassword = new JLabel(SvnBundle.message("label.ssl.certificate.password")); + myPanel.add(certificatePassword, gb); + + ++ gb.gridx; + gb.weightx = 1; + myCertificatePassword = new JPasswordField(); + myPanel.add(myCertificatePassword, gb); + + gb.gridx = 0; + ++ gb.gridy; + gb.weightx = 0; + mySaveAuth = new JCheckBox(SvnBundle.message("checkbox.ssl.keep.for.current.session"), authSaveAllowed); + mySaveAuth.setEnabled(authSaveAllowed); + myPanel.add(mySaveAuth, gb); + if (! authSaveAllowed) { + ++ gb.gridy; + gb.gridwidth = 2; + final JLabel cannotSaveLabel = new JLabel(SvnBundle.message("svn.cannot.save.credentials.store-auth-creds")); + cannotSaveLabel.setForeground(UIUtil.getInactiveTextColor()); + myPanel.add(cannotSaveLabel, gb); + } + } + + @Override + public JComponent getPreferredFocusedComponent() { + return myCertificatePath; + } + + public String getCertificatePath() { + return myCertificatePath.getText(); + } + + public char[] getCertificatePassword() { + return myCertificatePassword.getPassword(); + } + + public boolean getSaveAuth() { + return mySaveAuth.isSelected(); + } + + @Override + protected JComponent createNorthPanel() { + return myPanel; + } + + protected JComponent createCenterPanel() { + return null; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SelectFilesDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SelectFilesDialog.java new file mode 100644 index 000000000000..921ad0d4184c --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SelectFilesDialog.java @@ -0,0 +1,185 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.help.HelpManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.ui.OrderPanel; +import com.intellij.ui.OrderPanelListener; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.idea.svn17.SvnBundle; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; +import java.util.Map; +import java.util.TreeMap; +import java.util.TreeSet; + +/** + * @author alex + */ +public class SelectFilesDialog extends DialogWrapper implements ActionListener { + + private final String[] myFiles; + private FilesList myFilesList; + private JButton mySelectAllButton; + private JButton myDeselectAllButton; + private final String myLabel; + private final String myHelpID; + + public SelectFilesDialog(final Project project, String label, String title, String actionName, String[] paths, + @NonNls String helpID) { + super(project, true); + myHelpID = helpID; + setOKButtonText(actionName); + setTitle(title); + setResizable(true); + + myFiles = paths; + myLabel = label; + getHelpAction().setEnabled(myHelpID != null); + + init(); + } + + protected void doHelpAction() { + if (myHelpID != null) { + HelpManager.getInstance().invokeHelp(myHelpID); + } + } + + protected Action[] createActions() { + return new Action[]{getOKAction(), getCancelAction(), getHelpAction()}; + } + + protected void init() { + super.init(); + + mySelectAllButton.addActionListener(this); + myDeselectAllButton.addActionListener(this); + + myFilesList.addListener(new OrderPanelListener() { + public void entryMoved() { + getOKAction().setEnabled(isOKActionEnabled()); + } + }); + } + + protected String getDimensionServiceKey() { + return "svn.selectFilesDialog"; + } + + public boolean shouldCloseOnCross() { + return true; + } + + public JComponent getPreferredFocusedComponent() { + return myFilesList; + } + + public boolean isOKActionEnabled() { + return myFilesList.getSelectedPaths().length > 0; + } + + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(new BorderLayout(5,5)); + + JLabel label = new JLabel(myLabel); + panel.add(label, BorderLayout.NORTH); + + myFilesList = new FilesList(myFiles); + myFilesList.setCheckboxColumnName(""); + myFilesList.setEntriesEditable(false); + for (String myFile : myFiles) { + myFilesList.add(myFile); + } + Font font = myFilesList.getFont(); + FontMetrics fm = myFilesList.getFontMetrics(font); + int height = fm.getHeight(); + myFilesList.setPreferredSize(new Dimension(myFilesList.getPreferredSize().width, height*7)); + panel.add(ScrollPaneFactory.createScrollPane(myFilesList), BorderLayout.CENTER); + + JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + mySelectAllButton = new JButton(SvnBundle.message("button.text.select.all")); + myDeselectAllButton = new JButton(SvnBundle.message("button.text.deselect.all")); + + buttonsPanel.add(mySelectAllButton); + buttonsPanel.add(myDeselectAllButton); + + panel.add(buttonsPanel, BorderLayout.SOUTH); + return panel; + } + + public void actionPerformed(ActionEvent e) { + for (String file : myFiles) { + myFilesList.setChecked(file, e.getSource() == mySelectAllButton); + } + myFilesList.refresh(); + setOKActionEnabled(isOKActionEnabled()); + } + + public String[] getSelectedPaths() { + return myFilesList.getSelectedPaths(); + } + + private class FilesList extends OrderPanel { + + private final Map mySelectedFiles; + + protected FilesList(String[] files) { + super(String.class, true); + mySelectedFiles = new TreeMap(); + for (String file : files) { + mySelectedFiles.put(file, Boolean.TRUE); + } + } + public boolean isCheckable(final String entry) { + return true; + } + + public boolean isChecked(final String entry) { + return Boolean.TRUE.equals(mySelectedFiles.get(entry)); + } + + public void setChecked(final String entry, final boolean checked) { + mySelectedFiles.put(entry, checked ? Boolean.TRUE : Boolean.FALSE); + getOKAction().setEnabled(isOKActionEnabled()); + } + + public void refresh() { + clear(); + for (String path : mySelectedFiles.keySet()) { + add(path); + } + } + + public String[] getSelectedPaths() { + Collection selected = new TreeSet(); + for (String path : mySelectedFiles.keySet()) { + if (isChecked(path)) { + selected.add(path); + } + } + return ArrayUtil.toStringArray(selected); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SelectLocationDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SelectLocationDialog.java new file mode 100644 index 000000000000..ec944d5b9bc8 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SelectLocationDialog.java @@ -0,0 +1,249 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.help.HelpManager; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Ref; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.dialogs.browser.UrlOpeningExpander; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; +import org.tmatesoft.svn.core.io.SVNRepository; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import java.awt.*; + +/** + * @author alex + */ +public class SelectLocationDialog extends DialogWrapper { + private final Project myProject; + private RepositoryBrowserComponent myRepositoryBrowser; + private final SVNURL myURL; + private final String myDstName; + private final String myDstLabel; + private JTextField myDstText; + private final boolean myIsShowFiles; + + @NonNls private static final String HELP_ID = "vcs.subversion.common"; + + // todo check that works when authenticated + @Nullable + public static String selectLocation(Project project, String url) { + try { + SVNURL.parseURIEncoded(url); + final SVNURL svnurl = initRoot(project, url); + SelectLocationDialog dialog = new SelectLocationDialog(project, svnurl, null, null, true); + dialog.show(); + if (!dialog.isOK()) return null; + return dialog.getSelectedURL(); + } catch (SVNException e) { + Messages.showErrorDialog(project, e.getMessage(), SvnBundle.message("dialog.title.select.repository.location")); + return null; + } + } + + @Nullable + public static String selectCopyDestination(Project project, String url, String dstLabel, String dstName, boolean showFiles) { + try { + SVNURL.parseURIEncoded(url); + final SVNURL svnurl = initRoot(project, url); + SelectLocationDialog dialog = new SelectLocationDialog(project, svnurl, dstLabel, dstName, showFiles); + dialog.show(); + if (!dialog.isOK()) return null; + + final String result = dialog.getSelectedURL(); + final String name = dialog.getDestinationName(); + return SVNPathUtil.append(result, name); + } catch (SVNException e) { + Messages.showErrorDialog(project, SvnBundle.message("select.location.invalid.url.message", url), + SvnBundle.message("dialog.title.select.repository.location")); + return null; + } + } + + private SelectLocationDialog(Project project, SVNURL url, + String dstLabel, String dstName, boolean showFiles) throws SVNException { + super(project, true); + myProject = project; + myDstLabel = dstLabel; + myDstName = dstName; + myURL = url; + myIsShowFiles = showFiles; + setTitle(SvnBundle.message("dialog.title.select.repository.location")); + getHelpAction().setEnabled(true); + init(); + } + + protected void doHelpAction() { + HelpManager.getInstance().invokeHelp(HELP_ID); + } + + protected Action[] createActions() { + return new Action[]{getOKAction(), getCancelAction(), getHelpAction()}; + } + + protected String getDimensionServiceKey() { + return "svn.repositoryBrowser"; + } + + private static SVNURL initRoot(final Project project, final String urlString) throws SVNException { + final Ref result = new Ref(); + final Ref excRef = new Ref(); + + ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + public void run() { + SVNRepository repos = null; + try { + repos = SvnVcs17.getInstance(project).createRepository(urlString); + result.set(repos.getRepositoryRoot(true)); + } catch (SVNException e) { + excRef.set(e); + } finally { + if (repos != null) { + repos.closeSession(); + } + } + } + }, "Detecting repository root", true, project); + if (! excRef.isNull()) { + throw excRef.get(); + } + return result.get(); + } + + protected void init() { + super.init(); + final String urlString = myURL.toString(); + myRepositoryBrowser.setRepositoryURL(myURL, myIsShowFiles, new UrlOpeningExpander.Factory(urlString, urlString)); + myRepositoryBrowser.addChangeListener(new TreeSelectionListener() { + public void valueChanged(TreeSelectionEvent e) { + getOKAction().setEnabled(isOKActionEnabled()); + } + }); + } + + @Override + protected void dispose() { + super.dispose(); + Disposer.dispose(myRepositoryBrowser); + } + + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(); + panel.setLayout(new GridBagLayout()); + + GridBagConstraints gc = new GridBagConstraints(); + gc.insets = new Insets(2, 2, 2, 2); + gc.gridwidth = 2; + gc.gridheight = 1; + gc.gridx = 0; + gc.gridy = 0; + gc.anchor = GridBagConstraints.WEST; + gc.fill = GridBagConstraints.BOTH; + gc.weightx = 1; + gc.weighty = 1; + + + myRepositoryBrowser = new RepositoryBrowserComponent(SvnVcs17.getInstance(myProject)); + panel.add(myRepositoryBrowser, gc); + if (myDstName != null) { + gc.gridy += 1; + gc.gridwidth = 1; + gc.gridx = 0; + gc.fill = GridBagConstraints.NONE; + gc.weightx = 0; + gc.weighty = 0; + + JLabel dstLabel = new JLabel(myDstLabel); + panel.add(dstLabel, gc); + + gc.gridx += 1; + gc.weightx = 1; + gc.fill = GridBagConstraints.HORIZONTAL; + + myDstText = new JTextField(); + myDstText.setText(myDstName); + myDstText.selectAll(); + panel.add(myDstText, gc); + + myDstText.getDocument().addDocumentListener(new DocumentListener() { + public void insertUpdate(DocumentEvent e) { + getOKAction().setEnabled(isOKActionEnabled()); + } + + public void removeUpdate(DocumentEvent e) { + getOKAction().setEnabled(isOKActionEnabled()); + } + + public void changedUpdate(DocumentEvent e) { + getOKAction().setEnabled(isOKActionEnabled()); + } + }); + + dstLabel.setLabelFor(myDstText); + gc.gridx = 0; + gc.gridy += 1; + gc.gridwidth = 2; + + panel.add(new JSeparator(), gc); + } + + return panel; + } + + public JComponent getPreferredFocusedComponent() { + return (JComponent)myRepositoryBrowser.getPreferredFocusedComponent(); + } + + public boolean shouldCloseOnCross() { + return true; + } + + public boolean isOKActionEnabled() { + boolean ok = myRepositoryBrowser.getSelectedURL() != null; + if (ok && myDstText != null) { + return myDstText.getText().trim().length() > 0; + } + return ok; + } + + public String getDestinationName() { + return SVNEncodingUtil.uriEncode(myDstText.getText().trim()); + } + + public String getSelectedURL() { + return myRepositoryBrowser.getSelectedURL(); + } + + public SVNURL getSelectedSVNURL() { + return myRepositoryBrowser.getSelectedSVNURL(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ServerSSHDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ServerSSHDialog.java new file mode 100644 index 000000000000..933c9caf7b51 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ServerSSHDialog.java @@ -0,0 +1,108 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.tmatesoft.svn.core.auth.ISVNAuthenticationProvider; +import org.tmatesoft.svn.core.internal.util.SVNSSLUtil; + +import javax.swing.*; +import java.awt.*; + +/** + * @author irengrig + * Date: 1/31/11 + * Time: 5:48 PM + */ +public class ServerSSHDialog extends DialogWrapper { + private int myResult; + private String myFingerprints; + private JCheckBox myJCheckBox; + private final boolean myStore; + private final String myHost; + private final String myAlgorithm; + + public ServerSSHDialog(Project project, boolean store, @NotNull final String host, @Nullable final String algorithm, + @NotNull final byte[] fingerprints) { + super(project, true); + myStore = store; + myHost = host; + myAlgorithm = algorithm == null ? "" : algorithm; + // todo ? + myFingerprints = SVNSSLUtil.getFingerprint(fingerprints, "SHA1"); + myResult = ISVNAuthenticationProvider.REJECTED; + setOKButtonText(SvnBundle.message("button.text.ssh.accept")); + setCancelButtonText(SvnBundle.message("button.text.ssh.reject")); + setTitle(SvnBundle.message("dialog.title.ssh.examine.server.fingerprints")); + init(); + setResizable(false); + } + + public boolean shouldCloseOnCross() { + return false; + } + + protected Action[] createActions() { + return new Action[]{getOKAction(), getCancelAction()}; + } + + protected void doOKAction() { + myResult = myJCheckBox.isSelected() ? ISVNAuthenticationProvider.ACCEPTED : ISVNAuthenticationProvider.ACCEPTED_TEMPORARY; + super.doOKAction(); + } + + public void doCancelAction() { + myResult = ISVNAuthenticationProvider.REJECTED; + super.doCancelAction(); + } + + public int getResult() { + return myResult; + } + + protected String getDimensionServiceKey() { + return "org.jetbrains.idea.svn.dialogs.ServerSSHDialog"; + } + + @Override + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(new BorderLayout(5,5)); + final JPanel wrapper = new JPanel(); + final BoxLayout boxLayout = new BoxLayout(wrapper, BoxLayout.Y_AXIS); + wrapper.setLayout(boxLayout); + final JLabel label = new JLabel(SvnBundle.message("label.ssh.server.provided.fingerprints", myHost)); + wrapper.add(label); + final JLabel label2 = new JLabel(SvnBundle.message("label.ssh.server.provided.fingerprints2", myAlgorithm)); + wrapper.add(label2); + final JTextField textField = new JTextField(myFingerprints); + textField.setEditable(false); + textField.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0)); + wrapper.add(textField); + final JLabel label3 = new JLabel(SvnBundle.message("label.ssh.server.provided.fingerprints3")); + label3.setBorder(BorderFactory.createEmptyBorder(0,0,5,0)); + wrapper.add(label3); + panel.add(wrapper, BorderLayout.CENTER); + myJCheckBox = new JCheckBox(SvnBundle.message("checkbox.svn.ssh.cache.fingerprint")); + myJCheckBox.setSelected(myStore); + myJCheckBox.setEnabled(myStore); + panel.add(myJCheckBox, BorderLayout.SOUTH); + return panel; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ServerSSLDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ServerSSLDialog.java new file mode 100644 index 000000000000..9f51130436c7 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ServerSSLDialog.java @@ -0,0 +1,141 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.ui.ScrollPaneFactory; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.idea.svn17.SvnBundle; +import org.tmatesoft.svn.core.auth.ISVNAuthenticationProvider; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.security.MessageDigest; +import java.security.cert.X509Certificate; + +/** + * @author alex + */ +public class ServerSSLDialog extends DialogWrapper { + + private final X509Certificate myCertificate; + private Action myTempAction; + private int myResult; + @NonNls public static final String ALGORITHM_SHA1 = "SHA1"; + + protected ServerSSLDialog(final Project project, X509Certificate cert, boolean store) { + super(project, true); + myCertificate = cert; + myResult = ISVNAuthenticationProvider.REJECTED; + setOKButtonText(SvnBundle.message("button.text.ssl.accept")); + setOKActionEnabled(store); + setCancelButtonText(SvnBundle.message("button.text.ssl.reject")); + setTitle(SvnBundle.message("dialog.title.ssl.examine.server.crertificate")); + setResizable(true); + init(); + } + + public boolean shouldCloseOnCross() { + return false; + } + + protected Action[] createActions() { + return new Action[]{getOKAction(), getTempAction(), getCancelAction()}; + } + + private Action getTempAction() { + if (myTempAction == null) { + myTempAction = new AbstractAction(SvnBundle.message("server.ssl.accept.temporary.action.name")) { + public void actionPerformed(ActionEvent e) { + myResult = ISVNAuthenticationProvider.ACCEPTED_TEMPORARY; + close(0); + } + }; + } + return myTempAction; + } + + protected void doOKAction() { + myResult = ISVNAuthenticationProvider.ACCEPTED; + super.doOKAction(); + } + + public void doCancelAction() { + myResult = ISVNAuthenticationProvider.REJECTED; + super.doCancelAction(); + } + + public int getResult() { + return myResult; + } + + protected String getDimensionServiceKey() { + return "svn.sslDialog"; + } + + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(new BorderLayout(5,5)); + panel.add(new JLabel(SvnBundle.message("label.ssl.server.provided.certificate")), BorderLayout.NORTH); + JTextArea area = new JTextArea(5, 50); + area.setText(getServerCertificateInfo(myCertificate)); + area.setEditable(false); + panel.add(ScrollPaneFactory.createScrollPane(area), BorderLayout.CENTER); + + return panel; + } + + private static String getFingerprint(X509Certificate cert) { + StringBuffer s = new StringBuffer(); + try { + MessageDigest md = MessageDigest.getInstance(ALGORITHM_SHA1); + md.update(cert.getEncoded()); + byte[] digest = md.digest(); + for (int i= 0; i < digest.length; i++) { + if (i != 0) { + s.append(':'); + } + int b = digest[i] & 0xFF; + String hex = Integer.toHexString(b); + if (hex.length() == 1) { + s.append('0'); + } + s.append(hex.toLowerCase()); + } + } catch (Exception e) { + // + } + return s.toString(); + } + + @SuppressWarnings({"HardCodedStringLiteral"}) + private static String getServerCertificateInfo(X509Certificate cert) { + StringBuffer info = new StringBuffer(); + info.append(" - Subject: "); + info.append(cert.getSubjectDN().getName()); + info.append('\n'); + info.append(" - Valid: "); + info.append("from ").append(cert.getNotBefore()).append(" until ").append(cert.getNotAfter()); + info.append('\n'); + info.append(" - Issuer: "); + info.append(cert.getIssuerDN().getName()); + info.append('\n'); + info.append(" - Fingerprint: "); + info.append(getFingerprint(cert)); + return info.toString(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SetKeywordsDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SetKeywordsDialog.java new file mode 100644 index 000000000000..f78f6965ff35 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SetKeywordsDialog.java @@ -0,0 +1,106 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import org.jetbrains.annotations.Nullable; +import org.tmatesoft.svn.core.internal.wc.admin.SVNTranslator; + +import javax.swing.*; +import java.awt.*; +import java.util.Map; + +/** + * Created by IntelliJ IDEA. + * User: alex + * Date: Jun 21, 2006 + * Time: 6:37:41 PM + */ +public class SetKeywordsDialog extends DialogWrapper { + + private final String myPropValue; + + private JCheckBox myLastChangedDateCheckbox; + private JCheckBox myLastChangedRevisionCheckbox; + private JCheckBox myLastChangedByCheckbox; + private JCheckBox myURLCheckbox; + private JCheckBox myIDCheckbox; + + protected SetKeywordsDialog(Project project, String propValue) { + super(project, false); + myPropValue = propValue; + setTitle("SVN Keywords"); + setResizable(false); + init(); + } + + public String getKeywords() { + StringBuffer result = new StringBuffer(); + if (myLastChangedDateCheckbox.isSelected()) { + result.append("LastChangedDate "); + } + if (myLastChangedByCheckbox.isSelected()) { + result.append("LastChangedBy "); + } + if (myLastChangedRevisionCheckbox.isSelected()) { + result.append("LastChangedRevision "); + } + if (myURLCheckbox.isSelected()) { + result.append("HeadURL "); + } + if (myIDCheckbox.isSelected()) { + result.append("Id"); + } + if (result.length() > 0) { + return result.toString().trim(); + } + return null; + } + + @Nullable + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(new BorderLayout()); + panel.add(new JLabel("Select keywords to set: "), BorderLayout.NORTH); + JPanel buttonsPanel = new JPanel(new GridLayout(5, 1)); + + myIDCheckbox = new JCheckBox("Id"); + myURLCheckbox = new JCheckBox("HeadURL"); + myLastChangedDateCheckbox = new JCheckBox("LastChangedDate"); + myLastChangedRevisionCheckbox = new JCheckBox("LastChangedRevision"); + myLastChangedByCheckbox = new JCheckBox("LastChangedBy"); + + buttonsPanel.add(myIDCheckbox); + buttonsPanel.add(myURLCheckbox); + buttonsPanel.add(myLastChangedByCheckbox); + buttonsPanel.add(myLastChangedDateCheckbox); + buttonsPanel.add(myLastChangedRevisionCheckbox); + + panel.add(buttonsPanel, BorderLayout.CENTER); + initValues(); + + return panel; + } + + private void initValues() { + Map keywords = SVNTranslator.computeKeywords(myPropValue, "u", "a", "d", "r", null); + myLastChangedDateCheckbox.setSelected(keywords.containsKey("LastChangedDate")); + myLastChangedByCheckbox.setSelected(keywords.containsKey("LastChangedBy")); + myLastChangedRevisionCheckbox.setSelected(keywords.containsKey("LastChangedRevision")); + myURLCheckbox.setSelected(keywords.containsKey("HeadURL")); + myIDCheckbox.setSelected(keywords.containsKey("Id")); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SetPropertyDialog.form b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SetPropertyDialog.form new file mode 100644 index 000000000000..db6c23f53f84 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SetPropertyDialog.form @@ -0,0 +1,93 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SetPropertyDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SetPropertyDialog.java new file mode 100644 index 000000000000..811d9c1f150b --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SetPropertyDialog.java @@ -0,0 +1,228 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.help.HelpManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.ui.DocumentAdapter; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnPropertyKeys; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNPropertyValue; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.ISVNPropertyHandler; +import org.tmatesoft.svn.core.wc.SVNPropertyData; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import java.awt.*; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.io.File; +import java.util.Collection; +import java.util.TreeSet; + +/** + * @author alex + */ +public class SetPropertyDialog extends DialogWrapper { + private final String myPropertyName; + private final File[] myFiles; + + private JComboBox myPropertyNameBox; + private JRadioButton mySetPropertyButton; + private JTextArea myValueText; + private JRadioButton myDeletePropertyButton; + private JCheckBox myRecursiveButton; + private final boolean myIsRecursionAllowed; + private final SvnVcs17 myVCS; + + @NonNls private static final String HELP_ID = "vcs.subversion.property"; + private JPanel myMainPanel; + + public SetPropertyDialog(Project project, File[] files, String name, boolean allowRecursion) { + super(project, true); + myFiles = files; + myPropertyName = name; + myIsRecursionAllowed = allowRecursion; + myVCS = SvnVcs17.getInstance(project); + setResizable(true); + setTitle(SvnBundle.message("dialog.title.set.property")); + getHelpAction().setEnabled(true); + init(); + } + + protected void doHelpAction() { + HelpManager.getInstance().invokeHelp(HELP_ID); + } + + protected Action[] createActions() { + return new Action[]{getOKAction(), getCancelAction(), getHelpAction()}; + } + + + public JComponent getPreferredFocusedComponent() { + return myPropertyNameBox; + } + + public String getPropertyName() { + return (String)myPropertyNameBox.getEditor().getItem(); + } + + public String getPropertyValue() { + if (myDeletePropertyButton.isSelected()) { + return null; + } + return myValueText.getText(); + } + + public boolean isRecursive() { + return myRecursiveButton.isSelected(); + } + + public boolean shouldCloseOnCross() { + return true; + } + + protected String getDimensionServiceKey() { + return "svn.propertyDialog"; + } + + protected void init() { + super.init(); + if (myPropertyName != null) { + updatePropertyValue(myPropertyName); + } + else { + myPropertyNameBox.getEditor().setItem(""); + } + myPropertyNameBox.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + String name = getPropertyName(); + updatePropertyValue(name); + getOKAction().setEnabled(name != null && !"".equals(name.trim())); + } + } + }); + Component editor = myPropertyNameBox.getEditor().getEditorComponent(); + if (editor instanceof JTextField) { + JTextField jTextField = (JTextField)editor; + jTextField.getDocument().addDocumentListener(new DocumentAdapter() { + protected void textChanged(DocumentEvent e) { + String name = getPropertyName(); + updatePropertyValue(name); + getOKAction().setEnabled(name != null && !"".equals(name.trim())); + } + }); + + } + getOKAction().setEnabled(myPropertyName != null && !"".equals(myPropertyName.trim())); + } + + private void updatePropertyValue(String name) { + if (myFiles.length == 0 || myFiles.length > 1) { + return; + } + File file = myFiles[0]; + SVNPropertyData property; + try { + SVNWCClient client = myVCS.createWCClient(); + property = client.doGetProperty(file, name, SVNRevision.WORKING, SVNRevision.WORKING); + } + catch (SVNException e) { + property = null; + } + if (property != null) { + myValueText.setText(SVNPropertyValue.getPropertyAsString(property.getValue())); + myValueText.selectAll(); + } + else { + myValueText.setText(""); + } + } + + protected JComponent createCenterPanel() { + fillPropertyNames(myFiles); + if (myPropertyName != null) { + myPropertyNameBox.getEditor().setItem(myPropertyName); + myPropertyNameBox.getEditor().selectAll(); + } + mySetPropertyButton.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + if (mySetPropertyButton.isSelected()) { + myValueText.setEnabled(true); + myValueText.requestFocus(); + } + else { + myValueText.setEnabled(false); + } + } + }); + myRecursiveButton.setEnabled(myIsRecursionAllowed); + return myMainPanel; + } + + private void fillPropertyNames(File[] files) { + final Collection names = new TreeSet(); + if (files.length == 1) { + File file = files[0]; + try { + SVNWCClient client = myVCS.createWCClient(); + client.doGetProperty(file, null, SVNRevision.WORKING, SVNRevision.WORKING, false, + new ISVNPropertyHandler() { + public void handleProperty(File path, SVNPropertyData property) { + String name = property.getName(); + if (name != null) { + names.add(name); + } + } + public void handleProperty(SVNURL url, SVNPropertyData property) { + } + public void handleProperty(long revision, SVNPropertyData property) { + } + }); + } + catch (SVNException e) { + // + } + } + + fillProperties(names); + + for (final String name : names) { + myPropertyNameBox.addItem(name); + } + } + + @SuppressWarnings({"HardCodedStringLiteral"}) + private static void fillProperties(final Collection names) { + names.add(SvnPropertyKeys.SVN_EOL_STYLE); + names.add(SvnPropertyKeys.SVN_KEYWORDS); + names.add(SvnPropertyKeys.SVN_NEEDS_LOCK); + names.add(SvnPropertyKeys.SVN_MIME_TYPE); + names.add(SvnPropertyKeys.SVN_EXECUTABLE); + names.add(SvnPropertyKeys.SVN_IGNORE); + names.add(SvnPropertyKeys.SVN_EXTERNALS); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ShareDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ShareDialog.java new file mode 100644 index 000000000000..065785dd3fc7 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ShareDialog.java @@ -0,0 +1,198 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.ActionPopupMenu; +import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.project.Project; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import java.awt.*; +import java.text.MessageFormat; + +public class ShareDialog extends RepositoryBrowserDialog { + private String mySelectedURL; + + private static final String ourExisting = "At selected repository location"; + private static final String ourProject = "In new \"{0}\" folder at selected repository location"; + private static final String ourStandart = "In new \"{0}/trunk\" folder at selected repository location"; + + private final String myName; + private JRadioButton myExisting; + private JRadioButton mySameNameAsLocal; + private JRadioButton myTrunk; + private JCheckBox myCreateStandard; + + public ShareDialog(Project project, final String name) { + super(project, false, "Point to repository location"); + myName = name; + updateOptionsTexts(null); + myExisting.setToolTipText(ourExisting); + mySameNameAsLocal.setToolTipText(MessageFormat.format(ourProject, myName)); + myTrunk.setToolTipText(MessageFormat.format(ourStandart, myName)); + + myRepositoriesLabel.setFont(myRepositoriesLabel.getFont().deriveFont(Font.BOLD)); + } + + public void init() { + super.init(); + setTitle("Select Share Target"); + setOKButtonText("Share"); + getRepositoryBrowser().addChangeListener(new TreeSelectionListener() { + public void valueChanged(TreeSelectionEvent e) { + if (getOKAction() != null) { + final String selectedURL = getRepositoryBrowser().getSelectedURL(); + updateOptionsTexts(selectedURL); + getOKAction().setEnabled(selectedURL != null); + } + } + }); + getOKAction().setEnabled(getRepositoryBrowser().getSelectedURL() != null); + ((RepositoryTreeModel) getRepositoryBrowser().getRepositoryTree().getModel()).setShowFiles(false); + } + + private void updateOptionsTexts(String selectedURL) { + final boolean enabled = selectedURL != null; + if (! enabled) { + myExisting.setText(ourExisting); + mySameNameAsLocal.setText(MessageFormat.format(ourProject, myName)); + myTrunk.setText(MessageFormat.format(ourStandart, myName)); + } else { + myExisting.setText(selectedURL); + final String corrected = selectedURL.endsWith("/") || selectedURL.endsWith("\\") ? selectedURL : (selectedURL + "/"); + mySameNameAsLocal.setText(corrected + myName); + myTrunk.setText(corrected + myName + "/trunk"); + } + + myExisting.setEnabled(enabled); + mySameNameAsLocal.setEnabled(enabled); + myTrunk.setEnabled(enabled); + myCreateStandard.setEnabled(enabled && myTrunk.isSelected()); + } + + protected Action[] createActions() { + return new Action[] {getOKAction(), getCancelAction(), getHelpAction()}; + } + @Override + protected void doOKAction() { + mySelectedURL = getRepositoryBrowser().getSelectedURL(); + super.doOKAction(); + } + + public String getSelectedURL() { + return mySelectedURL; + } + + protected JPopupMenu createPopup(boolean toolWindow) { + DefaultActionGroup group = new DefaultActionGroup(); + DefaultActionGroup newGroup = new DefaultActionGroup("_New", true); + newGroup.add(new AddLocationAction()); + newGroup.add(new MkDirAction()); + group.add(newGroup); + group.addSeparator(); + group.add(new RefreshAction()); + group.add(new DiscardLocationAction()); + ActionPopupMenu menu = ActionManager.getInstance().createActionPopupMenu("", group); + return menu.getComponent(); + } + + @Override + public JComponent createCenterPanel() { + final JComponent repositoryPanel = super.createCenterPanel(); + final JPanel wrapper = new JPanel(new GridBagLayout()); + final GridBagConstraints gb = new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, + new Insets(1, 1, 1, 1), 0, 0); + gb.weightx = 1; + gb.weighty = 1; + wrapper.add(repositoryPanel, gb); + ++ gb.gridy; + gb.fill = GridBagConstraints.NONE; + gb.weightx = 0; + gb.weighty = 0; + wrapper.add(createFolderPanel(), gb); + return wrapper; + } + + public ShareTarget getShareTarget() { + if (myExisting.isSelected()) { + return ShareTarget.useSelected; + } + if (mySameNameAsLocal.isSelected()) { + return ShareTarget.useProjectName; + } + return ShareTarget.trunkUnderProjectName; + } + + public boolean createStandardStructure() { + return myCreateStandard.isSelected(); + } + + private JComponent createFolderPanel() { + final JPanel panel = new JPanel(new GridBagLayout()); + final GridBagConstraints gb = new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, + new Insets(1, 1, 1, 1), 0, 0); + gb.insets.top = 5; + final JLabel label = new JLabel("Define share target"); + label.setFont(label.getFont().deriveFont(Font.BOLD)); + panel.add(label, gb); + gb.insets.top = 1; + + final ButtonGroup bg = new ButtonGroup(); + myExisting = new JRadioButton(); + mySameNameAsLocal = new JRadioButton(); + myTrunk = new JRadioButton(); + + bg.add(myExisting); + bg.add(mySameNameAsLocal); + bg.add(myTrunk); + + gb.insets.top = 1; + ++ gb.gridy; + panel.add(myExisting, gb); + ++ gb.gridy; + panel.add(mySameNameAsLocal, gb); + ++ gb.gridy; + gb.insets.top = 5; + panel.add(myTrunk, gb); + myCreateStandard = new JCheckBox("Create /tags and /branches"); + myTrunk.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + myCreateStandard.setEnabled(myTrunk.isSelected()); + } + }); + myCreateStandard.setSelected(true); + ++ gb.gridy; + gb.insets.top = 0; + gb.insets.left = 10; + panel.add(myCreateStandard, gb); + + myTrunk.setSelected(true); + + return panel; + } + + public static enum ShareTarget { + useSelected, + useProjectName, + trunkUnderProjectName + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SimpleCredentialsDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SimpleCredentialsDialog.java new file mode 100644 index 000000000000..f0469e6d6421 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SimpleCredentialsDialog.java @@ -0,0 +1,198 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.help.HelpManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.idea.svn17.SvnBundle; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.*; + +/** + * @author alex + */ +public class SimpleCredentialsDialog extends DialogWrapper implements DocumentListener { + private boolean myAllowSave; + private String myUserName; + + private String myRealm; + private JTextField myUserNameText; + private JCheckBox myAllowSaveCheckBox; + private JPasswordField myPasswordText; + + @NonNls private static final String HELP_ID = "vcs.subversion.authentication"; + + protected SimpleCredentialsDialog(Project project) { + super(project, true); + setResizable(false); + } + + public void setup(String realm, String userName, boolean allowSave) { + myRealm = realm; + myUserName = userName; + myAllowSave = allowSave; + getHelpAction().setEnabled(true); + init(); + } + protected void doHelpAction() { + HelpManager.getInstance().invokeHelp(HELP_ID); + } + + protected Action[] createActions() { + return new Action[]{getOKAction(), getCancelAction(), getHelpAction()}; + } + + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(); + panel.setLayout(new GridBagLayout()); + + GridBagConstraints gb = new GridBagConstraints(); + + // top label. + gb.insets = new Insets(2, 2, 2, 2); + gb.weightx = 1; + gb.weighty = 0; + gb.gridwidth = 2; + gb.gridheight = 1; + gb.gridx = 0; + gb.gridy = 0; + gb.anchor = GridBagConstraints.WEST; + gb.fill = GridBagConstraints.HORIZONTAL; + + JLabel label = new JLabel(SvnBundle.message("label.auth.authentication.realm", myRealm)); + panel.add(label, gb); + + // user name + gb.gridy += 1; + gb.gridwidth = 1; + gb.weightx = 0; + gb.fill = GridBagConstraints.NONE; + + label = new JLabel(SvnBundle.message("label.auth.user.name")); + panel.add(label, gb); + + // user name field + gb.gridx = 1; + gb.weightx = 1; + gb.fill = GridBagConstraints.HORIZONTAL; + + myUserNameText = new JTextField(); + panel.add(myUserNameText, gb); + label.setLabelFor(myUserNameText); + + if (myUserName != null) { + myUserNameText.setText(myUserName); + } + myUserNameText.selectAll(); + myUserNameText.getDocument().addDocumentListener(this); + + gb.gridy += 1; + gb.weightx = 0; + gb.gridx = 0; + gb.fill = GridBagConstraints.NONE; + gb.gridwidth = 1; + + label = new JLabel(SvnBundle.message("label.auth.password")); + panel.add(label, gb); + + // passworde field + gb.gridx = 1; + gb.weightx = 1; + gb.fill = GridBagConstraints.HORIZONTAL; + + myPasswordText = new JPasswordField(); + panel.add(myPasswordText, gb); + label.setLabelFor(myPasswordText); + + gb.gridy += 1; + gb.gridx = 0; + gb.gridwidth = 2; + gb.weightx = 1; + gb.fill = GridBagConstraints.HORIZONTAL; + myAllowSaveCheckBox = new JCheckBox(SvnBundle.message("checkbox.auth.keep.for.current.session")); + panel.add(myAllowSaveCheckBox, gb); + gb.gridy += 1; + if (! myAllowSave) { + final JLabel cannotSaveLabel = new JLabel(SvnBundle.message("svn.cannot.save.credentials.store-auth-creds")); + cannotSaveLabel.setForeground(UIUtil.getInactiveTextColor()); + panel.add(cannotSaveLabel, gb); + gb.gridy += 1; + } + panel.add(new JSeparator(), gb); + + myAllowSaveCheckBox.setSelected(false); + myAllowSaveCheckBox.setEnabled(myAllowSave); + + return panel; + } + + protected String getDimensionServiceKey() { + return "svn.passwordDialog"; + } + + public JComponent getPreferredFocusedComponent() { + return myUserNameText; + } + + public boolean shouldCloseOnCross() { + return true; + } + + public boolean isOKActionEnabled() { + return myUserNameText != null && myUserNameText.getText().trim().length() > 0 + && myPasswordText != null && myPasswordText.getPassword() != null; + } + + public String getUserName() { + return isOK() && myUserNameText != null ? myUserNameText.getText() : null; + } + + public String getPassword() { + if (isOK() && myPasswordText != null) { + char[] pwd = myPasswordText.getPassword(); + if (pwd != null) { + return new String(pwd); + } + } + return null; + } + + public boolean isSaveAllowed() { + return isOK() && myAllowSave && myAllowSaveCheckBox != null && myAllowSaveCheckBox.isSelected(); + } + + public void insertUpdate(DocumentEvent e) { + updateOKButton(); + } + + public void removeUpdate(DocumentEvent e) { + updateOKButton(); + } + + public void changedUpdate(DocumentEvent e) { + updateOKButton(); + } + + private void updateOKButton() { + getOKAction().setEnabled(isOKActionEnabled()); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SvnAuthenticationProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SvnAuthenticationProvider.java new file mode 100644 index 000000000000..4d4ff41ec12b --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SvnAuthenticationProvider.java @@ -0,0 +1,87 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.util.SystemProperties; +import org.jetbrains.idea.svn17.SvnAuthenticationNotifier; +import org.jetbrains.idea.svn17.SvnVcs17; +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.auth.SVNUserNameAuthentication; + +import java.util.HashSet; +import java.util.Set; + +/** + * @author alex + */ +public class SvnAuthenticationProvider implements ISVNAuthenticationProvider { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.dialogs.SvnAuthenticationProvider"); + private final Project myProject; + private final SvnAuthenticationNotifier myAuthenticationNotifier; + private final ISVNAuthenticationProvider mySvnInteractiveAuthenticationProvider; + private static final Set ourForceInteractive = new HashSet(); + + public SvnAuthenticationProvider(final SvnVcs17 svnVcs, final ISVNAuthenticationProvider provider) { + myProject = svnVcs.getProject(); + myAuthenticationNotifier = svnVcs.getAuthNotifier(); + mySvnInteractiveAuthenticationProvider = provider; + } + + private void log(final String s) { + LOG.debug(s); + } + + public SVNAuthentication requestClientAuthentication(final String kind, + final SVNURL url, + final String realm, + SVNErrorMessage errorMessage, + final SVNAuthentication previousAuth, + final boolean authMayBeStored) { + if (ApplicationManager.getApplication().isUnitTestMode() && ISVNAuthenticationManager.USERNAME.equals(kind)) { + final String userName = previousAuth != null && previousAuth.getUserName() != null ? previousAuth.getUserName() : SystemProperties.getUserName(); + return new SVNUserNameAuthentication(userName, false); + } + final SvnAuthenticationNotifier.AuthenticationRequest obj = + new SvnAuthenticationNotifier.AuthenticationRequest(myProject, kind, url, realm); + final SVNURL wcUrl = myAuthenticationNotifier.getWcUrl(obj); + if (wcUrl == null || ourForceInteractive.contains(Thread.currentThread())) { + // outside-project url + return mySvnInteractiveAuthenticationProvider.requestClientAuthentication(kind, url, realm, errorMessage, previousAuth, authMayBeStored); + } else { + myAuthenticationNotifier.ensureNotify(obj); + } + return null; + } + + public static void forceInteractive() { + ourForceInteractive.add(Thread.currentThread()); + } + + public static void clearInteractive() { + ourForceInteractive.remove(Thread.currentThread()); + } + + public int acceptServerAuthentication(SVNURL url, String realm, final Object certificate, final boolean resultMayBeStored) { + return mySvnInteractiveAuthenticationProvider.acceptServerAuthentication(url, realm, certificate, resultMayBeStored); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SvnBranchPointsCalculator.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SvnBranchPointsCalculator.java new file mode 100644 index 000000000000..0cad0a34c5bf --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SvnBranchPointsCalculator.java @@ -0,0 +1,362 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.persistent.SmallMapSerializer; +import com.intellij.util.Consumer; +import com.intellij.util.ValueHolder; +import com.intellij.util.containers.Convertor; +import com.intellij.util.containers.MultiMap; +import com.intellij.util.continuation.TaskDescriptor; +import com.intellij.util.continuation.Where; +import com.intellij.util.io.DataExternalizer; +import com.intellij.util.io.EnumeratorStringDescriptor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.history.CopyData; +import org.jetbrains.idea.svn17.history.FirstInBranch; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.File; +import java.io.IOException; +import java.util.*; + +public class SvnBranchPointsCalculator { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.dialogs.SvnBranchPointsCalculator"); + private FactsCalculator> myCalculator; + private PersistentHolder myPersistentHolder; + private File myFile; + private final Project myProject; + + public SvnBranchPointsCalculator(final Project project) { + myProject = project; + final File vcs = new File(PathManager.getSystemPath(), "vcs"); + File file = new File(vcs, "svn_copy_sources"); + file.mkdirs(); + myFile = file; + myFile = new File(file, project.getLocationHash()); + } + + public void activate() { + ValueHolder, KeyData> cache = null; + + myPersistentHolder = new PersistentHolder(myFile); + cache = new ValueHolder, KeyData>() { + public WrapperInvertor getValue(KeyData dataHolder) { + final WrapperInvertor result = + myPersistentHolder.getBestHit(dataHolder.getRepoUrl(), dataHolder.getSourceUrl(), dataHolder.getTargetUrl()); + if (LOG.isDebugEnabled()) { + LOG.debug("Persistent for: " + dataHolder.toString() + " returned: " + (result == null ? null : result.toString())); + } + return result; + } + public void setValue(WrapperInvertor value, KeyData dataHolder) { + if (LOG.isDebugEnabled()) { + LOG.debug("Put into persistent: key: " + dataHolder.toString() + " value: " + value.toString()); + } + myPersistentHolder.put(dataHolder.getRepoUrl(), value.getWrapped().getTarget(), value.getWrapped()); + } + }; + + myCalculator = new FactsCalculator>(myProject, "Looking for branch origin", cache, new Loader(myProject)); + } + + public void deactivate() { + myPersistentHolder.close(); + myCalculator = null; + myPersistentHolder = null; + } + + private static class BranchDataExternalizer implements DataExternalizer> { + public void save(DataOutput out, TreeMap value) throws IOException { + out.writeInt(value.size()); + for (Map.Entry entry : value.entrySet()) { + out.writeUTF(entry.getKey()); + final BranchCopyData entryValue = entry.getValue(); + out.writeUTF(entryValue.getSource()); + out.writeUTF(entryValue.getTarget()); + out.writeLong(entryValue.getSourceRevision()); + out.writeLong(entryValue.getTargetRevision()); + } + } + + public TreeMap read(DataInput in) throws IOException { + final TreeMap result = new TreeMap(); + + final int num = in.readInt(); + for (int i = 0; i < num; i++) { + final String key = in.readUTF(); + final String source = in.readUTF(); + final String target = in.readUTF(); + final long sourceRevision = in.readLong(); + final long targetRevision = in.readLong(); + + result.put(key, new BranchCopyData(source, sourceRevision, target, targetRevision)); + } + return result; + } + } + + public static class WrapperInvertor> { + private final T myWrapped; + private final boolean myInvertedSense; + + public WrapperInvertor(boolean invertedSense, T wrapped) { + myInvertedSense = invertedSense; + myWrapped = wrapped; + } + + public boolean isInvertedSense() { + return myInvertedSense; + } + + public T getWrapped() { + return myWrapped; + } + + public T getTrue() { + return myInvertedSense ? myWrapped.invertSelf() : myWrapped; + } + + public T inverted() { + return myWrapped.invertSelf(); + } + + @Override + public String toString() { + return "inverted: " + myInvertedSense + " wrapped: " + myWrapped.toString(); + } + } + + private static class PersistentHolder { + private final SmallMapSerializer> myPersistentMap; + private final MultiMap myForSearchMap; + private final Object myLock; + + PersistentHolder(final File file) { + myLock = new Object(); + myPersistentMap = new SmallMapSerializer>( + file, new EnumeratorStringDescriptor(), new BranchDataExternalizer()); + // list for values by default + myForSearchMap = new MultiMap(); + for (String s : myPersistentMap.keySet()) { + final TreeMap map = myPersistentMap.get(s); + if (map != null) { + myForSearchMap.put(s, new ArrayList(map.keySet())); + } + } + + for (String key : myForSearchMap.keySet()) { + Collections.sort((List) myForSearchMap.get(key)); + } + } + + public void close() { + myPersistentMap.force(); + } + + public void put(final String uid, final String target, final BranchCopyData data) { + // todo - rewrite of rather big piece; consider rewriting + synchronized (myLock) { + TreeMap map = myPersistentMap.get(uid); + if (map == null) { + map = new TreeMap(); + } + map.put(target, data); + myPersistentMap.put(uid, map); + if (myForSearchMap.containsKey(uid)) { + final List list = (List)myForSearchMap.get(uid); + final int idx = Collections.binarySearch(list, target); + if (idx < 0) { + final int insertionIdx = - idx - 1; + list.add(insertionIdx, target); + } + } else { + myForSearchMap.putValue(uid, target); + } + } + myPersistentMap.force(); + } + + @Nullable + public WrapperInvertor getBestHit(final String repoUrl, final String source, final String target) { + final List keys; + synchronized (myLock) { + keys = (List) myForSearchMap.get(repoUrl); + } + // keys are never removed, so we can use 2 synchronized blocks + final String sourceMatching = getMatchingUrl(keys, source); + final String targetMatching = getMatchingUrl(keys, target); + + if (sourceMatching == null && targetMatching == null) return null; + + synchronized (myLock) { + final TreeMap map = myPersistentMap.get(repoUrl); + + final boolean sourceIsOut = sourceMatching == null; + if (sourceIsOut || targetMatching == null) { + // if found by "target" url - we correctly thought that target of copy is target + return sourceIsOut ? new WrapperInvertor(false, map.get(targetMatching)) : + new WrapperInvertor(true, map.get(sourceMatching)); + } + final BranchCopyData sourceData = map.get(sourceMatching); + final BranchCopyData targetData = map.get(targetMatching); + + final boolean inverted = sourceData.getTargetRevision() > targetData.getTargetRevision(); + return new WrapperInvertor(inverted, inverted ? sourceData : targetData); + } + } + + @Nullable + private String getMatchingUrl(List keys, String source) { + final int idx = Collections.binarySearch(keys, source); + if (idx >= 0) return keys.get(idx); + final int beforeInsertionIdx = - idx - 2; + if (beforeInsertionIdx < 0) return null; + final String candidate = keys.get(beforeInsertionIdx); + if (source.startsWith(candidate)) return candidate; + return null; + } + } + + private static class Loader implements Convertor> { + private SvnVcs17 myVcs; + + private Loader(final Project project) { + myVcs = SvnVcs17.getInstance(project); + } + + public WrapperInvertor convert(final KeyData keyData) { + final Ref> result = new Ref>(); + + new FirstInBranch(myVcs, keyData.getRepoUrl(), keyData.getTargetUrl(), keyData.getSourceUrl(), new Consumer() { + public void consume(CopyData copyData) { + if (copyData != null) { + final boolean correct = copyData.isTrunkSupposedCorrect(); + final BranchCopyData branchCopyData; + if (correct) { + branchCopyData = new BranchCopyData(keyData.getSourceUrl(), copyData.getCopySourceRevision(), keyData.getTargetUrl(), + copyData.getCopyTargetRevision()); + } else { + branchCopyData = new BranchCopyData(keyData.getTargetUrl(), copyData.getCopySourceRevision(), keyData.getSourceUrl(), + copyData.getCopyTargetRevision()); + } + result.set(new WrapperInvertor(! correct, branchCopyData)); + } + } + }).run(); + + final WrapperInvertor invertor = result.get(); + if (LOG.isDebugEnabled()) { + LOG.debug("Loader17 returned: for key: " + keyData.toString() + " result: " + (invertor == null ? null : invertor.toString())); + } + return invertor; + } + } + + private static class KeyData { + private final String myRepoUrl; + private final String mySourceUrl; + private final String myTargetUrl; + + public KeyData(final String repoUID, final String sourceUrl, final String targetUrl) { + myRepoUrl = repoUID; + mySourceUrl = sourceUrl; + myTargetUrl = targetUrl; + } + + public String getRepoUrl() { + return myRepoUrl; + } + + public String getSourceUrl() { + return mySourceUrl; + } + + public String getTargetUrl() { + return myTargetUrl; + } + + @Override + public String toString() { + return "repoURL: " + myRepoUrl + " sourceUrl:" + mySourceUrl + " targetUrl: " + myTargetUrl; + } + } + + public static class BranchCopyData implements Invertor { + private final String mySource; + private final String myTarget; + private final long mySourceRevision; + private final long myTargetRevision; + + public BranchCopyData(String source, long sourceRevision, String target, long targetRevision) { + mySource = source; + mySourceRevision = sourceRevision; + myTarget = target; + myTargetRevision = targetRevision; + } + + @Override + public String toString() { + return "source: " + mySource + "@" + mySourceRevision + " target: " + myTarget + "@" + myTargetRevision; + } + + public String getSource() { + return mySource; + } + + public long getSourceRevision() { + return mySourceRevision; + } + + public String getTarget() { + return myTarget; + } + + public long getTargetRevision() { + return myTargetRevision; + } + + public BranchCopyData invertSelf() { + return new BranchCopyData(myTarget, myTargetRevision, mySource, mySourceRevision); + } + } + + private interface Invertor { + T invertSelf(); + } + + public void getFirstCopyPoint(final String repoUID, final String sourceUrl, final String targetUrl, Consumer> consumer) { + myCalculator.get(new KeyData(repoUID, sourceUrl, targetUrl), consumer); + } + + public TaskDescriptor getFirstCopyPointTask(final String repoUID, final String sourceUrl, final String targetUrl, + final Consumer> consumer) { + return myCalculator.getTask(new KeyData(repoUID, sourceUrl, targetUrl), consumer); + } + + public static abstract class CopyPointAcceptorTask extends TaskDescriptor implements Consumer> { + protected CopyPointAcceptorTask(String name, @NotNull Where where) { + super(name, where); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SvnChangeListsPageEngine.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SvnChangeListsPageEngine.java new file mode 100644 index 000000000000..169f2304e3af --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SvnChangeListsPageEngine.java @@ -0,0 +1,56 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.vcs.CollectionSplitter; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; + +import java.util.List; + +public class SvnChangeListsPageEngine implements PageEngine> { + private List> mySplitData; + private int myIdx; + + public SvnChangeListsPageEngine(final List data, final int pageSize) { + final CollectionSplitter splitter = new CollectionSplitter(pageSize); + mySplitData = splitter.split(data); + myIdx = 0; + } + + public List getCurrent() { + return mySplitData.get(myIdx); + } + + public boolean hasNext() { + return myIdx < (mySplitData.size() - 1); + } + + public boolean hasPrevious() { + return myIdx > 0; + } + + public List next() { + if (! hasNext()) return null; + ++ myIdx; + return mySplitData.get(myIdx); + } + + public List previous() { + if (! hasPrevious()) return null; + -- myIdx; + return mySplitData.get(myIdx); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SvnFormatWorker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SvnFormatWorker.java new file mode 100644 index 000000000000..0782063fef56 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SvnFormatWorker.java @@ -0,0 +1,147 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.vcs.AbstractVcsHelper; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.changes.LocalChangeList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnUtil; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.WorkingCopyFormat; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +public class SvnFormatWorker extends Task.Backgroundable { + private List myExceptions; + private final Project myProject; + private final WorkingCopyFormat myNewFormat; + private final List myWcInfos; + private List myBeforeChangeLists; + private final SvnVcs17 myVcs; + + public SvnFormatWorker(final Project project, final WorkingCopyFormat newFormat, final List wcInfos) { + super(project, SvnBundle.message("action.change.wcopy.format.task.title"), false, DEAF); + myProject = project; + myNewFormat = newFormat; + myExceptions = new ArrayList(); + myWcInfos = wcInfos; + myVcs = SvnVcs17.getInstance(myProject); + } + + public SvnFormatWorker(final Project project, final WorkingCopyFormat newFormat, final WCInfo wcInfo) { + this(project, newFormat, Collections.singletonList(wcInfo)); + } + + public void checkForOutsideCopies() { + boolean canceled = false; + for (Iterator iterator = myWcInfos.iterator(); iterator.hasNext();) { + final WCInfo wcInfo = iterator.next(); + if (! wcInfo.isIsWcRoot()) { + File path = new File(wcInfo.getPath()); + path = SvnUtil.getWorkingCopyRoot(path); + int result = Messages.showYesNoCancelDialog(SvnBundle.message("upgrade.format.clarify.for.outside.copies.text", path), + SvnBundle.message("action.change.wcopy.format.task.title"), + Messages.getWarningIcon()); + if (DialogWrapper.CANCEL_EXIT_CODE == result) { + canceled = true; + break; + } else if (DialogWrapper.OK_EXIT_CODE != result) { + // no - for this copy only. maybe other + iterator.remove(); + } + } + } + if (canceled) { + myWcInfos.clear(); + } + } + + public boolean haveStuffToConvert() { + return ! myWcInfos.isEmpty(); + } + + @Override + public void onCancel() { + onSuccess(); + } + + @Override + public void onSuccess() { + if (myProject.isDisposed()) { + return; + } + + if (! myExceptions.isEmpty()) { + final List messages = new ArrayList(); + for (Throwable exception : myExceptions) { + messages.add(exception.getMessage()); + } + AbstractVcsHelper.getInstance(myProject) + .showErrors(Collections.singletonList(new VcsException(messages)), SvnBundle.message("action.change.wcopy.format.task.title")); + } + } + + public void run(@NotNull final ProgressIndicator indicator) { + ProjectLevelVcsManager.getInstanceChecked(myProject).startBackgroundVcsOperation(); + indicator.setIndeterminate(true); + final boolean supportsChangelists = myNewFormat.supportsChangelists(); + if (supportsChangelists) { + myBeforeChangeLists = ChangeListManager.getInstance(myProject).getChangeListsCopy(); + } + final SVNWCClient wcClient = myVcs.createWCClient(); + + try { + for (WCInfo wcInfo : myWcInfos) { + File path = new File(wcInfo.getPath()); + if (! wcInfo.isIsWcRoot()) { + path = SvnUtil.getWorkingCopyRoot(path); + } + indicator.setText(SvnBundle.message("action.change.wcopy.format.task.progress.text", path.getAbsolutePath(), + SvnUtil.formatRepresentation(wcInfo.getFormat()), SvnUtil.formatRepresentation(myNewFormat))); + try { + wcClient.doSetWCFormat(path, myNewFormat.getFormat()); + } catch (Throwable e) { + myExceptions.add(e); + } + } + } + finally { + ProjectLevelVcsManager.getInstance(myProject).stopBackgroundVcsOperation(); + + // to map to native + if (supportsChangelists) { + SvnVcs17.getInstance(myProject).processChangeLists(myBeforeChangeLists); + } + + ApplicationManager.getApplication().getMessageBus().syncPublisher(SvnVcs17.WC_CONVERTED).run(); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SvnInteractiveAuthenticationProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SvnInteractiveAuthenticationProvider.java new file mode 100644 index 000000000000..7323a2ae2628 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SvnInteractiveAuthenticationProvider.java @@ -0,0 +1,232 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier; +import com.intellij.util.SystemProperties; +import com.intellij.util.WaitForProgressToShow; +import org.jetbrains.idea.svn17.SvnAuthenticationManager; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnConfiguration17; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.auth.ProviderType; +import org.tmatesoft.svn.core.SVNErrorMessage; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.auth.*; + +import java.io.File; +import java.security.cert.X509Certificate; + +public class SvnInteractiveAuthenticationProvider implements ISVNAuthenticationProvider { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.dialogs.SvnInteractiveAuthenticationProvider"); + private final Project myProject; + private static final ThreadLocal myCallState = new ThreadLocal(); + private final SvnAuthenticationManager myManager; + + public SvnInteractiveAuthenticationProvider(final SvnVcs17 vcs, SvnAuthenticationManager manager) { + myManager = manager; + myProject = vcs.getProject(); + } + + public static void clearCallState() { + myCallState.set(null); + } + + public static boolean wasCalled() { + return myCallState.get() != null && myCallState.get().isWasCalled(); + } + + public static boolean wasCancelled() { + return myCallState.get() != null && myCallState.get().isWasCancelled(); + } + + public SVNAuthentication requestClientAuthentication(final String kind, + final SVNURL url, + final String realm, + SVNErrorMessage errorMessage, + final SVNAuthentication previousAuth, + final boolean authMayBeStored) { + final MyCallState callState = new MyCallState(true, false); + myCallState.set(callState); + // once we came here, we don't know _correct_ auth todo +- + final SvnConfiguration17 configuration = SvnConfiguration17.getInstance(myProject); + configuration.clearCredentials(kind, realm); + + final SVNAuthentication[] result = new SVNAuthentication[1]; + Runnable command = null; + + final boolean authCredsOn = authMayBeStored && myManager.getHostOptionsProvider().getHostOptions(url).isAuthStorageEnabled(); + + final String userName = + previousAuth != null && previousAuth.getUserName() != null ? previousAuth.getUserName() : SystemProperties.getUserName(); + if (ISVNAuthenticationManager.PASSWORD.equals(kind)) {// || ISVNAuthenticationManager.USERNAME.equals(kind)) { + command = new Runnable() { + public void run() { + SimpleCredentialsDialog dialog = new SimpleCredentialsDialog(myProject); + dialog.setup(realm, userName, authCredsOn); + if (previousAuth == null) { + dialog.setTitle(SvnBundle.message("dialog.title.authentication.required")); + } + else { + dialog.setTitle(SvnBundle.message("dialog.title.authentication.required.was.failed")); + } + dialog.show(); + if (dialog.isOK()) { + result[0] = new SVNPasswordAuthentication(dialog.getUserName(), dialog.getPassword(), dialog.isSaveAllowed(), url, false); + } + } + }; + } + else if (ISVNAuthenticationManager.USERNAME.equals(kind)) { + if (ApplicationManager.getApplication().isUnitTestMode()) { + return new SVNUserNameAuthentication(userName, false); + } + command = new Runnable() { + public void run() { + UserNameCredentialsDialog dialog = new UserNameCredentialsDialog(myProject); + dialog.setup(realm, userName, authCredsOn); + if (previousAuth == null) { + dialog.setTitle(SvnBundle.message("dialog.title.authentication.required")); + } + else { + dialog.setTitle(SvnBundle.message("dialog.title.authentication.required.was.failed")); + } + dialog.show(); + if (dialog.isOK()) { + result[0] = new SVNUserNameAuthentication(dialog.getUserName(), dialog.isSaveAllowed(), url, false); + } + } + }; + } + else if (ISVNAuthenticationManager.SSH.equals(kind)) { + command = new Runnable() { + public void run() { + SSHCredentialsDialog dialog = new SSHCredentialsDialog(myProject, realm, userName, authCredsOn, url.getPort()); + if (previousAuth == null) { + dialog.setTitle(SvnBundle.message("dialog.title.authentication.required")); + } + else { + dialog.setTitle(SvnBundle.message("dialog.title.authentication.required.was.failed")); + } + dialog.show(); + if (dialog.isOK()) { + int port = dialog.getPortNumber(); + if (dialog.getKeyFile() != null && dialog.getKeyFile().trim().length() > 0) { + String passphrase = dialog.getPassphrase(); + if (passphrase != null && passphrase.length() == 0) { + passphrase = null; + } + result[0] = + new SVNSSHAuthentication(dialog.getUserName(), new File(dialog.getKeyFile()), passphrase, port, dialog.isSaveAllowed(), + url, false); + } + else { + result[0] = new SVNSSHAuthentication(dialog.getUserName(), dialog.getPassword(), port, dialog.isSaveAllowed(), url, false); + } + } + } + }; + } else if (ISVNAuthenticationManager.SSL.equals(kind)) { + command = new Runnable() { + public void run() { + final SSLCredentialsDialog dialog = new SSLCredentialsDialog(myProject, realm, authCredsOn); + if (previousAuth == null) { + dialog.setTitle(SvnBundle.message("dialog.title.authentication.required")); + } + else { + dialog.setTitle(SvnBundle.message("dialog.title.authentication.required.was.failed")); + } + dialog.show(); + if (dialog.isOK()) { + result[0] = new SVNSSLAuthentication(new File(dialog.getCertificatePath()), String.valueOf(dialog.getCertificatePassword()), + dialog.getSaveAuth(), url, false); + } + } + }; + } + + if (command != null) { + WaitForProgressToShow.runOrInvokeAndWaitAboveProgress(command); + log("3 authentication result: " + result[0]); + } + + final boolean wasCanceled = result[0] == null; + callState.setWasCancelled(wasCanceled); + myManager.requested(ProviderType.interactive, url, realm, kind, wasCanceled); + return result[0]; + } + + public int acceptServerAuthentication(final SVNURL url, String realm, final Object certificate, final boolean resultMayBeStored) { + final int[] result = new int[1]; + Runnable command; + if (certificate instanceof X509Certificate) { + command = new Runnable() { + public void run() { + ServerSSLDialog dialog = new ServerSSLDialog(myProject, (X509Certificate)certificate, resultMayBeStored); + dialog.show(); + result[0] = dialog.getResult(); + } + }; + } else if (certificate instanceof byte[]) { + final String sshKeyAlgorithm = myManager.getSSHKeyAlgorithm(); + command = new Runnable() { + @Override + public void run() { + final ServerSSHDialog serverSSHDialog = + new ServerSSHDialog(myProject, resultMayBeStored, url.toDecodedString(), sshKeyAlgorithm, (byte[])certificate); + serverSSHDialog.show(); + result[0] = serverSSHDialog.getResult(); + } + }; + } else { + VcsBalloonProblemNotifier.showOverChangesView(myProject, "Subversion: unknown certificate type from " + url.toDecodedString(), + MessageType.ERROR); + return REJECTED; + } + WaitForProgressToShow.runOrInvokeAndWaitAboveProgress(command); + return result[0]; + } + + private void log(final String s) { + LOG.debug(s); + } + + public static class MyCallState { + private final boolean myWasCalled; + private boolean myWasCancelled; + + public MyCallState(boolean wasCalled, boolean wasCancelled) { + myWasCalled = wasCalled; + myWasCancelled = wasCancelled; + } + + public boolean isWasCalled() { + return myWasCalled; + } + + public boolean isWasCancelled() { + return myWasCancelled; + } + + public void setWasCancelled(boolean wasCancelled) { + myWasCancelled = wasCancelled; + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SvnRepositoryTreeCellRenderer.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SvnRepositoryTreeCellRenderer.java new file mode 100644 index 000000000000..cff2431be37f --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/SvnRepositoryTreeCellRenderer.java @@ -0,0 +1,83 @@ +/* + * 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.dialogs; + +import com.intellij.CommonBundle; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.ui.ColoredTreeCellRenderer; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.util.PlatformIcons; +import com.intellij.util.text.DateFormatUtil; +import org.tmatesoft.svn.core.SVNDirEntry; +import org.tmatesoft.svn.core.SVNErrorMessage; +import org.tmatesoft.svn.core.SVNNodeKind; + +import javax.swing.*; +import javax.swing.tree.DefaultMutableTreeNode; + +public class SvnRepositoryTreeCellRenderer extends ColoredTreeCellRenderer { + + private boolean myIsShowDetails; + + + public void customizeCellRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { + setIcon(null); + if (value instanceof RepositoryTreeNode) { + RepositoryTreeNode node = (RepositoryTreeNode) value; + if (node.getSVNDirEntry() == null) { + append(node.getURL().toString(), SimpleTextAttributes.REGULAR_ATTRIBUTES); + setDirectoryIcon(expanded); + } else { + String name = node.getSVNDirEntry().getName(); + append(name, node.isCached() ? SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES : SimpleTextAttributes.REGULAR_ATTRIBUTES); + if (myIsShowDetails) { + SVNDirEntry entry = node.getSVNDirEntry(); + append(" " + entry.getRevision(), SimpleTextAttributes.GRAY_ATTRIBUTES); + if (entry.getAuthor() != null) { + append(" " + entry.getAuthor(), SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES); + } + if (entry.getDate() != null) { + append(" " + DateFormatUtil.formatPrettyDateTime(entry.getDate()), SimpleTextAttributes.GRAY_ATTRIBUTES); + } + } + if (node.getSVNDirEntry().getKind() == SVNNodeKind.FILE) { + setIcon(FileTypeManager.getInstance().getFileTypeByFileName(name).getIcon()); + } else { + setDirectoryIcon(expanded); + } + } + } else if (value instanceof DefaultMutableTreeNode) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) value; + if (node.getUserObject() instanceof String) { + append(CommonBundle.getLoadingTreeNodeText(), SimpleTextAttributes.REGULAR_ATTRIBUTES); + } else if (node.getUserObject() instanceof SVNErrorMessage) { + append(node.getUserObject().toString(), SimpleTextAttributes.ERROR_ATTRIBUTES); + } + } + } + + private void setDirectoryIcon(final boolean expanded) { + if (expanded) { + setIcon(PlatformIcons.DIRECTORY_OPEN_ICON); + } else { + setIcon(PlatformIcons.DIRECTORY_CLOSED_ICON); + } + } + + public void setShowDetails(boolean state) { + myIsShowDetails = state; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ToBeMergedDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ToBeMergedDialog.java new file mode 100644 index 000000000000..b398ed9a3d1b --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/ToBeMergedDialog.java @@ -0,0 +1,402 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonShortcuts; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Splitter; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vcs.ObjectsConvertor; +import com.intellij.openapi.vcs.QuantitySelection; +import com.intellij.openapi.vcs.SelectionResult; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.committed.CommittedChangeListDecorator; +import com.intellij.openapi.vcs.changes.committed.CommittedChangeListRenderer; +import com.intellij.openapi.vcs.changes.committed.CommittedChangesTreeBrowser; +import com.intellij.openapi.vcs.changes.committed.RepositoryChangesBrowser; +import com.intellij.openapi.vcs.changes.issueLinks.AbstractBaseTagMouseListener; +import com.intellij.openapi.vcs.changes.ui.ChangeNodeDecorator; +import com.intellij.openapi.vcs.changes.ui.ChangesBrowserNodeRenderer; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.ui.ColoredTreeCellRenderer; +import com.intellij.ui.SimpleColoredComponent; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.ui.TableViewSpeedSearch; +import com.intellij.ui.table.TableView; +import com.intellij.util.containers.Convertor; +import com.intellij.util.ui.ColumnInfo; +import com.intellij.util.ui.ListTableModel; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.idea.svn17.history.SvnChangeList; +import org.jetbrains.idea.svn17.mergeinfo.MergeChecker; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.TableCellRenderer; +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import java.util.List; + +public class ToBeMergedDialog extends DialogWrapper { + public static final int MERGE_ALL_CODE = 222; + private final JPanel myPanel; + private final Project myProject; + private final PageEngine> myListsEngine; + private TableView myRevisionsList; + private RepositoryChangesBrowser myRepositoryChangesBrowser; + private Splitter mySplitter; + + private final QuantitySelection myWiseSelection; + + private final Set myAlreadyMerged; + private final MergeChecker myMergeChecker; + + public ToBeMergedDialog(final Project project, final List lists, final String title, final MergeChecker mergeChecker) { + super(project, true); + myMergeChecker = mergeChecker; + setTitle(title); + myProject = project; + + // todo removing pages for a while + //myListsEngine = new BasePageEngine(lists, ourPageSize); + myListsEngine = new BasePageEngine(lists, lists.size()); + + myPanel = new JPanel(new BorderLayout()); + myWiseSelection = new QuantitySelection(true); + myAlreadyMerged = new HashSet(); + setOKButtonText("Merge Selected"); + initUI(); + init(); + } + + @Override + protected Action[] createActions() { + return new Action[]{getOKAction(), new DialogWrapperAction("Merge All") { + @Override + protected void doAction(ActionEvent e) { + close(MERGE_ALL_CODE); + } + }, getCancelAction()}; + } + + public List getSelected() { + final SelectionResult selected = myWiseSelection.getSelected(); + final SelectionResult unselected = myWiseSelection.getUnselected(); + + final List result = new LinkedList(); + result.addAll(myListsEngine.getCurrent()); + while (myListsEngine.hasNext()) { + result.addAll(myListsEngine.next()); + } + // todo: can be made faster + if (selected.isAll()) { + final Set excluded = unselected.getMarked(); + for (Iterator iterator = result.iterator(); iterator.hasNext();) { + final CommittedChangeList list = iterator.next(); + if (excluded.contains(list.getNumber())) iterator.remove(); + } + } else { + final Set included = selected.getMarked(); + for (Iterator iterator = result.iterator(); iterator.hasNext();) { + final CommittedChangeList list = iterator.next(); + if (! included.contains(list.getNumber())) iterator.remove(); + } + } + return result; + } + + @Override + protected String getDimensionServiceKey() { + return "org.jetbrains.idea.svn.dialogs.ToBeMergedDialog"; + } + + private void initUI() { + final ListSelectionListener selectionListener = new ListSelectionListener() { + public void valueChanged(ListSelectionEvent e) { + final List objects = myRevisionsList.getSelectedObjects(); + myRepositoryChangesBrowser.setChangesToDisplay(Collections.emptyList()); + myAlreadyMerged.clear(); + if (objects != null && (!objects.isEmpty())) { + final List lists = + ObjectsConvertor.convert(objects, new Convertor() { + public CommittedChangeList convert(Object o) { + if (o instanceof CommittedChangeList) { + final CommittedChangeList cl = (CommittedChangeList)o; + final Collection notMerged = myMergeChecker.getNotMergedPaths(cl.getNumber()); + final SvnChangeList svnList = (SvnChangeList)cl; + + final Collection forCheck = new HashSet(); + forCheck.addAll(svnList.getAddedPaths()); + forCheck.addAll(svnList.getChangedPaths()); + forCheck.addAll(svnList.getDeletedPaths()); + for (String path : forCheck) { + if ((notMerged != null) && (!notMerged.isEmpty()) && !notMerged.contains(path)) { + myAlreadyMerged.add(((SvnChangeList)cl).getByPath(path)); + } + } + return cl; + } + return null; + } + }, ObjectsConvertor.NOT_NULL); + final List changes = CommittedChangesTreeBrowser.collectChanges(lists, false); + myRepositoryChangesBrowser.setChangesToDisplay(changes); + } + + mySplitter.doLayout(); + myRepositoryChangesBrowser.repaint(); + } + }; + final MyListCellRenderer listCellRenderer = new MyListCellRenderer(); + myRevisionsList = new TableView() { + @Override + public TableCellRenderer getCellRenderer(int row, int column) { + return listCellRenderer; + } + + @Override + public void valueChanged(ListSelectionEvent e) { + super.valueChanged(e); + selectionListener.valueChanged(e); + } + }; + myRevisionsList.getExpandableItemsHandler().setEnabled(false); + new TableViewSpeedSearch(myRevisionsList) { + @Override + protected String getElementText(Object element) { + if (element instanceof CommittedChangeList) { + return ((CommittedChangeList) element).getComment(); + } + return null; + } + }; + final ListTableModel flatModel = new ListTableModel(FAKE_COLUMN); + myRevisionsList.setModel(flatModel); + myRevisionsList.setTableHeader(null); + myRevisionsList.setShowGrid(false); + final AbstractBaseTagMouseListener mouseListener = new AbstractBaseTagMouseListener() { + @Override + protected Object getTagAt(MouseEvent e) { + Object tag = null; + JTable table = (JTable)e.getSource(); + int row = table.rowAtPoint(e.getPoint()); + int column = table.columnAtPoint(e.getPoint()); + if (row == -1 || column == -1) return null; + listCellRenderer.customizeCellRenderer(table, table.getValueAt(row, column), table.isRowSelected(row), false, row, column); + final ColoredTreeCellRenderer renderer = listCellRenderer.myRenderer; + final Rectangle rc = table.getCellRect(row, column, false); + int index = renderer.findFragmentAt(e.getPoint().x - rc.x); + if (index >= 0) { + tag = renderer.getFragmentTag(index); + } + return tag; + } + }; + mouseListener.install(myRevisionsList); + + final PagedListWithActions.InnerComponentManager listsManager = + new PagedListWithActions.InnerComponentManager() { + public Component getComponent() { + return myRevisionsList; + } + public void setData(List committedChangeLists) { + flatModel.setItems(committedChangeLists); + flatModel.fireTableDataChanged(); + } + public void refresh() { + myRevisionsList.revalidate(); + myRevisionsList.repaint(); + } + }; + final PagedListWithActions byRevisions = + new PagedListWithActions(myListsEngine, listsManager, new MySelectAll(), new MyUnselectAll()); + + mySplitter = new Splitter(false, 0.7f); + mySplitter.setFirstComponent(byRevisions.getComponent()); + + flatModel.setItems(myListsEngine.getCurrent()); + flatModel.fireTableDataChanged(); + + myRepositoryChangesBrowser = new RepositoryChangesBrowser(myProject, Collections.emptyList(), Collections.emptyList(), null); + myRepositoryChangesBrowser.getDiffAction().registerCustomShortcutSet(CommonShortcuts.getDiff(), myRevisionsList); + setChangesDecorator(); + mySplitter.setSecondComponent(myRepositoryChangesBrowser); + mySplitter.setDividerWidth(2); + + addRevisionListListeners(); + + myPanel.add(mySplitter, BorderLayout.CENTER); + } + + private void setChangesDecorator() { + myRepositoryChangesBrowser.setDecorator(new ChangeNodeDecorator() { + public void decorate(Change change, SimpleColoredComponent component, boolean isShowFlatten) { + } + public List> stressPartsOfFileName(Change change, String parentPath) { + return null; + } + public void preDecorate(Change change, ChangesBrowserNodeRenderer renderer, boolean showFlatten) { + if (myAlreadyMerged.contains(change)) { + renderer.append(" [already merged] ", SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); + } + } + }); + } + + private void addRevisionListListeners() { + final int checkboxWidth = new JCheckBox().getPreferredSize().width; + myRevisionsList.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + final int idx = myRevisionsList.rowAtPoint(e.getPoint()); + if (idx >= 0) { + final Rectangle baseRect = myRevisionsList.getCellRect(idx, 0, false); + baseRect.setSize(checkboxWidth, baseRect.height); + if (baseRect.contains(e.getPoint())) { + final SvnChangeList changeList = (SvnChangeList)myRevisionsList.getModel().getValueAt(idx, 0); + final long number = changeList.getNumber(); + toggleInclusion(number); + myRevisionsList.repaint(baseRect); + e.consume(); + } + } + } + }); + myRevisionsList.addKeyListener(new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + if (KeyEvent.VK_SPACE == e.getKeyCode()) { + final List selected = myRevisionsList.getSelectedObjects(); + if (selected == null || selected.isEmpty()) return; + + for (Object o : selected) { + if (o instanceof SvnChangeList) { + final SvnChangeList changeList = (SvnChangeList) o; + toggleInclusion(changeList.getNumber()); + } + } + myRevisionsList.repaint(); + e.consume(); + } + } + }); + } + + private void toggleInclusion(final long number) { + if (myWiseSelection.isSelected(number)) { + myWiseSelection.remove(number); + } else { + myWiseSelection.add(number); + } + } + + @Override + protected JComponent createCenterPanel() { + return myPanel; + } + + private class MySelectAll extends DumbAwareAction { + private MySelectAll() { + super("Select All", "Select All", IconLoader.getIcon("/actions/selectall.png")); + } + + @Override + public void actionPerformed(AnActionEvent e) { + myWiseSelection.setAll(); + myRevisionsList.repaint(); + } + } + + private class MyUnselectAll extends DumbAwareAction { + private MyUnselectAll() { + super("Unselect All", "Unselect All", IconLoader.getIcon("/actions/unselectall.png")); + } + + @Override + public void actionPerformed(AnActionEvent e) { + myWiseSelection.clearAll(); + myRevisionsList.repaint(); + } + } + + private class MyListCellRenderer implements TableCellRenderer { + private final JPanel myPanel; + private final CommittedChangeListRenderer myRenderer; + private JCheckBox myCheckBox; + + private MyListCellRenderer() { + myPanel = new JPanel(new BorderLayout()); + myCheckBox = new JCheckBox(); + myCheckBox.setEnabled(true); + myCheckBox.setSelected(true); + myRenderer = new CommittedChangeListRenderer(myProject, Collections.emptyList()); + } + + protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) { + myPanel.removeAll(); + myPanel.setBackground(null); + myRenderer.clear(); + myRenderer.setBackground(null); + + // 7-8, a hack + if (value instanceof SvnChangeList) { + final SvnChangeList changeList = (SvnChangeList)value; + myRenderer.renderChangeList(table, changeList); + + final Color bg = selected ? UIUtil.getTableSelectionBackground() : UIUtil.getTableBackground(); + final Color fg = selected ? UIUtil.getTableSelectionForeground() : UIUtil.getTableForeground(); + + myRenderer.setBackground(bg); + myRenderer.setForeground(fg); + myCheckBox.setBackground(bg); + myCheckBox.setForeground(fg); + + myPanel.setBackground(bg); + myPanel.setForeground(fg); + + myCheckBox.setSelected(myWiseSelection.isSelected(changeList.getNumber())); + myPanel.add(myCheckBox, BorderLayout.WEST); + myPanel.add(myRenderer, BorderLayout.CENTER); + } + } + + public final Component getTableCellRendererComponent( + JTable table, + Object value, + boolean isSelected, + boolean hasFocus, + int row, + int column + ){ + customizeCellRenderer(table, value, isSelected, hasFocus, row, column); + return myPanel; + } + } + + private final static int ourPageSize = 30; + + private static final ColumnInfo FAKE_COLUMN = new ColumnInfo("fake column"){ + @Override + public CommittedChangeList valueOf(CommittedChangeList committedChangeList) { + return committedChangeList; + } + }; +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/UpgradeFormatDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/UpgradeFormatDialog.java new file mode 100644 index 000000000000..ce2580e197ce --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/UpgradeFormatDialog.java @@ -0,0 +1,179 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.application.ApplicationNamesInfo; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.MultiLineLabelUI; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnConfiguration17; +import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; + +import javax.swing.*; +import java.awt.*; +import java.io.File; + +public class UpgradeFormatDialog extends DialogWrapper { + private JRadioButton myUpgradeNoneButton; + private JRadioButton myUpgradeAutoButton; + private JRadioButton myUpgradeAuto15Button; + private JRadioButton myUpgradeAuto16Button; + private JRadioButton myUpgradeAuto17Button; + + protected File myPath; + + public UpgradeFormatDialog(Project project, File path, boolean canBeParent) { + this(project, path, canBeParent, true); + } + + protected UpgradeFormatDialog(Project project, File path, boolean canBeParent, final boolean initHere) { + super(project, canBeParent); + myPath = path; + setResizable(false); + setTitle(SvnBundle.message("dialog.upgrade.wcopy.format.title")); + + if (initHere) { + init(); + } + } + + protected Action[] createActions() { + return new Action[]{getOKAction(), getCancelAction()}; + } + + @NonNls + protected String getDimensionServiceKey() { + return "svn.upgradeDialog"; + } + + public void setData(final boolean display13format, final String selectedFormat) { + if (SvnConfiguration17.UPGRADE_AUTO_17.equals(selectedFormat)) { + myUpgradeAuto17Button.setSelected(true); + } else if (SvnConfiguration17.UPGRADE_AUTO_16.equals(selectedFormat)) { + myUpgradeAuto16Button.setSelected(true); + } else if (SvnConfiguration17.UPGRADE_AUTO.equals(selectedFormat)) { + myUpgradeAutoButton.setSelected(true); + } else if (SvnConfiguration17.UPGRADE_AUTO_15.equals(selectedFormat)) { + myUpgradeAuto15Button.setSelected(true); + } else { + myUpgradeNoneButton.setSelected(true); + } + myUpgradeNoneButton.setVisible(display13format); + if (myUpgradeNoneButton.isSelected() && (! display13format)) { + myUpgradeAutoButton.setSelected(true); + } + } + + protected String getTopMessage(final String label) { + return SvnBundle.message(new StringBuilder().append("label.configure.").append(label).append(".label").toString(), + ApplicationNamesInfo.getInstance().getFullProductName()); + } + + @Nullable + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(); + panel.setLayout(new GridBagLayout()); + + GridBagConstraints gb = new GridBagConstraints(); + + + // top label. + gb.insets = new Insets(2, 2, 2, 2); + gb.weightx = 1; + gb.weighty = 0; + gb.gridwidth = 2; + gb.gridheight = 1; + gb.gridx = 0; + gb.gridy = 0; + gb.anchor = GridBagConstraints.WEST; + gb.fill = GridBagConstraints.HORIZONTAL; + + File adminPath = new File(myPath, SVNFileUtil.getAdminDirectoryName()); + final boolean adminPathIsDirectory = adminPath.isDirectory(); + final String label = getMiddlePartOfResourceKey(adminPathIsDirectory); + + JLabel topLabel = new JLabel(getTopMessage(label)); + topLabel.setUI(new MultiLineLabelUI()); + panel.add(topLabel, gb); + gb.gridy += 1; + + myUpgradeNoneButton = new JRadioButton(SvnBundle.message(new StringBuilder().append("radio.configure.").append(label).append(".none").toString())); + myUpgradeAutoButton = new JRadioButton(SvnBundle.message(new StringBuilder().append("radio.configure.").append(label).append(".auto").toString())); + myUpgradeAuto15Button = new JRadioButton(SvnBundle.message(new StringBuilder().append("radio.configure.").append(label).append(".auto.15format").toString())); + myUpgradeAuto16Button = new JRadioButton(SvnBundle.message(new StringBuilder().append("radio.configure.").append(label).append(".auto.16format").toString())); + myUpgradeAuto17Button = new JRadioButton(SvnBundle.message(new StringBuilder().append("radio.configure.").append(label).append(".auto.17format").toString())); + + ButtonGroup group = new ButtonGroup(); + group.add(myUpgradeNoneButton); + group.add(myUpgradeAutoButton); + group.add(myUpgradeAuto15Button); + group.add(myUpgradeAuto16Button); + group.add(myUpgradeAuto17Button); + panel.add(myUpgradeNoneButton, gb); + gb.gridy += 1; + panel.add(myUpgradeAutoButton, gb); + gb.gridy += 1; + panel.add(myUpgradeAuto15Button, gb); + gb.gridy += 1; + panel.add(myUpgradeAuto16Button, gb); + gb.gridy += 1; + panel.add(myUpgradeAuto17Button, gb); + gb.gridy += 1; + + myUpgradeNoneButton.setSelected(true); + + final JPanel auxiliaryPanel = getBottomAuxiliaryPanel(); + if (auxiliaryPanel != null) { + panel.add(auxiliaryPanel, gb); + gb.gridy += 1; + } + + return panel; + } + + @Nullable + protected JPanel getBottomAuxiliaryPanel() { + return null; + } + + protected String getMiddlePartOfResourceKey(final boolean adminPathIsDirectory) { + return ! adminPathIsDirectory ? "create" : "upgrade"; + } + + protected boolean showHints() { + return true; + } + + @Nullable + public String getUpgradeMode() { + if (myUpgradeAuto17Button.isSelected()) { + return SvnConfiguration17.UPGRADE_AUTO_17; + } else if (myUpgradeNoneButton.isSelected()) { + return SvnConfiguration17.UPGRADE_NONE; + } else if (myUpgradeAutoButton.isSelected()) { + return SvnConfiguration17.UPGRADE_AUTO; + } else if (myUpgradeAuto15Button.isSelected()) { + return SvnConfiguration17.UPGRADE_AUTO_15; + } else if (myUpgradeAuto16Button.isSelected()) { + return SvnConfiguration17.UPGRADE_AUTO_16; + } + return null; + } + +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/UserNameCredentialsDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/UserNameCredentialsDialog.java new file mode 100644 index 000000000000..71c8915a52ce --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/UserNameCredentialsDialog.java @@ -0,0 +1,167 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.help.HelpManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.idea.svn17.SvnBundle; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.*; + +public class UserNameCredentialsDialog extends DialogWrapper implements DocumentListener { + private boolean myAllowSave; + private String myUserName; + + private String myRealm; + private JTextField myUserNameText; + private JCheckBox myAllowSaveCheckBox; + + @NonNls + private static final String HELP_ID = "vcs.subversion.authentication"; + + protected UserNameCredentialsDialog(Project project) { + super(project, true); + setResizable(false); + } + + public void setup(String realm, String userName, boolean allowSave) { + myRealm = realm; + myUserName = userName; + myAllowSave = allowSave; + getHelpAction().setEnabled(true); + init(); + } + protected void doHelpAction() { + HelpManager.getInstance().invokeHelp(HELP_ID); + } + + protected Action[] createActions() { + return new Action[]{getOKAction(), getCancelAction(), getHelpAction()}; + } + + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(); + panel.setLayout(new GridBagLayout()); + + GridBagConstraints gb = new GridBagConstraints(); + + // top label. + gb.insets = new Insets(2, 2, 2, 2); + gb.weightx = 1; + gb.weighty = 0; + gb.gridwidth = 2; + gb.gridheight = 1; + gb.gridx = 0; + gb.gridy = 0; + gb.anchor = GridBagConstraints.WEST; + gb.fill = GridBagConstraints.HORIZONTAL; + + JLabel label = new JLabel(SvnBundle.message("label.auth.authentication.realm", myRealm)); + panel.add(label, gb); + + // user name + gb.gridy += 1; + gb.gridwidth = 1; + gb.weightx = 0; + gb.fill = GridBagConstraints.NONE; + + label = new JLabel(SvnBundle.message("label.auth.user.name")); + panel.add(label, gb); + + // user name field + gb.gridx = 1; + gb.weightx = 1; + gb.fill = GridBagConstraints.HORIZONTAL; + + myUserNameText = new JTextField(); + panel.add(myUserNameText, gb); + label.setLabelFor(myUserNameText); + + if (myUserName != null) { + myUserNameText.setText(myUserName); + } + myUserNameText.selectAll(); + myUserNameText.getDocument().addDocumentListener(this); + + gb.gridy += 1; + gb.gridx = 0; + gb.gridwidth = 2; + gb.weightx = 1; + gb.fill = GridBagConstraints.HORIZONTAL; + myAllowSaveCheckBox = new JCheckBox(SvnBundle.message("checkbox.auth.keep.for.current.session")); + panel.add(myAllowSaveCheckBox, gb); + gb.gridy += 1; + // todo check needed + if (! myAllowSave) { + final JLabel cannotSaveLabel = new JLabel(SvnBundle.message("svn.cannot.save.credentials.store-auth-creds")); + cannotSaveLabel.setForeground(UIUtil.getInactiveTextColor()); + panel.add(cannotSaveLabel, gb); + gb.gridy += 1; + } + panel.add(new JSeparator(), gb); + + myAllowSaveCheckBox.setSelected(false); + myAllowSaveCheckBox.setEnabled(myAllowSave); + + return panel; + } + + protected String getDimensionServiceKey() { + return "svn.userNameDialog"; + } + + public JComponent getPreferredFocusedComponent() { + return myUserNameText; + } + + public boolean shouldCloseOnCross() { + return true; + } + + public boolean isOKActionEnabled() { + return myUserNameText != null && myUserNameText.getText().trim().length() != 0; + } + + public String getUserName() { + return isOK() && myUserNameText != null ? myUserNameText.getText() : null; + } + + public boolean isSaveAllowed() { + return isOK() && myAllowSave && myAllowSaveCheckBox != null && myAllowSaveCheckBox.isSelected(); + } + + public void insertUpdate(DocumentEvent e) { + updateOKButton(); + } + + public void removeUpdate(DocumentEvent e) { + updateOKButton(); + } + + public void changedUpdate(DocumentEvent e) { + updateOKButton(); + } + + private void updateOKButton() { + getOKAction().setEnabled(isOKActionEnabled()); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/WCInfo.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/WCInfo.java new file mode 100644 index 000000000000..6f30409beb73 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/WCInfo.java @@ -0,0 +1,100 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn17.NestedCopyType; +import org.jetbrains.idea.svn17.WorkingCopyFormat; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNURL; + +public class WCInfo implements WCPaths { + private final String myPath; + private final SVNURL myUrl; + private final WorkingCopyFormat myFormat; + private final String myRepositoryRoot; + private final boolean myIsWcRoot; + private final NestedCopyType myType; + private final SVNDepth myStickyDepth; + + public WCInfo(final String path, final SVNURL url, final WorkingCopyFormat format, final String repositoryRoot, final boolean isWcRoot, + final NestedCopyType type, SVNDepth stickyDepth) { + myPath = path; + myUrl = url; + myFormat = format; + myRepositoryRoot = repositoryRoot; + myIsWcRoot = isWcRoot; + myType = type; + myStickyDepth = stickyDepth; + } + + public SVNDepth getStickyDepth() { + return myStickyDepth; + } + + public String getPath() { + return myPath; + } + + public VirtualFile getVcsRoot() { + return null; + } + + public SVNURL getUrl() { + return myUrl; + } + + public String getRootUrl() { + return myUrl.toString(); + } + + public String getRepoUrl() { + return myRepositoryRoot; + } + + public WorkingCopyFormat getFormat() { + return myFormat; + } + + public String getRepositoryRoot() { + return myRepositoryRoot; + } + + public boolean isIsWcRoot() { + return myIsWcRoot; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof WCInfo)) return false; + + final WCInfo wcInfo = (WCInfo)o; + + if (myPath != null ? !myPath.equals(wcInfo.myPath) : wcInfo.myPath != null) return false; + + return true; + } + + @Override + public int hashCode() { + return (myPath != null ? myPath.hashCode() : 0); + } + + public NestedCopyType getType() { + return myType; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/WCInfoWithBranches.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/WCInfoWithBranches.java new file mode 100644 index 000000000000..1b33cf8e8be6 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/WCInfoWithBranches.java @@ -0,0 +1,105 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn17.NestedCopyType; +import org.jetbrains.idea.svn17.WorkingCopyFormat; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; + +import java.util.List; + +public class WCInfoWithBranches extends WCInfo { + private final List myBranches; + private final VirtualFile myRoot; + private final String myTrunkRoot; + + public WCInfoWithBranches(final String path, final SVNURL url, final WorkingCopyFormat format, final String repositoryRoot, final boolean isWcRoot, + final List branches, + final VirtualFile root, + final String trunkToot, + final NestedCopyType type, final SVNDepth depth) { + super(path, url, format, repositoryRoot, isWcRoot, type, depth); + myBranches = branches; + myRoot = root; + myTrunkRoot = trunkToot; + } + + // to be used in combo + @Override + public String toString() { + return getPath(); + } + + @Override + public VirtualFile getVcsRoot() { + return myRoot; + } + + public List getBranches() { + return myBranches; + } + + public VirtualFile getRoot() { + return myRoot; + } + + public String getTrunkRoot() { + return myTrunkRoot; + } + + public static class Branch { + private final String myName; + private final String myUrl; + + public Branch(final String url) { + myName = SVNPathUtil.tail(url); + myUrl = url; + } + + public String getName() { + return myName; + } + + public String getUrl() { + return myUrl; + } + + @Override + public String toString() { + return myName; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final Branch branch = (Branch)o; + + if (myUrl != null ? !myUrl.equals(branch.myUrl) : branch.myUrl != null) return false; + + return true; + } + + @Override + public int hashCode() { + return (myUrl != null ? myUrl.hashCode() : 0); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/WCPaths.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/WCPaths.java new file mode 100644 index 000000000000..40df3caadbae --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/WCPaths.java @@ -0,0 +1,25 @@ +/* + * 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.dialogs; + +import com.intellij.openapi.vfs.VirtualFile; + +public interface WCPaths { + String getRootUrl(); + String getRepoUrl(); + String getPath(); + VirtualFile getVcsRoot(); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/AbstractOpeningExpander.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/AbstractOpeningExpander.java new file mode 100644 index 000000000000..9ccff6bf7724 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/AbstractOpeningExpander.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.dialogs.browser; + +import org.jetbrains.idea.svn17.dialogs.RepositoryBrowserComponent; +import org.jetbrains.idea.svn17.dialogs.RepositoryTreeNode; +import org.jetbrains.idea.svn17.dialogs.browserCache.Expander; +import org.jetbrains.idea.svn17.dialogs.browserCache.KeepingExpandedExpander; + +import javax.swing.tree.TreeNode; +import java.util.Enumeration; + +public abstract class AbstractOpeningExpander implements Expander { + private final RepositoryBrowserComponent myBrowser; + private final KeepingExpandedExpander myKeepingExpander; + private final String mySelectionPath; + + protected AbstractOpeningExpander(final RepositoryBrowserComponent browser, final String selectionPath) { + myBrowser = browser; + myKeepingExpander = new KeepingExpandedExpander(browser); + mySelectionPath = selectionPath; + } + + public void onBeforeRefresh(final RepositoryTreeNode node) { + myKeepingExpander.onBeforeRefresh(node); + } + + protected enum ExpandVariants { + DO_NOTHING, + EXPAND_AND_EXIT, + EXPAND_CONTINUE + } + + protected abstract ExpandVariants expandNode(final String url); + protected abstract boolean checkChild(final String childUrl); + + public void onAfterRefresh(final RepositoryTreeNode node) { + myKeepingExpander.onAfterRefresh(node); + + if (node.isLeaf()) { + return; + } + + final String myUrl = node.getURL().toString(); + final ExpandVariants expandVariant = expandNode(myUrl); + + if (ExpandVariants.DO_NOTHING.equals(expandVariant)) { + return; + } + + // then expanded + myBrowser.expandNode(node); + + if (ExpandVariants.EXPAND_AND_EXIT.equals(expandVariant)) { + removeSelf(); + } else { + final Enumeration children = node.children(); + while (children.hasMoreElements()) { + final TreeNode treeNode = (TreeNode) children.nextElement(); + if (treeNode instanceof RepositoryTreeNode) { + final RepositoryTreeNode repositoryTreeNode = (RepositoryTreeNode) treeNode; + final String childUrl = repositoryTreeNode.getURL().toString(); + if (checkChild(childUrl)) { + if ((mySelectionPath != null) && (mySelectionPath.equals(childUrl))) { + myBrowser.setSelectedNode(repositoryTreeNode); + } + repositoryTreeNode.reload(this, false); + return; + } + } + } + removeSelf(); + } + } + + private void removeSelf() { + myBrowser.setLazyLoadingExpander(new KeepingExpandedExpander.Factory()); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/CheckoutOptionsDialog.form b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/CheckoutOptionsDialog.form new file mode 100644 index 000000000000..7204b41d60ab --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/CheckoutOptionsDialog.form @@ -0,0 +1,111 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/CheckoutOptionsDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/CheckoutOptionsDialog.java new file mode 100644 index 000000000000..e51f1e5c5214 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/CheckoutOptionsDialog.java @@ -0,0 +1,168 @@ +/* + * 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.dialogs.browser; + +import com.intellij.openapi.fileChooser.FileChooser; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.FixedSizeButton; +import com.intellij.openapi.vcs.checkout.CheckoutStrategy; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.DepthCombo; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.revision.SvnSelectRevisionPanel; +import org.jetbrains.idea.svn17.update.SvnRevisionPanel; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class CheckoutOptionsDialog extends DialogWrapper { + private JCheckBox myExternalsCheckbox; + private JLabel myUrlLabel; + private JPanel myTopPanel; + private SvnSelectRevisionPanel svnSelectRevisionPanel; + private DepthCombo myDepthCombo; + private JLabel myDepthLabel; + private JList myLocalTargetList; + private FixedSizeButton mySelectTarget; + private final String myRelativePath; + + public CheckoutOptionsDialog(Project project, SVNURL url, File target, final VirtualFile root, final String relativePath) { + super(project, true); + myRelativePath = relativePath; + final String urlText = url.toString(); + myUrlLabel.setText(urlText); + + fillTargetList(target); + validateTargetSelected(); + + mySelectTarget.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + // choose directory here/ + FileChooserDescriptor fcd = FileChooserDescriptorFactory.createSingleFolderDescriptor(); + fcd.setShowFileSystemRoots(true); + fcd.setTitle(SvnBundle.message("checkout.directory.chooser.title")); + fcd.setDescription(SvnBundle.message("checkout.directory.chooser.prompt")); + fcd.setHideIgnored(false); + VirtualFile file = FileChooser.chooseFile(getContentPane(), fcd, null); + if (file == null) { + return; + } + fillTargetList(new File(file.getPath())); + validateTargetSelected(); + } + }); + myLocalTargetList.addListSelectionListener(new ListSelectionListener() { + public void valueChanged(final ListSelectionEvent e) { + validateTargetSelected(); + } + }); + + svnSelectRevisionPanel.setRoot(root); + svnSelectRevisionPanel.setProject(project); + svnSelectRevisionPanel.setUrlProvider(new SvnRevisionPanel.UrlProvider() { + public String getUrl() { + return urlText; + } + }); + + setTitle(SvnBundle.message("checkout.options.dialog.title")); + myDepthLabel.setLabelFor(myDepthCombo); + init(); + } + + private void validateTargetSelected() { + Object[] objects = myLocalTargetList.getSelectedValues(); + boolean disable = objects == null || objects.length != 1; + setOKActionEnabled(! disable); + } + + private void fillTargetList(final File target) { + final DefaultListModel listModel = new DefaultListModel(); + final List strategies = new ArrayList(); + Collections.addAll(strategies, CheckoutStrategy.createAllStrategies(target, new File(myRelativePath), false)); + strategies.add(new SvnTrunkCheckoutStrategy(target, new File(myRelativePath), false)); + final List targets = new ArrayList(5); + for (CheckoutStrategy strategy : strategies) { + final File result = strategy.getResult(); + if (result != null && (! targets.contains(result))) { + targets.add(result); + } + } + Collections.sort(targets); + for (File file : targets) { + listModel.addElement(file); + } + myLocalTargetList.setModel(listModel); + myLocalTargetList.setVisibleRowCount(4); + myLocalTargetList.setMinimumSize(new Dimension(20, 80)); + myLocalTargetList.setSelectedValue(target, true); + if (myLocalTargetList.getSelectedValues() == null && (! targets.isEmpty())) { + myLocalTargetList.setSelectedIndex(0); + } + } + + @NonNls + protected String getDimensionServiceKey() { + return "svn4idea.checkout.options"; + } + + @Nullable + public File getTarget() { + Object[] objects = myLocalTargetList.getSelectedValues(); + return (objects == null) || (objects.length != 1) ? null : (File) objects[0]; + } + + public SVNDepth getDepth() { + return myDepthCombo.getDepth(); + } + + public boolean isIgnoreExternals() { + return !myExternalsCheckbox.isSelected(); + } + + @Nullable + protected JComponent createCenterPanel() { + return myTopPanel; + } + + @NotNull + public SVNRevision getRevision() throws ConfigurationException { + return svnSelectRevisionPanel.getRevision(); + } + + private void createUIComponents() { + mySelectTarget = new FixedSizeButton(20); + myDepthCombo = new DepthCombo(false); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/CopyOptionsDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/CopyOptionsDialog.java new file mode 100644 index 000000000000..966bfa1ad250 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/CopyOptionsDialog.java @@ -0,0 +1,270 @@ +/* + * 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.dialogs.browser; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Splitter; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vcs.VcsConfiguration; +import com.intellij.openapi.vcs.ui.CommitMessage; +import com.intellij.ui.DocumentAdapter; +import com.intellij.ui.EditorTextField; +import com.intellij.util.ui.GridBag; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.dialogs.RepositoryBrowserComponent; +import org.jetbrains.idea.svn17.dialogs.RepositoryTreeNode; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.TreeNode; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Collections; + +public class CopyOptionsDialog extends DialogWrapper { + + private final SVNURL myURL; + private EditorTextField myCommitMessage; + private final Project myProject; + private JTextField myNameField; + private JLabel myURLLabel; + private RepositoryBrowserComponent myBrowser; + private JLabel myTargetURL; + private JComboBox myMessagesBox; + private JPanel myMainPanel; + + public CopyOptionsDialog(String title, Project project, final RepositoryTreeNode root, final RepositoryTreeNode node, + final boolean copy) { + super(project, true); + myProject = project; + myURL = node.getURL(); + createUI(); + + if (copy) { + myTargetURL.setForeground(FileStatus.COLOR_ADDED); + setOKButtonText("Copy"); + } else { + myTargetURL.setForeground(FileStatus.COLOR_MODIFIED); + setOKButtonText("Move"); + } + + myURLLabel.setText(myURL.toString()); + + final TreeNode[] path = node.getSelfPath(); + final TreeNode[] subPath = new TreeNode[path.length - 1]; + System.arraycopy(path, 1, subPath, 0, path.length - 1); + + myBrowser.setRepositoryURL(root.getURL(), false, + new OpeningExpander.Factory(subPath, (RepositoryTreeNode)((node.getParent() instanceof RepositoryTreeNode) ? node.getParent() : null))); + myBrowser.addChangeListener(new TreeSelectionListener() { + public void valueChanged(TreeSelectionEvent e) { + update(); + } + }); + + myNameField.setText(SVNPathUtil.tail(myURL.getPath())); + myNameField.selectAll(); + myNameField.getDocument().addDocumentListener(new DocumentAdapter() { + protected void textChanged(final DocumentEvent e) { + update(); + } + }); + + ArrayList messages = VcsConfiguration.getInstance(myProject).getRecentMessages(); + Collections.reverse(messages); + Object[] model = messages.toArray(); + myMessagesBox.setModel(new DefaultComboBoxModel(model)); + myMessagesBox.setRenderer(new MessageBoxCellRenderer()); + + String lastMessage = VcsConfiguration.getInstance(myProject).getLastNonEmptyCommitMessage(); + if (lastMessage != null) { + myCommitMessage.setText(lastMessage); + myCommitMessage.selectAll(); + } + myMessagesBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + final Object item = myMessagesBox.getSelectedItem(); + if (item != null) { + myCommitMessage.setText(item.toString()); + myCommitMessage.selectAll(); + } + } + }); + Disposer.register(getDisposable(), myBrowser); + + setTitle(title); + init(); + update(); + } + + private void createUI() { + myMainPanel = new JPanel(new BorderLayout()); + myBrowser = new RepositoryBrowserComponent(SvnVcs17.getInstance(myProject)); + + final Splitter splitter = new Splitter(true); + splitter.setProportion(0.7f); + final JPanel wrapper = createBrowserPartWrapper(); + splitter.setFirstComponent(wrapper); + final JPanel commitMessageWrapper = createCommitMessageWrapper(); + splitter.setSecondComponent(commitMessageWrapper); + + myMainPanel.add(splitter, BorderLayout.CENTER); + final JPanel recentMessagesWrapper = new JPanel(new BorderLayout()); + recentMessagesWrapper.add(new JLabel("Recent Messages:"), BorderLayout.NORTH); + myMessagesBox = new JComboBox(); + recentMessagesWrapper.add(myMessagesBox, BorderLayout.SOUTH); + recentMessagesWrapper.setBorder(BorderFactory.createEmptyBorder(4,0,0,0)); + myMainPanel.add(recentMessagesWrapper, BorderLayout.SOUTH); + } + + private JPanel createCommitMessageWrapper() { + final JPanel commitMessageWrapper = new JPanel(new BorderLayout()); + commitMessageWrapper.add(new JLabel("Commit Message:"), BorderLayout.NORTH); + + myCommitMessage = CommitMessage.createCommitTextEditor(myProject, true); + + commitMessageWrapper.add(myCommitMessage, BorderLayout.CENTER); + return commitMessageWrapper; + } + + private JPanel createBrowserPartWrapper() { + final JPanel wrapper = new JPanel(new GridBagLayout()); + final GridBag gridBag = new GridBag().setDefaultAnchor(GridBagConstraints.NORTHWEST) + .setDefaultFill(GridBagConstraints.NONE).setDefaultInsets(1,1,1,1).setDefaultWeightX(1).setDefaultWeightY(0); + + gridBag.nextLine().next(); + gridBag.weightx(0); + wrapper.add(new JLabel("Source URL:"), gridBag); + + gridBag.next(); + gridBag.fillCellHorizontally(); + myURLLabel = new JLabel(); + myURLLabel.setFont(myURLLabel.getFont().deriveFont(Font.BOLD)); + wrapper.add(myURLLabel, gridBag); + + gridBag.nextLine().next(); + gridBag.weightx(0); + gridBag.pady(4); + wrapper.add(new JLabel("Target Location:"), gridBag); + + gridBag.nextLine().next(); + gridBag.fillCell(); + gridBag.weighty(1); + gridBag.coverLine(2); + wrapper.add(myBrowser, gridBag); + + gridBag.nextLine().next(); + gridBag.weightx(0); + gridBag.pady(4); + wrapper.add(new JLabel("Target Name:"), gridBag); + + gridBag.next(); + gridBag.fillCellHorizontally(); + myNameField = new JTextField(); + wrapper.add(myNameField, gridBag); + + gridBag.nextLine().next(); + gridBag.weightx(0); + gridBag.pady(2); + wrapper.add(new JLabel("Target URL:"), gridBag); + + gridBag.next(); + gridBag.fillCellHorizontally(); + myTargetURL = new JLabel(); + myTargetURL.setFont(myTargetURL.getFont().deriveFont(Font.BOLD)); + wrapper.add(myTargetURL, gridBag); + return wrapper; + } + + @NonNls + protected String getDimensionServiceKey() { + return "svn4idea.copy.options"; + } + + public String getCommitMessage() { + return myCommitMessage.getText(); + } + + public SVNURL getSourceURL() { + return myURL; + } + + public String getName() { + return myNameField.getText(); + } + + @Nullable + public SVNURL getTargetURL() { + if (getOKAction().isEnabled()) { + try { + return SVNURL.parseURIEncoded(myTargetURL.getText()); + } catch (SVNException e) { + // + } + } + return null; + } + + @Nullable + public RepositoryTreeNode getTargetParentNode() { + return myBrowser.getSelectedNode(); + } + + @Nullable + protected JComponent createCenterPanel() { + return myMainPanel; + } + + private void update() { + RepositoryTreeNode baseNode = myBrowser.getSelectedNode(); + if (baseNode == null) { + myTargetURL.setText(""); + getOKAction().setEnabled(false); + return; + } + SVNURL baseURL = baseNode.getURL(); + String name = myNameField.getText(); + if (name == null || "".equals(name)) { + getOKAction().setEnabled(false); + return; + } + try { + baseURL = baseURL.appendPath(myNameField.getText(), false); + } catch (SVNException e) { + // + getOKAction().setEnabled(false); + return; + } + myTargetURL.setText(baseURL.toString()); + getOKAction().setEnabled(!myURL.toString().equals(myTargetURL.getText())); + } + + + public JComponent getPreferredFocusedComponent() { + return myNameField; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/DeleteOptionsDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/DeleteOptionsDialog.java new file mode 100644 index 000000000000..a994a2cb6e39 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/DeleteOptionsDialog.java @@ -0,0 +1,121 @@ +/* + * 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.dialogs.browser; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.vcs.VcsConfiguration; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Collections; + +public class DeleteOptionsDialog extends DialogWrapper { + + private JTextArea myCommitMessage; + private final Project myProject; + + public DeleteOptionsDialog(Project project) { + super(project, true); + myProject = project; + setTitle("SVN Delete"); + init(); + } + + @NonNls + protected String getDimensionServiceKey() { + return "svn4idea.delete.options"; + } + + public String getCommitMessage() { + return myCommitMessage.getText(); + } + + @Nullable + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(new GridBagLayout()); + + GridBagConstraints gc = new GridBagConstraints(); + gc.insets = new Insets(2, 2, 2, 2); + gc.gridwidth = 1; + gc.gridheight = 1; + gc.gridx = 0; + gc.gridy = 0; + gc.anchor = GridBagConstraints.WEST; + gc.weightx = 0; + gc.weighty = 0; + gc.weightx = 0; + gc.gridwidth = 3; + gc.fill = GridBagConstraints.NONE; + panel.add(new JLabel("Commit Message:"), gc); + gc.gridy += 1; + gc.gridwidth = 3; + gc.gridx = 0; + gc.weightx = 1; + gc.weighty = 1; + gc.anchor = GridBagConstraints.NORTH; + gc.fill = GridBagConstraints.BOTH; + + myCommitMessage = new JTextArea(10, 0); + myCommitMessage.setWrapStyleWord(true); + myCommitMessage.setLineWrap(true); + panel.add(ScrollPaneFactory.createScrollPane(myCommitMessage), gc); + + gc.gridy += 1; + gc.gridwidth = 3; + gc.gridx = 0; + gc.weightx = 0; + gc.weighty = 0; + gc.anchor = GridBagConstraints.NORTH; + gc.fill = GridBagConstraints.HORIZONTAL; + panel.add(new JLabel("Recent Messages: "), gc); + gc.gridy += 1; + + final ArrayList messages = VcsConfiguration.getInstance(myProject).getRecentMessages(); + Collections.reverse(messages); + + final String[] model = ArrayUtil.toStringArray(messages); + final JComboBox messagesBox = new JComboBox(model); + messagesBox.setRenderer(new MessageBoxCellRenderer()); + panel.add(messagesBox, gc); + + String lastMessage = VcsConfiguration.getInstance(myProject).getLastNonEmptyCommitMessage(); + if (lastMessage != null) { + myCommitMessage.setText(lastMessage); + myCommitMessage.selectAll(); + } + messagesBox.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + myCommitMessage.setText(messagesBox.getSelectedItem().toString()); + myCommitMessage.selectAll(); + } + }); + return panel; + } + + public JComponent getPreferredFocusedComponent() { + return myCommitMessage; + } + +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/DiffOptionsDialog.form b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/DiffOptionsDialog.form new file mode 100644 index 000000000000..5be0133b48e4 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/DiffOptionsDialog.form @@ -0,0 +1,116 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/DiffOptionsDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/DiffOptionsDialog.java new file mode 100644 index 000000000000..a13d5e44a60b --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/DiffOptionsDialog.java @@ -0,0 +1,192 @@ +/* + * 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. + */ + +/* + * Created by IntelliJ IDEA. + * User: Alexander.Kitaev + * Date: 19.07.2006 + * Time: 16:51:09 + */ +package org.jetbrains.idea.svn17.dialogs.browser; + +import com.intellij.openapi.fileChooser.FileChooser; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.DocumentAdapter; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.dialogs.RepositoryBrowserComponent; +import org.jetbrains.idea.svn17.dialogs.RepositoryTreeNode; +import org.tmatesoft.svn.core.SVNURL; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; + +public class DiffOptionsDialog extends DialogWrapper implements ActionListener { + + private final SVNURL myURL; + private final Project myProject; + private RepositoryBrowserComponent myBrowser; + private final SVNURL myRootURL; + + private JRadioButton myUnifiedDiffButton; + private JRadioButton myUIDiffButton; + private TextFieldWithBrowseButton myFileBrowser; + private JCheckBox myReverseDiffButton; + private JLabel mySourceUrlLabel; + private JPanel myMainPanel; + private JLabel myErrorLabel; + @NonNls private static final String DEFAULT_PATCH_NAME = "diff.txt"; + + public DiffOptionsDialog(Project project, SVNURL rootURL, SVNURL url) { + super(project, true); + myURL = url; + myRootURL = rootURL; + myProject = project; + setTitle(SvnBundle.message("diff.options.title")); + mySourceUrlLabel.setText(myURL.toString()); + myBrowser.setRepositoryURL(myRootURL, false); + myBrowser.addChangeListener(new TreeSelectionListener() { + public void valueChanged(TreeSelectionEvent e) { + update(); + } + }); + myUIDiffButton.addActionListener(this); + myUnifiedDiffButton.addActionListener(this); + init(); + myFileBrowser.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + File f = selectFile("Patch File", "Select file to store unified diff"); + if (f != null) { + if (f.exists() && f.isDirectory()) { + f = new File(f, DEFAULT_PATCH_NAME); + } + myFileBrowser.setText(f.getAbsolutePath()); + } + } + }); + myFileBrowser.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { + protected void textChanged(final DocumentEvent e) { + update(); + } + }); + final VirtualFile baseDir = myProject.getBaseDir(); + if (baseDir != null) { + String projectDir = baseDir.getPath(); + projectDir = projectDir.replace('/', File.separatorChar); + myFileBrowser.setText(projectDir + File.separatorChar + DEFAULT_PATCH_NAME); + } + } + + @NonNls + protected String getDimensionServiceKey() { + return "svn4idea.diff.options"; + } + + @Override + protected void dispose() { + super.dispose(); + Disposer.dispose(myBrowser); + } + + public SVNURL getSourceURL() { + return myURL; + } + + public boolean isReverseDiff() { + return myReverseDiffButton.isSelected(); + } + + public SVNURL getTargetURL() { + if (getOKAction().isEnabled() && myBrowser.getSelectedNode() != null) { + return myBrowser.getSelectedNode().getURL(); + } + return null; + } + + public File getTargetFile() { + return new File(myFileBrowser.getText()); + } + + public boolean isUnifiedDiff() { + return myUnifiedDiffButton.isSelected(); + } + + protected JComponent createCenterPanel() { + update(); + return myMainPanel; + } + + private void update() { + RepositoryTreeNode baseNode = myBrowser.getSelectedNode(); + if (baseNode == null) { + myErrorLabel.setText(SvnBundle.message("diff.options.no.url.error")); + getOKAction().setEnabled(false); + return; + } + if (myURL.equals(getTargetURL())) { + myErrorLabel.setText(SvnBundle.message("diff.options.same.url.error")); + getOKAction().setEnabled(false); + return; + } + if (myUnifiedDiffButton.isSelected() && (myFileBrowser.getText().length() == 0 || getTargetFile().getParentFile() == null)) { + myErrorLabel.setText(SvnBundle.message("diff.options.no.patch.file.error")); + getOKAction().setEnabled(false); + return; + } + myErrorLabel.setText(" "); + getOKAction().setEnabled(true); + } + + public JComponent getPreferredFocusedComponent() { + return myBrowser; + } + + @Nullable + private File selectFile(String title, String description) { + FileChooserDescriptor fcd = FileChooserDescriptorFactory.createSingleFileOrFolderDescriptor(); + fcd.setShowFileSystemRoots(true); + fcd.setTitle(title); + fcd.setDescription(description); + fcd.setHideIgnored(false); + VirtualFile file = FileChooser.chooseFile(myBrowser, fcd); + if (file == null) { + return null; + } + return new File(file.getPath()); + } + + public void actionPerformed(ActionEvent e) { + myFileBrowser.setEnabled(myUnifiedDiffButton.isSelected()); + update(); + } + + private void createUIComponents() { + myBrowser = new RepositoryBrowserComponent(SvnVcs17.getInstance(myProject)); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/ExportOptionsDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/ExportOptionsDialog.java new file mode 100644 index 000000000000..e4d90f4e1c18 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/ExportOptionsDialog.java @@ -0,0 +1,179 @@ +/* + * 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.dialogs.browser; + +import com.intellij.openapi.fileChooser.FileChooser; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.DepthCombo; +import org.jetbrains.idea.svn17.SvnBundle; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNURL; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; + +public class ExportOptionsDialog extends DialogWrapper implements ActionListener { + + private final SVNURL myURL; + private final File myFile; + private TextFieldWithBrowseButton myPathField; + private DepthCombo myDepth; + private JCheckBox myExternalsCheckbox; + private JCheckBox myForceCheckbox; + private JComboBox myEOLStyleBox; + + public ExportOptionsDialog(Project project, SVNURL url, File target) { + super(project, true); + myURL = url; + myFile = target; + setTitle("SVN Export Options"); + init(); + } + + @NonNls + protected String getDimensionServiceKey() { + return "svn4idea.export.options"; + } + + public File getTarget() { + return new File(myPathField.getText()); + } + + public SVNDepth getDepth() { + return myDepth.getDepth(); + } + + public boolean isForce() { + return myForceCheckbox.isSelected(); + } + + public boolean isIgnoreExternals() { + return !myExternalsCheckbox.isSelected(); + } + + public String getEOLStyle() { + if (myEOLStyleBox.getSelectedIndex() == 0) { + return null; + } + return (String) myEOLStyleBox.getSelectedItem(); + } + + @Nullable + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(new GridBagLayout()); + + GridBagConstraints gc = new GridBagConstraints(); + gc.insets = new Insets(2, 2, 2, 2); + gc.gridwidth = 1; + gc.gridheight = 1; + gc.gridx = 0; + gc.gridy = 0; + gc.anchor = GridBagConstraints.WEST; + gc.fill = GridBagConstraints.NONE; + gc.weightx = 0; + gc.weighty = 0; + + panel.add(new JLabel("Export:"), gc); + gc.gridx += 1; + gc.gridwidth = 2; + gc.weightx = 1; + gc.fill = GridBagConstraints.HORIZONTAL; + JLabel urlLabel = new JLabel(myURL.toString()); + urlLabel.setFont(urlLabel.getFont().deriveFont(Font.BOLD)); + panel.add(urlLabel, gc); + + gc.gridy += 1; + gc.gridwidth = 1; + gc.gridx = 0; + gc.weightx = 0; + gc.fill = GridBagConstraints.NONE; + panel.add(new JLabel("Destination:"), gc); + gc.gridx += 1; + gc.gridwidth = 2; + gc.weightx = 1; + gc.fill = GridBagConstraints.HORIZONTAL; + + myPathField = new TextFieldWithBrowseButton(this); + myPathField.setText(myFile.getAbsolutePath()); + myPathField.setEditable(false); + panel.add(myPathField, gc); + + gc.gridy += 1; + gc.gridx = 0; + gc.weightx = 0; + gc.gridwidth = 3; + gc.fill = GridBagConstraints.NONE; + + // other options. + final JLabel depthLabel = new JLabel(SvnBundle.message("label.depth.text")); + depthLabel.setToolTipText(SvnBundle.message("label.depth.description")); + panel.add(depthLabel, gc); + ++ gc.gridx; + myDepth = new DepthCombo(false); + panel.add(myDepth, gc); + depthLabel.setLabelFor(myDepth); + + gc.gridx = 0; + gc.gridy += 1; + myForceCheckbox = new JCheckBox("Replace existing files"); + myForceCheckbox.setSelected(true); + panel.add(myForceCheckbox, gc); + gc.gridy += 1; + myExternalsCheckbox = new JCheckBox("Include externals locations"); + myExternalsCheckbox.setSelected(true); + panel.add(myExternalsCheckbox, gc); + gc.gridy += 1; + gc.gridwidth = 2; + panel.add(new JLabel("Override 'native' EOLs with:"), gc); + gc.gridx += 2; + gc.gridwidth = 1; + myEOLStyleBox = new JComboBox(new Object[] {"None", "LF", "CRLF", "CR"}); + panel.add(myEOLStyleBox, gc); + gc.gridy += 1; + gc.gridwidth = 3; + gc.gridx = 0; + gc.weightx = 1; + gc.weighty = 1; + gc.anchor = GridBagConstraints.SOUTH; + gc.fill = GridBagConstraints.HORIZONTAL; + panel.add(new JSeparator(), gc); + return panel; + } + + public void actionPerformed(ActionEvent e) { + // choose directory here/ + FileChooserDescriptor fcd = FileChooserDescriptorFactory.createSingleFolderDescriptor(); + fcd.setShowFileSystemRoots(true); + fcd.setTitle("Export Directory"); + fcd.setDescription("Select directory to export from subversion"); + fcd.setHideIgnored(false); + VirtualFile file = FileChooser.chooseFile(getContentPane(), fcd); + if (file == null) { + return; + } + myPathField.setText(file.getPath().replace('/', File.separatorChar)); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/ImportOptionsDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/ImportOptionsDialog.java new file mode 100644 index 000000000000..5d8dc9fb3ac7 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/ImportOptionsDialog.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.dialogs.browser; + +import com.intellij.openapi.fileChooser.FileChooser; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.vcs.VcsConfiguration; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.DepthCombo; +import org.jetbrains.idea.svn17.SvnBundle; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNURL; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; + +public class ImportOptionsDialog extends DialogWrapper implements ActionListener { + + private final SVNURL myURL; + private final File myFile; + private TextFieldWithBrowseButton myPathField; + private DepthCombo myDepth; + private JCheckBox myIncludeIgnoredCheckbox; + private JTextArea myCommitMessage; + private final Project myProject; + + public ImportOptionsDialog(Project project, SVNURL url, File target) { + super(project, true); + myURL = url; + myFile = target; + myProject = project; + setTitle("SVN Import Options"); + init(); + } + + @NonNls + protected String getDimensionServiceKey() { + return "svn4idea.import.options"; + } + + public File getTarget() { + return new File(myPathField.getText()); + } + + public SVNDepth getDepth() { + return myDepth.getDepth(); + } + + public boolean isIncludeIgnored() { + return myIncludeIgnoredCheckbox.isSelected(); + } + + public String getCommitMessage() { + return myCommitMessage.getText(); + } + + @Nullable + protected JComponent createCenterPanel() { + JPanel panel = new JPanel(new GridBagLayout()); + + GridBagConstraints gc = new GridBagConstraints(); + gc.insets = new Insets(2, 2, 2, 2); + gc.gridwidth = 1; + gc.gridheight = 1; + gc.gridx = 0; + gc.gridy = 0; + gc.anchor = GridBagConstraints.WEST; + gc.fill = GridBagConstraints.NONE; + gc.weightx = 0; + gc.weighty = 0; + + panel.add(new JLabel("Import to:"), gc); + gc.gridx += 1; + gc.gridwidth = 2; + gc.weightx = 1; + gc.fill = GridBagConstraints.HORIZONTAL; + JLabel urlLabel = new JLabel(myURL.toString()); + urlLabel.setFont(urlLabel.getFont().deriveFont(Font.BOLD)); + panel.add(urlLabel, gc); + + gc.gridy += 1; + gc.gridwidth = 1; + gc.gridx = 0; + gc.weightx = 0; + gc.fill = GridBagConstraints.NONE; + panel.add(new JLabel("Import from:"), gc); + gc.gridx += 1; + gc.gridwidth = 2; + gc.weightx = 1; + gc.fill = GridBagConstraints.HORIZONTAL; + + myPathField = new TextFieldWithBrowseButton(this); + myPathField.setText(myFile.getAbsolutePath()); + myPathField.setEditable(false); + panel.add(myPathField, gc); + + // other options. + gc.gridy += 1; + gc.gridx = 0; + gc.weightx = 0; + gc.gridwidth = 3; + gc.fill = GridBagConstraints.NONE; + + final JLabel depthLabel = new JLabel(SvnBundle.message("label.depth.text")); + depthLabel.setToolTipText(SvnBundle.message("label.depth.description")); + panel.add(depthLabel, gc); + ++gc.gridx; + myDepth = new DepthCombo(false); + panel.add(myDepth, gc); + depthLabel.setLabelFor(myDepth); + + gc.gridx = 0; + gc.gridy += 1; + myIncludeIgnoredCheckbox = new JCheckBox("Include ignored resources"); + myIncludeIgnoredCheckbox.setSelected(true); + panel.add(myIncludeIgnoredCheckbox, gc); + gc.gridy += 1; + panel.add(new JLabel("Commit Message:"), gc); + gc.gridy += 1; + gc.gridwidth = 3; + gc.gridx = 0; + gc.weightx = 1; + gc.weighty = 1; + gc.anchor = GridBagConstraints.NORTH; + gc.fill = GridBagConstraints.BOTH; + + myCommitMessage = new JTextArea(10, 0); + myCommitMessage.setWrapStyleWord(true); + myCommitMessage.setLineWrap(true); + panel.add(ScrollPaneFactory.createScrollPane(myCommitMessage), gc); + + gc.gridy += 1; + gc.gridwidth = 3; + gc.gridx = 0; + gc.weightx = 0; + gc.weighty = 0; + gc.anchor = GridBagConstraints.NORTH; + gc.fill = GridBagConstraints.HORIZONTAL; + panel.add(new JLabel("Recent Messages: "), gc); + gc.gridy += 1; + + final ArrayList messages = VcsConfiguration.getInstance(myProject).getRecentMessages(); + Collections.reverse(messages); + + final String[] model = ArrayUtil.toStringArray(messages); + final JComboBox messagesBox = new JComboBox(model); + messagesBox.setRenderer(new MessageBoxCellRenderer()); + panel.add(messagesBox, gc); + + String lastMessage = VcsConfiguration.getInstance(myProject).getLastNonEmptyCommitMessage(); + if (lastMessage != null) { + myCommitMessage.setText(lastMessage); + myCommitMessage.selectAll(); + } + messagesBox.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + myCommitMessage.setText(messagesBox.getSelectedItem().toString()); + myCommitMessage.selectAll(); + } + }); + return panel; + } + + + public JComponent getPreferredFocusedComponent() { + return myCommitMessage; + } + + public void actionPerformed(ActionEvent e) { + // choose directory here/ + FileChooserDescriptor fcd = FileChooserDescriptorFactory.createSingleFolderDescriptor(); + fcd.setShowFileSystemRoots(true); + fcd.setTitle("Checkout Directory"); + fcd.setDescription("Select directory to checkout from subversion"); + fcd.setHideIgnored(false); + VirtualFile file = FileChooser.chooseFile(getContentPane(), fcd); + if (file == null) { + return; + } + myPathField.setText(file.getPath().replace('/', File.separatorChar)); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/MessageBoxCellRenderer.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/MessageBoxCellRenderer.java new file mode 100644 index 000000000000..a0f58db9940a --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/MessageBoxCellRenderer.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. + */ + +/* + * Created by IntelliJ IDEA. + * User: Alexander.Kitaev + * Date: 24.07.2006 + * Time: 16:58:22 + */ +package org.jetbrains.idea.svn17.dialogs.browser; + +import javax.swing.*; +import java.awt.*; + +class MessageBoxCellRenderer extends DefaultListCellRenderer { + + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + if (value != null) { + String message = (String) value; + message = message.replace('\r', '|'); + message = message.replace('\n', '|'); + if (message.length() > 50) { + message = message.substring(0, 50) + "[...]"; + } + value = message; + } + return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/MkdirOptionsDialog.form b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/MkdirOptionsDialog.form new file mode 100644 index 000000000000..0407077d5ae7 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/MkdirOptionsDialog.form @@ -0,0 +1,93 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/MkdirOptionsDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/MkdirOptionsDialog.java new file mode 100644 index 000000000000..89cab6dfd9a4 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/MkdirOptionsDialog.java @@ -0,0 +1,139 @@ +/* + * 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.dialogs.browser; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.vcs.VcsConfiguration; +import com.intellij.ui.DocumentAdapter; +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Collections; + +public class MkdirOptionsDialog extends DialogWrapper { + + private SVNURL myURL; + private JTextArea myCommitMessage; + private JTextField myNameField; + private JLabel myURLLabel; + private JComboBox myMessagesBox; + private JPanel myMainPanel; + private JLabel myRecentMessagesLabel; + private final SVNURL myOriginalURL; + + public MkdirOptionsDialog(Project project, SVNURL url) { + super(project, true); + myOriginalURL = url; + try { + myURL = url.appendPath("NewFolder", true); + } + catch (SVNException ignore) { + } + setTitle("New Remote Folder"); + init(); + myURLLabel.setText(myURL.toString()); + myNameField.selectAll(); + myNameField.getDocument().addDocumentListener(new DocumentAdapter() { + protected void textChanged(final DocumentEvent e) { + updateURL(); + } + }); + + if (!project.isDefault()) { + final ArrayList messages = VcsConfiguration.getInstance(project).getRecentMessages(); + Collections.reverse(messages); + + final String[] model = ArrayUtil.toStringArray(messages); + myMessagesBox.setModel(new DefaultComboBoxModel(model)); + myMessagesBox.setRenderer(new MessageBoxCellRenderer()); + } + else { + myRecentMessagesLabel.setVisible(false); + myMessagesBox.setVisible(false); + } + + String lastMessage = VcsConfiguration.getInstance(project).getLastNonEmptyCommitMessage(); + if (lastMessage != null) { + myCommitMessage.setText(lastMessage); + myCommitMessage.selectAll(); + } + myMessagesBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + myCommitMessage.setText(myMessagesBox.getSelectedItem().toString()); + myCommitMessage.selectAll(); + } + }); + } + + @NonNls + protected String getDimensionServiceKey() { + return "svn4idea.mkdir.options"; + } + + public String getCommitMessage() { + return myCommitMessage.getText(); + } + + public SVNURL getURL() { + if (getOKAction().isEnabled()) { + try { + return SVNURL.parseURIEncoded(myURLLabel.getText()); + } + catch (SVNException ignore) { + } + } + return null; + } + + public String getName() { + return myNameField.getText(); + } + + @Nullable + protected JComponent createCenterPanel() { + return myMainPanel; + } + + public JComponent getPreferredFocusedComponent() { + return myNameField; + } + + private void updateURL() { + String newName = myNameField.getText(); + if (newName == null || "".equals(newName)) { + myURLLabel.setText(myOriginalURL.toString()); + getOKAction().setEnabled(false); + return; + } + try { + myURLLabel.setText(myOriginalURL.appendPath(newName, false).toString()); + getOKAction().setEnabled(true); + } + catch (SVNException e) { + myURLLabel.setText(myOriginalURL.toString()); + getOKAction().setEnabled(false); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/OpeningExpander.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/OpeningExpander.java new file mode 100644 index 000000000000..2bdf54abdea0 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/OpeningExpander.java @@ -0,0 +1,71 @@ +/* + * 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.dialogs.browser; + +import com.intellij.util.NotNullFunction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn17.dialogs.RepositoryBrowserComponent; +import org.jetbrains.idea.svn17.dialogs.RepositoryTreeNode; +import org.jetbrains.idea.svn17.dialogs.browserCache.Expander; + +import javax.swing.tree.TreeNode; +import java.util.LinkedList; +import java.util.List; + +public class OpeningExpander extends AbstractOpeningExpander { + private final List pathElements; + private final String myLongest; + + public OpeningExpander(final TreeNode[] path, final RepositoryBrowserComponent browser, final RepositoryTreeNode selectionPath) { + super(browser, selectionPath.getURL().toString()); + pathElements = new LinkedList(); + + for (TreeNode aPath : path) { + RepositoryTreeNode node = (RepositoryTreeNode)aPath; + pathElements.add(node.getURL().toString()); + } + myLongest = pathElements.get(pathElements.size() - 1); + } + + protected ExpandVariants expandNode(final String url) { + if (pathElements.contains(url)) { + if (myLongest.equals(url)) { + return ExpandVariants.EXPAND_AND_EXIT; + } + return ExpandVariants.EXPAND_CONTINUE; + } + return ExpandVariants.DO_NOTHING; + } + + protected boolean checkChild(final String childUrl) { + return pathElements.contains(childUrl); + } + + public static class Factory implements NotNullFunction { + private final TreeNode[] myPath; + private final RepositoryTreeNode mySelectionPath; + + public Factory(final TreeNode[] path, final RepositoryTreeNode selectionPath) { + myPath = path; + mySelectionPath = selectionPath; + } + + @NotNull + public Expander fun(final RepositoryBrowserComponent repositoryBrowserComponent) { + return new OpeningExpander(myPath, repositoryBrowserComponent, mySelectionPath); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/SvnTrunkCheckoutStrategy.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/SvnTrunkCheckoutStrategy.java new file mode 100644 index 000000000000..37534f9b50b3 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/SvnTrunkCheckoutStrategy.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.dialogs.browser; + +import com.intellij.openapi.vcs.checkout.CheckoutStrategy; + +import java.io.File; + +/** + * @author yole + */ +public class SvnTrunkCheckoutStrategy extends CheckoutStrategy { + public SvnTrunkCheckoutStrategy(File selectedLocation, File cvsPath, boolean isForFile) { + super(selectedLocation, cvsPath, isForFile); + } + + @Override + public File getResult() { + File path = getCvsPath(); + if (path.getName().equalsIgnoreCase("trunk") && (path.getParent() != null)) { + return new File(getSelectedLocation(), path.getParent()); + } + path = path.getParentFile(); + if ((path != null) && path.getName().equalsIgnoreCase("branches") && (path.getParent() != null)) { + return new File(getSelectedLocation(), path.getParent()); + } + if ((path != null) && path.getName().equalsIgnoreCase("tags") && (path.getParent() != null)) { + return new File(getSelectedLocation(), path.getParent()); + } + return null; + } + + @Override + public boolean useAlternativeCheckoutLocation() { + return true; + } + + @Override + public File getCheckoutDirectory() { + return getSelectedLocation(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/UrlOpeningExpander.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/UrlOpeningExpander.java new file mode 100644 index 000000000000..56488c8c8b15 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browser/UrlOpeningExpander.java @@ -0,0 +1,63 @@ +/* + * 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.dialogs.browser; + +import com.intellij.util.NotNullFunction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn17.dialogs.RepositoryBrowserComponent; +import org.jetbrains.idea.svn17.dialogs.browserCache.Expander; + +public class UrlOpeningExpander extends AbstractOpeningExpander { + private final String myUrl; + + UrlOpeningExpander(@NotNull final RepositoryBrowserComponent browser, final String selectionPath, @NotNull final String url) { + super(browser, selectionPath); + myUrl = (url.endsWith("/")) ? url.substring(0, url.length() - 1) : url; + } + + protected ExpandVariants expandNode(final String url) { + if (myUrl.equals(url)) { + return ExpandVariants.EXPAND_AND_EXIT; + } + if (myUrl.startsWith(url.endsWith("/") ? url : (url + '/'))) { +/* if ((myUrl.length() == url.length()) || ((myUrl.length() - 1) == url.length())) { + // do not expand myUrl - the last node just must be visible + return ExpandVariants.DO_NOTHING; + }*/ + return ExpandVariants.EXPAND_CONTINUE; + } + return ExpandVariants.DO_NOTHING; + } + + protected boolean checkChild(final String childUrl) { + return myUrl.startsWith(childUrl); + } + + public static class Factory implements NotNullFunction { + private final String myUrl; + private final String mySelectionUrl; + + public Factory(final String url, final String selectionUrl) { + myUrl = url; + mySelectionUrl = selectionUrl; + } + + @NotNull + public Expander fun(final RepositoryBrowserComponent repositoryBrowserComponent) { + return new UrlOpeningExpander(repositoryBrowserComponent, mySelectionUrl, myUrl); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/CacheLoader17.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/CacheLoader17.java new file mode 100644 index 000000000000..00e53c7c1c5f --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/CacheLoader17.java @@ -0,0 +1,68 @@ +/* + * 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.dialogs.browserCache; + +import com.intellij.openapi.components.ServiceManager; +import org.jetbrains.idea.svn17.dialogs.RepositoryTreeNode; +import org.tmatesoft.svn.core.SVNDirEntry; +import org.tmatesoft.svn.core.SVNErrorMessage; + +import javax.swing.*; +import java.util.List; + +public class CacheLoader17 extends Loader17 { + private final Loader17 myRepositoryLoader; + + public static Loader17 getInstance() { + return ServiceManager.getService(Loader17.class); + } + + public CacheLoader17() { + super(SvnRepositoryCache17.getInstance()); + myRepositoryLoader = new RepositoryLoader17(myCache); + } + + public void load(final RepositoryTreeNode node, final Expander expander) { + SwingUtilities.invokeLater(new Runnable(){ + public void run() { + final String nodeUrl = node.getURL().toString(); + + final List cached = myCache.getChildren(nodeUrl); + if (cached != null) { + refreshNode(node, cached, expander); + } + final SVNErrorMessage error = myCache.getError(nodeUrl); + if (error != null) { + refreshNodeError(node, error); + } + // refresh anyway + myRepositoryLoader.load(node, expander); + } + }); + } + + public void forceRefresh(final String repositoryRootUrl) { + myCache.clear(repositoryRootUrl); + } + + protected NodeLoadState getNodeLoadState() { + return NodeLoadState.CACHED; + } + + public Loader17 getRepositoryLoader() { + return myRepositoryLoader; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/Expander.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/Expander.java new file mode 100644 index 000000000000..78c77b284538 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/Expander.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.dialogs.browserCache; + +import org.jetbrains.idea.svn17.dialogs.RepositoryTreeNode; + +public interface Expander { + void onBeforeRefresh(final RepositoryTreeNode node); + void onAfterRefresh(final RepositoryTreeNode node); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/KeepingExpandedExpander.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/KeepingExpandedExpander.java new file mode 100644 index 000000000000..160d08068c5d --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/KeepingExpandedExpander.java @@ -0,0 +1,120 @@ +/* + * 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.dialogs.browserCache; + +import com.intellij.util.NotNullFunction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn17.dialogs.RepositoryBrowserComponent; +import org.jetbrains.idea.svn17.dialogs.RepositoryTreeNode; + +import javax.swing.tree.TreeNode; +import java.util.*; + +public class KeepingExpandedExpander implements Expander { + private final Expander mySelectionExpander; + private final RepositoryBrowserComponent myBrowser; + + private Map myThirdLevel; + private Collection myExpanded; + + public KeepingExpandedExpander(final RepositoryBrowserComponent browser, final Expander selectionInstaller) { + myBrowser = browser; + mySelectionExpander = selectionInstaller; + } + + public KeepingExpandedExpander(final RepositoryBrowserComponent browser) { + this(browser, new KeepingSelectionExpander(browser)); + } + + public void onBeforeRefresh(final RepositoryTreeNode node) { + mySelectionExpander.onBeforeRefresh(node); + + myThirdLevel = new HashMap(); + myExpanded = new ArrayList(); + + final Enumeration children = node.children(); + while (children.hasMoreElements()) { + final TreeNode treeNode = children.nextElement(); + if (treeNode instanceof RepositoryTreeNode) { + final RepositoryTreeNode repositoryNode = (RepositoryTreeNode) treeNode; + final List thirdLevelChildren = repositoryNode.getAllAlreadyLoadedChildren(); + + final String nodeName = repositoryNode.getSVNDirEntry().getName(); + + if (! thirdLevelChildren.isEmpty()) { + final boolean selfExpanded = myBrowser.isExpanded(repositoryNode); + myThirdLevel.put(nodeName, new ChildrenData(selfExpanded, thirdLevelChildren, repositoryNode.getChildrenLoadState())); + if (selfExpanded) { + myExpanded.addAll(myBrowser.getExpandedSubTree(repositoryNode)); + } + } + } + } + } + + public void onAfterRefresh(final RepositoryTreeNode node) { + final Enumeration children = node.children(); + while (children.hasMoreElements()) { + final RepositoryTreeNode treeNode = (RepositoryTreeNode) children.nextElement(); + final String name = treeNode.getSVNDirEntry().getName(); + + final ChildrenData thirdLevelLoaded = myThirdLevel.get(name); + if (thirdLevelLoaded != null) { + treeNode.setAlienChildren(thirdLevelLoaded.getChildren(), thirdLevelLoaded.getChildrenState()); + if (thirdLevelLoaded.isExpanded()) { + myBrowser.expandNode(treeNode); + } + } + } + + for (TreeNode expandedNode : myExpanded) { + myBrowser.expandNode(expandedNode); + } + + mySelectionExpander.onAfterRefresh(node); + } + + public static class Factory implements NotNullFunction { + @NotNull + public Expander fun(final RepositoryBrowserComponent repositoryBrowserComponent) { + return new KeepingExpandedExpander(repositoryBrowserComponent); + } + } + + private static class ChildrenData { + private final boolean myExpanded; + private final List myChildren; + private final NodeLoadState myChildrenState; + + private ChildrenData(final boolean expanded, final List children, final NodeLoadState childrenState) { + myExpanded = expanded; + myChildren = children; + myChildrenState = childrenState; + } + + public List getChildren() { + return myChildren; + } + + public NodeLoadState getChildrenState() { + return myChildrenState; + } + + public boolean isExpanded() { + return myExpanded; + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/KeepingSelectionExpander.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/KeepingSelectionExpander.java new file mode 100644 index 000000000000..83c7799ff3d7 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/KeepingSelectionExpander.java @@ -0,0 +1,67 @@ +/* + * 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.dialogs.browserCache; + +import org.jetbrains.idea.svn17.dialogs.RepositoryBrowserComponent; +import org.jetbrains.idea.svn17.dialogs.RepositoryTreeNode; + +import javax.swing.*; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; +import java.util.Enumeration; + +public class KeepingSelectionExpander implements Expander { + private final JTree myTree; + private TreePath mySelectionPath; + + public KeepingSelectionExpander(final RepositoryBrowserComponent browser) { + myTree = browser.getRepositoryTree(); + } + + public void onBeforeRefresh(final RepositoryTreeNode node) { + mySelectionPath = myTree.getSelectionPath(); + } + + public void onAfterRefresh(final RepositoryTreeNode node) { + if (mySelectionPath == null) { + return; + } + + setSelection(mySelectionPath.getPath(), 1, (TreeNode) myTree.getModel().getRoot()); + } + + private boolean setSelection(final Object[] oldSelectionPath, final int idx, final TreeNode node) { + if (idx >= oldSelectionPath.length) { + return false; + } + if ((oldSelectionPath[idx] != null) && (oldSelectionPath[idx].toString() != null)) { + final Enumeration children = node.children(); + while (children.hasMoreElements()) { + final TreeNode child = (TreeNode) children.nextElement(); + if (oldSelectionPath[idx].toString().equals(child.toString())) { + final boolean childStatus = setSelection(oldSelectionPath, idx + 1, child); + if (! childStatus) { + myTree.setSelectionPath(new TreePath(((DefaultTreeModel) myTree.getModel()).getPathToRoot(child))); + } + return true; + } + } + } + myTree.setSelectionPath(new TreePath(((DefaultTreeModel) myTree.getModel()).getPathToRoot(node))); + return true; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/Loader17.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/Loader17.java new file mode 100644 index 000000000000..83a61e9896f0 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/Loader17.java @@ -0,0 +1,66 @@ +/* + * 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.dialogs.browserCache; + +import org.jetbrains.idea.svn17.dialogs.RepositoryTreeNode; +import org.tmatesoft.svn.core.SVNDirEntry; +import org.tmatesoft.svn.core.SVNErrorMessage; + +import java.util.List; + +public abstract class Loader17 { + protected final SvnRepositoryCache17 myCache; + + protected Loader17(final SvnRepositoryCache17 cache) { + myCache = cache; + } + + public abstract void load(final RepositoryTreeNode node, final Expander afterRefreshExpander); + public abstract void forceRefresh(final String repositoryRootUrl); + protected abstract NodeLoadState getNodeLoadState(); + + protected void refreshNodeError(final RepositoryTreeNode node, final SVNErrorMessage text) { + if (node.isDisposed()) { + return; + } + final RepositoryTreeNode existingNode = node.getNodeWithSamePathUnderModelRoot(); + if (existingNode == null) { + return; + } + if (existingNode.isDisposed()) { + return; + } + + existingNode.setErrorNode(text, getNodeLoadState()); + } + + protected void refreshNode(final RepositoryTreeNode node, final List data, final Expander expander) { + if (node.isDisposed()) { + return; + } + final RepositoryTreeNode existingNode = node.getNodeWithSamePathUnderModelRoot(); + if (existingNode == null) { + return; + } + + if (existingNode.isDisposed()) { + return; + } + expander.onBeforeRefresh(existingNode); + existingNode.setChildren(data, getNodeLoadState()); + expander.onAfterRefresh(existingNode); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/NodeLoadState.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/NodeLoadState.java new file mode 100644 index 000000000000..3deefa91af70 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/NodeLoadState.java @@ -0,0 +1,24 @@ +/* + * 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.dialogs.browserCache; + +public enum NodeLoadState { + EMPTY, + LOADING, // for children + ERROR, // for children + CACHED, + REFRESHED +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/RepositoryLoader17.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/RepositoryLoader17.java new file mode 100644 index 000000000000..52896e56ffbf --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/RepositoryLoader17.java @@ -0,0 +1,153 @@ +/* + * 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.dialogs.browserCache; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.util.Pair; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.dialogs.RepositoryTreeNode; +import org.jetbrains.idea.svn17.dialogs.SvnAuthenticationProvider; +import org.tmatesoft.svn.core.ISVNDirEntryHandler; +import org.tmatesoft.svn.core.SVNDirEntry; +import org.tmatesoft.svn.core.SVNErrorMessage; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.io.SVNRepository; + +import javax.swing.*; +import java.util.*; + +class RepositoryLoader17 extends Loader17 { + // may be several requests if: several same-level nodes are expanded simultaneosly; or browser can be opening into some expanded state + private final Queue> myLoadQueue; + private boolean myQueueProcessorActive; + + RepositoryLoader17(final SvnRepositoryCache17 cache) { + super(cache); + + myLoadQueue = new LinkedList>(); + myQueueProcessorActive = false; + } + + public void load(final RepositoryTreeNode node, final Expander afterRefreshExpander) { + ApplicationManager.getApplication().assertIsDispatchThread(); + + final Pair data = new Pair(node, afterRefreshExpander); + if (! myQueueProcessorActive) { + startLoadTask(data); + myQueueProcessorActive = true; + } else { + myLoadQueue.offer(data); + } + } + + private void setResults(final Pair data, final List children) { + myCache.put(data.first.getURL().toString(), children); + refreshNode(data.first, children, data.second); + } + + private void setError(final Pair data, final SVNErrorMessage message) { + myCache.put(data.first.getURL().toString(), message); + refreshNodeError(data.first, message); + } + + private void startNext() { + ApplicationManager.getApplication().assertIsDispatchThread(); + + final Pair data = myLoadQueue.poll(); + if (data == null) { + myQueueProcessorActive = false; + return; + } + if (data.first.isDisposed()) { + // ignore if node is already disposed + startNext(); + } else { + startLoadTask(data); + } + } + + private void startLoadTask(final Pair data) { + final ModalityState state = ModalityState.current(); + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + ProgressManager.getInstance().runProcess(new LoadTask(data), new EmptyProgressIndicator() { + @NotNull + @Override + public ModalityState getModalityState() { + return state; + } + }); + } + }); + } + + public void forceRefresh(final String repositoryRootUrl) { + // ? remove + } + + protected NodeLoadState getNodeLoadState() { + return NodeLoadState.REFRESHED; + } + + private class LoadTask implements Runnable { + private final Pair myData; + + private LoadTask(final Pair data) { + myData = data; + } + + public void run() { + final Collection entries = new TreeSet(); + final RepositoryTreeNode node = myData.first; + final SvnVcs17 vcs = node.getVcs(); + SVNRepository repository = null; + SvnAuthenticationProvider.forceInteractive(); + try { + repository = vcs.createRepository(node.getURL().toString()); + repository.getDir("", -1, null, new ISVNDirEntryHandler() { + public void handleDirEntry(final SVNDirEntry dirEntry) throws SVNException { + entries.add(dirEntry); + } + }); + } catch (final SVNException e) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + setError(myData, e.getErrorMessage()); + startNext(); + } + }); + return; + } finally { + SvnAuthenticationProvider.clearInteractive(); + if (repository != null) { + repository.closeSession(); + } + } + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + setResults(myData, new ArrayList(entries)); + startNext(); + } + }); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/SvnRepositoryCache17.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/SvnRepositoryCache17.java new file mode 100644 index 000000000000..a3be57378921 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/SvnRepositoryCache17.java @@ -0,0 +1,74 @@ +/* + * 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.dialogs.browserCache; + +import com.intellij.openapi.components.ServiceManager; +import com.intellij.util.containers.SoftHashMap; +import org.jetbrains.annotations.Nullable; +import org.tmatesoft.svn.core.SVNDirEntry; +import org.tmatesoft.svn.core.SVNErrorMessage; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class SvnRepositoryCache17 { + private final Map> myMap; + private final Map myErrorsMap; + + public static SvnRepositoryCache17 getInstance() { + return ServiceManager.getService(SvnRepositoryCache17.class); + } + + private SvnRepositoryCache17() { + myMap = new SoftHashMap>(); + myErrorsMap = new SoftHashMap(); + } + + @Nullable + public List getChildren(final String parent) { + return myMap.get(parent); + } + + @Nullable + public SVNErrorMessage getError(final String parent) { + return myErrorsMap.get(parent); + } + + public void put(final String parent, final SVNErrorMessage error) { + myMap.remove(parent); + myErrorsMap.put(parent, error); + } + + public void put(final String parent, List children) { + myErrorsMap.remove(parent); + myMap.put(parent, children); + } + + public void remove(final String parent) { + myErrorsMap.remove(parent); + myMap.remove(parent); + } + + public void clear(final String repositoryRootUrl) { + for (Iterator>> iterator = myMap.entrySet().iterator(); iterator.hasNext();) { + final Map.Entry> entry = iterator.next(); + if (entry.getKey().startsWith(repositoryRootUrl)) { + iterator.remove(); + } + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/SyntheticWorker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/SyntheticWorker.java new file mode 100644 index 000000000000..ee5ac7fd6e85 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/dialogs/browserCache/SyntheticWorker.java @@ -0,0 +1,141 @@ +/* + * 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.dialogs.browserCache; + +import com.intellij.util.NotNullFunction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn17.dialogs.RepositoryTreeNode; +import org.tmatesoft.svn.core.SVNDirEntry; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNNodeKind; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.util.*; + +public class SyntheticWorker { + private final SvnRepositoryCache17 myCache; + private final SVNURL myUrl; + + public SyntheticWorker(final SVNURL url) { + myCache = SvnRepositoryCache17.getInstance(); + myUrl = url; + } + + public void removeSelf() { + final String parentUrl; + try { + parentUrl = myUrl.removePathTail().toString(); + } + catch (SVNException e) { + return; + } + + final List children = myCache.getChildren(parentUrl); + if (children == null) { + return; + } + for (Iterator iterator = children.iterator(); iterator.hasNext();) { + final SVNDirEntry entry = iterator.next(); + if (myUrl.equals(entry.getURL())) { + iterator.remove(); + } + } + myCache.put(parentUrl, children); + } + + public void addSyntheticChildToSelf(final SVNURL newUrl, final SVNURL repositoryUrl, final String name, final boolean isDir) { + final String currentUrlAsString = myUrl.toString(); + + final List children = myCache.getChildren(currentUrlAsString); + if (children == null) { + return; + } + children.add(createSyntheticEntry(newUrl, repositoryUrl, name, isDir)); + + Collections.sort(children, new Comparator() { + public int compare(final SVNDirEntry o1, final SVNDirEntry o2) { + final boolean dirStatus = SVNNodeKind.DIR.equals(o1.getKind()) ^ SVNNodeKind.DIR.equals(o1.getKind()); + if (dirStatus) { + return SVNNodeKind.DIR.equals(o1.getKind()) ? -1 : 1; + } + return o1.toString().compareTo(o2.toString()); + } + }); + myCache.put(currentUrlAsString, children); + } + + public void copyTreeToSelf(final RepositoryTreeNode node) { + try { + node.doOnSubtree(new Adder(node.getURL().removePathTail().toString().length(), myUrl)); + } + catch (SVNException e) { + // + } + } + + public static void removeTreeOf(final RepositoryTreeNode node) { + node.doOnSubtree(new Remover()); + } + + public static SVNDirEntry createSyntheticEntry(final SVNURL newUrl, final SVNURL repositoryUrl, final String name, final boolean isDir) { + return new SVNDirEntry(newUrl, repositoryUrl, name, isDir ? SVNNodeKind.DIR : SVNNodeKind.FILE, 0, false, SVNRevision.UNDEFINED.getNumber(), null, null); + } + + private static class Remover implements NotNullFunction { + private final SvnRepositoryCache17 myCache = SvnRepositoryCache17.getInstance(); + + @NotNull + public Object fun(final RepositoryTreeNode repositoryTreeNode) { + myCache.remove(repositoryTreeNode.getURL().toString()); + return Boolean.FALSE; + } + } + + private class Adder implements NotNullFunction { + private final int myOldPrefixLen; + private final SVNURL myNewParentUrl; + + private Adder(final int oldPrefixLen, final SVNURL newParentUrl) { + myOldPrefixLen = oldPrefixLen; + myNewParentUrl = newParentUrl; + } + + @NotNull + public Object fun(final RepositoryTreeNode repositoryTreeNode) { + final List children = myCache.getChildren(repositoryTreeNode.getURL().toString()); + if (children == null) { + return Boolean.FALSE; + } + final List newChildren = new ArrayList(children.size()); + + try { + for (SVNDirEntry child : children) { + newChildren.add(createSyntheticEntry(convertUrl(child.getURL()), child.getRepositoryRoot(), child.getName(), SVNNodeKind.DIR.equals(child.getKind()))); + } + myCache.put(convertUrl(repositoryTreeNode.getURL()).toString(), newChildren); + } + catch (SVNException e) { + // + } + return Boolean.FALSE; + } + + private SVNURL convertUrl(final SVNURL currentUrl) throws SVNException { + return myNewParentUrl.appendPath(currentUrl.toString().substring(myOldPrefixLen), true); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/BunchFactory.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/BunchFactory.java new file mode 100644 index 000000000000..18fc0f465441 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/BunchFactory.java @@ -0,0 +1,124 @@ +/* + * 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.history; + +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import org.tmatesoft.svn.core.SVNException; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class BunchFactory { + private final LiveProvider myLiveProvider; + private final List myResult; + private final Iterator myProviderIterator; + private BunchProvider myCurrentProvider; + + // they will move + private int myBunchSize; + private long myYoungest; + + public BunchFactory(final CachedProvider internallyCached, final CachedProvider visuallyCached, final LiveProvider liveProvider) { + myLiveProvider = liveProvider; + + final List providers = new ArrayList(); + if (internallyCached != null) { + providers.add(internallyCached); + } + if (visuallyCached != null) { + providers.add(visuallyCached); + } + providers.add(myLiveProvider); + + myResult = new ArrayList(); + myProviderIterator = providers.iterator(); + while (myProviderIterator.hasNext()) { + myCurrentProvider = myProviderIterator.next(); + if (! myCurrentProvider.isEmpty()) { + break; + } + } + myYoungest = -1; + } + + public List goBack(final int bunchSize, final Ref myYoungestRead) throws SVNException { + execute(bunchSize); + myYoungestRead.set(myLiveProvider.isEarliestRevisionWasAccessed()); + return new ArrayList(myResult); + } + + private void addToResult(final Fragment fragment) { + if ((myBunchSize == 0) || (fragment.getList().isEmpty())) { + return; + } + + final List list = fragment.getList(); + + final List subList = (myBunchSize >= list.size()) ? list : list.subList(0, myBunchSize); + myResult.add(new Fragment(fragment.getOrigin(), subList, fragment.isConsistentWithOlder(), fragment.isConsistentWithYounger(), + fragment.getOriginBunch())); + myBunchSize -= subList.size(); + myBunchSize = (myBunchSize < 0) ? 0 : myBunchSize; + myYoungest = subList.get(subList.size() - 1).getNumber(); + } + + private void execute(final int bunchSize) throws SVNException { + myBunchSize = bunchSize; + myResult.clear(); + + int defender = 1000; + while (true) { + while (true) { + -- defender; + if (defender == 0) { + return; + } + final Fragment fragment = myCurrentProvider.getEarliestBunchInInterval(myYoungest, 0, + (myYoungest == -1) ? myBunchSize : (myBunchSize + 1), (myYoungest == -1), true); + if ((fragment == null) || (fragment.getList().isEmpty())) { + // switch to next provider + break; + } + + final List bunchLists = fragment.getList(); + if (! fragment.isConsistentWithYounger()) { + final long endRevision = bunchLists.get(0).getNumber(); + if ((endRevision < myYoungest) || (myYoungest == -1)) { + final Fragment liveFragment = myLiveProvider.getEarliestBunchInInterval(myYoungest, endRevision, + (myYoungest == -1) ? (myBunchSize + 1) : (myBunchSize + 2), (myYoungest == -1), false); + if (liveFragment != null) { + addToResult(liveFragment); + if (myBunchSize == 0) { + return; + } + } + } + } + addToResult(fragment); + if (myBunchSize == 0) { + return; + } + } + if (myProviderIterator.hasNext()) { + myCurrentProvider = myProviderIterator.next(); + } else { + break; + } + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/BunchProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/BunchProvider.java new file mode 100644 index 000000000000..f4719ae78148 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/BunchProvider.java @@ -0,0 +1,34 @@ +/* + * 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.history; + +import org.jetbrains.annotations.Nullable; +import org.tmatesoft.svn.core.SVNException; + +public interface BunchProvider { + long getEarliestRevision(); + + /** + * @param desirableSize - not the size that should be returned. it is the size that should be found, and then start or/and end + * bound revisions might be removed from it. + * so, if need maximum 50 revisions between 10 and 100, not including 10 and 100 revisions, arguments should be: + * 100, 10, 52, false, false + */ + @Nullable + Fragment getEarliestBunchInInterval(final long earliestRevision, final long oldestRevision, final int desirableSize, + final boolean includeYoungest, final boolean includeOldest) throws SVNException; + boolean isEmpty(); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/CachedProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/CachedProvider.java new file mode 100644 index 000000000000..ba75f81ea11b --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/CachedProvider.java @@ -0,0 +1,177 @@ +/* + * 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.history; + +import com.intellij.openapi.vcs.changes.committed.ChangesBunch; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import org.jetbrains.annotations.Nullable; +import org.tmatesoft.svn.core.SVNException; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public abstract class CachedProvider implements BunchProvider { + private final Iterator myIterator; + private long myEarliestKeepedRevision; + protected ChangesBunch myAlreadyReaded; + protected Origin myOrigin; + + private boolean myHadBeenAccessed; + + protected CachedProvider(final Iterator iterator, final Origin origin) { + myIterator = iterator; + myEarliestKeepedRevision = -1; + myAlreadyReaded = null; + myOrigin = origin; + } + + public abstract void doCacheUpdate(final List> fragments); + + protected static List getAllBeforeVisuallyCached(final List> fragmentsListList) { + final List lists = new ArrayList(); + // take those _after_ committed + for (List fragmentList : fragmentsListList) { + for (Fragment fragment : fragmentList) { + if (Origin.VISUAL.equals(fragment.getOrigin())) { + break; + } + lists.addAll(fragment.getList()); + } + } + + return lists; + } + + public long getEarliestRevision() { + if (myEarliestKeepedRevision == -1) { + try { + while (myIterator.hasNext()) { + final ChangesBunch changesBunch = myIterator.next(); + if (changesBunch == null) { + break; + } + addToLoaded(changesBunch); + final List list = myAlreadyReaded.getList(); + if (! list.isEmpty()) { + myEarliestKeepedRevision = list.get(0).getNumber(); + break; + } + } + } catch (SwitchRevisionsProviderException e) { + // means that committed cache now should be queried instead of internally cached + // just return -1 -> queries will be redirected + myEarliestKeepedRevision = -1; + } + } + return myEarliestKeepedRevision; + } + + protected void addToLoaded(final ChangesBunch loaded) { + myAlreadyReaded = loaded; + } + + @Nullable + public Fragment getEarliestBunchInInterval(final long earliestRevision, final long oldestRevision, final int desirableSize, + final boolean includeYoungest, final boolean includeOldest) throws SVNException { + if ((earliestRevision > getEarliestRevision()) || (earliestRevision == -1)) { + if (myAlreadyReaded == null) { + return null; + } + // just return first + return createFromLoaded(myAlreadyReaded, earliestRevision, oldestRevision, desirableSize, includeYoungest, includeOldest, false); + } + + final ChangesBunch loadedBunch = myAlreadyReaded; + + final List list = loadedBunch.getList(); + if (list.isEmpty()) { + return null; + } + final long oldest = list.get(list.size() - 1).getNumber(); + + if ((! includeYoungest) && (oldest == earliestRevision)) { + return packNext(earliestRevision, oldestRevision, desirableSize, includeOldest, loadedBunch.isConsistentWithPrevious()); + //} else if ((oldest <= earliestRevision) && (youngest >= earliestRevision)) { + } else if (oldest <= earliestRevision) { + return createFromLoaded(loadedBunch, earliestRevision, oldestRevision, desirableSize, includeYoungest, includeOldest, false); + } + + return null; + } + + private Fragment packNext(final long earliestRevision, final long oldestRevision, final int desirableSize, final boolean includeOldest, + final boolean wasConsistentWithPrevious) { + try { + if (myIterator.hasNext()) { + final ChangesBunch changesBunch = myIterator.next(); + if (changesBunch == null) { + return null; + } + addToLoaded(changesBunch); + + // there is no earliestRevision + // always consistent since there were exactly 'earliest revision' in previous potion + return createFromLoaded(changesBunch, earliestRevision, oldestRevision, desirableSize, true, includeOldest, wasConsistentWithPrevious); + } + } catch (SwitchRevisionsProviderException e) { + // means that committed cache now should be queried instead of internally cached + // just return null -> queries will be redirected + } + return null; + } + + public boolean hadBeenSuccessfullyAccessed() { + return myHadBeenAccessed; + } + + @Nullable + private Fragment createFromLoaded(final ChangesBunch loadedBunch, final long earliestRevision, final long oldestRevision, + final int desirableSize, final boolean includeYoungest, final boolean includeOldest, final boolean consistent) { + boolean consistentWithPrevious = loadedBunch.isConsistentWithPrevious(); + boolean consistentWithYounger = consistent; + + final List list = loadedBunch.getList(); + + final List sublist = new ArrayList(); + for (int i = 0; i < list.size(); i++) { + final CommittedChangeList changeList = list.get(i); + if ((! includeOldest) && (changeList.getNumber() == oldestRevision)) { + continue; + } + if (changeList.getNumber() == earliestRevision) { + consistentWithYounger = true; + } + if ((earliestRevision == -1) || (changeList.getNumber() < earliestRevision) || (includeYoungest && (changeList.getNumber() == earliestRevision))) { + sublist.add(changeList); + } + if ((sublist.size() == desirableSize) || (changeList.getNumber() < oldestRevision)) { + if (! consistentWithPrevious) { + consistentWithPrevious = (i > 0); + } + break; + } + } + if (! myHadBeenAccessed) { + myHadBeenAccessed = (! sublist.isEmpty()); + } + return (sublist.isEmpty()) ? null : new Fragment(myOrigin, sublist, consistentWithPrevious, consistentWithYounger, loadedBunch); + } + + public boolean isEmpty() { + return getEarliestRevision() == -1; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/CopyData.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/CopyData.java new file mode 100644 index 000000000000..c22e329a0100 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/CopyData.java @@ -0,0 +1,40 @@ +/* + * 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.history; + +public class CopyData { + private final long myCopySourceRevision; + private final long myCopyTargetRevision; + private final boolean myTrunkSupposedCorrect; + + public CopyData(long copySourceRevision, long copyTargetRevision, boolean trunkSupposedCorrect) { + myCopySourceRevision = copySourceRevision; + myCopyTargetRevision = copyTargetRevision; + myTrunkSupposedCorrect = trunkSupposedCorrect; + } + + public long getCopySourceRevision() { + return myCopySourceRevision; + } + + public long getCopyTargetRevision() { + return myCopyTargetRevision; + } + + public boolean isTrunkSupposedCorrect() { + return myTrunkSupposedCorrect; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/FirstInBranch.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/FirstInBranch.java new file mode 100644 index 000000000000..7ee191583dd1 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/FirstInBranch.java @@ -0,0 +1,134 @@ +/* + * 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.history; + +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.ConcurrentTasks; +import com.intellij.util.Consumer; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.*; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; +import org.tmatesoft.svn.core.wc.SVNLogClient; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.util.Map; + +public class FirstInBranch implements Runnable { + private final SvnVcs17 myVcs; + private final String myFullBranchUrl; + private final String myFullTrunkUrl; + private final String myBranchUrl; + private final String myTrunkUrl; + private final Consumer myConsumer; + + public FirstInBranch(final SvnVcs17 vcs, final String repositoryRoot, final String branchUrl, final String trunkUrl, final Consumer consumer) { + myVcs = vcs; + myConsumer = consumer; + + myFullBranchUrl = branchUrl; + myFullTrunkUrl = trunkUrl; + myBranchUrl = relativePath(repositoryRoot, branchUrl); + myTrunkUrl = relativePath(repositoryRoot, trunkUrl); + } + + private String relativePath(final String parent, final String child) { + String path = SVNPathUtil.getRelativePath(parent, child); + return path.startsWith("/") ? path : "/" + path; + } + + public void run() { + final SVNURL branchURL; + final SVNURL trunkURL; + try { + branchURL = SVNURL.parseURIEncoded(myFullBranchUrl); + trunkURL = SVNURL.parseURIEncoded(myFullTrunkUrl); + } + catch (SVNException e) { + myConsumer.consume(null); + return; + } + + final ConcurrentTasks tasks = + new ConcurrentTasks(ProgressManager.getInstance().getProgressIndicator(), createTask(branchURL), createTask(trunkURL)); + tasks.compute(); + if (tasks.isResultKnown()) { + myConsumer.consume(tasks.getResult()); + } else { + myConsumer.consume(null); + } + } + + private Consumer> createTask(final SVNURL branchURL) { + return new Consumer>() { + public void consume(final Consumer copyDataConsumer) { + final SVNLogClient logClient = myVcs.createLogClient(); + final long start1 = getStart(logClient, branchURL); + if (start1 > 0) { + final SVNRevision start1Rev = SVNRevision.create(start1); + try { + logClient.doLog(branchURL, null, SVNRevision.UNDEFINED, start1Rev, start1Rev, true, true, 1, new ISVNLogEntryHandler() { + public void handleLogEntry(SVNLogEntry logEntry) throws SVNException { + checkForCopy(logEntry, copyDataConsumer); + } + }); + } catch (SVNException e) { + // + } + } + } + }; + } + + private static long getStart(final SVNLogClient logClient, final SVNURL url) { + final Ref myRevisionCandidate = new Ref(0L); + try { + logClient.doLog(url, null, SVNRevision.UNDEFINED, SVNRevision.HEAD, SVNRevision.create(0), + true, false, 0, new ISVNLogEntryHandler() { + public void handleLogEntry(SVNLogEntry logEntry) throws SVNException { + ProgressManager.checkCanceled(); + + myRevisionCandidate.set(logEntry.getRevision()); + } + }); + } + catch (SVNException e) { + // + } + return myRevisionCandidate.get(); + } + + private void checkForCopy(final SVNLogEntry logEntry, final Consumer result) { + final Map map = logEntry.getChangedPaths(); + for (Object o : map.values()) { + final SVNLogEntryPath path = (SVNLogEntryPath) o; + final String localPath = path.getPath(); + final String copyPath = path.getCopyPath(); + + if ('A' == path.getType()) { + if ((myBranchUrl.equals(localPath) || SVNPathUtil.isAncestor(localPath, myBranchUrl)) && + ((myTrunkUrl.equals(copyPath)) || SVNPathUtil.isAncestor(copyPath, myTrunkUrl))) { + result.consume(new CopyData(path.getCopyRevision(), logEntry.getRevision(), true)); + } else { + if ((myBranchUrl.equals(copyPath) || SVNPathUtil.isAncestor(copyPath, myBranchUrl)) && + ((myTrunkUrl.equals(localPath)) || SVNPathUtil.isAncestor(localPath, myTrunkUrl))) { + result.consume(new CopyData(path.getCopyRevision(), logEntry.getRevision(), false)); + } + } + } + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/Fragment.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/Fragment.java new file mode 100644 index 000000000000..26cdb06a8833 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/Fragment.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.history; + +import com.intellij.openapi.vcs.changes.committed.ChangesBunch; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class Fragment { + private final Origin myOrigin; + private final List myList; + private final boolean myConsistentWithOlder; + private final boolean myConsistentWithYounger; + @Nullable + private final ChangesBunch myOriginBunch; + + public Fragment(final Origin origin, final List list, final boolean consistentWithOlder, + final boolean consistentWithYounger, final ChangesBunch originBunch) { + myOrigin = origin; + myList = list; + myConsistentWithOlder = consistentWithOlder; + myConsistentWithYounger = consistentWithYounger; + myOriginBunch = originBunch; + } + + public Origin getOrigin() { + return myOrigin; + } + + public List getList() { + return myList; + } + + @Nullable + public ChangesBunch getOriginBunch() { + return myOriginBunch; + } + + public boolean isConsistentWithOlder() { + return myConsistentWithOlder; + } + + public boolean isConsistentWithYounger() { + return myConsistentWithYounger; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/InternallyCachedChangesBunch.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/InternallyCachedChangesBunch.java new file mode 100644 index 000000000000..26d3d2f0a89f --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/InternallyCachedChangesBunch.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.history; + +import com.intellij.openapi.vcs.changes.committed.ChangesBunch; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; + +import java.util.List; + +public class InternallyCachedChangesBunch extends ChangesBunch { + private final int mySelfAddress; + private final int myPreviousAddress; + + public InternallyCachedChangesBunch(final List list, final boolean consistentWithPrevious, + final int selfAddress, + final int previousAddress) { + super(list, consistentWithPrevious); + mySelfAddress = selfAddress; + myPreviousAddress = previousAddress; + } + + public int getSelfAddress() { + return mySelfAddress; + } + + public int getPreviousAddress() { + return myPreviousAddress; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/LatestExistentSearcher.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/LatestExistentSearcher.java new file mode 100644 index 000000000000..93a1e699e13e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/LatestExistentSearcher.java @@ -0,0 +1,172 @@ +/* + * 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.history; + +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.RootUrlInfo; +import org.jetbrains.idea.svn17.SvnFileUrlMapping17; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.*; +import org.tmatesoft.svn.core.io.SVNRepository; +import org.tmatesoft.svn.core.wc.SVNInfo; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.io.File; +import java.util.Map; + +public class LatestExistentSearcher { + private long myStartNumber; + private boolean myStartExistsKnown; + private final SVNURL myUrl; + private final SvnVcs17 myVcs; + private long myEndNumber; + + public LatestExistentSearcher(final SvnVcs17 vcs, final SVNURL url) { + this(0, -1, false, vcs, url); + } + + public LatestExistentSearcher(final long startNumber, final long endNumber, final boolean startExistsKnown, final SvnVcs17 vcs, final SVNURL url) { + myStartNumber = startNumber; + myEndNumber = endNumber; + myStartExistsKnown = startExistsKnown; + myVcs = vcs; + myUrl = url; + } + + public long getDeletionRevision() { + if (! detectStartRevision()) return -1; + + final Ref latest = new Ref(myStartNumber); + SVNRepository repository = null; + try { + repository = myVcs.createRepository(myUrl.toString()); + final SVNURL repRoot = repository.getRepositoryRoot(true); + if (repRoot != null) { + if (myEndNumber == -1) { + myEndNumber = repository.getLatestRevision(); + } + + final SVNURL existingParent = getExistingParent(myUrl, repository, repRoot.toString().length()); + if (existingParent == null) { + return myStartNumber; + } + + final String urlRelativeString = myUrl.toString().substring(repRoot.toString().length()); + final SVNRevision startRevision = SVNRevision.create(myStartNumber); + myVcs.createLogClient().doLog(existingParent, new String[]{""}, startRevision, startRevision, SVNRevision.HEAD, false, true, 0, + new ISVNLogEntryHandler() { + public void handleLogEntry(final SVNLogEntry logEntry) throws SVNException { + final Map changedPaths = logEntry.getChangedPaths(); + for (Object o : changedPaths.values()) { + final SVNLogEntryPath path = (SVNLogEntryPath) o; + if ((path.getType() == 'D') && (urlRelativeString.equals(path.getPath()))) { + latest.set(logEntry.getRevision()); + throw new SVNException(SVNErrorMessage.UNKNOWN_ERROR_MESSAGE); + } + } + } + }); + } + } + catch (SVNException e) { + // + } finally { + if (repository != null) { + repository.closeSession(); + } + } + + return latest.get().longValue(); + } + + public long getLatestExistent() { + if (! detectStartRevision()) return myStartNumber; + + SVNRepository repository = null; + long latestOk = myStartNumber; + try { + repository = myVcs.createRepository(myUrl.toString()); + final SVNURL repRoot = repository.getRepositoryRoot(true); + if (repRoot != null) { + if (myEndNumber == -1) { + myEndNumber = repository.getLatestRevision(); + } + final String urlString = myUrl.toString().substring(repRoot.toString().length()); + for (long i = myStartNumber + 1; i < myEndNumber; i++) { + final SVNNodeKind kind = repository.checkPath(urlString, i); + if (SVNNodeKind.DIR.equals(kind) || SVNNodeKind.FILE.equals(kind)) { + latestOk = i; + } + } + } + } + catch (SVNException e) { + // + } finally { + if (repository != null) { + repository.closeSession(); + } + } + + return latestOk; + } + + private boolean detectStartRevision() { + if (! myStartExistsKnown) { + final SvnFileUrlMapping17 mapping = myVcs.getSvnFileUrlMapping(); + final RootUrlInfo rootUrlInfo = mapping.getWcRootForUrl(myUrl.toString()); + if (rootUrlInfo == null) return true; + final VirtualFile vf = rootUrlInfo.getVirtualFile(); + if (vf == null) { + return true; + } + final SVNWCClient client = myVcs.createWCClient(); + try { + final SVNInfo info = client.doInfo(new File(vf.getPath()), SVNRevision.UNDEFINED); + if ((info == null) || (info.getRevision() == null)) { + return false; + } + myStartNumber = info.getRevision().getNumber(); + myStartExistsKnown = true; + } + catch (SVNException e) { + return false; + } + } + return true; + } + + @Nullable + private SVNURL getExistingParent(final SVNURL url, final SVNRepository repository, final int repoRootLen) throws SVNException { + final String urlString = url.toString().substring(repoRootLen); + if (urlString.length() == 0) { + // === repository url + return url; + } + final SVNNodeKind kind = repository.checkPath(urlString, myEndNumber); + if (SVNNodeKind.DIR.equals(kind) || SVNNodeKind.FILE.equals(kind)) { + return url; + } + final SVNURL parentUrl = url.removePathTail(); + if (parentUrl == null) { + return null; + } + return getExistingParent(parentUrl, repository, repoRootLen); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/LiveProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/LiveProvider.java new file mode 100644 index 000000000000..d5d38be58960 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/LiveProvider.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.history; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNErrorCode; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.util.List; + +public class LiveProvider implements BunchProvider { + private final SvnLogLoader myLoader; + private final SvnRepositoryLocation myLocation; + private boolean myEarliestRevisionWasAccessed; + private final long myYoungestRevision; + private final SvnVcs17 myVcs; + + public LiveProvider(final SvnVcs17 vcs, final SvnRepositoryLocation location, final long latestRevision, final SvnLogLoader loader) { + myVcs = vcs; + myLoader = loader; + myLocation = location; + myYoungestRevision = latestRevision; + } + + public long getEarliestRevision() { + return -1; + } + + public boolean isEmpty() { + return false; + } + + public Fragment getEarliestBunchInInterval(final long earliestRevision, final long oldestRevision, final int desirableSize, + final boolean includeYoungest, final boolean includeOldest) throws SVNException { + if ((myEarliestRevisionWasAccessed) || ((oldestRevision == myYoungestRevision) && ((! includeYoungest) || (! includeOldest)))) { + return null; + } + final SVNRevision youngRevision = (earliestRevision == -1) ? SVNRevision.HEAD : SVNRevision.create(earliestRevision); + + final Ref> refToList = new Ref>(); + final Ref exceptionRef = new Ref(); + + final Runnable loader = new Runnable() { + public void run() { + try { + refToList.set( + myLoader.loadInterval(youngRevision, SVNRevision.create(oldestRevision), desirableSize, includeYoungest, includeOldest)); + } + catch (SVNException e) { + exceptionRef.set(e); + } + } + }; + + if (ApplicationManager.getApplication().isUnitTestMode()) { + loader.run(); + } else { + ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + public void run() { + final ProgressIndicator ind = ProgressManager.getInstance().getProgressIndicator(); + if (ind != null) { + ind.setText(SvnBundle.message("progress.live.provider.loading.revisions.details.text")); + } + loader.run(); + } + }, SvnBundle.message("progress.live.provider.loading.revisions.text"), false, myVcs.getProject()); + } + + if (exceptionRef.get() != null) { + final SVNException e = exceptionRef.get(); + if (SVNErrorCode.FS_NOT_FOUND.equals(e.getErrorMessage().getErrorCode())) { + // occurs when target URL is deleted in repository + // try to find latest existent revision. expensive ... + final LatestExistentSearcher searcher = new LatestExistentSearcher(oldestRevision, myYoungestRevision, (oldestRevision != 0), + myVcs, SVNURL.parseURIEncoded(myLocation.getURL())); + final long existent = searcher.getLatestExistent(); + if ((existent == -1) || (existent == earliestRevision)) { + myEarliestRevisionWasAccessed = true; + return null; + } + return getEarliestBunchInInterval(existent, oldestRevision, includeYoungest ? desirableSize : (desirableSize + 1), true, includeOldest); + } + throw e; + } + + final List list = refToList.get(); + if (list.isEmpty()) { + myEarliestRevisionWasAccessed = (oldestRevision == 0); + return null; + } + myEarliestRevisionWasAccessed = (oldestRevision == 0) && ((list.size() + ((! includeOldest) ? 1 : 0) + ((! includeYoungest) ? 1 : 0)) < desirableSize); + return new Fragment(Origin.LIVE, list, true, true, null); + } + + public boolean isEarliestRevisionWasAccessed() { + return myEarliestRevisionWasAccessed; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/LoadedRevisionsCache17.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/LoadedRevisionsCache17.java new file mode 100644 index 000000000000..d17309a3dab1 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/LoadedRevisionsCache17.java @@ -0,0 +1,203 @@ +/* + * 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.history; + +import com.intellij.lifecycle.PeriodicalTasksCloser; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.vcs.RepositoryLocation; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.committed.ChangesBunch; +import com.intellij.openapi.vcs.changes.committed.CommittedChangesCache; +import com.intellij.openapi.vcs.changes.committed.CommittedChangesListener; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.util.containers.SoftHashMap; +import com.intellij.util.messages.MessageBusConnection; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class LoadedRevisionsCache17 implements Disposable { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.history.LoadedRevisionsCache17"); + + private final Project myProject; + private final Map myMap; + private final Object refreshLock = new Object(); + private long myRefreshTime; + private final MessageBusConnection myConnection; + + public static LoadedRevisionsCache17 getInstance(final Project project) { + return PeriodicalTasksCloser.getInstance().safeGetService(project, LoadedRevisionsCache17.class); + } + + private LoadedRevisionsCache17(final Project project) { + myProject = project; + myMap = (ApplicationManager.getApplication().isUnitTestMode()) ? new HashMap() : new SoftHashMap(); + + myConnection = project.getMessageBus().connect(); + myConnection.subscribe(CommittedChangesCache.COMMITTED_TOPIC, new CommittedChangesListener() { + public void changesLoaded(final RepositoryLocation location, final List changes) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + myMap.clear(); + setRefreshTime(System.currentTimeMillis()); + } + }); + } + + public void incomingChangesUpdated(@Nullable final List receivedChanges) { + } + + public void refreshErrorStatusChanged(@Nullable final VcsException lastError) { + } + }); + Disposer.register(myProject, this); + setRefreshTime(0); + } + + private long getRefreshTime() { + synchronized (refreshLock) { + return myRefreshTime; + } + } + + private void setRefreshTime(final long refreshTime) { + synchronized (refreshLock) { + myRefreshTime = refreshTime; + } + } + + private void debugInfo(final List data, final boolean consistentWithPrevious, final Bunch bindTo) { + LOG.debug(">>> cache internal >>> consistent: " + consistentWithPrevious + " bindTo: " + bindTo + + " oldest list: " + data.get(data.size() - 1).getNumber() + ", youngest list: " + data.get(0).getNumber()); + } + + public void dispose() { + myConnection.disconnect(); + myMap.clear(); + } + + private List> split(final List list, final int size) { + final int listSize = list.size(); + if (listSize < size) { + return Collections.singletonList(list); + } + final int first = listSize % size; + + int start = 0; + int end = (first == 0) ? (Math.min(listSize, size)) : first; + final List> result = new ArrayList>(listSize / size + 1); + while (start < listSize) { + result.add(list.subList(start, end)); + start = end; + end += size; + } + return result; + } + + @Nullable + public Bunch put(final List data, final boolean consistentWithPrevious, final Bunch bindTo) { + if (data.isEmpty()) { + return null; + } + final SvnRepositoryLocation repositoryLocation = ((SvnChangeList) data.get(0)).getLocation(); + final String location = repositoryLocation.getURL(); + + final List> list = split(data, SvnRevisionsNavigationMediator.CHUNK_SIZE); + + Bunch bindToBunch = bindTo; + if (bindToBunch == null) { + final Bunch fromCache = myMap.get(location); + if (fromCache != null) { + final long passedSmallestNumber = data.get(data.size() - 1).getNumber(); + final List cachedList = fromCache.getList(); + final long greatestNumber = cachedList.get(0).getNumber(); + if (greatestNumber < passedSmallestNumber) { + bindToBunch = fromCache; + } + } + } + boolean consistent = consistentWithPrevious; + for (int i = list.size() - 1; i >= 0; -- i) { + final List changeLists = list.get(i); + debugInfo(changeLists, consistent, bindToBunch); + bindToBunch = new Bunch(changeLists, consistent, bindToBunch); + consistent = true; + } + + myMap.put(location, bindToBunch); + return bindToBunch; + } + + @Nullable + public Iterator iterator(final String location) { + final Bunch bunch = myMap.get(location); + if (bunch == null) { + return null; + } + return new BunchIterator(bunch); + } + + private class BunchIterator implements Iterator { + private final long myCreationTime; + private Bunch myBunch; + + private BunchIterator(final Bunch bunch) { + myBunch = bunch; + myCreationTime = System.currentTimeMillis(); + } + + private void checkValidity() { + ApplicationManager.getApplication().assertIsDispatchThread(); + + if (myCreationTime <= getRefreshTime()) { + throw new SwitchRevisionsProviderException(); + } + } + + public boolean hasNext() { + checkValidity(); + return myBunch != null; + } + + public ChangesBunch next() { + checkValidity(); + final Bunch current = myBunch; + myBunch = myBunch.myNext; + return current; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + + public static class Bunch extends ChangesBunch { + private final Bunch myNext; + + private Bunch(final List list, final boolean consistent, final Bunch next) { + super(list, consistent); + myNext = next; + } + + public Bunch getNext() { + return myNext; + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/LocationDetector.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/LocationDetector.java new file mode 100644 index 000000000000..0da57a3b57a3 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/LocationDetector.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.history; + +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.actions.VcsContextFactory; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.NotNullFunction; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnUtil; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +public class LocationDetector { + private final Map myMap; + + public LocationDetector(final SvnVcs17 vcs) { + myMap = new HashMap(); + + final ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + final VirtualFile[] roots = ProjectLevelVcsManager.getInstance(vcs.getProject()).getRootsUnderVcs(vcs); + + for (VirtualFile root : roots) { + myMap.putAll(SvnUtil.getLocationInfoForModule(vcs, new File(root.getPath()), progress)); + } + } + + @Nullable + public FilePath crawlForPath(final String fullPath, final NotNullFunction detector) { + for (Map.Entry entry : myMap.entrySet()) { + final String url = entry.getKey(); + if (SVNPathUtil.isAncestor(url, fullPath)) { + return filePathByUrlAndPath(fullPath, url, entry.getValue().getAbsolutePath(), detector); + } + } + return null; + } + + static FilePath filePathByUrlAndPath(final String longPath, final String parentUrl, final String parentPath, final NotNullFunction detector) { + final String relPath = longPath.substring(parentUrl.length()); + final File localFile = new File(parentPath, relPath); + return VcsContextFactory.SERVICE.getInstance().createFilePathOn(localFile, detector); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/MergeInfoUpdatesListener.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/MergeInfoUpdatesListener.java new file mode 100644 index 000000000000..ee9834059a6e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/MergeInfoUpdatesListener.java @@ -0,0 +1,142 @@ +/* + * 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.history; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.VcsListener; +import com.intellij.openapi.vcs.ZipperUpdater; +import com.intellij.openapi.vcs.changes.committed.CommittedChangesTreeBrowser; +import com.intellij.openapi.vcs.changes.committed.VcsConfigurationChangeListener; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; +import com.intellij.util.messages.MessageBusConnection; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.integrate.Merger; +import org.jetbrains.idea.svn17.mergeinfo.SvnMergeInfoCache17; + +import java.util.ArrayList; +import java.util.List; + +public class MergeInfoUpdatesListener { + private final static int DELAY = 300; + + private final Project myProject; + private final MessageBusConnection myConnection; + private List myMergeInfoRefreshActions; + private final ZipperUpdater myUpdater; + + public MergeInfoUpdatesListener(final Project project, final MessageBusConnection connection) { + myConnection = connection; + myProject = project; + myUpdater = new ZipperUpdater(DELAY, myProject); + } + + public void addPanel(final RootsAndBranches action) { + if (myMergeInfoRefreshActions == null) { + myMergeInfoRefreshActions = new ArrayList(); + myMergeInfoRefreshActions.add(action); + + myConnection.subscribe(VcsConfigurationChangeListener.BRANCHES_CHANGED, new VcsConfigurationChangeListener.Notification() { + public void execute(final Project project, final VirtualFile vcsRoot) { + callReloadMergeInfo(); + } + }); + final Runnable reloadRunnable = new Runnable() { + public void run() { + callReloadMergeInfo(); + } + }; + myConnection.subscribe(SvnVcs17.WC_CONVERTED, reloadRunnable); + myConnection.subscribe(RootsAndBranches.REFRESH_REQUEST, reloadRunnable); + + myConnection.subscribe(SvnVcs17.ROOTS_RELOADED, reloadRunnable); + + ProjectLevelVcsManager.getInstance(myProject).addVcsListener(new VcsListener() { + public void directoryMappingChanged() { + callReloadMergeInfo(); + } + }); + + myConnection.subscribe(CommittedChangesTreeBrowser.ITEMS_RELOADED, new CommittedChangesTreeBrowser.CommittedChangesReloadListener() { + public void itemsReloaded() { + reloadRunnable.run(); + } + public void emptyRefresh() { + } + }); + + myConnection.subscribe(SvnMergeInfoCache17.SVN_MERGE_INFO_CACHE, new SvnMergeInfoCache17.SvnMergeInfoCacheListener() { + public void copyRevisionUpdated() { + doForEachInitialized(new Consumer() { + public void consume(final RootsAndBranches rootsAndBranches) { + rootsAndBranches.fireRepaint(); + } + }); + } + }); + + myConnection.subscribe(Merger.COMMITTED_CHANGES_MERGED_STATE, new Merger.CommittedChangesMergedStateChanged() { + public void event(final List list) { + doForEachInitialized(new Consumer() { + public void consume(RootsAndBranches rootsAndBranches) { + rootsAndBranches.refreshByLists(list); + } + }); + } + }); + } else { + myMergeInfoRefreshActions.add(action); + } + } + + private void doForEachInitialized(final Consumer consumer) { + myUpdater.queue(new Runnable() { + public void run() { + for (final RootsAndBranches action : myMergeInfoRefreshActions) { + if (action.strategyInitialized()) { + if (ApplicationManager.getApplication().isDispatchThread()) { + consumer.consume(action); + } else { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + consumer.consume(action); + } + }); + } + } + } + } + }); + } + + private void callReloadMergeInfo() { + doForEachInitialized(new Consumer() { + public void consume(final RootsAndBranches rootsAndBranches) { + rootsAndBranches.reloadPanels(); + rootsAndBranches.refresh(); + } + }); + } + + public void removePanel(final RootsAndBranches action) { + if (myMergeInfoRefreshActions != null) { + myMergeInfoRefreshActions.remove(action); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/MergeSourceDetailsAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/MergeSourceDetailsAction.java new file mode 100644 index 000000000000..ff5506209f7f --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/MergeSourceDetailsAction.java @@ -0,0 +1,72 @@ +/* + * 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.history; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CustomShortcutSet; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.vcs.VcsDataKeys; +import com.intellij.openapi.vcs.history.VcsFileRevision; +import com.intellij.openapi.vfs.VirtualFile; + +import javax.swing.*; +import java.awt.event.KeyEvent; + +public class MergeSourceDetailsAction extends AnAction implements DumbAware { + + @Override + public void update(AnActionEvent e) { + e.getPresentation().setIcon(getIcon()); + e.getPresentation().setText("Show merge sources details"); + e.getPresentation().setEnabled(enabled(e)); + } + + private static class IconHolder { + private static final Icon myIcon = IconLoader.getIcon("/icons/mergeSourcesDetails.png"); + } + + private Icon getIcon() { + return IconHolder.myIcon; + } + + public void registerSelf(final JComponent comp) { + registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_M, KeyEvent.ALT_MASK | KeyEvent.CTRL_MASK)), comp); + } + + private boolean enabled(final AnActionEvent e) { + final Project project = e.getData(PlatformDataKeys.PROJECT); + if (project == null) return false; + final VirtualFile revisionVirtualFile = e.getData(VcsDataKeys.VCS_VIRTUAL_FILE); + if (revisionVirtualFile == null) return false; + final VcsFileRevision revision = e.getData(VcsDataKeys.VCS_FILE_REVISION); + if (revision == null) return false; + if (! (revision instanceof SvnFileRevision)) return false; + return ! ((SvnFileRevision) revision).getMergeSources().isEmpty(); + } + + public void actionPerformed(AnActionEvent e) { + if (! enabled(e)) return; + + final Project project = e.getData(PlatformDataKeys.PROJECT); + final VcsFileRevision revision = e.getData(VcsDataKeys.VCS_FILE_REVISION); + final VirtualFile revisionVirtualFile = e.getData(VcsDataKeys.VCS_VIRTUAL_FILE); + SvnMergeSourceDetails.showMe(project, (SvnFileRevision) revision, revisionVirtualFile); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/Origin.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/Origin.java new file mode 100644 index 000000000000..f5c027afdfaf --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/Origin.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.history; + +public enum Origin { + LIVE, + INTERNAL, + VISUAL +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/RootsAndBranches.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/RootsAndBranches.java new file mode 100644 index 000000000000..679039ec918d --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/RootsAndBranches.java @@ -0,0 +1,905 @@ +/* + * 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.history; + +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.MultiLineLabelUI; +import com.intellij.openapi.util.Getter; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vcs.RepositoryLocation; +import com.intellij.openapi.vcs.changes.BackgroundFromStartOption; +import com.intellij.openapi.vcs.changes.committed.*; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.util.Consumer; +import com.intellij.util.NullableFunction; +import com.intellij.util.messages.Topic; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.actions.AbstractIntegrateChangesAction; +import org.jetbrains.idea.svn17.actions.ChangeListsMergerFactory; +import org.jetbrains.idea.svn17.actions.RecordOnlyMergerFactory; +import org.jetbrains.idea.svn17.actions.ShowSvnMapAction; +import org.jetbrains.idea.svn17.dialogs.WCInfoWithBranches; +import org.jetbrains.idea.svn17.integrate.*; +import org.jetbrains.idea.svn17.mergeinfo.MergeInfoHolder; +import org.jetbrains.idea.svn17.update.UpdateEventHandler; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNRevisionRange; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.*; +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class RootsAndBranches implements CommittedChangeListDecorator { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.history.RootsAndBranches"); + private final Project myProject; + private final DecoratorManager myManager; + private final RepositoryLocation myLocation; + private JPanel myPanel; + private final Map myMergePanels; + private final Map myHolders; + + private boolean myHighlightingOn; + private boolean myFromHereDirection; + private JPanel myPanelWrapper; + private final MergePanelFiltering myStrategy; + private final FilterOutMerged myFilterMerged; + private final FilterOutNotMerged myFilterNotMerged; + private final FilterOutAlien myFilterAlien; + private final IntegrateChangeListsAction myIntegrateAction; + private final UndoIntegrateChangeListsAction myUndoIntegrateChangeListsAction; + private JComponent myToolbarComponent; + + private boolean myDisposed; + + private final WcInfoLoader myDataLoader; + + public static final Topic REFRESH_REQUEST = new Topic("REFRESH_REQUEST", Runnable.class); + + private MergeInfoHolder getHolder(final String key) { + final MergeInfoHolder holder = myHolders.get(key); + if (holder != null) { + return holder; + } + return myHolders.get(key.endsWith(File.separator) ? key.substring(0, key.length() - 1) : key + File.separator); + } + + private SvnMergeInfoRootPanelManual getPanelData(final String key) { + final SvnMergeInfoRootPanelManual panel = myMergePanels.get(key); + if (panel != null) { + return panel; + } + return myMergePanels.get(key.endsWith(File.separator) ? key.substring(0, key.length() - 1) : key + File.separator); + } + + public RootsAndBranches(final Project project, final DecoratorManager manager, final RepositoryLocation location) { + myProject = project; + myManager = manager; + myLocation = location; + + myDataLoader = new WcInfoLoader(myProject, myLocation); + + myMergePanels = new HashMap(); + myHolders = new HashMap(); + + myFilterMerged = new FilterOutMerged(); + myFilterNotMerged = new FilterOutNotMerged(); + myFilterAlien = new FilterOutAlien(); + myIntegrateAction = new IntegrateChangeListsAction(); + myUndoIntegrateChangeListsAction = new UndoIntegrateChangeListsAction(); + + myPanel = new JPanel(new GridBagLayout()); + myFromHereDirection = true; + createToolbar(); + final GridBagConstraints gb = + new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.NORTH, GridBagConstraints.NONE, new Insets(1, 1, 1, 1), 0, 0); + gb.insets = new Insets(20, 1, 1, 1); + myPanel.add(new JLabel("Loading..."), gb); + + myPanel.setPreferredSize(new Dimension(200, 60)); + + myManager.install(this); + setDirectionToPanels(); + + myStrategy = new MergePanelFiltering(getPanel()); + } + + public IntegrateChangeListsAction getIntegrateAction() { + return myIntegrateAction; + } + + public UndoIntegrateChangeListsAction getUndoIntegrateAction() { + return myUndoIntegrateChangeListsAction; + } + + public void reloadPanels() { + final Map, SvnMergeInfoRootPanelManual.InfoHolder> states = new HashMap, SvnMergeInfoRootPanelManual.InfoHolder>(); + for (Map.Entry entry : myMergePanels.entrySet()) { + final String localPath = entry.getKey(); + final WCInfoWithBranches wcInfo = entry.getValue().getWcInfo(); + states.put(new Pair(localPath, wcInfo.getUrl().toString()), entry.getValue().getInfo()); + } + createPanels(myLocation, new Runnable() { + public void run() { + for (Map.Entry entry : myMergePanels.entrySet()) { + final String localPath = entry.getKey(); + final WCInfoWithBranches wcInfo = entry.getValue().getWcInfo(); + final Pair key = new Pair(localPath, wcInfo.getUrl().toString()); + final SvnMergeInfoRootPanelManual.InfoHolder infoHolder = states.get(key); + if (infoHolder != null) { + entry.getValue().initSelection(infoHolder); + } + } + } + }); + } + + public void turnFromHereHighlighting() { + myHighlightingOn = true; + myFromHereDirection = true; + setDirectionToPanels(); + for (MergeInfoHolder holder : myHolders.values()) { + holder.updateMixedRevisionsForPanel(); + } + + myManager.repaintTree(); + } + + public void turnFromThereHighlighting() { + myHighlightingOn = true; + myFromHereDirection = false; + setDirectionToPanels(); + + myManager.repaintTree(); + } + + private void setDirectionToPanels() { + for (SvnMergeInfoRootPanelManual panel : myMergePanels.values()) { + panel.setDirection(myFromHereDirection); + } + } + + public void turnOff() { + myHighlightingOn = false; + for (SvnMergeInfoRootPanelManual panelManual : myMergePanels.values()) { + panelManual.setMixedRevisions(false); + } + + myManager.repaintTree(); + } + + public Icon decorate(final CommittedChangeList list) { + final MergeInfoHolder.ListMergeStatus status = getStatus(list, false); + return (status == null) ? MergeInfoHolder.ListMergeStatus.ALIEN.getIcon() : status.getIcon(); + } + + private void createPanels(final RepositoryLocation location, final Runnable afterRefresh) { + final Task.Backgroundable backgroundable = new Task.Backgroundable(myProject, "Subversion: loading working copies data..", false, + BackgroundFromStartOption.getInstance()) { + public void run(@NotNull final ProgressIndicator indicator) { + indicator.setIndeterminate(true); + final Map panels = new HashMap(); + final Map holders = new HashMap(); + final JPanel mainPanel = prepareData(panels, holders); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + if (myDisposed) return; + + myMergePanels.clear(); + myHolders.clear(); + myMergePanels.putAll(panels); + myHolders.putAll(holders); + + if (myPanelWrapper != null) { + myPanelWrapper.removeAll(); + if (myMergePanels.isEmpty()) { + final JPanel emptyPanel = new JPanel(new GridBagLayout()); + final GridBagConstraints gb = + new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 0, 5), 0, 0); + final JLabel label = new JLabel("No Subversion 1.5 working copies\nof 1.5 repositories in the project"); + label.setUI(new MultiLineLabelUI()); + emptyPanel.add(label, gb); + gb.fill = GridBagConstraints.HORIZONTAL; + myPanelWrapper.add(emptyPanel, gb); + } else { + for (MergeInfoHolder holder : myHolders.values()) { + holder.updateMixedRevisionsForPanel(); + } + myPanelWrapper.add(mainPanel, new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); + } + myPanelWrapper.repaint(); + } else { + myPanel = mainPanel; + } + if (afterRefresh != null) { + afterRefresh.run(); + } + } + }); + } + }; + ProgressManager.getInstance().run(backgroundable); + } + + public void refreshByLists(final List committedChangeLists) { + if (! committedChangeLists.isEmpty()) { + final SvnChangeList svnList = (SvnChangeList) committedChangeLists.get(0); + final String wcPath = svnList.getWcPath(); + if (wcPath != null) { + final MergeInfoHolder holder = getHolder(wcPath); + if (holder != null) { + holder.refresh(true); + } + } + } + } + + private void createToolbar() { + final DefaultActionGroup svnGroup = createActions(); + final ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, svnGroup, true); + myToolbarComponent = actionToolbar.getComponent(); + } + + private JPanel prepareData(final Map panels, final Map holders) { + final List roots = myDataLoader.loadRoots(); + + final JPanel mainPanel = new JPanel(new GridBagLayout()); + boolean onlyOneRoot = roots.size() == 1; + final GridBagConstraints gb = new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, + new Insets(0,0,0,0), 0, 0); + mainPanel.add(myToolbarComponent, gb); + ++ gb.gridy; + + for (final WCInfoWithBranches root : roots) { + if (root == null) { + continue; + } + final SvnMergeInfoRootPanelManual panel = new SvnMergeInfoRootPanelManual(myProject, + new NullableFunction() { + public WCInfoWithBranches fun(final WCInfoWithBranches wcInfoWithBranches) { + + final WCInfoWithBranches newInfo = + myDataLoader.reloadInfo(wcInfoWithBranches); + if (newInfo == null) { + // reload all items + myProject.getMessageBus().syncPublisher(SvnVcs17.WC_CONVERTED).run(); + // do not reload right now + return wcInfoWithBranches; + } + return newInfo; + } + }, new Runnable() { + public void run() { + final MergeInfoHolder holder = getHolder(root.getPath()); + if (holder != null) { + holder.refresh(false); + } + } + }, onlyOneRoot, root); + panels.put(root.getPath(), panel); + holders.put(root.getPath(), createHolder(panel)); + + final JPanel contentPanel = panel.getContentPanel(); + mainPanel.add(contentPanel, gb); + ++ gb.gridy; + } + if (panels.size() == 1) { + for (SvnMergeInfoRootPanelManual panel : panels.values()) { + panel.setOnlyOneRoot(true); + } + } + return mainPanel; + } + + private DefaultActionGroup createActions() { + final DefaultActionGroup svnGroup = new DefaultActionGroup(); + svnGroup.add(new HighlightFrom()); + svnGroup.add(myIntegrateAction); + svnGroup.add(myUndoIntegrateChangeListsAction); + svnGroup.add(new MarkAsMerged()); + svnGroup.add(new MarkAsNotMerged()); + svnGroup.add(myFilterMerged); + svnGroup.add(myFilterNotMerged); + svnGroup.add(myFilterAlien); + svnGroup.add(new ShowSvnMapAction()); + svnGroup.add(new MyRefresh()); + return svnGroup; + } + + private MergeInfoHolder createHolder(final SvnMergeInfoRootPanelManual panel) { + return new MergeInfoHolder(myProject, myManager, new Getter() { + public WCInfoWithBranches get() { + if (myFromHereDirection) { + return panel.getWcInfo(); + } else { + // actually not used + return null; + } + } + }, new Getter() { + public WCInfoWithBranches.Branch get() { + if (myFromHereDirection) { + return panel.getBranch(); + } else { + final WCInfoWithBranches wcInfo = panel.getWcInfo(); + return new WCInfoWithBranches.Branch(wcInfo.getUrl().toString()); + } + } + }, new Getter() { + public String get() { + if (myFromHereDirection) { + return panel.getLocalBranch(); + } else { + final WCInfoWithBranches wcInfo = panel.getWcInfo(); + return wcInfo.getPath(); + } + } + }, new Getter() { + public Boolean get() { + return myHighlightingOn && panel.isEnabled(); + } + }, new Consumer() { + public void consume(final Boolean aBoolean) { + panel.setMixedRevisions(aBoolean); + } + }); + } + + public JComponent getPanel() { + myPanelWrapper = new JPanel(new GridBagLayout()) { + @Override + public Dimension getPreferredSize() { + final Dimension oldSize = super.getPreferredSize(); + oldSize.width = 200; + return oldSize; + } + }; + //myPanelWrapper.setPreferredSize(new Dimension(200, 800)); + final GridBagConstraints gb = + new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); + myPanelWrapper.add(myPanel, gb); + return ScrollPaneFactory + .createScrollPane(myPanelWrapper, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + } + + // todo refactor to get rid of duplicate code dealing with separators + private String ensureEndsWithSeparator(final String wcPath) { + return wcPath.endsWith(File.separator) ? wcPath : (wcPath + File.separator); + } + + public void refresh() { + final Map refreshers = new HashMap(); + + for (Map.Entry entry : myHolders.entrySet()) { + final CommittedChangeListsListener refresher = entry.getValue().createRefresher(false); + if (refresher != null) { + refreshers.put(ensureEndsWithSeparator(entry.getKey()), refresher); + } + } + + if (! refreshers.isEmpty()) { + myManager.reportLoadedLists(new CommittedChangeListsListener() { + public void onBeforeStartReport() { + } + + public boolean report(final CommittedChangeList list) { + if (list instanceof SvnChangeList) { + final SvnChangeList svnList = (SvnChangeList) list; + final String wcPath = svnList.getWcPath(); + if (wcPath != null) { + final CommittedChangeListsListener refresher = refreshers.get(ensureEndsWithSeparator(wcPath)); + if (refresher != null) { + refresher.report(list); + } + } + } + return true; + } + + public void onAfterEndReport() { + for (CommittedChangeListsListener refresher : refreshers.values()) { + refresher.onAfterEndReport(); + } + myStrategy.notifyListener(); + } + }); + myManager.repaintTree(); + } + } + + + private class MyRefresh extends AnAction { + private MyRefresh() { + super(SvnBundle.message("committed.changes.action.merge.highlighting.refresh.text"), + SvnBundle.message("committed.changes.action.merge.highlighting.refresh.description"), IconLoader.getIcon("/actions/sync.png")); + } + + @Override + public void update(final AnActionEvent e) { + for (MergeInfoHolder holder : myHolders.values()) { + if (holder.refreshEnabled(false)) { + e.getPresentation().setEnabled(true); + return; + } + } + e.getPresentation().setEnabled(false); + } + + public void actionPerformed(final AnActionEvent e) { + final Presentation presentation = e.getPresentation(); + presentation.setEnabled(false); + + refresh(); + } + } + + private class HighlightTo extends ToggleAction { + @Override + public void update(final AnActionEvent e) { + super.update(e); + final Presentation presentation = e.getPresentation(); + presentation.setIcon(IconLoader.getIcon("/icons/ShowIntegratedTo.png")); + } + + public boolean isSelected(final AnActionEvent e) { + return myHighlightingOn && (! myFromHereDirection); + } + + public void setSelected(final AnActionEvent e, final boolean state) { + if (state) { + turnFromThereHighlighting(); + } else { + turnOff(); + } + } + } + + private class HighlightFrom extends ToggleAction { + @Override + public void update(final AnActionEvent e) { + super.update(e); + final Presentation presentation = e.getPresentation(); + presentation.setIcon(IconLoader.getIcon("/icons/ShowIntegratedFrom.png")); + presentation.setText(SvnBundle.message("committed.changes.action.enable.merge.highlighting")); + presentation.setDescription(SvnBundle.message("committed.changes.action.enable.merge.highlighting.description.text")); + } + + public boolean isSelected(final AnActionEvent e) { + return myHighlightingOn && myFromHereDirection; + } + + public void setSelected(final AnActionEvent e, final boolean state) { + if (state) { + turnFromHereHighlighting(); + } else { + turnOff(); + } + } + } + + private abstract class CommonFilter extends ToggleAction { + private boolean mySelected; + private final Icon myIcon; + + protected CommonFilter(final Icon icon, final String text) { + super(text); + myIcon = icon; + } + + @Override + public void update(final AnActionEvent e) { + super.update(e); + final Presentation presentation = e.getPresentation(); + presentation.setEnabled(myHighlightingOn); + presentation.setIcon(myIcon); + presentation.setText(getTemplatePresentation().getText()); + } + + public boolean isSelected(final AnActionEvent e) { + return mySelected; + } + + public void setSelected(final AnActionEvent e, final boolean state) { + mySelected = state; + myStrategy.notifyListener(); + } + } + + private class FilterOutMerged extends CommonFilter { + private FilterOutMerged() { + super(IconLoader.getIcon("/icons/FilterIntegrated.png"), SvnBundle.message("tab.repository.merge.panel.filter.plus")); + } + } + + private class FilterOutNotMerged extends CommonFilter { + private FilterOutNotMerged() { + super(IconLoader.getIcon("/icons/FilterNotIntegrated.png"), SvnBundle.message("tab.repository.merge.panel.filter.minus")); + } + } + + private class FilterOutAlien extends CommonFilter { + private FilterOutAlien() { + super(IconLoader.getIcon("/icons/FilterOthers.png"), SvnBundle.message("tab.repository.merge.panel.filter.others")); + } + } + + private boolean mergeEnabled(final List listsList, final boolean forMerge) { + if ((listsList == null) || (listsList.isEmpty())) { + return false; + } + for (CommittedChangeList list : listsList) { + if (! mergeEnabled(list, forMerge)) { + return false; + } + } + return true; + } + + private boolean mergeEnabled(final CommittedChangeList list, final boolean forMerge) { + final MergeInfoHolder.ListMergeStatus mergeStatus = getStatus(list, true); + if ((mergeStatus == null) || (MergeInfoHolder.ListMergeStatus.ALIEN.equals(mergeStatus))) { + return false; + } else if (MergeInfoHolder.ListMergeStatus.REFRESHING.equals(mergeStatus)) { + return true; + } + if (forMerge) { + return MergeInfoHolder.ListMergeStatus.NOT_MERGED.equals(mergeStatus); + } + return MergeInfoHolder.ListMergeStatus.MERGED.equals(mergeStatus); + } + + private class MarkAsMerged extends AbstractIntegrateChangesAction { + private final String myText; + private final String myDescription; + + private MarkAsMerged() { + super(false); + myText = SvnBundle.message("action.mark.list.as.merged.text"); + myDescription = SvnBundle.message("action.mark.list.as.merged.description"); + } + + @NotNull + protected MergerFactory createMergerFactory(SelectedChangeListsChecker checker) { + return new RecordOnlyMergerFactory(checker.getSelectedLists(), false); + } + + @NotNull + protected SelectedChangeListsChecker createChecker() { + return new SelectedChangeListsChecker(); + } + + @Override + protected void updateWithChecker(AnActionEvent e, SelectedCommittedStuffChecker checker) { + final Presentation presentation = e.getPresentation(); + presentation.setIcon(IconLoader.getIcon("/icons/MarkAsMerged.png")); + presentation.setText(myText); + presentation.setDescription(myDescription); + presentation.setEnabled(presentation.isEnabled() && mergeEnabled(checker.getSelectedLists(), true)); + } + + @Nullable + protected String getSelectedBranchUrl(SelectedCommittedStuffChecker checker) { + final SvnMergeInfoRootPanelManual data = getPanelData(checker.getSelectedLists()); + if (data != null) { + return data.getBranch().getUrl(); + } + return null; + } + + @Nullable + protected String getSelectedBranchLocalPath(SelectedCommittedStuffChecker checker) { + final SvnMergeInfoRootPanelManual data = getPanelData(checker.getSelectedLists()); + if (data != null) { + return data.getLocalBranch(); + } + return null; + } + + protected String getDialogTitle() { + return myText; + } + } + + private class MarkAsNotMerged extends AbstractIntegrateChangesAction { + private final String myText; + private final String myDescription; + + private MarkAsNotMerged() { + super(false); + myText = SvnBundle.message("action.mark.list.as.not.merged.title"); + myDescription = SvnBundle.message("action.mark.list.as.not.merged.descrition"); + } + + @NotNull + protected MergerFactory createMergerFactory(SelectedChangeListsChecker checker) { + return new RecordOnlyMergerFactory(checker.getSelectedLists(), true); + } + + @NotNull + protected SelectedChangeListsChecker createChecker() { + return new SelectedChangeListsChecker(); + } + + @Override + protected void updateWithChecker(AnActionEvent e, SelectedCommittedStuffChecker checker) { + final Presentation presentation = e.getPresentation(); + presentation.setIcon(IconLoader.getIcon("/icons/MarkAsNotMerged.png")); + presentation.setText(myText); + presentation.setDescription(myDescription); + presentation.setEnabled(presentation.isEnabled() && mergeEnabled(checker.getSelectedLists(), false)); + } + + @Nullable + protected String getSelectedBranchUrl(SelectedCommittedStuffChecker checker) { + final SvnMergeInfoRootPanelManual data = getPanelData(checker.getSelectedLists()); + if (data != null) { + return data.getBranch().getUrl(); + } + return null; + } + + @Nullable + protected String getSelectedBranchLocalPath(SelectedCommittedStuffChecker checker) { + final SvnMergeInfoRootPanelManual data = getPanelData(checker.getSelectedLists()); + if (data != null) { + return data.getLocalBranch(); + } + return null; + } + + protected String getDialogTitle() { + return myText; + } + } + + private class UndoIntegrateChangeListsAction extends IntegrateChangeListsAction { + private UndoIntegrateChangeListsAction() { + super(false); + } + + @Override + protected void updateWithChecker(AnActionEvent e, SelectedCommittedStuffChecker checker) { + e.getPresentation().setIcon(IconLoader.getIcon("/icons/UndoIntegrateToBranch.png")); + e.getPresentation().setText(SvnBundle.message("undo.integrate.to.branch")); + e.getPresentation().setDescription(SvnBundle.message("undo.integrate.to.branch.description")); + } + + @Override + protected String getDialogTitle() { + return SvnBundle.message("undo.integrate.to.branch.dialog.title"); + } + } + + private class IntegrateChangeListsAction extends AbstractIntegrateChangesAction { + private final boolean myDirect; + + public IntegrateChangeListsAction() { + this(true); + } + + protected IntegrateChangeListsAction(final boolean direct) { + super(false); + myDirect = direct; + } + + @NotNull + protected MergerFactory createMergerFactory(final SelectedChangeListsChecker checker) { + return new ChangeListsMergerFactory(checker.getSelectedLists()) { + @Override + public IMerger createMerger(final SvnVcs17 vcs, + final File target, + final UpdateEventHandler handler, + final SVNURL currentBranchUrl, + String branchName) { + return new Merger(vcs, myChangeListsList, target, handler, currentBranchUrl, branchName) { + @Override + protected SVNRevisionRange createRange() { + if (myDirect) { + return super.createRange(); + } else { + return new SVNRevisionRange(SVNRevision.create(myLatestProcessed.getNumber()), SVNRevision.create(myLatestProcessed.getNumber() - 1)); + } + } + }; + } + }; + } + + @NotNull + protected SelectedChangeListsChecker createChecker() { + return new SelectedChangeListsChecker(); + } + + @Override + protected void updateWithChecker(AnActionEvent e, SelectedCommittedStuffChecker checker) { + e.getPresentation().setIcon(IconLoader.getIcon("/icons/IntegrateToBranch.png")); + } + + protected String getSelectedBranchUrl(SelectedCommittedStuffChecker checker) { + final SvnMergeInfoRootPanelManual data = getPanelData(checker.getSelectedLists()); + if (data != null && data.getBranch() != null) { + return data.getBranch().getUrl(); + } + return null; + } + + protected String getSelectedBranchLocalPath(SelectedCommittedStuffChecker checker) { + final SvnMergeInfoRootPanelManual data = getPanelData(checker.getSelectedLists()); + if (data != null) { + return data.getLocalBranch(); + } + return null; + } + + protected String getDialogTitle() { + return null; + } + } + + private SvnMergeInfoRootPanelManual getPanelData(final List listsList) { + for (CommittedChangeList list : listsList) { + if (! (list instanceof SvnChangeList)) { + return null; + } + final SvnChangeList svnList = (SvnChangeList) list; + final String wcPath = svnList.getWcPath(); + if (wcPath == null) { + continue; + } + return getPanelData(wcPath); + } + return null; + } + + @Nullable + public MergeInfoHolder.ListMergeStatus getStatus(final CommittedChangeList list, final boolean ignoreEnabled) { + if (! (list instanceof SvnChangeList)) { + return null; + } + + final SvnChangeList svnList = (SvnChangeList) list; + final String wcPath = svnList.getWcPath(); + MergeInfoHolder holder = null; + if (wcPath == null) { + for (Map.Entry entry : myMergePanels.entrySet()) { + final SvnMergeInfoRootPanelManual panelManual = entry.getValue(); + if ((panelManual.getBranch() != null) && (panelManual.getBranch().getUrl() != null) && + svnList.allPathsUnder(panelManual.getBranch().getUrl())) { + holder = getHolder(entry.getKey()); + } + } + } else { + holder = getHolder(wcPath); + } + if (holder != null) { + return holder.getDecorator().check(list, ignoreEnabled); + } + return null; + } + + public MergePanelFiltering getStrategy() { + return myStrategy; + } + + public boolean strategyInitialized() { + return myStrategy.isInitialized(); + } + + private class MergePanelFiltering implements ChangeListFilteringStrategy { + private final JComponent myPanel; + private ChangeListener myListener; + private boolean myInitialized; + private final static String ourKey = "MERGE_PANEL"; + + public MergePanelFiltering(final JComponent panel) { + myPanel = panel; + } + + public boolean isInitialized() { + return myInitialized; + } + + public JComponent getFilterUI() { + if (! myInitialized) { + createPanels(myLocation, null); + } + myInitialized = true; + return myPanel; + } + + @Override + public CommittedChangesFilterKey getKey() { + return new CommittedChangesFilterKey(ourKey, CommittedChangesFilterPriority.MERGE); + } + + public void setFilterBase(final List changeLists) { + } + + public void addChangeListener(final ChangeListener listener) { + myListener = listener; + } + + public void removeChangeListener(final ChangeListener listener) { + myListener = null; + } + + public void resetFilterBase() { + } + + public void appendFilterBase(List changeLists) { + } + + @NotNull + public List filterChangeLists(final List changeLists) { + if ((! myFilterAlien.isSelected(null)) && (! myFilterNotMerged.isSelected(null)) && (! myFilterMerged.isSelected(null))) { + return changeLists; + } + + final List result = new ArrayList(); + for (CommittedChangeList list : changeLists) { + final MergeInfoHolder.ListMergeStatus status = getStatus(list, true); + if (MergeInfoHolder.ListMergeStatus.REFRESHING.equals(status)) { + result.add(list); + } else if ((status == null) || MergeInfoHolder.ListMergeStatus.ALIEN.equals(status)) { + if (! myFilterAlien.isSelected(null)) { + result.add(list); + } + } else if (MergeInfoHolder.ListMergeStatus.MERGED.equals(status) || MergeInfoHolder.ListMergeStatus.COMMON.equals(status)) { + if (! myFilterMerged.isSelected(null)) { + result.add(list); + } + } else { + // not merged + if (! myFilterNotMerged.isSelected(null)) { + result.add(list); + } + } + } + return result; + } + + public void notifyListener() { + if (myListener != null) { + myListener.stateChanged(new ChangeEvent(this)); + } + myManager.repaintTree(); + } + } + + public void fireRepaint() { + myManager.repaintTree(); + } + + public void dispose() { + myDisposed = true; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnChangeList.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnChangeList.java new file mode 100644 index 000000000000..e4cd8edf7a92 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnChangeList.java @@ -0,0 +1,666 @@ +/* + * 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. + */ + +/* + * Created by IntelliJ IDEA. + * User: yole + * Date: 28.11.2006 + * Time: 17:20:32 + */ +package org.jetbrains.idea.svn17.history; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.FilePathImpl; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ContentRevision; +import com.intellij.openapi.vcs.changes.ExternallyRenamedChange; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.ConstantFunction; +import com.intellij.util.NotNullFunction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.*; +import org.tmatesoft.svn.core.*; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; +import org.tmatesoft.svn.core.io.SVNRepository; +import org.tmatesoft.svn.core.wc.SVNLogClient; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.File; +import java.io.IOException; +import java.util.*; + +public class SvnChangeList implements CommittedChangeList { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.history"); + + private final SvnVcs17 myVcs; + private final SvnRepositoryLocation myLocation; + private String myRepositoryRoot; + private long myRevision; + private String myAuthor; + private Date myDate; + private String myMessage; + private final Set myChangedPaths = new HashSet(); + private final Set myAddedPaths = new HashSet(); + private final Set myDeletedPaths = new HashSet(); + private final Set myReplacedPaths = new HashSet(); + + private ChangesListCreationHelper myListsHolder; + + private SVNURL myBranchUrl; + + private boolean myCachedInfoLoaded; + + // key: added path, value: copied-from + private final Map myCopiedAddedPaths = new HashMap(); + private RootUrlInfo myWcRoot; + private final CommonPathSearcher myCommonPathSearcher; + + public SvnChangeList(@NotNull final List lists, @NotNull final SvnRepositoryLocation location) { + + final SvnChangeList sample = (SvnChangeList) lists.get(0); + myVcs = sample.myVcs; + myLocation = location; + myRevision = sample.myRevision; + myAuthor = sample.myAuthor; + myDate = sample.myDate; + myMessage = sample.myMessage; + myRepositoryRoot = sample.myRepositoryRoot; + myCommonPathSearcher = new CommonPathSearcher(); + + for (CommittedChangeList list : lists) { + final SvnChangeList svnList = (SvnChangeList) list; + myChangedPaths.addAll(svnList.myChangedPaths); + myAddedPaths.addAll(svnList.myAddedPaths); + myDeletedPaths.addAll(svnList.myDeletedPaths); + myReplacedPaths.addAll(svnList.myReplacedPaths); + } + } + + public SvnChangeList(SvnVcs17 vcs, @NotNull final SvnRepositoryLocation location, final SVNLogEntry logEntry, String repositoryRoot) { + myVcs = vcs; + myLocation = location; + myRevision = logEntry.getRevision(); + final String author = logEntry.getAuthor(); + myAuthor = author == null ? "" : author; + myDate = logEntry.getDate(); + final String message = logEntry.getMessage(); + myMessage = message == null ? "" : message; + + myRepositoryRoot = repositoryRoot.endsWith("/") ? repositoryRoot.substring(0, repositoryRoot.length() - 1) : repositoryRoot; + + myCommonPathSearcher = new CommonPathSearcher(); + + for(Object o: logEntry.getChangedPaths().values()) { + final SVNLogEntryPath entry = (SVNLogEntryPath) o; + final String path = entry.getPath(); + + myCommonPathSearcher.next(path); + + if (entry.getType() == 'A') { + if (entry.getCopyPath() != null) { + myCopiedAddedPaths.put(path, entry.getCopyPath()); + } + myAddedPaths.add(path); + } + else if (entry.getType() == 'D') { + myDeletedPaths.add(path); + } + else { + if (entry.getType() == 'R') { + myReplacedPaths.add(path); + } + myChangedPaths.add(path); + } + } + } + + public SvnChangeList(SvnVcs17 vcs, @NotNull SvnRepositoryLocation location, DataInput stream, final boolean supportsCopyFromInfo, + final boolean supportsReplaced) throws IOException { + myVcs = vcs; + myLocation = location; + readFromStream(stream, supportsCopyFromInfo, supportsReplaced); + myCommonPathSearcher = new CommonPathSearcher(); + for (String path : myAddedPaths) { + myCommonPathSearcher.next(path); + } + for (String path : myDeletedPaths) { + myCommonPathSearcher.next(path); + } + for (String path : myChangedPaths) { + myCommonPathSearcher.next(path); + } + } + + public Change getByPath(final String path) { + if (myListsHolder == null) { + createLists(); + } + return myListsHolder.getByPath(path); + } + + public String getCommitterName() { + return myAuthor; + } + + public Date getCommitDate() { + return myDate; + } + + + public Collection getChanges() { + if (myListsHolder == null) { + createLists(); + } + return myListsHolder.getList(); + } + + private void createLists() { + myListsHolder = new ChangesListCreationHelper(); + + // key: copied-from + final Map copiedAddedChanges = new HashMap(); + + for(String path: myAddedPaths) { + final Change addedChange; + if (myCopiedAddedPaths.containsKey(path)) { + if (myDeletedPaths.contains(myCopiedAddedPaths.get(path))) { + addedChange = new ExternallyRenamedChange(myListsHolder.createRevisionLazily(myCopiedAddedPaths.get(path), true), + myListsHolder.createRevisionLazily(path, false), myCopiedAddedPaths.get(path)); + addedChange.getMoveRelativePath(myVcs.getProject()); + ((ExternallyRenamedChange) addedChange).setCopied(false); + } else { + addedChange = new ExternallyRenamedChange(null, myListsHolder.createRevisionLazily(path, false), myCopiedAddedPaths.get(path)); + } + copiedAddedChanges.put(myCopiedAddedPaths.get(path), (ExternallyRenamedChange) addedChange); + } else { + addedChange = new Change(null, myListsHolder.createRevisionLazily(path, false)); + } + myListsHolder.add(path, addedChange); + } + for(String path: myDeletedPaths) { + final Change deletedChange; + if (copiedAddedChanges.containsKey(path)) { + final ExternallyRenamedChange addedChange = copiedAddedChanges.get(path); + final FilePath source = addedChange.getAfterRevision().getFile(); + deletedChange = new ExternallyRenamedChange(myListsHolder.createDeletedItemRevision(path, true), null, source.getPresentableUrl()); + ((ExternallyRenamedChange) deletedChange).setCopied(false); + //noinspection ConstantConditions + //addedChange.setRenamedOrMovedTarget(deletedChange.getBeforeRevision().getFile()); + //noinspection ConstantConditions + ((ExternallyRenamedChange) deletedChange).setRenamedOrMovedTarget(source); + } else { + deletedChange = new Change(myListsHolder.createDeletedItemRevision(path, true), null); + } + myListsHolder.add(path, deletedChange); + } + for(String path: myChangedPaths) { + boolean moveAndChange = false; + final boolean replaced = myReplacedPaths.contains(path); + + // this piece: for copied-from (or moved) and further modified + for (String addedPath : myAddedPaths) { + String copyFromPath = myCopiedAddedPaths.get(addedPath); + if ((copyFromPath != null) && (SVNPathUtil.isAncestor(addedPath, path))) { + if (addedPath.length() < path.length()) { + final String relative = SVNPathUtil.getRelativePath(addedPath, path); + copyFromPath = SVNPathUtil.append(copyFromPath, relative); + } + final ExternallyRenamedChange renamedChange = new ExternallyRenamedChange(myListsHolder.createRevisionLazily(copyFromPath, true), + myListsHolder.createRevisionLazily(path, false), copyFromPath); + moveAndChange = true; + renamedChange.getMoveRelativePath(myVcs.getProject()); + renamedChange.setIsReplaced(replaced); + + final ExternallyRenamedChange addedChange = copiedAddedChanges.get(myCopiedAddedPaths.get(addedPath)); + if ((addedChange != null) && (addedChange.isCopied())) { + renamedChange.setCopied(true); + } else { + renamedChange.setCopied(false); + } + + myListsHolder.add(path, renamedChange); + break; + } + } + if (! moveAndChange) { + final ExternallyRenamedChange renamedChange = + new ExternallyRenamedChange(myListsHolder.createRevisionLazily(path, true), myListsHolder.createRevisionLazily(path, false), + null); + renamedChange.setIsReplaced(replaced); + renamedChange.setCopied(false); + myListsHolder.add(path, renamedChange); + } + } + } + + @Nullable + private FilePath getLocalPath(final String path, final NotNullFunction detector) { + final String fullPath = myRepositoryRoot + path; + return myLocation.getLocalPath(fullPath, detector, myVcs); + } + + private long getRevision(final boolean isBeforeRevision) { + return isBeforeRevision ? (myRevision - 1) : myRevision; + } + + public SvnRepositoryLocation getLocation() { + return myLocation; + } + + /** + * needed to track in which changes non-local files live + */ + private class ChangesListCreationHelper { + private final List myList; + private final Map myPathToChangeMapping; + private List myDetailedList; + private final List> myWithoutDirStatus; + private SVNRepository myRepository; + + private ChangesListCreationHelper() { + myList = new ArrayList(); + myWithoutDirStatus = new ArrayList>(); + myPathToChangeMapping = new HashMap(); + } + + public void add(final String path, final Change change) { + myList.add(change); + myPathToChangeMapping.put(path, change); + } + + public Change getByPath(final String path) { + return myPathToChangeMapping.get(path); + } + + private FilePath localDeletedPath(final String fullPath) { + final SvnFileUrlMapping17 urlMapping = myVcs.getSvnFileUrlMapping(); + final String path = urlMapping.getLocalPath(fullPath); + if (path != null) { + final File file = new File(path); + return FilePathImpl.createForDeletedFile(file, file.isDirectory()); + } + + return null; + } + + public SvnRepositoryContentRevision createDeletedItemRevision(final String path, final boolean isBeforeRevision) { + final String fullPath = myRepositoryRoot + path; + myWithoutDirStatus.add(new Pair(myList.size(), isBeforeRevision)); + return SvnRepositoryContentRevision.create(myVcs, myRepositoryRoot, path, localDeletedPath(fullPath), getRevision(isBeforeRevision)); + } + + public SvnRepositoryContentRevision createRevisionLazily(final String path, final boolean isBeforeRevision) { + return SvnRepositoryContentRevision.create(myVcs, myRepositoryRoot, path, + getLocalPath(path, new NotNullFunction() { + @NotNull + public Boolean fun(final File file) { + // list will be next + myWithoutDirStatus.add(new Pair(myList.size(), isBeforeRevision)); + return Boolean.FALSE; + } + }), getRevision(isBeforeRevision)); + } + + public List getList() { + return myList; + } + + public List getDetailedList() { + if (myDetailedList == null) { + myDetailedList = new ArrayList(myList); + + try { + myRepository = myVcs.createRepository(myRepositoryRoot); + + doRemoteDetails(); + uploadDeletedRenamedChildren(); + } + catch (SVNException e) { + LOG.info(e); + } finally { + if (myRepository != null) { + myRepository.closeSession(); + myRepository = null; + } + } + } + return myDetailedList; + } + + private void doRemoteDetails() throws SVNException { + for (Pair idxData : myWithoutDirStatus) { + final Change sourceChange = myDetailedList.get(idxData.first.intValue()); + final SvnRepositoryContentRevision revision = (SvnRepositoryContentRevision) + (idxData.second.booleanValue() ? sourceChange.getBeforeRevision() : sourceChange.getAfterRevision()); + if (revision == null) { + continue; + } + final boolean status = SVNNodeKind.DIR.equals(myRepository.checkPath(revision.getPath(), getRevision(idxData.second.booleanValue()))); + final Change replacingChange = new Change(createRevision((SvnRepositoryContentRevision) sourceChange.getBeforeRevision(), status), + createRevision((SvnRepositoryContentRevision) sourceChange.getAfterRevision(), status)); + replacingChange.setIsReplaced(sourceChange.isIsReplaced()); + myDetailedList.set(idxData.first.intValue(), replacingChange); + } + + myWithoutDirStatus.clear(); + } + + @Nullable + private SvnRepositoryContentRevision createRevision(final SvnRepositoryContentRevision previousRevision, final boolean isDir) { + return previousRevision == null ? null : + SvnRepositoryContentRevision.create(myVcs, myRepositoryRoot, previousRevision.getPath(), + new FilePathImpl(previousRevision.getFile().getIOFile(), isDir), + ((SvnRevisionNumber) previousRevision.getRevisionNumber()).getRevision().getNumber()); + } + + private void uploadDeletedRenamedChildren() throws SVNException { + // cannot insert when iterate + final List detailsOnly = new ArrayList(); + + final Set> duplicateControl = new HashSet>(); + for (Change change : myDetailedList) { + if (change.getBeforeRevision() != null) { + duplicateControl.add(new Pair(Boolean.TRUE, ((SvnRepositoryContentRevision) change.getBeforeRevision()).getPath())); + } + if (change.getAfterRevision() != null) { + duplicateControl.add(new Pair(Boolean.FALSE, ((SvnRepositoryContentRevision) change.getAfterRevision()).getPath())); + } + } + + for (Change change : myDetailedList) { + // directory statuses are already uploaded + if ((change.getAfterRevision() == null) && (change.getBeforeRevision().getFile().isDirectory())) { + final SvnRepositoryContentRevision revision = (SvnRepositoryContentRevision) change.getBeforeRevision(); + detailsOnly.addAll(getChildrenAsChanges(revision.getPath(), true, duplicateControl)); + } else if ((change.getBeforeRevision() == null) && (change.getAfterRevision().getFile().isDirectory())) { + // look for renamed folders contents + final SvnRepositoryContentRevision revision = (SvnRepositoryContentRevision) change.getAfterRevision(); + if (myCopiedAddedPaths.containsKey(revision.getPath())) { + detailsOnly.addAll(getChildrenAsChanges(revision.getPath(), false, duplicateControl)); + } + } + } + + myDetailedList.addAll(detailsOnly); + } + + @NotNull + private Collection getChildrenAsChanges(final String path, final boolean isBefore, final Set> duplicateControl) + throws SVNException { + final List result = new ArrayList(); + + final SVNLogClient client = myVcs.createLogClient(); + + final long revision = getRevision(isBefore); + client.doList(myRepository.getLocation().appendPath(path, true), SVNRevision.create(revision), SVNRevision.create(revision), + true, new ISVNDirEntryHandler() { + public void handleDirEntry(final SVNDirEntry dirEntry) throws SVNException { + final String childPath = path + '/' + dirEntry.getRelativePath(); + + if (! duplicateControl.contains(new Pair(isBefore, childPath))) { + final ContentRevision contentRevision = createRevision(childPath, isBefore, SVNNodeKind.DIR.equals(dirEntry.getKind())); + result.add(new Change(isBefore ? contentRevision : null, isBefore ? null : contentRevision)); + } + } + }); + + return result; + } + + private SvnRepositoryContentRevision createRevision(final String path, final boolean isBeforeRevision, final boolean isDir) { + return SvnRepositoryContentRevision.create(myVcs, myRepositoryRoot, path, + getLocalPath(path, new ConstantFunction(isDir)), getRevision(isBeforeRevision)); + } + } + + @NotNull + public String getName() { + return myMessage; + } + + public String getComment() { + return myMessage; + } + + public long getNumber() { + return myRevision; + } + + public AbstractVcs getVcs() { + return myVcs; + } + + public Collection getChangesWithMovedTrees() { + if (myListsHolder == null) { + createLists(); + } + + return myListsHolder.getDetailedList(); + } + + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final SvnChangeList that = (SvnChangeList)o; + + if (myRevision != that.myRevision) return false; + if (myAuthor != null ? !myAuthor.equals(that.myAuthor) : that.myAuthor != null) return false; + if (myDate != null ? !myDate.equals(that.myDate) : that.myDate != null) return false; + if (myMessage != null ? !myMessage.equals(that.myMessage) : that.myMessage != null) return false; + + return true; + } + + public int hashCode() { + int result; + result = (int)(myRevision ^ (myRevision >>> 32)); + result = 31 * result + (myAuthor != null ? myAuthor.hashCode() : 0); + result = 31 * result + (myDate != null ? myDate.hashCode() : 0); + result = 31 * result + (myMessage != null ? myMessage.hashCode() : 0); + return result; + } + + public String toString() { + return myMessage; + } + + public void writeToStream(final DataOutput stream) throws IOException { + stream.writeUTF(myRepositoryRoot); + stream.writeLong(myRevision); + stream.writeUTF(myAuthor); + stream.writeLong(myDate.getTime()); + writeUTFTruncated(stream, myMessage); + writeFiles(stream, myChangedPaths); + writeFiles(stream, myAddedPaths); + writeFiles(stream, myDeletedPaths); + writeMap(stream, myCopiedAddedPaths); + writeFiles(stream, myReplacedPaths); + } + + // to be able to update plugin only + public static void writeUTFTruncated(final DataOutput stream, final String text) throws IOException { + // we should not compare number of symbols to 65635 -> it is number of bytes what should be compared + // ? 4 bytes per symbol - rough estimation + if (text.length() > 16383) { + stream.writeUTF(text.substring(0, 16383)); + } + else { + stream.writeUTF(text); + } + } + + private static void writeFiles(final DataOutput stream, final Set paths) throws IOException { + stream.writeInt(paths.size()); + for(String s: paths) { + stream.writeUTF(s); + } + } + + private void readFromStream(final DataInput stream, final boolean supportsCopyFromInfo, final boolean supportsReplaced) throws IOException { + myRepositoryRoot = stream.readUTF(); + myRevision = stream.readLong(); + myAuthor = stream.readUTF(); + myDate = new Date(stream.readLong()); + myMessage = stream.readUTF(); + readFiles(stream, myChangedPaths); + readFiles(stream, myAddedPaths); + readFiles(stream, myDeletedPaths); + + if (supportsCopyFromInfo) { + readMap(stream, myCopiedAddedPaths); + } + + if (supportsReplaced) { + readFiles(stream, myReplacedPaths); + } + } + + private static void writeMap(final DataOutput stream, final Map map) throws IOException { + stream.writeInt(map.size()); + for (Map.Entry entry : map.entrySet()) { + stream.writeUTF(entry.getKey()); + stream.writeUTF(entry.getValue()); + } + } + + private static void readMap(final DataInput stream, final Map map) throws IOException { + int count = stream.readInt(); + for (int i = 0; i < count; i++) { + map.put(stream.readUTF(), stream.readUTF()); + } + } + + private static void readFiles(final DataInput stream, final Set paths) throws IOException { + int count = stream.readInt(); + for(int i=0; i getChangedPaths() { + return myChangedPaths; + } + + public Set getAddedPaths() { + return myAddedPaths; + } + + public Set getDeletedPaths() { + return myDeletedPaths; + } + + @Nullable + public String getWcPath() { + final RootUrlInfo rootInfo = getWcRootInfo(); + if (rootInfo == null) { + return null; + } + return rootInfo.getIoFile().getAbsolutePath(); + } + + public boolean allPathsUnder(final String path) { + final String commonRelative = myCommonPathSearcher.getCommon(); + if (commonRelative != null) { + return SVNPathUtil.isAncestor(path, SVNPathUtil.append(myRepositoryRoot, commonRelative)); + } + return false; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnCommittedChangesProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnCommittedChangesProvider.java new file mode 100644 index 000000000000..a846fefba253 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnCommittedChangesProvider.java @@ -0,0 +1,712 @@ +/* + * 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.history; + +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +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.util.IconLoader; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vcs.*; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ContentRevision; +import com.intellij.openapi.vcs.changes.committed.*; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings; +import com.intellij.openapi.vcs.versionBrowser.ChangesBrowserSettingsEditor; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.AsynchConsumer; +import com.intellij.util.Consumer; +import com.intellij.util.PairConsumer; +import com.intellij.util.ThrowableConsumer; +import com.intellij.util.containers.MultiMap; +import com.intellij.util.messages.MessageBusConnection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.*; +import org.jetbrains.idea.svn17.actions.ConfigureBranchesAction; +import org.tmatesoft.svn.core.*; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; +import org.tmatesoft.svn.core.io.SVNRepository; +import org.tmatesoft.svn.core.wc.SVNInfo; +import org.tmatesoft.svn.core.wc.SVNLogClient; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.File; +import java.io.IOException; +import java.util.*; + +/** + * @author yole + */ +public class SvnCommittedChangesProvider implements CachingCommittedChangesProvider { + private final Project myProject; + private final SvnVcs17 myVcs; + private final MessageBusConnection myConnection; + private MergeInfoUpdatesListener myMergeInfoUpdatesListener; + private final MyZipper myZipper; + + public final static int VERSION_WITH_COPY_PATHS_ADDED = 2; + public final static int VERSION_WITH_REPLACED_PATHS = 3; + + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.history.SvnCommittedChangesProvider"); + + public SvnCommittedChangesProvider(final Project project) { + myProject = project; + myVcs = SvnVcs17.getInstance(myProject); + myZipper = new MyZipper(); + + myConnection = myProject.getMessageBus().connect(); + + myConnection.subscribe(VcsConfigurationChangeListener.BRANCHES_CHANGED_RESPONSE, new VcsConfigurationChangeListener.DetailedNotification() { + public void execute(final Project project, final VirtualFile vcsRoot, final List cachedList) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + if (project.isDisposed()) { + return; + } + for (CommittedChangeList committedChangeList : cachedList) { + if ((committedChangeList instanceof SvnChangeList) && + ((vcsRoot == null) || (vcsRoot.equals(((SvnChangeList)committedChangeList).getVcsRoot())))) { + ((SvnChangeList) committedChangeList).forceReloadCachedInfo(true); + } + } + } + }); + } + }); + } + + public ChangeBrowserSettings createDefaultSettings() { + return new ChangeBrowserSettings(); + } + + public ChangesBrowserSettingsEditor createFilterUI(final boolean showDateFilter) { + return new SvnVersionFilterComponent(showDateFilter); + } + + @Nullable + public RepositoryLocation getLocationFor(final FilePath root) { + final ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + String[] urls = SvnUtil.getLocationsForModule(myVcs, root.getIOFile(), progress); + if (urls.length == 1) { + return new SvnRepositoryLocation(urls [0]); + } + return null; + } + + public RepositoryLocation getLocationFor(final FilePath root, final String repositoryPath) { + if (repositoryPath == null) { + return getLocationFor(root); + } + + return new SvnLoadingRepositoryLocation(repositoryPath, myVcs); + } + + public VcsCommittedListsZipper getZipper() { + return myZipper; + } + + private class MyZipper implements VcsCommittedListsZipper { + public Pair, List> groupLocations(final List in) { + final List groups = new ArrayList(); + final List singles = new ArrayList(); + + final MultiMap map = new MultiMap(); + + for (RepositoryLocation location : in) { + final SvnRepositoryLocation svnLocation = (SvnRepositoryLocation) location; + final String url = svnLocation.getURL(); + + final SVNURL root = SvnUtil.getRepositoryRoot(myVcs, url); + if (root == null) { + // should not occur + LOG.info("repository root not found for location:"+ location.toPresentableString()); + singles.add(location); + } else { + map.putValue(root, svnLocation); + } + } + + final Set keys = map.keySet(); + for (SVNURL key : keys) { + final Collection repositoryLocations = map.get(key); + if (repositoryLocations.size() == 1) { + singles.add(repositoryLocations.iterator().next()); + } else { + final SvnRepositoryLocationGroup group = new SvnRepositoryLocationGroup(key, repositoryLocations); + groups.add(group); + } + } + return new Pair, List>(groups, singles); + } + + public CommittedChangeList zip(final RepositoryLocationGroup group, final List lists) { + return new SvnChangeList(lists, new SvnRepositoryLocation(group.toPresentableString())); + } + + public long getNumber(final CommittedChangeList list) { + return list.getNumber(); + } + } + + public void loadCommittedChanges(ChangeBrowserSettings settings, + RepositoryLocation location, + int maxCount, + final AsynchConsumer consumer) + throws VcsException { + try { + final SvnRepositoryLocation svnLocation = (SvnRepositoryLocation) location; + final ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + if (progress != null) { + progress.setText(SvnBundle.message("progress.text.changes.collecting.changes")); + progress.setText2(SvnBundle.message("progress.text2.changes.establishing.connection", location)); + } + + final String repositoryRoot; + SVNRepository repository = null; + try { + repository = myVcs.createRepository(svnLocation.getURL()); + repositoryRoot = repository.getRepositoryRoot(true).toString(); + } + catch (SVNException e) { + throw new VcsException(e); + } finally { + if (repository != null) { + repository.closeSession(); + } + } + + final ChangeBrowserSettings.Filter filter = settings.createFilter(); + + getCommittedChangesImpl(settings, svnLocation.getURL(), new String[]{""}, maxCount, new Consumer() { + public void consume(final SVNLogEntry svnLogEntry) { + final SvnChangeList cl = new SvnChangeList(myVcs, svnLocation, svnLogEntry, repositoryRoot); + if (filter.accepts(cl)) { + consumer.consume(cl); + } + } + }, false, true); + } + finally { + consumer.finished(); + } + } + + public List getCommittedChanges(ChangeBrowserSettings settings, final RepositoryLocation location, final int maxCount) throws VcsException { + final SvnRepositoryLocation svnLocation = (SvnRepositoryLocation) location; + final ArrayList result = new ArrayList(); + final ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + if (progress != null) { + progress.setText(SvnBundle.message("progress.text.changes.collecting.changes")); + progress.setText2(SvnBundle.message("progress.text2.changes.establishing.connection", location)); + } + + final String repositoryRoot; + SVNRepository repository = null; + try { + repository = myVcs.createRepository(svnLocation.getURL()); + repositoryRoot = repository.getRepositoryRoot(true).toString(); + repository.closeSession(); + } + catch (SVNException e) { + throw new VcsException(e); + } finally { + if (repository != null) { + repository.closeSession(); + } + } + + getCommittedChangesImpl(settings, svnLocation.getURL(), new String[]{""}, maxCount, new Consumer() { + public void consume(final SVNLogEntry svnLogEntry) { + result.add(new SvnChangeList(myVcs, svnLocation, svnLogEntry, repositoryRoot)); + } + }, false, true); + settings.filterChanges(result); + return result; + } + + public void getCommittedChangesWithMergedRevisons(final ChangeBrowserSettings settings, + final RepositoryLocation location, final int maxCount, + final PairConsumer> finalConsumer) + throws VcsException { + final SvnRepositoryLocation svnLocation = (SvnRepositoryLocation) location; + final ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + if (progress != null) { + progress.setText(SvnBundle.message("progress.text.changes.collecting.changes")); + progress.setText2(SvnBundle.message("progress.text2.changes.establishing.connection", location)); + } + + final String repositoryRoot; + SVNRepository repository = null; + try { + repository = myVcs.createRepository(svnLocation.getURL()); + repositoryRoot = repository.getRepositoryRoot(true).toString(); + } + catch (SVNException e) { + throw new VcsException(e); + } finally { + if (repository != null) { + repository.closeSession(); + } + } + + final MergeTrackerProxy proxy = new MergeTrackerProxy(new Consumer>() { + public void consume(TreeStructureNode node) { + finalConsumer.consume(new SvnChangeList(myVcs, svnLocation, node.getMe(), repositoryRoot), node); + } + }); + final SvnMergeSourceTracker mergeSourceTracker = new SvnMergeSourceTracker(new ThrowableConsumer, SVNException>() { + public void consume(Pair svnLogEntryIntegerPair) throws SVNException { + proxy.consume(svnLogEntryIntegerPair); + } + }); + + getCommittedChangesImpl(settings, svnLocation.getURL(), new String[]{""}, maxCount, new Consumer() { + public void consume(final SVNLogEntry svnLogEntry) { + try { + mergeSourceTracker.consume(svnLogEntry); + } + catch (SVNException e) { + throw new RuntimeException(e); + // will not occur actually but anyway never eat them + } + } + }, true, false); + + proxy.finish(); + } + + + private static class MergeTrackerProxy implements ThrowableConsumer, SVNException> { + private TreeStructureNode myCurrentHierarchy; + private final Consumer> myConsumer; + + private MergeTrackerProxy(Consumer> consumer) { + myConsumer = consumer; + } + + public void consume(Pair svnLogEntryIntegerPair) throws SVNException { + final SVNLogEntry logEntry = svnLogEntryIntegerPair.getFirst(); + final Integer mergeLevel = svnLogEntryIntegerPair.getSecond(); + + if (mergeLevel < 0) { + if (myCurrentHierarchy != null) { + myConsumer.consume(myCurrentHierarchy); + } + if (logEntry.hasChildren()) { + myCurrentHierarchy = new TreeStructureNode(logEntry); + } else { + // just pass + myCurrentHierarchy = null; + myConsumer.consume(new TreeStructureNode(logEntry)); + } + } else { + addToLevel(myCurrentHierarchy, logEntry, mergeLevel); + } + } + + public void finish() { + if (myCurrentHierarchy != null) { + myConsumer.consume(myCurrentHierarchy); + } + } + + private static void addToLevel(final TreeStructureNode tree, final SVNLogEntry entry, final int left) { + assert tree != null; + if (left == 0) { + tree.add(entry); + } else { + final List> children = tree.getChildren(); + assert ! children.isEmpty(); + addToLevel(children.get(children.size() - 1), entry, left - 1); + } + } + } + + private void getCommittedChangesImpl(ChangeBrowserSettings settings, final String url, final String[] filterUrls, + final int maxCount, final Consumer resultConsumer, final boolean includeMergedRevisions, + final boolean filterOutByDate) throws VcsException { + final ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + if (progress != null) { + progress.setText(SvnBundle.message("progress.text.changes.collecting.changes")); + progress.setText2(SvnBundle.message("progress.text2.changes.establishing.connection", url)); + } + try { + SVNLogClient logger = myVcs.createLogClient(); + + final String author = settings.getUserFilter(); + final Date dateFrom = settings.getDateAfterFilter(); + final Long changeFrom = settings.getChangeAfterFilter(); + final Date dateTo = settings.getDateBeforeFilter(); + final Long changeTo = settings.getChangeBeforeFilter(); + + final SVNRevision revisionBefore; + if (dateTo != null) { + revisionBefore = SVNRevision.create(dateTo); + } + else if (changeTo != null) { + revisionBefore = SVNRevision.create(changeTo.longValue()); + } + else { + SVNRepository repository = null; + final long revision; + try { + repository = myVcs.createRepository(url); + revision = repository.getLatestRevision(); + } finally { + if (repository != null) { + repository.closeSession(); + } + } + revisionBefore = SVNRevision.create(revision); + } + final SVNRevision revisionAfter; + if (dateFrom != null) { + revisionAfter = SVNRevision.create(dateFrom); + } + else if (changeFrom != null) { + revisionAfter = SVNRevision.create(changeFrom.longValue()); + } + else { + revisionAfter = SVNRevision.create(1); + } + + logger.doLog(SVNURL.parseURIEncoded(url), filterUrls, revisionBefore, revisionBefore, revisionAfter, + settings.STOP_ON_COPY, true, includeMergedRevisions, maxCount, null, + new ISVNLogEntryHandler() { + public void handleLogEntry(SVNLogEntry logEntry) { + if (myProject.isDisposed()) throw new ProcessCanceledException(); + if (progress != null) { + progress.setText2(SvnBundle.message("progress.text2.processing.revision", logEntry.getRevision())); + progress.checkCanceled(); + } + if (filterOutByDate && logEntry.getDate() == null) { + // do not add lists without info - this situation is possible for lists where there are paths that user has no rights to observe + return; + } + if (author == null || author.equalsIgnoreCase(logEntry.getAuthor())) { + resultConsumer.consume(logEntry); + } + } + }); + } + catch (SVNException e) { + throw new VcsException(e); + } + } + + public ChangeListColumn[] getColumns() { + return new ChangeListColumn[] { + new ChangeListColumn.ChangeListNumberColumn(SvnBundle.message("revision.title")), + ChangeListColumn.NAME, ChangeListColumn.DATE, ChangeListColumn.DESCRIPTION + }; + } + + private void refreshMergeInfo(final RootsAndBranches action) { + if (myMergeInfoUpdatesListener == null) { + myMergeInfoUpdatesListener = new MergeInfoUpdatesListener(myProject, myConnection); + } + myMergeInfoUpdatesListener.addPanel(action); + } + + private static class ShowHideMergePanel extends ToggleAction { + private final DecoratorManager myManager; + private final ChangeListFilteringStrategy myStrategy; + private boolean myIsSelected; + + public ShowHideMergePanel(final DecoratorManager manager, final ChangeListFilteringStrategy strategy) { + myManager = manager; + myStrategy = strategy; + } + + @Override + public void update(final AnActionEvent e) { + super.update(e); + final Presentation presentation = e.getPresentation(); + presentation.setIcon(IconLoader.getIcon("/icons/ShowIntegratedFrom.png")); + presentation.setText(SvnBundle.message("committed.changes.action.enable.merge.highlighting")); + presentation.setDescription(SvnBundle.message("committed.changes.action.enable.merge.highlighting.description.text")); + } + + public boolean isSelected(final AnActionEvent e) { + return myIsSelected; + } + + public void setSelected(final AnActionEvent e, final boolean state) { + myIsSelected = state; + if (state) { + myManager.setFilteringStrategy(myStrategy); + } else { + myManager.removeFilteringStrategy(myStrategy.getKey()); + } + } + } + + @Nullable + public VcsCommittedViewAuxiliary createActions(final DecoratorManager manager, @Nullable final RepositoryLocation location) { + final RootsAndBranches rootsAndBranches = new RootsAndBranches(myProject, manager, location); + refreshMergeInfo(rootsAndBranches); + + final DefaultActionGroup popup = new DefaultActionGroup(myVcs.getDisplayName(), true); + popup.add(rootsAndBranches.getIntegrateAction()); + popup.add(rootsAndBranches.getUndoIntegrateAction()); + popup.add(new ConfigureBranchesAction()); + + final ShowHideMergePanel action = new ShowHideMergePanel(manager, rootsAndBranches.getStrategy()); + + return new VcsCommittedViewAuxiliary(Collections.singletonList(popup), new Runnable() { + public void run() { + if (myMergeInfoUpdatesListener != null) { + myMergeInfoUpdatesListener.removePanel(rootsAndBranches); + rootsAndBranches.dispose(); + } + } + }, Collections.singletonList(action)); + } + + public int getUnlimitedCountValue() { + return 0; + } + + @Override + public Pair getOneList(final VirtualFile file, VcsRevisionNumber number) throws VcsException { + final RootUrlInfo rootUrlInfo = myVcs.getSvnFileUrlMapping().getWcRootForFilePath(new File(file.getPath())); + if (rootUrlInfo == null) return null; + final VirtualFile root = rootUrlInfo.getVirtualFile(); + if (root == null) return null; + final SvnRepositoryLocation svnRootLocation = (SvnRepositoryLocation)getLocationFor(new FilePathImpl(root)); + if (svnRootLocation == null) return null; + final String url = svnRootLocation.getURL(); + final long revision; + try { + revision = Long.parseLong(number.asString()); + } catch (NumberFormatException e) { + throw new VcsException(e); + } + + final SvnChangeList[] result = new SvnChangeList[1]; + final SVNLogClient logger; + final SVNRevision revisionBefore; + final SVNURL repositoryUrl; + final SVNURL svnurl; + final SVNInfo targetInfo; + try { + logger = myVcs.createLogClient(); + revisionBefore = SVNRevision.create(revision); + + svnurl = SVNURL.parseURIEncoded(url); + final SVNWCClient client = myVcs.createWCClient(); + final SVNInfo info = client.doInfo(svnurl, SVNRevision.UNDEFINED, SVNRevision.HEAD); + targetInfo = client.doInfo(new File(file.getPath()), SVNRevision.UNDEFINED); + if (info == null) { + throw new VcsException("Can not get repository URL"); + } + repositoryUrl = info.getRepositoryRootURL(); + } + catch (SVNException e) { + throw new VcsException(e); + } + + tryExactHit(svnRootLocation, result, logger, revisionBefore, repositoryUrl, svnurl); + if (result[0] == null) { + tryByRoot(result, logger, revisionBefore, repositoryUrl); + if (result[0] == null) { + FilePath path = tryStepByStep(svnRootLocation, result, logger, revisionBefore, targetInfo, svnurl); + path = path == null ? new FilePathImpl(file) : path; + // and pass & take rename context there + return new Pair(result[0], path); + } + } + if (result[0].getChanges().size() == 1) { + final Collection changes = result[0].getChanges(); + final Change change = changes.iterator().next(); + final ContentRevision afterRevision = change.getAfterRevision(); + if (afterRevision != null) { + return new Pair(result[0], afterRevision.getFile()); + } else { + return new Pair(result[0], new FilePathImpl(file)); + } + } + String relativePath = SVNPathUtil.getRelativePath(targetInfo.getRepositoryRootURL().toString(), targetInfo.getURL().toString()); + relativePath = relativePath.startsWith("/") ? relativePath : "/" + relativePath; + final Change targetChange = result[0].getByPath(relativePath); + if (targetChange == null) { + FilePath path = tryStepByStep(svnRootLocation, result, logger, revisionBefore, targetInfo, svnurl); + path = path == null ? new FilePathImpl(file) : path; + // and pass & take rename context there + return new Pair(result[0], path); + } + return new Pair(result[0], new FilePathImpl(file)); + } + + private static class RenameContext { + @NotNull + private String myCurrentPath; + private String myRepositoryRoot; + private boolean myHadChanged; + + private RenameContext(final SVNInfo info) { + myRepositoryRoot = info.getRepositoryRootURL().toString(); + myCurrentPath = SVNPathUtil.getRelativePath(myRepositoryRoot, info.getURL().toString()); + myCurrentPath = myCurrentPath.startsWith("/") ? myCurrentPath : ("/" + myCurrentPath); + } + + public void accept(final SVNLogEntry entry) { + final Map changedPaths = entry.getChangedPaths(); + if (changedPaths == null) return; + + for (Object o : changedPaths.values()) { + final SVNLogEntryPath entryPath = (SVNLogEntryPath) o; + if (entryPath != null && 'A' == entryPath.getType()) { + if (myCurrentPath.equals(entryPath.getPath())) { + myHadChanged = true; + myCurrentPath = entryPath.getCopyPath(); + } + } + } + } + + @Nullable + public FilePath getFilePath(final SvnVcs17 vcs) { + if (! myHadChanged) return null; + final SvnFileUrlMapping17 svnFileUrlMapping = vcs.getSvnFileUrlMapping(); + final String absolutePath = SVNPathUtil.append(myRepositoryRoot, myCurrentPath); + final String localPath = svnFileUrlMapping.getLocalPath(absolutePath); + if (localPath == null) { + LOG.info("Cannot find local path for url: " + absolutePath); + return null; + } + return new FilePathImpl(new File(localPath), false); + } + } + + private void tryByRoot(SvnChangeList[] result, SVNLogClient logger, SVNRevision revisionBefore, SVNURL repositoryUrl) throws VcsException { + final boolean authorized = SvnAuthenticationNotifier.passiveValidation(myProject, repositoryUrl); + if (! authorized) return; + tryExactHit(new SvnRepositoryLocation(repositoryUrl.toString()), result, logger, revisionBefore, repositoryUrl, repositoryUrl); + } + + // return changed path, if any + private FilePath tryStepByStep(final SvnRepositoryLocation svnRepositoryLocation, + final SvnChangeList[] result, + SVNLogClient logger, + final SVNRevision revisionBefore, final SVNInfo info, SVNURL svnurl) throws VcsException { + final String repositoryRoot = info.getRepositoryRootURL().toString(); + try { + final RenameContext renameContext = new RenameContext(info); + logger.doLog(svnurl, null, SVNRevision.UNDEFINED, SVNRevision.HEAD, revisionBefore, + false, true, false, 0, null, + new ISVNLogEntryHandler() { + public void handleLogEntry(SVNLogEntry logEntry) { + if (myProject.isDisposed()) throw new ProcessCanceledException(); + if (logEntry.getDate() == null) { + // do not add lists without info - this situation is possible for lists where there are paths that user has no rights to observe + return; + } + renameContext.accept(logEntry); + if (logEntry.getRevision() == revisionBefore.getNumber()) { + result[0] = new SvnChangeList(myVcs, svnRepositoryLocation, logEntry, repositoryRoot); + } + } + }); + return renameContext.getFilePath(myVcs); + } + catch (SVNException e) { + throw new VcsException(e); + } + } + + private void tryExactHit(final SvnRepositoryLocation location, + final SvnChangeList[] result, + SVNLogClient logger, + SVNRevision revisionBefore, + final SVNURL repositoryUrl, SVNURL svnurl) throws VcsException { + try { + logger.doLog(svnurl, null, SVNRevision.UNDEFINED, revisionBefore, revisionBefore, + false, true, false, 1, null, + new ISVNLogEntryHandler() { + public void handleLogEntry(SVNLogEntry logEntry) { + if (myProject.isDisposed()) throw new ProcessCanceledException(); + if (logEntry.getDate() == null) { + // do not add lists without info - this situation is possible for lists where there are paths that user has no rights to observe + return; + } + result[0] = new SvnChangeList(myVcs, location, logEntry, repositoryUrl.toString()); + } + }); + } + catch (SVNException e) { + if (SVNErrorCode.FS_CATEGORY == e.getErrorMessage().getErrorCode().getCategory()) { + // pass to step by step looking for revision + return; + } + throw new VcsException(e); + } + } + + public int getFormatVersion() { + return VERSION_WITH_REPLACED_PATHS; + } + + public void writeChangeList(final DataOutput dataStream, final SvnChangeList list) throws IOException { + list.writeToStream(dataStream); + } + + public SvnChangeList readChangeList(final RepositoryLocation location, final DataInput stream) throws IOException { + final int version = getFormatVersion(); + return new SvnChangeList(myVcs, (SvnRepositoryLocation) location, stream, + VERSION_WITH_COPY_PATHS_ADDED <= version, VERSION_WITH_REPLACED_PATHS <= version); + } + + public boolean isMaxCountSupported() { + return true; + } + + public Collection getIncomingFiles(final RepositoryLocation location) { + return null; + } + + public boolean refreshCacheByNumber() { + return true; + } + + public String getChangelistTitle() { + return SvnBundle.message("changes.browser.revision.term"); + } + + public boolean isChangeLocallyAvailable(FilePath filePath, @Nullable VcsRevisionNumber localRevision, VcsRevisionNumber changeRevision, + final SvnChangeList changeList) { + return localRevision != null && localRevision.compareTo(changeRevision) >= 0; + } + + public boolean refreshIncomingWithCommitted() { + return true; + } + + public void deactivate() { + myConnection.disconnect(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnCommittedChangesTableModel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnCommittedChangesTableModel.java new file mode 100644 index 000000000000..6f660f326c13 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnCommittedChangesTableModel.java @@ -0,0 +1,60 @@ +/* + * 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.history; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.ChangeListColumn; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.committed.CommittedChangesNavigation; +import com.intellij.openapi.vcs.changes.committed.CommittedChangesTableModel; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.openapi.vfs.VirtualFile; + +import java.util.ArrayList; + +public class SvnCommittedChangesTableModel extends CommittedChangesTableModel implements CommittedChangesNavigation { + private final SvnRevisionsNavigationMediator myMediator; + + public SvnCommittedChangesTableModel(final SvnRepositoryLocation location, final Project project, final VirtualFile vcsRoot, + final ChangeListColumn[] columns) throws VcsException { + super(new ArrayList(), columns, false); + myMediator = new SvnRevisionsNavigationMediator(location, project, vcsRoot); + setItems(myMediator.getCurrent()); + } + + public boolean canGoBack() { + return myMediator.canGoBack(); + } + + public boolean canGoForward() { + return myMediator.canGoForward(); + } + + public void goBack() throws VcsException { + myMediator.goBack(); + setItems(myMediator.getCurrent()); + } + + public void goForward() { + myMediator.goForward(); + setItems(myMediator.getCurrent()); + } + + public void onBeforeClose() { + myMediator.onBeforeClose(); + } + +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnFileRevision.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnFileRevision.java new file mode 100644 index 000000000000..d8f1911e0b41 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnFileRevision.java @@ -0,0 +1,212 @@ +/* + * 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.history; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.util.Throwable2Computable; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.actions.VcsContextFactory; +import com.intellij.openapi.vcs.history.VcsFileRevision; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import com.intellij.openapi.vcs.impl.ContentRevisionCache; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnRevisionNumber; +import org.jetbrains.idea.svn17.SvnUtil; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNLogEntry; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class SvnFileRevision implements VcsFileRevision { + private final static Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.history.SvnFileRevision"); + + private final Date myDate; + private final String myCommitMessage; + private final String myAuthor; + private final VcsRevisionNumber myRevisionNumber; + private final SvnVcs17 myVCS; + private final String myURL; + private final SVNRevision myPegRevision; + private final SVNRevision myRevision; + private final String myCopyFromPath; + private final List myMergeSources; + private final Charset myCharset; + + public SvnFileRevision(SvnVcs17 vcs, + SVNRevision pegRevision, + SVNRevision revision, + String url, + String author, + Date date, + String commitMessage, + String copyFromPath, Charset charset) { + myCharset = charset; + myRevisionNumber = new SvnRevisionNumber(revision); + myPegRevision = pegRevision; + myRevision = revision; + myAuthor = author; + myDate = date; + myCommitMessage = commitMessage; + myCopyFromPath = copyFromPath; + myVCS = vcs; + myURL = url; + myMergeSources = new ArrayList(); + } + + public SvnFileRevision(SvnVcs17 vcs, + SVNRevision pegRevision, + SVNLogEntry logEntry, + String url, + String copyFromPath, Charset charset) { + myCharset = charset; + final SVNRevision revision = SVNRevision.create(logEntry.getRevision()); + myRevisionNumber = new SvnRevisionNumber(revision); + myPegRevision = pegRevision; + myRevision = revision; + myAuthor = logEntry.getAuthor(); + myDate = logEntry.getDate(); + myCommitMessage = logEntry.getMessage(); + myCopyFromPath = copyFromPath; + myVCS = vcs; + myURL = url; + myMergeSources = new ArrayList(); + } + + public String getURL() { + return myURL; + } + + public SVNRevision getPegRevision() { + return myPegRevision; + } + + + public VcsRevisionNumber getRevisionNumber() { + return myRevisionNumber; + } + + public String getBranchName() { + return null; + } + + public Date getRevisionDate() { + return myDate; + } + + public String getAuthor() { + return myAuthor; + } + + public String getCommitMessage() { + return myCommitMessage; + } + + public void addMergeSource(final SvnFileRevision source) { + myMergeSources.add(source); + } + + public List getMergeSources() { + return myMergeSources; + } + + public byte[] loadContent() throws IOException, VcsException { + ByteArrayOutputStream contents = new ByteArrayOutputStream(); + ContentLoader loader = new ContentLoader(myURL, contents, myRevision, myPegRevision); + if (ApplicationManager.getApplication().isDispatchThread() && + !myRevision.isLocal()) { + ProgressManager.getInstance().runProcessWithProgressSynchronously(loader, SvnBundle.message("progress.title.loading.file.content"), false, myVCS.getProject()); + } + else { + loader.run(); + } + if (loader.getException() == null) { + return contents.toByteArray(); + } + else { + final SVNException svnException = loader.getException(); + LOG.info("Failed to load file '" + myURL + "' content at revision: " + myRevision + "\n" + svnException.getMessage(), svnException); + throw new VcsException(svnException); + } + } + + public byte[] getContent() throws IOException, VcsException { + return ContentRevisionCache.getOrLoadAsBytes(myVCS.getProject(), VcsContextFactory.SERVICE.getInstance() + .createFilePathOnNonLocal(myURL, false), + getRevisionNumber(), myVCS.getKeyInstanceMethod(), ContentRevisionCache.UniqueType.REMOTE_CONTENT, + new Throwable2Computable() { + @Override + public byte[] compute() throws VcsException, IOException { + byte[] bytes = loadContent(); + return SvnUtil.decode(myCharset, bytes); + } + }); + } + + public String getCopyFromPath() { + return myCopyFromPath; + } + + private class ContentLoader implements Runnable { + private final SVNRevision myRevision; + private final SVNRevision myPegRevision; + private final String myURL; + private final OutputStream myDst; + private SVNException myException; + + public ContentLoader(String url, OutputStream dst, SVNRevision revision, SVNRevision pegRevision) { + myURL = url; + myDst = dst; + myRevision = revision; + myPegRevision = pegRevision; + } + + public SVNException getException() { + return myException; + } + + public void run() { + ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + if (progress != null) { + progress.setText(SvnBundle.message("progress.text.loading.contents", myURL)); + progress.setText2(SvnBundle.message("progress.text2.revision.information", myRevision)); + } + try { + SVNWCClient client = myVCS.createWCClient(); + client.doGetFileContents(SVNURL.parseURIEncoded(myURL), myRevision, myRevision, true, myDst); + } + catch (SVNException e) { + myException = e; + } + } + } + + public SVNRevision getRevision() { + return myRevision; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnHistoryProvider.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnHistoryProvider.java new file mode 100644 index 000000000000..29094da3a29a --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnHistoryProvider.java @@ -0,0 +1,801 @@ +/* + * 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.history; + +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.ActionPlaces; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.annotate.ShowAllAffectedGenericAction; +import com.intellij.openapi.vcs.changes.ChangesUtil; +import com.intellij.openapi.vcs.changes.issueLinks.TableLinkMouseListener; +import com.intellij.openapi.vcs.history.*; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.ColoredTableCellRenderer; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.util.Consumer; +import com.intellij.util.ThrowableConsumer; +import com.intellij.util.ui.ColumnInfo; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnRevisionNumber; +import org.jetbrains.idea.svn17.SvnUtil; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.*; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; +import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; +import org.tmatesoft.svn.core.wc.*; +import org.tmatesoft.svn.util.SVNLogType; + +import javax.swing.*; +import javax.swing.table.TableCellRenderer; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.io.File; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +public class SvnHistoryProvider implements VcsHistoryProvider, VcsCacheableHistorySessionFactory { + private final SvnVcs17 myVcs; + + public SvnHistoryProvider(SvnVcs17 vcs) { + myVcs = vcs; + } + + public boolean supportsHistoryForDirectories() { + return true; + } + + public VcsDependentHistoryComponents getUICustomization(final VcsHistorySession session, JComponent forShortcutRegistration) { + final ColumnInfo[] columns; + final Consumer listener; + final JComponent addComp; + if (((MyHistorySession) session).isSupports15()) { + final MergeSourceColumnInfo mergeSourceColumn = new MergeSourceColumnInfo((MyHistorySession)session); + columns = new ColumnInfo[] {new CopyFromColumnInfo(), mergeSourceColumn}; + + final JTextArea field = new JTextArea(); + field.setEditable(false); + field.setBackground(UIUtil.getComboBoxDisabledBackground()); + field.setWrapStyleWord(true); + listener = new Consumer() { + public void consume(VcsFileRevision vcsFileRevision) { + field.setText(mergeSourceColumn.getText(vcsFileRevision)); + } + }; + final JPanel panel = new JPanel(new GridBagLayout()); + final GridBagConstraints gb = + new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); + + final JLabel mergeLabel = new JLabel("Merge Sources:"); + final MergeSourceDetailsAction sourceAction = new MergeSourceDetailsAction(); + sourceAction.registerSelf(forShortcutRegistration); + final DefaultActionGroup group = new DefaultActionGroup(); + group.add(sourceAction); + final JComponent toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true).getComponent(); + + panel.add(mergeLabel, gb); + ++ gb.gridx; + gb.insets.left = 10; + gb.anchor = GridBagConstraints.NORTHWEST; + panel.add(toolbar, gb); + + ++ gb.gridy; + gb.insets.left = 0; + gb.gridx = 0; + gb.gridwidth = 2; + gb.weightx = gb.weighty = 1; + gb.fill = GridBagConstraints.BOTH; + panel.add(ScrollPaneFactory.createScrollPane(field), gb); + addComp = panel; + } else { + columns = new ColumnInfo[] {new CopyFromColumnInfo()}; + addComp = null; + listener = null; + } + return new VcsDependentHistoryComponents(columns, listener, addComp); + } + + @Override + public FilePath getUsedFilePath(MyHistorySession session) { + return session.getCommittedPath(); + } + + @Override + public Boolean getAddinionallyCachedData(MyHistorySession session) { + return session.isSupports15(); + } + + @Override + public MyHistorySession createFromCachedData(Boolean aBoolean, + @NotNull List revisions, + @NotNull FilePath filePath, + VcsRevisionNumber currentRevision) { + return new MyHistorySession(revisions, filePath, aBoolean, currentRevision); + } + + class MyHistorySession extends VcsAbstractHistorySession { + private final FilePath myCommittedPath; + private final boolean mySupports15; + + private MyHistorySession(final List revisions, final FilePath committedPath, final boolean supports15, + @Nullable final VcsRevisionNumber currentRevision) { + super(revisions, currentRevision); + myCommittedPath = committedPath; + mySupports15 = supports15; + shouldBeRefreshed(); + } + + public HistoryAsTreeProvider getHistoryAsTreeProvider() { + return null; + } + + @Nullable + public VcsRevisionNumber calcCurrentRevisionNumber() { + if (myCommittedPath == null) { + return null; + } + return getCurrentRevision(myCommittedPath); + } + + public FilePath getCommittedPath() { + return myCommittedPath; + } + + @Override + public boolean isContentAvailable(final VcsFileRevision revision) { + return ! myCommittedPath.isDirectory(); + } + + public boolean isSupports15() { + return mySupports15; + } + + @Override + public VcsHistorySession copy() { + return new MyHistorySession(getRevisionList(), myCommittedPath, mySupports15, getCurrentRevisionNumber()); + } + } + + @Nullable + public VcsHistorySession createSessionFor(final FilePath filePath) throws VcsException { + final VcsAppendableHistoryPartnerAdapter adapter = new VcsAppendableHistoryPartnerAdapter(); + reportAppendableHistory(filePath, adapter); + adapter.check(); + + return adapter.getSession(); + } + + public void reportAppendableHistory(FilePath path, final VcsAppendableHistorySessionPartner partner) throws VcsException { + final FilePath committedPath = ChangesUtil.getCommittedPath(myVcs.getProject(), path); + + final LogLoader logLoader; + if (path.isNonLocal()) { + logLoader = new RepositoryLoader(myVcs, path); + } else { + logLoader = new LocalLoader(myVcs, path); + } + + try { + logLoader.preliminary(); + } catch (SVNCancelException e) { + return; + } catch (SVNException e) { + throw new VcsException(e); + } + logLoader.initSupports15(); + + final MyHistorySession historySession = + new MyHistorySession(Collections.emptyList(), committedPath, Boolean.TRUE.equals(logLoader.mySupport15), null); + + final Ref sessionReported = new Ref(); + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (indicator != null) { + indicator.setText(SvnBundle.message("progress.text2.collecting.history", path.getName())); + } + final Consumer consumer = new Consumer() { + public void consume(VcsFileRevision vcsFileRevision) { + if (!Boolean.TRUE.equals(sessionReported.get())) { + partner.reportCreatedEmptySession(historySession); + sessionReported.set(true); + } + partner.acceptRevision(vcsFileRevision); + } + }; + + logLoader.setConsumer(consumer); + logLoader.load(); + logLoader.check(); + } + + @Nullable + private void getRevisionsList(final FilePath file, final Ref supports15Ref, + final Consumer consumer) throws VcsException { + final VcsException[] exception = new VcsException[1]; + + Runnable command = new Runnable() { + public void run() { + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (indicator != null) { + indicator.setText(SvnBundle.message("progress.text2.collecting.history", file.getName())); + } + try { + if (! file.isNonLocal()) { + collectLogEntries(indicator, file, exception, consumer, supports15Ref); + } else { + collectLogEntriesForRepository(indicator, file, consumer, supports15Ref); + } + } + catch(SVNCancelException ex) { + throw new ProcessCanceledException(ex); + } + catch (SVNException e) { + exception[0] = new VcsException(e); + } + catch (VcsException e) { + exception[0] = e; + } + } + }; + + command.run(); + + if (exception[0] != null) { + throw exception[0]; + } + } + + private static abstract class LogLoader { + protected String myUrl; + protected boolean mySupport15; + protected final SvnVcs17 myVcs; + protected final FilePath myFile; + protected Consumer myConsumer; + protected final ProgressIndicator myPI; + protected VcsException myException; + + protected LogLoader(SvnVcs17 vcs, FilePath file) { + myVcs = vcs; + myFile = file; + myPI = ProgressManager.getInstance().getProgressIndicator(); + } + + public void setConsumer(Consumer consumer) { + myConsumer = consumer; + } + + protected void initSupports15() { + assert myUrl != null; + mySupport15 = SvnUtil.checkRepositoryVersion15(myVcs, myUrl); + } + + public void check() throws VcsException { + if (myException != null) throw myException; + } + + protected abstract void preliminary() throws SVNException; + protected abstract void load(); + } + + private static class LocalLoader extends LogLoader { + private SVNInfo myInfo; + + private LocalLoader(SvnVcs17 vcs, FilePath file) { + super(vcs, file); + } + + @Override + protected void preliminary() throws SVNException { + SVNWCClient wcClient = myVcs.createWCClient(); + myInfo = wcClient.doInfo(new File(myFile.getIOFile().getAbsolutePath()), SVNRevision.UNDEFINED); + wcClient.setEventHandler(new ISVNEventHandler() { + public void handleEvent(SVNEvent event, double progress) throws SVNException { + } + + public void checkCancelled() throws SVNCancelException { + myPI.checkCanceled(); + } + }); + if (myInfo == null || myInfo.getRepositoryRootURL() == null) { + myException = new VcsException("File ''{0}'' is not under version control" + myFile.getIOFile()); + return; + } + if (myInfo.getURL() == null) { + myException = new VcsException("File " + myFile.getPath() + " is not under Subversion control"); + return; + } + myUrl = myInfo.getURL().toString(); + } + + @Override + protected void load() { + String relativeUrl = myUrl; + final SVNURL repoRootURL = myInfo.getRepositoryRootURL(); + + final String root = repoRootURL.toString(); + if (myUrl != null && myUrl.startsWith(root)) { + relativeUrl = myUrl.substring(root.length()); + } + if (myPI != null) { + myPI.setText2(SvnBundle.message("progress.text2.changes.establishing.connection", myUrl)); + } + final SVNRevision pegRevision = myInfo.getRevision(); + SVNLogClient client = myVcs.createLogClient(); + try { + client.doLog(new File[]{new File(myFile.getIOFile().getAbsolutePath())}, SVNRevision.HEAD, SVNRevision.create(1), SVNRevision.UNDEFINED, + false, true, mySupport15, 0, null, + new MyLogEntryHandler(myVcs, myUrl, pegRevision, relativeUrl, myConsumer, repoRootURL, myFile.getCharset())); + } catch (SVNCancelException e) { + // + } catch (SVNException e) { + myException = new VcsException(e); + } + catch (VcsException e) { + myException = e; + } + } + } + + private void collectLogEntries(final ProgressIndicator indicator, FilePath file, VcsException[] exception, + final Consumer result, final Ref supports15Ref) throws SVNException, + VcsException { + SVNWCClient wcClient = myVcs.createWCClient(); + SVNInfo info = wcClient.doInfo(new File(file.getIOFile().getAbsolutePath()), SVNRevision.UNDEFINED); + wcClient.setEventHandler(new ISVNEventHandler() { + public void handleEvent(SVNEvent event, double progress) throws SVNException { + } + + public void checkCancelled() throws SVNCancelException { + indicator.checkCanceled(); + } + }); + if (info == null || info.getRepositoryRootURL() == null) { + exception[0] = new VcsException("File ''{0}'' is not under version control" + file.getIOFile()); + return; + } + final String url = info.getURL() == null ? null : info.getURL().toString(); + String relativeUrl = url; + final SVNURL repoRootURL = info.getRepositoryRootURL(); + + final String root = repoRootURL.toString(); + if (url != null && url.startsWith(root)) { + relativeUrl = url.substring(root.length()); + } + if (indicator != null) { + indicator.setText2(SvnBundle.message("progress.text2.changes.establishing.connection", url)); + } + final SVNRevision pegRevision = info.getRevision(); + SVNLogClient client = myVcs.createLogClient(); + + final boolean supports15 = SvnUtil.checkRepositoryVersion15(myVcs, url); + supports15Ref.set(supports15); + client.doLog(new File[]{new File(file.getIOFile().getAbsolutePath())}, SVNRevision.HEAD, SVNRevision.create(1), SVNRevision.UNDEFINED, + false, true, supports15, 0, null, + new MyLogEntryHandler(myVcs, url, pegRevision, relativeUrl, result, repoRootURL, file.getCharset())); + } + + private static class RepositoryLoader extends LogLoader { + private RepositoryLoader(SvnVcs17 vcs, FilePath file) { + super(vcs, file); + } + + @Override + protected void preliminary() throws SVNException { + myUrl = myFile.getPath().replace('\\', '/'); + } + + @Override + protected void load() { + if (myPI != null) { + myPI.setText2(SvnBundle.message("progress.text2.changes.establishing.connection", myUrl)); + } + SVNWCClient wcClient = myVcs.createWCClient(); + try { + final SVNURL svnurl = SVNURL.parseURIEncoded(myUrl); + SVNInfo info = null; + info = wcClient.doInfo(svnurl, SVNRevision.UNDEFINED, SVNRevision.HEAD); + final String root = info.getRepositoryRootURL().toString(); + String relativeUrl = myUrl; + if (myUrl.startsWith(root)) { + relativeUrl = myUrl.substring(root.length()); + } + SVNLogClient client = myVcs.createLogClient(); + client.doLog(svnurl, new String[] {}, SVNRevision.UNDEFINED, SVNRevision.HEAD, SVNRevision.create(1), false, true, mySupport15, 0, null, + new RepositoryLogEntryHandler(myVcs, myUrl, SVNRevision.UNDEFINED, relativeUrl, myConsumer, info.getRepositoryRootURL())); + } catch (SVNCancelException e) { + // + } catch (SVNException e) { + myException = new VcsException(e); + } + catch (VcsException e) { + myException = e; + } + } + } + + private void collectLogEntriesForRepository(final ProgressIndicator indicator, FilePath file, final Consumer result, + final Ref supports15Ref) throws SVNException, VcsException { + final String url = file.getPath().replace('\\', '/'); + if (indicator != null) { + indicator.setText2(SvnBundle.message("progress.text2.changes.establishing.connection", url)); + } + SVNWCClient wcClient = myVcs.createWCClient(); + final SVNURL svnurl = SVNURL.parseURIEncoded(url); + SVNInfo info = wcClient.doInfo(svnurl, SVNRevision.UNDEFINED, SVNRevision.HEAD); + final String root = info.getRepositoryRootURL().toString(); + String relativeUrl = url; + if (url.startsWith(root)) { + relativeUrl = url.substring(root.length()); + } + SVNLogClient client = myVcs.createLogClient(); + final boolean supports15 = SvnUtil.checkRepositoryVersion15(myVcs, root); + supports15Ref.set(supports15); + // todo log in history provider + client.doLog(svnurl, new String[] {}, SVNRevision.UNDEFINED, SVNRevision.HEAD, SVNRevision.create(1), false, true, supports15, 0, null, + new RepositoryLogEntryHandler(myVcs, url, SVNRevision.UNDEFINED, relativeUrl, result, info.getRepositoryRootURL())); + } + + public String getHelpId() { + return null; + } + + @Nullable + private VcsRevisionNumber getCurrentRevision(FilePath file) { + if (file.isNonLocal()) { + // technically, it does not make sense, since there's no "current" revision for non-local history (if look how it's used) + // but ok, lets keep it for now + return new SvnRevisionNumber(SVNRevision.HEAD); + } + try { + SVNWCClient wcClient = myVcs.createWCClient(); + SVNInfo info = wcClient.doInfo(new File(file.getPath()).getAbsoluteFile(), SVNRevision.UNDEFINED); + if (info != null) { + return new SvnRevisionNumber(info.getRevision()); + } else { + return null; + } + } + catch (SVNException e) { + return null; + } + } + + public AnAction[] getAdditionalActions(final Runnable refresher) { + return new AnAction[]{new ShowAllAffectedGenericAction(), new MergeSourceDetailsAction()}; + } + + public boolean isDateOmittable() { + return false; + } + + private static class MyLogEntryHandler implements ISVNLogEntryHandler { + private final ProgressIndicator myIndicator; + protected final SvnVcs17 myVcs; + private String myLastPath; + private final Charset myCharset; + protected final Consumer myResult; + private VcsFileRevision myPrevious; + private final SVNRevision myPegRevision; + protected final String myUrl; + private final SvnMergeSourceTracker myTracker; + private SVNURL myRepositoryRoot; + + public MyLogEntryHandler(SvnVcs17 vcs, final String url, + final SVNRevision pegRevision, + String lastPath, + final Consumer result, + SVNURL repoRootURL, Charset charset) + throws SVNException, VcsException { + myVcs = vcs; + myLastPath = lastPath; + myCharset = charset; + myIndicator = ProgressManager.getInstance().getProgressIndicator(); + myResult = result; + myPegRevision = pegRevision; + myUrl = url; + myRepositoryRoot = repoRootURL; + myTracker = new SvnMergeSourceTracker(new ThrowableConsumer, SVNException>() { + public void consume(final Pair svnLogEntryIntegerPair) throws SVNException { + final SVNLogEntry logEntry = svnLogEntryIntegerPair.getFirst(); + + if (myIndicator != null) { + if (myIndicator.isCanceled()) { + SVNErrorManager.cancel(SvnBundle.message("exception.text.update.operation.cancelled"), SVNLogType.DEFAULT); + } + myIndicator.setText2(SvnBundle.message("progress.text2.revision.processed", logEntry.getRevision())); + } + String copyPath = null; + SVNLogEntryPath entryPath = (SVNLogEntryPath)logEntry.getChangedPaths().get(myLastPath); + if (entryPath != null) { + copyPath = entryPath.getCopyPath(); + } + else { + String path = SVNPathUtil.removeTail(myLastPath); + while(path.length() > 0) { + entryPath = (SVNLogEntryPath) logEntry.getChangedPaths().get(path); + if (entryPath != null) { + String relativePath = myLastPath.substring(entryPath.getPath().length()); + copyPath = entryPath.getCopyPath() + relativePath; + break; + } + path = SVNPathUtil.removeTail(path); + } + } + + final int mergeLevel = svnLogEntryIntegerPair.getSecond(); + final SvnFileRevision revision = createRevision(logEntry, copyPath); + if (copyPath != null) { + myLastPath = copyPath; + } + if (mergeLevel >= 0) { + addToListByLevel((SvnFileRevision) myPrevious, revision, mergeLevel); + } else { + myResult.consume(revision); + myPrevious = revision; + } + } + }); + } + + public void handleLogEntry(SVNLogEntry logEntry) throws SVNException { + myTracker.consume(logEntry); + } + + private void addToListByLevel(final SvnFileRevision revision, final SvnFileRevision revisionToAdd, final int level) { + if (level < 0) { + return; + } + if (level == 0) { + revision.addMergeSource(revisionToAdd); + return; + } + final List sources = revision.getMergeSources(); + if (! sources.isEmpty()) { + addToListByLevel(sources.get(sources.size() - 1), revisionToAdd, level - 1); + } + } + + protected SvnFileRevision createRevision(final SVNLogEntry logEntry, final String copyPath) throws SVNException { + Date date = logEntry.getDate(); + String author = logEntry.getAuthor(); + String message = logEntry.getMessage(); + SVNRevision rev = SVNRevision.create(logEntry.getRevision()); + final SVNURL url = myRepositoryRoot.appendPath(myLastPath, true); + return new SvnFileRevision(myVcs, myPegRevision, rev, url.toString(), author, date, message, copyPath, myCharset); + } + } + + private static class RepositoryLogEntryHandler extends MyLogEntryHandler { + public RepositoryLogEntryHandler(final SvnVcs17 vcs, final String url, + final SVNRevision pegRevision, + String lastPath, + final Consumer result, + SVNURL repoRootURL) + throws VcsException, SVNException { + super(vcs, url, pegRevision, lastPath, result, repoRootURL, null); + } + + @Override + protected SvnFileRevision createRevision(final SVNLogEntry logEntry, final String copyPath) { + return new SvnFileRevision(myVcs, SVNRevision.UNDEFINED, logEntry, myUrl, copyPath, null); + } + } + + private class MergeSourceColumnInfo extends ColumnInfo { + private final MergeSourceRenderer myRenderer; + + private MergeSourceColumnInfo(final MyHistorySession session) { + super("Merge Sources"); + myRenderer = new MergeSourceRenderer(session); + } + + @Override + public TableCellRenderer getRenderer(final VcsFileRevision vcsFileRevision) { + return myRenderer; + } + + public VcsFileRevision valueOf(final VcsFileRevision vcsFileRevision) { + return vcsFileRevision; + } + + public String getText(final VcsFileRevision vcsFileRevision) { + return myRenderer.getText(vcsFileRevision); + } + + @Override + public int getAdditionalWidth() { + return 20; + } + + @Override + public String getPreferredStringValue() { + return "1234567, 1234567, 1234567"; + } + } + + private static final Object MERGE_SOURCE_DETAILS_TAG = new Object(); + + private class MergeSourceDetailsLinkListener extends TableLinkMouseListener { + private final VirtualFile myFile; + private final Object myTag; + + private MergeSourceDetailsLinkListener(final Object tag, final VirtualFile file) { + myTag = tag; + myFile = file; + } + + public void mouseClicked(final MouseEvent e) { + if (e.getButton() == 1 && !e.isPopupTrigger()) { + Object tag = getTagAt(e); + if (tag == myTag) { + final SvnFileRevision revision = getSelectedRevision(e); + if (revision != null) { + SvnMergeSourceDetails.showMe(myVcs.getProject(), revision, myFile); + } + } + } + } + + @Nullable + private SvnFileRevision getSelectedRevision(final MouseEvent e) { + JTable table = (JTable)e.getSource(); + int row = table.rowAtPoint(e.getPoint()); + int column = table.columnAtPoint(e.getPoint()); + + final Object value = table.getModel().getValueAt(row, column); + if (value instanceof SvnFileRevision) { + return (SvnFileRevision) value; + } + return null; + } + + public void mouseMoved(MouseEvent e) { + JTable table = (JTable) e.getSource(); + Object tag = getTagAt(e); + if (tag == myTag) { + table.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + } + else { + table.setCursor(Cursor.getDefaultCursor()); + } + } + } + + private class MergeSourceRenderer extends ColoredTableCellRenderer { + private MergeSourceDetailsLinkListener myListener; + private final VirtualFile myFile; + + private MergeSourceRenderer(final MyHistorySession session) { + myFile = session.getCommittedPath().getVirtualFile(); + } + + public String getText(final VcsFileRevision value) { + if (! (value instanceof SvnFileRevision)) return ""; + final SvnFileRevision revision = (SvnFileRevision) value; + final List mergeSources = revision.getMergeSources(); + if (mergeSources.isEmpty()) { + return ""; + } + final StringBuilder sb = new StringBuilder(); + for (SvnFileRevision source : mergeSources) { + if (sb.length() != 0) { + sb.append(", "); + } + sb.append(source.getRevisionNumber().asString()); + if (! source.getMergeSources().isEmpty()) { + sb.append("*"); + } + } + return sb.toString(); + } + + protected void customizeCellRenderer(final JTable table, + final Object value, + final boolean selected, + final boolean hasFocus, + final int row, + final int column) { + if (myListener == null) { + myListener = new MergeSourceDetailsLinkListener(MERGE_SOURCE_DETAILS_TAG, myFile); + myListener.install(table); + } + if (! (value instanceof SvnFileRevision)) { + append("", SimpleTextAttributes.REGULAR_ATTRIBUTES); + return; + } + final SvnFileRevision revision = (SvnFileRevision) value; + final String text = getText(revision); + if (text.length() == 0) { + append("", SimpleTextAttributes.REGULAR_ATTRIBUTES); + return; + } + + append(cutString(text, table.getCellRect(row, column, false).getWidth()), SimpleTextAttributes.REGULAR_ATTRIBUTES); + } + + private String cutString(final String text, final double value) { + final FontMetrics m = getFontMetrics(getFont()); + final Graphics g = getGraphics(); + + if (m.getStringBounds(text, g).getWidth() < value) return text; + + final String dots = "..."; + final double dotsWidth = m.getStringBounds(dots, g).getWidth(); + if (dotsWidth >= value) { + return dots; + } + + for (int i = 1; i < text.length(); i++) { + if ((m.getStringBounds(text, 0, i, g).getWidth() + dotsWidth) >= value) { + if (i < 2) return dots; + return text.substring(0, i - 1) + dots; + } + } + return text; + } + } + + private static class CopyFromColumnInfo extends ColumnInfo { + private final Icon myIcon = IconLoader.getIcon("/actions/menu-copy.png"); + private final ColoredTableCellRenderer myRenderer = new ColoredTableCellRenderer() { + protected void customizeCellRenderer(final JTable table, final Object value, final boolean selected, final boolean hasFocus, final int row, final int column) { + if (value instanceof String && ((String) value).length() > 0) { + setIcon(myIcon); + setToolTipText(SvnBundle.message("copy.column.tooltip", value)); + } + else { + setToolTipText(""); + } + } + }; + + public CopyFromColumnInfo() { + super(SvnBundle.message("copy.column.title")); + } + + public String valueOf(final VcsFileRevision o) { + return o instanceof SvnFileRevision ? ((SvnFileRevision) o).getCopyFromPath() : ""; + } + + @Override + public TableCellRenderer getRenderer(final VcsFileRevision vcsFileRevision) { + return myRenderer; + } + + @Override + public String getMaxStringValue() { + return SvnBundle.message("copy.column.title"); + } + + @Override + public int getAdditionalWidth() { + return 6; + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnLoadingRepositoryLocation.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnLoadingRepositoryLocation.java new file mode 100644 index 000000000000..78d98d690226 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnLoadingRepositoryLocation.java @@ -0,0 +1,35 @@ +/* + * 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.history; + +import com.intellij.openapi.vcs.FilePath; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn17.SvnVcs17; + +public class SvnLoadingRepositoryLocation extends SvnRepositoryLocation { + private final LocationDetector locationDetector; + + public SvnLoadingRepositoryLocation(final FilePath rootFile, @NotNull final String URL, @NotNull final SvnVcs17 vcs) { + super(URL); + locationDetector = new LocationDetector(vcs); + } + + public SvnLoadingRepositoryLocation(@NotNull final String URL, @NotNull final SvnVcs17 vcs) { + super(URL); + locationDetector = new LocationDetector(vcs); + } + +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnLogLoader.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnLogLoader.java new file mode 100644 index 000000000000..11697317d9b5 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnLogLoader.java @@ -0,0 +1,27 @@ +/* + * 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.history; + +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.util.List; + +public interface SvnLogLoader { + List loadInterval(final SVNRevision fromIncluding, final SVNRevision toIncluding, + final int maxCount, final boolean includingYoungest, final boolean includeOldest) throws SVNException; +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnLogUtil.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnLogUtil.java new file mode 100644 index 000000000000..9734cdc0d305 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnLogUtil.java @@ -0,0 +1,84 @@ +/* + * 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.history; + +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.versionBrowser.CommittedChangeList; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.ISVNLogEntryHandler; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNLogEntry; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.SVNLogClient; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.util.ArrayList; +import java.util.List; + +public class SvnLogUtil implements SvnLogLoader { + private final Project myProject; + private final SvnVcs17 myVcs; + private final SvnRepositoryLocation myLocation; + private final SVNURL myRepositoryRoot; + private final String myRelative; + + public SvnLogUtil(final Project project, final SvnVcs17 vcs, final SvnRepositoryLocation location, final SVNURL repositoryRoot) { + myProject = project; + myVcs = vcs; + myLocation = location; + myRepositoryRoot = repositoryRoot; + + final String repositoryRootPath = repositoryRoot.toString(); + myRelative = myLocation.getURL().substring(repositoryRootPath.length()); + } + + public List loadInterval(final SVNRevision fromIncluding, final SVNRevision toIncluding, + final int maxCount, final boolean includingYoungest, final boolean includeOldest) throws SVNException { + final List result = new ArrayList(); + loadRevisions(fromIncluding, toIncluding, null, maxCount, result, includingYoungest, includeOldest); + return result; + } + + private void loadRevisions(final SVNRevision fromIncluding, final SVNRevision toIncluding, final String author, final int maxCount, + final List result, + final boolean includingYoungest, final boolean includeOldest) throws SVNException { + SVNLogClient logger = myVcs.createLogClient(); + logger.doLog(myRepositoryRoot, new String[]{myRelative}, SVNRevision.UNDEFINED, fromIncluding, toIncluding, true, true, maxCount, + new ISVNLogEntryHandler() { + public void handleLogEntry(SVNLogEntry logEntry) { + if (myProject.isDisposed()) throw new ProcessCanceledException(); + final ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + if (progress != null) { + progress.setText2(SvnBundle.message("progress.text2.processing.revision", logEntry.getRevision())); + progress.checkCanceled(); + } + if ((! includingYoungest) && (logEntry.getRevision() == fromIncluding.getNumber())) { + return; + } + if ((! includeOldest) && (logEntry.getRevision() == toIncluding.getNumber())) { + return; + } + if (author == null || author.equalsIgnoreCase(logEntry.getAuthor())) { + result.add(new SvnChangeList(myVcs, myLocation, logEntry, myRepositoryRoot.toString())); + } + } + }); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnMergeInfoRootPanelManual.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnMergeInfoRootPanelManual.java new file mode 100644 index 000000000000..89320196f485 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnMergeInfoRootPanelManual.java @@ -0,0 +1,345 @@ +/* + * 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.history; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.FixedSizeButton; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.NullableFunction; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBranchMapperManager17; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnUtil; +import org.jetbrains.idea.svn17.actions.SelectBranchPopup; +import org.jetbrains.idea.svn17.branchConfig.SvnBranchConfigurationNew; +import org.jetbrains.idea.svn17.dialogs.WCInfoWithBranches; +import org.jetbrains.idea.svn17.integrate.IntegratedSelectedOptionsDialog; +import org.jetbrains.idea.svn17.integrate.WorkingCopyInfo; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.*; + +public class SvnMergeInfoRootPanelManual { + private JCheckBox myInclude; + private JLabel myHereLabel; + private JLabel myThereLabel; + private TextFieldWithBrowseButton myBranchField; + private FixedSizeButton myFixedSelectLocal; + private JPanel myContentPanel; + private JTextArea myUrlText; + private JTextArea myLocalArea; + private JTextArea myMixedRevisions; + + private final Project myProject; + private final NullableFunction myRefresher; + private final Runnable myListener; + private boolean myOnlyOneRoot; + private WCInfoWithBranches myInfo; + private final Map myBranchToLocal; + private WCInfoWithBranches.Branch mySelectedBranch; + + public SvnMergeInfoRootPanelManual(final Project project, final NullableFunction refresher, + final Runnable listener, + final boolean onlyOneRoot, + final WCInfoWithBranches info) { + myOnlyOneRoot = onlyOneRoot; + myInfo = info; + myProject = project; + myRefresher = refresher; + myListener = listener; + myBranchToLocal = new HashMap(); + + init(); + myInclude.setVisible(! onlyOneRoot); + initWithData(); + } + + private void initWithData() { + myInclude.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + myListener.run(); + } + }); + + final String path = myInfo.getPath(); + final String urlString = myInfo.getUrl().toString(); + myUrlText.setText(urlString); + + myFixedSelectLocal.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + if (mySelectedBranch != null) { + final Pair info = + IntegratedSelectedOptionsDialog.selectWorkingCopy(myProject, myInfo.getUrl(), mySelectedBranch.getUrl(), false, null, + null); + if (info != null) { + final String local = info.getFirst().getLocalPath(); + calculateBranchPathByBranch(mySelectedBranch.getUrl(), local); + } + myListener.run(); + } + } + }); + + myBranchField.getTextField().setEditable(false); + myBranchField.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + final VirtualFile vf = SvnUtil.getVirtualFile(myInfo.getPath()); + if (vf != null) { + SelectBranchPopup.show(myProject, vf, new SelectBranchPopup.BranchSelectedCallback() { + public void branchSelected(final Project project, final SvnBranchConfigurationNew configuration, final String url, final long revision) { + refreshSelectedBranch(url); + calculateBranchPathByBranch(mySelectedBranch.getUrl(), null); + myListener.run(); + } + }, SvnBundle.message("select.branch.popup.general.title")); + } + } + }); + + if (myInfo.getBranches().isEmpty()) { + calculateBranchPathByBranch(null, null); + } else { + final WCInfoWithBranches.Branch branch = myInfo.getBranches().get(0); + refreshSelectedBranch(branch.getUrl()); + calculateBranchPathByBranch(mySelectedBranch.getUrl(), null); + } + } + + private void init() { + myContentPanel = new JPanel(new GridBagLayout()) { + @Override + public void setBounds(final Rectangle r) { + super.setBounds(r); + } + }; + myContentPanel.setMinimumSize(new Dimension(200, 100)); + + final GridBagConstraints gb = + new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(1, 1, 1, 1), 0, 0); + + myInclude = new JCheckBox(); + gb.fill = GridBagConstraints.NONE; + gb.weightx = 0; + myContentPanel.add(myInclude, gb); + + // newline + myHereLabel = new JLabel("From:"); + ++ gb.gridy; + gb.gridx = 0; + myContentPanel.add(myHereLabel, gb); + + myUrlText = new JTextArea(); + myUrlText.setLineWrap(true); + myUrlText.setBackground(UIUtil.getLabelBackground()); + myUrlText.setWrapStyleWord(true); + gb.weightx = 1; + ++ gb.gridx; + gb.gridwidth = 2; + gb.fill = GridBagConstraints.HORIZONTAL; + myContentPanel.add(myUrlText, gb); + + // newline + gb.fill = GridBagConstraints.NONE; + myThereLabel = new JLabel("To:"); + gb.weightx = 0; + gb.gridwidth = 1; + ++ gb.gridy; + gb.gridx = 0; + myContentPanel.add(myThereLabel, gb); + + myBranchField = new TextFieldWithBrowseButton(); + gb.weightx = 1; + ++ gb.gridx; + gb.gridwidth = 2; + gb.fill = GridBagConstraints.HORIZONTAL; + myContentPanel.add(myBranchField, gb); + + // newline + gb.gridx = 1; + ++ gb.gridy; + gb.gridwidth = 1; + myLocalArea = new JTextArea(); + myLocalArea.setBackground(UIUtil.getLabelBackground()); + myLocalArea.setLineWrap(true); + myLocalArea.setWrapStyleWord(true); + myContentPanel.add(myLocalArea, gb); + + ++ gb.gridx; + gb.weightx = 0; + gb.fill = GridBagConstraints.NONE; + myFixedSelectLocal = new FixedSizeButton(20); + myContentPanel.add(myFixedSelectLocal, gb); + + ++ gb.gridy; + gb.gridx = 0; + gb.gridwidth = 2; + myMixedRevisions = new JTextArea("Mixed Revision Working Copy"); + myMixedRevisions.setForeground(Color.red); + myMixedRevisions.setBackground(myContentPanel.getBackground()); + myContentPanel.add(myMixedRevisions, gb); + + myMixedRevisions.setVisible(false); + } + + public void setMixedRevisions(final boolean value) { + myMixedRevisions.setVisible(value); + } + + @Nullable + private String getLocal(@NotNull final String url, @Nullable final String localPath) { + final Set paths = SvnBranchMapperManager17.getInstance().get(url); + if (paths != null && (! paths.isEmpty())) { + if (localPath != null) { + // check whether it is still actual + for (String path : paths) { + if (path.equals(localPath)) { + return path; + } + } + } else { + final java.util.List list = new ArrayList(paths); + Collections.sort(list); + return list.get(0); + } + } + return null; + } + + // always assign to local area here + private void calculateBranchPathByBranch(final String url, final String localPath) { + final String local = url == null ? null : getLocal(url, localPath == null ? myBranchToLocal.get(url) : localPath); + if (local == null) { + myLocalArea.setForeground(Color.red); + myLocalArea.setText(SvnBundle.message("tab.repository.merge.panel.root.panel.select.local")); + } else { + myLocalArea.setForeground(UIUtil.getInactiveTextColor()); + myLocalArea.setText(local); + myBranchToLocal.put(url, local); + } + } + + // always assign to selected branch here + private void refreshSelectedBranch(final String url) { + final String branch = SVNPathUtil.tail(url); + myBranchField.setText(branch); + + if (initSelectedBranch(url)) return; + myInfo = myRefresher.fun(myInfo); + initSelectedBranch(url); + } + + private boolean initSelectedBranch(final String branch) { + for (WCInfoWithBranches.Branch item : myInfo.getBranches()) { + if (item.getUrl().equals(branch)) { + mySelectedBranch = item; + return true; + } + } + return false; + } + + public void setDirection(final boolean fromHere) { + if (fromHere) { + myHereLabel.setText(SvnBundle.message("tab.repository.merge.panel.root.panel.from")); + myThereLabel.setText(SvnBundle.message("tab.repository.merge.panel.root.panel.to")); + } else { + myThereLabel.setText(SvnBundle.message("tab.repository.merge.panel.root.panel.from")); + myHereLabel.setText(SvnBundle.message("tab.repository.merge.panel.root.panel.to")); + } + } + + public void setOnlyOneRoot(final boolean onlyOneRoot) { + myOnlyOneRoot = onlyOneRoot; + myInclude.setEnabled(! myOnlyOneRoot); + myInclude.setSelected(true); + } + + public void selectCheckbox(final boolean select) { + myInclude.setSelected(select); + } + + public JPanel getContentPanel() { + return myContentPanel; + } + + private void createUIComponents() { + myFixedSelectLocal = new FixedSizeButton(20); + } + + public InfoHolder getInfo() { + return new InfoHolder(mySelectedBranch, getLocalBranch(), myInclude.isSelected()); + } + + public void initSelection(final InfoHolder holder) { + myInclude.setSelected(holder.isEnabled()); + if (holder.getBranch() != null) { + refreshSelectedBranch(holder.getBranch().getUrl()); + calculateBranchPathByBranch(mySelectedBranch.getUrl(), holder.getLocal()); + } + } + + public static class InfoHolder { + private final WCInfoWithBranches.Branch myBranch; + private final String myLocal; + private final boolean myEnabled; + + public InfoHolder(final WCInfoWithBranches.Branch branch, final String local, final boolean enabled) { + myBranch = branch; + myLocal = local; + myEnabled = enabled; + } + + public WCInfoWithBranches.Branch getBranch() { + return myBranch; + } + + public String getLocal() { + return myLocal; + } + + public boolean isEnabled() { + return myEnabled; + } + } + + public WCInfoWithBranches getWcInfo() { + return myInfo; + } + + public WCInfoWithBranches.Branch getBranch() { + return mySelectedBranch; + } + + public String getLocalBranch() { + if (mySelectedBranch != null) { + return myBranchToLocal.get(mySelectedBranch.getUrl()); + } + return null; + } + + public boolean isEnabled() { + return myOnlyOneRoot || myInclude.isSelected(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnMergeSourceDetails.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnMergeSourceDetails.java new file mode 100644 index 000000000000..348cd894d97e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnMergeSourceDetails.java @@ -0,0 +1,277 @@ +/* + * 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.history; + +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.MasterDetailsComponent; +import com.intellij.openapi.ui.NamedConfigurable; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vcs.changes.committed.CommittedChangeListRenderer; +import com.intellij.openapi.vcs.changes.ui.ChangeListViewerDialog; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowId; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.ui.ColoredTreeCellRenderer; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.ui.content.Content; +import com.intellij.ui.content.ContentFactory; +import com.intellij.ui.content.ContentManager; +import com.intellij.util.ContentsUtil; +import org.jetbrains.annotations.Nls; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnRevisionNumber; +import org.jetbrains.idea.svn17.SvnVcs17; + +import javax.swing.*; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; +import java.awt.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class SvnMergeSourceDetails extends MasterDetailsComponent { + private final Project myProject; + private final SvnFileRevision myRevision; + private final VirtualFile myFile; + private final Map myListsMap; + + private SvnMergeSourceDetails(final Project project, final SvnFileRevision revision, final VirtualFile file) { + myProject = project; + myRevision = revision; + myFile = file; + myListsMap = new HashMap(); + initTree(); + fillTree(); + + getSplitter().setProportion(0.5f); + } + + public static void showMe(final Project project, final SvnFileRevision revision, final VirtualFile file) { + if (ModalityState.NON_MODAL.equals(ModalityState.current())) { + ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.VCS); + final ContentManager contentManager = toolWindow.getContentManager(); + + final MyDialog dialog = new MyDialog(project, revision, file); + + Content content = ContentFactory.SERVICE.getInstance().createContent(dialog.createCenterPanel(), + SvnBundle.message("merge.source.details.title", (file == null) ? revision.getURL() : file.getName(), revision.getRevisionNumber().asString()), true); + ContentsUtil.addOrReplaceContent(contentManager, content, true); + + toolWindow.activate(null); + } else { + new MyDialog(project, revision, file).show(); + } + } + + protected void processRemovedItems() { + + } + + protected boolean wasObjectStored(final Object editableObject) { + return false; + } + + @Nls + public String getDisplayName() { + return null; + } + + public Icon getIcon() { + return null; + } + + public String getHelpTopic() { + return null; + } + + private void addRecursively(final SvnFileRevision revision, final MyNode node, final List nodesToExpand) { + final MyNode current = new MyNode(new MyNamedConfigurable(revision, myFile, myProject, myListsMap)); + node.add(current); + final TreeNode[] path = ((DefaultTreeModel)myTree.getModel()).getPathToRoot(node); + nodesToExpand.add(new TreePath(path)); + final List mergeSources = revision.getMergeSources(); + for (SvnFileRevision source : mergeSources) { + addRecursively(source, current, nodesToExpand); + } + } + + private class MyTreeCellRenderer extends ColoredTreeCellRenderer { + private final static int ourMaxWidth = 100; + private final static String ourDots = "(...)"; + + public void customizeCellRenderer(final JTree tree, + final Object value, + final boolean selected, + final boolean expanded, + final boolean leaf, + final int row, + final boolean hasFocus) { + final FontMetrics metrics = tree.getFontMetrics(tree.getFont()); + final SvnFileRevision revision; + if (value instanceof MyRootNode) { + revision = myRevision; + } else { + final MyNode myNode = (MyNode)value; + final MyNamedConfigurable configurable = (MyNamedConfigurable) myNode.getConfigurable(); + revision = configurable.getRevision(); + } + + final String revisonNumber = revision.getRevisionNumber().asString(); + final Pair info = CommittedChangeListRenderer.getDescriptionOfChangeList(revision.getCommitMessage()); + String description = info.getFirst(); + int width = metrics.stringWidth(description); + int dotsWidth = metrics.stringWidth(ourDots); + boolean descriptionTruncated = info.getSecond(); + if ((descriptionTruncated && (ourMaxWidth - dotsWidth < width)) || (! descriptionTruncated) && (ourMaxWidth < width)) { + description = CommittedChangeListRenderer.truncateDescription(description, metrics, ourMaxWidth - dotsWidth); + descriptionTruncated = true; + } + if (descriptionTruncated) { + description += ourDots; + } + + final String date = CommittedChangeListRenderer.getDateOfChangeList(revision.getRevisionDate()); + + final String author = revision.getAuthor(); + + append(revisonNumber + " ", SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); + append(description + " ", SimpleTextAttributes.REGULAR_ATTRIBUTES); + append(author, SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); + append(", " + date, SimpleTextAttributes.REGULAR_ATTRIBUTES); + } + } + + private void fillTree() { + myTree.setCellRenderer(new MyTreeCellRenderer()); + myRoot.removeAllChildren(); + + final List nodesToExpand = new ArrayList(); + addRecursively(myRevision, myRoot, nodesToExpand); + + ((DefaultTreeModel) myTree.getModel()).reload(myRoot); + for (TreePath treePath : nodesToExpand) { + myTree.expandPath(treePath); + } + myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + } + + private static class MyNamedConfigurable extends NamedConfigurable { + private final SvnFileRevision myRevision; + private final VirtualFile myFile; + private final Project myProject; + private final Map myListsMap; + private JComponent myPanel; + + private MyNamedConfigurable(final SvnFileRevision revision, final VirtualFile file, final Project project, + final Map listsMap) { + myRevision = revision; + myFile = file; + myProject = project; + myListsMap = listsMap; + } + + public void setDisplayName(final String name) { + } + + public SvnFileRevision getEditableObject() { + return myRevision; + } + + public String getBannerSlogan() { + return myRevision.getRevisionNumber().asString(); + } + + private SvnChangeList getList() { + SvnChangeList list = myListsMap.get(myRevision); + if (list == null) { + list = (SvnChangeList)SvnVcs17.getInstance(myProject).loadRevisions(myFile, myRevision.getRevisionNumber()); + myListsMap.put(((SvnRevisionNumber) myRevision.getRevisionNumber()).getRevision().getNumber(), list); + } + return list; + } + + public JComponent createOptionsPanel() { + if (myPanel == null) { + final SvnChangeList list = getList(); + if (list == null) { + myPanel = new JPanel(); + } else { + myPanel = new ChangeListViewerDialog(myProject, list).createCenterPanel(); + } + } + return myPanel; + } + + @Nls + public String getDisplayName() { + return getBannerSlogan(); + } + + public Icon getIcon() { + return null; + } + + public String getHelpTopic() { + return null; + } + + public boolean isModified() { + return false; + } + + public void apply() throws ConfigurationException { + } + + public void reset() { + } + + public void disposeUIResources() { + } + + public SvnFileRevision getRevision() { + return myRevision; + } + } + + private static class MyDialog extends DialogWrapper { + private final Project myProject; + private final SvnFileRevision myRevision; + private final VirtualFile myFile; + + private MyDialog(final Project project, final SvnFileRevision revision, final VirtualFile file) { + super(project, true); + myProject = project; + myRevision = revision; + myFile = file; + setTitle(SvnBundle.message("merge.source.details.title", (myFile == null) ? myRevision.getURL() : myFile.getName(), myRevision.getRevisionNumber().asString())); + init(); + } + + public JComponent createCenterPanel() { + final JComponent component = new SvnMergeSourceDetails(myProject, myRevision, myFile).createComponent(); + component.setMinimumSize(new Dimension(300, 200)); + return component; + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnMergeSourceTracker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnMergeSourceTracker.java new file mode 100644 index 000000000000..f9980c275999 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnMergeSourceTracker.java @@ -0,0 +1,43 @@ +/* + * 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.history; + +import com.intellij.openapi.util.Pair; +import com.intellij.util.ThrowableConsumer; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNLogEntry; + +public class SvnMergeSourceTracker { + private int myMergeLevel; + // -1 - not merge source; 0 - direct merge source + private ThrowableConsumer, SVNException> myConsumer; + + public SvnMergeSourceTracker(final ThrowableConsumer, SVNException> consumer) { + myConsumer = consumer; + myMergeLevel = -1; + } + + public void consume(final SVNLogEntry logEntry) throws SVNException { + if (logEntry.getRevision() < 0) { + -- myMergeLevel; + return; + } + myConsumer.consume(new Pair(logEntry, myMergeLevel)); + if (logEntry.hasChildren()) { + ++ myMergeLevel; + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnRepositoryBinaryContentRevision.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnRepositoryBinaryContentRevision.java new file mode 100644 index 000000000000..272b34b73a3c --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnRepositoryBinaryContentRevision.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.history; + +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.BinaryContentRevision; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnVcs17; + +/** + * @author yole + */ +public class SvnRepositoryBinaryContentRevision extends SvnRepositoryContentRevision implements BinaryContentRevision { + private byte[] myBinaryContent; + + public SvnRepositoryBinaryContentRevision(final SvnVcs17 vcs, final String repositoryRoot, final String path, + @Nullable final FilePath localPath, final long revision) { + super(vcs, repositoryRoot, path, localPath, revision); + } + + @Nullable + public byte[] getBinaryContent() throws VcsException { + if (myBinaryContent == null) { + myBinaryContent = loadContent().toByteArray(); + } + return myBinaryContent; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnRepositoryContentRevision.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnRepositoryContentRevision.java new file mode 100644 index 000000000000..57ae26efeea3 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnRepositoryContentRevision.java @@ -0,0 +1,183 @@ +/* + * 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. + */ + +/* + * Created by IntelliJ IDEA. + * User: yole + * Date: 28.11.2006 + * Time: 17:48:18 + */ +package org.jetbrains.idea.svn17.history; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.util.Throwable2Computable; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.actions.VcsContextFactory; +import com.intellij.openapi.vcs.changes.ContentRevision; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import com.intellij.openapi.vcs.impl.ContentRevisionCache; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnRevisionNumber; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.io.SVNRepository; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class SvnRepositoryContentRevision implements ContentRevision { + private final String myRepositoryRoot; + private final SvnVcs17 myVcs; + private final String myPath; + @NotNull private final FilePath myFilePath; + private final long myRevision; + + SvnRepositoryContentRevision(final SvnVcs17 vcs, final String repositoryRoot, final String path, @Nullable final FilePath localPath, + final long revision) { + myVcs = vcs; + myPath = path; + myRepositoryRoot = repositoryRoot; + if (localPath != null) { + myFilePath = localPath; + } + else { + myFilePath = VcsContextFactory.SERVICE.getInstance().createFilePathOnNonLocal(myPath, false); + } + myRevision = revision; + } + + @Nullable + public String getContent() throws VcsException { + try { + myFilePath.hardRefresh(); + return ContentRevisionCache.getOrLoadAsString(myVcs.getProject(), myFilePath, getRevisionNumber(), myVcs.getKeyInstanceMethod(), + ContentRevisionCache.UniqueType.REPOSITORY_CONTENT, new Throwable2Computable() { + @Override + public byte[] compute() throws VcsException, IOException { + final ByteArrayOutputStream buffer = loadContent(); + return buffer.toByteArray(); + } + }); + } + catch (IOException e) { + throw new VcsException(e); + } + } + + protected ByteArrayOutputStream loadContent() throws VcsException { + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + ContentLoader loader = new ContentLoader(myPath, buffer, myRevision); + if (ApplicationManager.getApplication().isDispatchThread()) { + ProgressManager.getInstance() + .runProcessWithProgressSynchronously(loader, SvnBundle.message("progress.title.loading.file.content"), false, null); + } + else { + loader.run(); + } + final SVNException exception = loader.getException(); + if (exception != null) { + throw new VcsException(exception); + } + return buffer; + } + + @NotNull + public FilePath getFile() { + return myFilePath; + } + + @NotNull + public VcsRevisionNumber getRevisionNumber() { + return new SvnRevisionNumber(SVNRevision.create(myRevision)); + } + + public static SvnRepositoryContentRevision create(final SvnVcs17 vcs, final String repositoryRoot, final String path, + @Nullable final FilePath localPath, final long revision) { + int fileNamePos = path.lastIndexOf('/'); + if (fileNamePos >= 0) { + String fileName = path.substring(fileNamePos); + final FileType fileType = FileTypeManager.getInstance().getFileTypeByFileName(fileName); + if (fileType.isBinary()) { + return new SvnRepositoryBinaryContentRevision(vcs, repositoryRoot, path, localPath, revision); + } + } + return new SvnRepositoryContentRevision(vcs, repositoryRoot, path, localPath, revision); + } + + @Override + public String toString() { + return myFilePath.getIOFile() + "#" + myRevision; + } + + private class ContentLoader implements Runnable { + private final String myPath; + private final long myRevision; + private final OutputStream myDst; + private SVNException myException; + + public ContentLoader(String path, OutputStream dst, long revision) { + myPath = path; + myDst = dst; + myRevision = revision; + } + + public SVNException getException() { + return myException; + } + + public void run() { + ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + if (progress != null) { + progress.setText(SvnBundle.message("progress.text.loading.contents", myPath)); + progress.setText2(SvnBundle.message("progress.text2.revision.information", myRevision)); + } + try { + SVNRepository repository = myVcs.createRepository(getFullPath()); + try { + repository.getFile("", myRevision, null, myDst); + } + finally { + repository.closeSession(); + } + } + catch (SVNException e) { + myException = e; + } + } + } + + public String getFullPath() { + String fullPath = myRepositoryRoot; + if (!fullPath.endsWith("/") && !myPath.startsWith("/")) { + fullPath += "/"; + } + fullPath += myPath; + return fullPath; + } + + public String getPath() { + return myPath; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnRepositoryLocation.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnRepositoryLocation.java new file mode 100644 index 000000000000..2eaa47e6c682 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnRepositoryLocation.java @@ -0,0 +1,72 @@ +/* + * 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.history; + +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.RepositoryLocation; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.util.NotNullFunction; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.RootUrlInfo; +import org.jetbrains.idea.svn17.SvnVcs17; + +import java.io.File; + +/** + * @author yole + */ +public class SvnRepositoryLocation implements RepositoryLocation { + private final String myURL; + + public SvnRepositoryLocation(final String URL) { + myURL = URL; + } + + public String toString() { + return myURL; + } + + public String toPresentableString() { + return myURL; + } + + public String getURL() { + return myURL; + } + + public String getKey() { + return myURL; + } + + @Override + public void onBeforeBatch() throws VcsException { + } + + @Override + public void onAfterBatch() { + } + + @Nullable + public static FilePath getLocalPath(final String fullPath, final NotNullFunction detector, final SvnVcs17 vcs) { + if (vcs.getProject().isDefault()) return null; + final RootUrlInfo rootForUrl = vcs.getSvnFileUrlMapping().getWcRootForUrl(fullPath); + if (rootForUrl != null) { + return LocationDetector.filePathByUrlAndPath(fullPath, rootForUrl.getUrl().toString(), rootForUrl.getIoFile().getAbsolutePath(), detector); + } + + return null; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnRepositoryLocationGroup.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnRepositoryLocationGroup.java new file mode 100644 index 000000000000..2b4796fe06b8 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnRepositoryLocationGroup.java @@ -0,0 +1,39 @@ +/* + * 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.history; + +import com.intellij.openapi.vcs.RepositoryLocation; +import com.intellij.openapi.vcs.changes.committed.RepositoryLocationGroup; +import org.jetbrains.annotations.NotNull; +import org.tmatesoft.svn.core.SVNURL; + +import java.util.Collection; + +public class SvnRepositoryLocationGroup extends RepositoryLocationGroup { + private final SVNURL myUrl; + + public SvnRepositoryLocationGroup(@NotNull final SVNURL url, final Collection locations) { + super(url.toString()); + myUrl = url; + for (RepositoryLocation location : locations) { + add(location); + } + } + + public SVNURL getUrl() { + return myUrl; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnRevisionsNavigationMediator.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnRevisionsNavigationMediator.java new file mode 100644 index 000000000000..677ef50f7cc8 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnRevisionsNavigationMediator.java @@ -0,0 +1,244 @@ +/* + * 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.history; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.RepositoryLocation; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.committed.ChangesBunch; +import com.intellij.openapi.vcs.changes.committed.CommittedChangesCache; +import com.intellij.openapi.vcs.changes.committed.CommittedChangesNavigation; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.io.SVNRepository; + +import java.util.*; + +public class SvnRevisionsNavigationMediator implements CommittedChangesNavigation { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.history.SvnRevisionsNavigationMediator"); + + public final static int CHUNK_SIZE = 50; + + private final InternallyCachedProvider myInternallyCached; + private final VisuallyCachedProvider myVisuallyCached; + + private final List> myChunks; + private boolean myCanNotGoBack; + private int myCurrentIdx; + private final BunchFactory myChunkFactory; + private final Project myProject; + + public SvnRevisionsNavigationMediator(final SvnRepositoryLocation location, final Project project, final VirtualFile vcsRoot) throws + VcsException { + myProject = project; + final SvnVcs17 vcs = SvnVcs17.getInstance(project); + + myChunks = new LinkedList>(); + + SVNRepository repository = null; + final SVNURL repositoryRoot; + final long youngRevision; + try { + repository = vcs.createRepository(location.getURL()); + youngRevision = repository.getLatestRevision(); + repositoryRoot = repository.getRepositoryRoot(false); + } + catch (SVNException e) { + throw new VcsException(e); + } + finally { + if (repository != null) { + repository.closeSession(); + } + } + + final Iterator visualIterator = project.isDefault() ? null : + CommittedChangesCache.getInstance(project).getBackBunchedIterator(vcs, vcsRoot, location, CHUNK_SIZE); + final Iterator internalIterator = project.isDefault() ? null : LoadedRevisionsCache17.getInstance(project).iterator(location.getURL()); + + myInternallyCached = (internalIterator == null) ? null : new InternallyCachedProvider(internalIterator, myProject); + myVisuallyCached = (visualIterator == null) ? null : new VisuallyCachedProvider(visualIterator, myProject, location); + + myChunkFactory = new BunchFactory(myInternallyCached, myVisuallyCached, + new LiveProvider(vcs, location, youngRevision, new SvnLogUtil(myProject, vcs, location, repositoryRoot))); + + myCurrentIdx = -1; + // init first screen + goBack(); + } + + public boolean canGoBack() { + return ((myCurrentIdx + 1) < myChunks.size()) || (! myCanNotGoBack); + } + + public boolean canGoForward() { + return myCurrentIdx > 0; + } + + public void goBack() throws VcsException { + if ((myCurrentIdx + 1) < myChunks.size()) { + ++ myCurrentIdx; + return; + } + + final Ref canNotGoBackRef = new Ref(); + final List fragments; + try { + fragments = myChunkFactory.goBack(CHUNK_SIZE, canNotGoBackRef); + } + catch (SVNException e) { + throw new VcsException(e); + } + myCanNotGoBack = canNotGoBackRef.get().booleanValue(); + + if (! fragments.isEmpty()) { + // load + ++ myCurrentIdx; + myChunks.add(fragments); + } + } + + public void goForward() { + -- myCurrentIdx; + } + + public List getCurrent() { + debugPrinting(); + return (List) ((myChunks.isEmpty()) ? Collections.>emptyList() : + fragmentsToLists(myChunks.get(myCurrentIdx))); + } + + private void debugPrinting() { + LOG.debug("== showing screen (" + myCurrentIdx + "): =="); + if (! myChunks.isEmpty()) { + for (Fragment fragment : myChunks.get(myCurrentIdx)) { + LOG.debug(fragment.getOrigin().toString() + " from: " + fragment.getList().get(0).getNumber() + + " to: " + fragment.getList().get(fragment.getList().size() - 1).getNumber()); + } + } + LOG.debug("== end of screen =="); + } + + private List fragmentsToLists(final List fragments) { + final List result = new ArrayList(); + for (Fragment fragment : fragments) { + result.addAll(fragment.getList()); + } + return result; + } + + public void onBeforeClose() { + if ((myVisuallyCached != null) && (myVisuallyCached.hadBeenSuccessfullyAccessed())) { + myVisuallyCached.doCacheUpdate(myChunks); + // keep also in internal cache + InternallyCachedProvider.initCache(myChunks, myProject); + } else if (myInternallyCached != null) { + myInternallyCached.doCacheUpdate(myChunks); + } else { + InternallyCachedProvider.initCache(myChunks, myProject); + } + } + + private static class VisuallyCachedProvider extends CachedProvider { + private final Project myProject; + private final RepositoryLocation myLocation; + + private VisuallyCachedProvider(final Iterator iterator, final Project project, final RepositoryLocation location) { + super(iterator, Origin.VISUAL); + myProject = project; + myLocation = location; + } + + public void doCacheUpdate(final List> fragmentsListList) { + final List lists = getAllBeforeVisuallyCached(fragmentsListList); + CommittedChangesCache.getInstance(myProject).submitExternallyLoaded(myLocation, myAlreadyReaded.getList().get(0).getNumber(), lists); + } + } + + private static class InternallyCachedProvider extends CachedProvider { + private final Project myProject; + private boolean myHolesDetected; + + private InternallyCachedProvider(final Iterator iterator, final Project project) { + super(iterator, Origin.INTERNAL); + myProject = project; + } + + @Override + protected void addToLoaded(final ChangesBunch loaded) { + myHolesDetected |= (! loaded.isConsistentWithPrevious()); + super.addToLoaded(loaded); + } + + public static void initCache(final List> fragmentListList, final Project project) { + final List lists = getAllBeforeVisuallyCached(fragmentListList); + if (! lists.isEmpty()) { + LoadedRevisionsCache17.getInstance(project).put(lists, false, null); + } + } + + public void doCacheUpdate(final List> fragmentsListList) { + final List lists = new ArrayList(); + LoadedRevisionsCache17.Bunch bindAddress = null; + boolean consistent = false; + + if (myHolesDetected) { + boolean liveMet = false; + for (int i = 0; i < fragmentsListList.size(); i++) { + final List fragmentList = fragmentsListList.get(i); + for (int j = 0; j < fragmentList.size(); j++) { + final Fragment fragment = fragmentList.get(j); + liveMet |= Origin.LIVE.equals(fragment.getOrigin()); + if (Origin.INTERNAL.equals(fragment.getOrigin())) { + bindAddress = ((LoadedRevisionsCache17.Bunch) fragment.getOriginBunch()).getNext(); + // latest element + if ((i == (fragmentsListList.size() - 1)) && (j == (fragmentList.size() - 1))) { + lists.addAll(fragment.getOriginBunch().getList()); + consistent = fragment.getOriginBunch().isConsistentWithPrevious(); + break; + } + } + lists.addAll(fragment.getList()); + } + } + if (! liveMet) { + return; + } + } else { + // until _first_internally + for (List fragmentList : fragmentsListList) { + for (Fragment fragment : fragmentList) { + if (Origin.INTERNAL.equals(fragment.getOrigin())) { + bindAddress = (LoadedRevisionsCache17.Bunch) fragment.getOriginBunch(); + consistent = true; + break; + } + lists.addAll(fragment.getList()); + } + } + } + + if (! lists.isEmpty()) { + LoadedRevisionsCache17.getInstance(myProject).put(lists, consistent, bindAddress); + } + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnVersionFilterComponent.form b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnVersionFilterComponent.form new file mode 100644 index 000000000000..a49eed8d37a7 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnVersionFilterComponent.form @@ -0,0 +1,47 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnVersionFilterComponent.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnVersionFilterComponent.java new file mode 100644 index 000000000000..401d7f2cd3a4 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SvnVersionFilterComponent.java @@ -0,0 +1,90 @@ +/* + * 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.history; + +import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings; +import com.intellij.openapi.vcs.versionBrowser.StandardVersionFilterComponent; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class SvnVersionFilterComponent extends StandardVersionFilterComponent { + private JCheckBox myUseAuthorFilter; + private JTextField myAuthorField; + private JPanel myPanel; + private JPanel myStandardPanel; + private JCheckBox myStopOnCopyCheckBox; + + public SvnVersionFilterComponent(final boolean showDateFilter) { + super(showDateFilter); + myStandardPanel.setLayout(new BorderLayout()); + myStandardPanel.add(getStandardPanel(), BorderLayout.CENTER); + init(new ChangeBrowserSettings()); + } + + protected void updateAllEnabled(final ActionEvent e) { + super.updateAllEnabled(e); + updatePair(myUseAuthorFilter, myAuthorField, e); + } + + protected void initValues(ChangeBrowserSettings settings) { + super.initValues(settings); + myUseAuthorFilter.setSelected(settings.USE_USER_FILTER); + myAuthorField.setText(settings.USER); + myStopOnCopyCheckBox.setSelected(settings.STOP_ON_COPY); + } + + public void saveValues(ChangeBrowserSettings settings) { + super.saveValues(settings); + settings.USER = myAuthorField.getText(); + settings.USE_USER_FILTER = myUseAuthorFilter.isSelected(); + settings.STOP_ON_COPY = myStopOnCopyCheckBox.isSelected(); + } + + protected void installCheckBoxListener(final ActionListener filterListener) { + super.installCheckBoxListener(filterListener); + myUseAuthorFilter.addActionListener(filterListener); + myAuthorField.addActionListener(filterListener); + myStopOnCopyCheckBox.addActionListener(filterListener); + } + + public JPanel getPanel() { + return myPanel; + } + + @Nullable + public String getAuthorFilter() { + if (myUseAuthorFilter.isSelected() && myAuthorField.getText().length() > 0) { + return myAuthorField.getText(); + } + else { + return null; + } + } + + @Override + protected String getChangeNumberTitle() { + return SvnBundle.message("revision.title"); + } + + public JComponent getComponent() { + return getPanel(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SwitchRevisionsProviderException.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SwitchRevisionsProviderException.java new file mode 100644 index 000000000000..0839ebddd43e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/SwitchRevisionsProviderException.java @@ -0,0 +1,19 @@ +/* + * 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.history; + +public class SwitchRevisionsProviderException extends RuntimeException { +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/TreeStructureNode.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/TreeStructureNode.java new file mode 100644 index 000000000000..cd3365938263 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/TreeStructureNode.java @@ -0,0 +1,41 @@ +/* + * 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.history; + +import java.util.LinkedList; +import java.util.List; + +public class TreeStructureNode { + private final T myMe; + private final List> myChildren; + + public TreeStructureNode(final T me) { + myChildren = new LinkedList>(); + myMe = me; + } + + public void add(final T child) { + myChildren.add(new TreeStructureNode(child)); + } + + public List> getChildren() { + return myChildren; + } + + public T getMe() { + return myMe; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/WcInfoLoader.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/WcInfoLoader.java new file mode 100644 index 000000000000..d1498dcaae8a --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/history/WcInfoLoader.java @@ -0,0 +1,157 @@ +/* + * 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.history; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.vcs.RepositoryLocation; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.*; +import org.jetbrains.idea.svn17.branchConfig.SvnBranchConfigurationNew; +import org.jetbrains.idea.svn17.dialogs.WCInfo; +import org.jetbrains.idea.svn17.dialogs.WCInfoWithBranches; +import org.jetbrains.idea.svn17.integrate.SvnBranchItem; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class WcInfoLoader { + private final static Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.history.WcInfoLoader"); + private final Project myProject; + /** + * filled when showing for selected location + */ + private final RepositoryLocation myLocation; + + public WcInfoLoader(final Project project, final RepositoryLocation location) { + myProject = project; + myLocation = location; + } + + public List loadRoots() { + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + if (vcs == null) { + return Collections.emptyList(); + } + final SvnFileUrlMapping17 urlMapping = vcs.getSvnFileUrlMapping(); + final List wcInfoList = vcs.getAllWcInfos(); + + final List result = new ArrayList(); + for (WCInfo info : wcInfoList) { + final WCInfoWithBranches wcInfoWithBranches = createInfo(info, vcs, urlMapping); + if (wcInfoWithBranches != null) { + result.add(wcInfoWithBranches); + } + } + return result; + } + + @Nullable + public WCInfoWithBranches reloadInfo(final WCInfoWithBranches info) { + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + if (vcs == null) { + return null; + } + final SvnFileUrlMapping17 urlMapping = vcs.getSvnFileUrlMapping(); + final File file = new File(info.getPath()); + final RootUrlInfo rootInfo = urlMapping.getWcRootForFilePath(file); + if (rootInfo == null) { + return null; + } + final WCInfo wcInfo = new WCInfo(rootInfo.getIoFile().getAbsolutePath(), rootInfo.getAbsoluteUrlAsUrl(), + SvnFormatSelector.getWorkingCopyFormat(file), rootInfo.getRepositoryUrl(), SvnUtil.isWorkingCopyRoot(file), + rootInfo.getType(), SvnUtil.getDepth(vcs, file)); + return createInfo(wcInfo, vcs, urlMapping); + } + + @Nullable + private WCInfoWithBranches createInfo(final WCInfo info, final SvnVcs17 vcs, final SvnFileUrlMapping17 urlMapping) { + if (! info.getFormat().supportsMergeInfo()) { + return null; + } + + final String url = info.getUrl().toString(); + if ((myLocation != null) && (! myLocation.toPresentableString().startsWith(url)) && + (! url.startsWith(myLocation.toPresentableString()))) { + return null; + } + if (!SvnUtil.checkRepositoryVersion15(vcs, url)) { + return null; + } + + // check of WC version + final RootUrlInfo rootForUrl = urlMapping.getWcRootForUrl(url); + if (rootForUrl == null) { + return null; + } + final VirtualFile root = rootForUrl.getRoot(); + final VirtualFile wcRoot = rootForUrl.getVirtualFile(); + if (wcRoot == null) { + return null; + } + final SvnBranchConfigurationNew configuration; + try { + configuration = SvnBranchConfigurationManager17.getInstance(myProject).get(wcRoot); + } + catch (VcsException e) { + LOG.info(e); + return null; + } + if (configuration == null) { + return null; + } + + final List items = new ArrayList(); + final String branchRoot = createBranchesList(url, configuration, items); + return new WCInfoWithBranches(info.getPath(), info.getUrl(), info.getFormat(), + info.getRepositoryRoot(), info.isIsWcRoot(), items, root, + branchRoot, info.getType(), info.getStickyDepth()); + } + + private static String createBranchesList(final String url, final SvnBranchConfigurationNew configuration, + final List items) { + String result = null; + final String trunkUrl = configuration.getTrunkUrl(); + if ((trunkUrl != null) && (! SVNPathUtil.isAncestor(trunkUrl, url))) { + items.add(new WCInfoWithBranches.Branch(trunkUrl)); + } else if (trunkUrl != null) { + result = trunkUrl; + } + for(String branchUrl: configuration.getBranchUrls()) { + for (SvnBranchItem branchItem : configuration.getBranches(branchUrl)) { + if (! SVNPathUtil.isAncestor(branchItem.getUrl(), url)) { + items.add(new WCInfoWithBranches.Branch(branchItem.getUrl())); + } else { + result = branchItem.getUrl(); + } + } + } + + Collections.sort(items, new Comparator() { + public int compare(final WCInfoWithBranches.Branch o1, final WCInfoWithBranches.Branch o2) { + return Comparing.compare(o1.getUrl(), o2.getUrl()); + } + }); + return result; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/ignore/FileGroupInfo.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ignore/FileGroupInfo.java new file mode 100644 index 000000000000..26dcc876a4c6 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ignore/FileGroupInfo.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.ignore; + +import com.intellij.openapi.vfs.VirtualFile; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class FileGroupInfo implements FileIterationListener, IgnoreInfoGetter { + private String commonExtension; + private final Map> folders; + + private int fileCount; + private boolean sameExtensionCase; + + public FileGroupInfo() { + fileCount = 0; + sameExtensionCase = true; + folders = new HashMap>(); + } + + public void onFileEnabled(final VirtualFile file) { + ++ fileCount; + + if (sameExtensionCase) { + final String extension = file.getExtension(); + if (extension == null) { + sameExtensionCase = false; + } else if (commonExtension == null) { + commonExtension = extension; + } else { + sameExtensionCase &= commonExtension.equals(extension); + } + } + + final VirtualFile parentVirtualFile = file.getParent(); + + if (parentVirtualFile != null) { + Set namesList = folders.get(parentVirtualFile); + if (namesList == null) { + namesList = new HashSet(); + folders.put(parentVirtualFile, namesList); + } + namesList.add(file.getName()); + } + } + + public boolean oneFileSelected() { + return fileCount == 1; + } + + public boolean sameExtension() { + return sameExtensionCase; + } + + public String getExtensionMask() { + return "*." + commonExtension; + } + + public Map> getInfo(final boolean useCommonExtension) { + if (! useCommonExtension) { + return folders; + } + final Map> result = new HashMap>(folders.size(), 1); + for (final Map.Entry> entry : folders.entrySet()) { + final Set set = new HashSet(); + set.add(getExtensionMask()); + result.put(entry.getKey(), set); + } + return result; + } + + public String getFileName() { + return folders.values().iterator().next().iterator().next(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/ignore/FileIterationListener.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ignore/FileIterationListener.java new file mode 100644 index 000000000000..83380741eca0 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ignore/FileIterationListener.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.ignore; + +import com.intellij.openapi.vfs.VirtualFile; + +public interface FileIterationListener { + void onFileEnabled(final VirtualFile file); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/ignore/IgnoreGroupHelperAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ignore/IgnoreGroupHelperAction.java new file mode 100644 index 000000000000..2862bd4c80c2 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ignore/IgnoreGroupHelperAction.java @@ -0,0 +1,103 @@ +/* + * 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.ignore; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn17.SvnStatusUtil; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.actions.BasicAction; + +public class IgnoreGroupHelperAction extends BasicAction { + private boolean myAllCanBeIgnored; + private boolean myAllAreIgnored; + private FileIterationListener myListener; + + protected String getActionName(final AbstractVcs vcs) { + return null; + } + + public void update(final AnActionEvent e) { + myAllAreIgnored = true; + myAllCanBeIgnored = true; + + super.update(e); + } + + public void setFileIterationListener(final FileIterationListener listener) { + myListener = listener; + } + + private boolean isEnabledImpl(final SvnVcs17 vcs, final VirtualFile file) { + final ChangeListManager clManager = ChangeListManager.getInstance(vcs.getProject()); + + if (SvnStatusUtil.isIgnoredInAnySense(clManager, file)) { + myAllCanBeIgnored = false; + return myAllAreIgnored | myAllCanBeIgnored; + } else if (clManager.isUnversioned(file)) { + // check parent + final VirtualFile parent = file.getParent(); + if (parent != null) { + if ((! SvnStatusUtil.isIgnoredInAnySense(clManager, parent)) && (! clManager.isUnversioned(parent))) { + myAllAreIgnored = false; + return myAllAreIgnored | myAllCanBeIgnored; + } + } + } + myAllCanBeIgnored = false; + myAllAreIgnored = false; + return false; + } + + protected boolean isEnabled(final Project project, final SvnVcs17 vcs, final VirtualFile file) { + final boolean result = isEnabledImpl(vcs, file); + if (result) { + myListener.onFileEnabled(file); + } + return result; + } + + public boolean allCanBeIgnored() { + return myAllCanBeIgnored; + } + + public boolean allAreIgnored() { + return myAllAreIgnored; + } + + protected boolean needsFiles() { + return true; + } + + protected void perform(final Project project, final SvnVcs17 activeVcs, final VirtualFile file, final DataContext context) + throws VcsException { + + } + + protected void batchPerform(final Project project, final SvnVcs17 activeVcs, final VirtualFile[] file, final DataContext context) + throws VcsException { + + } + + protected boolean isBatchAction() { + return false; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/ignore/IgnoreInfoGetter.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ignore/IgnoreInfoGetter.java new file mode 100644 index 000000000000..73f13d59486e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ignore/IgnoreInfoGetter.java @@ -0,0 +1,25 @@ +/* + * 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.ignore; + +import com.intellij.openapi.vfs.VirtualFile; + +import java.util.Map; +import java.util.Set; + +public interface IgnoreInfoGetter { + Map> getInfo(final boolean useCommonExtension); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/ignore/SvnPropertyService.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ignore/SvnPropertyService.java new file mode 100644 index 000000000000..85377099b6ca --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/ignore/SvnPropertyService.java @@ -0,0 +1,296 @@ +/* + * 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.ignore; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnPropertyKeys; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNPropertyValue; +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; +import java.util.*; + +public class SvnPropertyService { + private final static Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.ignore.SvnPropertyService"); + + private SvnPropertyService() { + } + + public static void doAddToIgnoreProperty(final SvnVcs17 activeVcs, final Project project, final boolean useCommonExtension, + final VirtualFile[] file, final IgnoreInfoGetter getter) throws VcsException { + final IgnorePropertyAdder adder = new IgnorePropertyAdder(activeVcs, project, useCommonExtension); + adder.execute(file, getter); + } + + public static void doRemoveFromIgnoreProperty(final SvnVcs17 activeVcs, final Project project, final boolean useCommonExtension, + final VirtualFile[] file, final IgnoreInfoGetter getter) throws VcsException{ + final IgnorePropertyRemover remover = new IgnorePropertyRemover(activeVcs, project, useCommonExtension); + remover.execute(file, getter); + } + + public static void doCheckIgnoreProperty(final SvnVcs17 activeVcs, final Project project, final VirtualFile[] file, + final IgnoreInfoGetter getter, final String extensionPattern, final Ref filesOk, final Ref extensionOk) { + final IgnorePropertyChecker checker = new IgnorePropertyChecker(activeVcs, project, extensionPattern); + try { + checker.execute(file, getter); + } catch (VcsException e) { + // ignore - actually never thrown inside + } + filesOk.set(checker.filesOk()); + extensionOk.set(checker.extensionOk()); + } + + private static abstract class IgnorePropertyWorkTemplate { + protected final SvnVcs17 myVcs; + protected final SVNWCClient myClient; + protected final Project myProject; + protected final boolean myUseCommonExtension; + protected final boolean myCanUseCachedProperty; + + protected abstract void processFolder(final VirtualFile folder, final File folderDir, final Set data, + final SVNPropertyValue propertyValue) throws SVNException; + protected abstract void onAfterProcessing(final VirtualFile[] file) throws VcsException; + protected abstract void onSVNException(SVNException e); + protected abstract boolean stopIteration(); + + private IgnorePropertyWorkTemplate(final SvnVcs17 activeVcs, final Project project, final boolean useCommonExtension, + final boolean canUseCachedProperty) { + myVcs = activeVcs; + myCanUseCachedProperty = canUseCachedProperty; + myClient = activeVcs.createWCClient(); + myProject = project; + myUseCommonExtension = useCommonExtension; + } + + public void execute(final VirtualFile[] file, final IgnoreInfoGetter getter) throws VcsException { + final Map> foldersInfo = getter.getInfo(myUseCommonExtension); + for (final Map.Entry> entry : foldersInfo.entrySet()) { + if (stopIteration()) { + break; + } + final File dir = new File(entry.getKey().getPath()); + try { + final SVNPropertyValue value; + if (myCanUseCachedProperty) { + value = myVcs.getPropertyWithCaching(entry.getKey(), SvnPropertyKeys.SVN_IGNORE); + } else { + final SVNPropertyData data = + myVcs.createWCClient().doGetProperty(dir, SvnPropertyKeys.SVN_IGNORE, SVNRevision.UNDEFINED, SVNRevision.WORKING); + value = data == null ? null : data.getValue(); + } + processFolder(entry.getKey(), dir, entry.getValue(), value); + } + catch (SVNException e) { + onSVNException(e); + } + } + onAfterProcessing(file); + } + } + + @Nullable + public static Set getIgnoreStringsUnder(final SvnVcs17 vcs, final VirtualFile dir) { + try { + final SVNPropertyData data = vcs.createWCClient().doGetProperty(new File(dir.getPath()), SvnPropertyKeys.SVN_IGNORE, SVNRevision.WORKING, SVNRevision.WORKING); + final SVNPropertyValue value = (data == null) ? null : data.getValue(); + if (value != null) { + final Set ignorePatterns = new HashSet(); + final String propAsString = SVNPropertyValue.getPropertyAsString(value); + final StringTokenizer st = new StringTokenizer(propAsString, "\r\n "); + while (st.hasMoreElements()) { + final String ignorePattern = (String) st.nextElement(); + ignorePatterns.add(ignorePattern); + } + return ignorePatterns; + } + } + catch (SVNException e) { + LOG.info(e); + } + return null; + } + + private static class IgnorePropertyChecker extends IgnorePropertyWorkTemplate { + private final String myExtensionPattern; + private boolean myFilesOk; + private boolean myExtensionOk; + + private IgnorePropertyChecker(final SvnVcs17 activeVcs, final Project project, final String extensionPattern) { + super(activeVcs, project, false, true); + myExtensionPattern = extensionPattern; + myExtensionOk = true; + myFilesOk = true; + } + + protected boolean stopIteration() { + return (! myFilesOk) && (! myExtensionOk); + } + + protected void processFolder(final VirtualFile folder, final File folderDir, final Set data, final SVNPropertyValue propertyValue) + throws SVNException { + if (propertyValue == null) { + myFilesOk = false; + myExtensionOk = false; + return; + } + final Set ignorePatterns = new HashSet(); + final StringTokenizer st = new StringTokenizer(SVNPropertyValue.getPropertyAsString(propertyValue), "\r\n "); + while (st.hasMoreElements()) { + final String ignorePattern = (String)st.nextElement(); + ignorePatterns.add(ignorePattern); + } + + myExtensionOk &= ignorePatterns.contains(myExtensionPattern); + for (final String fileName : data) { + if (!ignorePatterns.contains(fileName)) { + myFilesOk = false; + } + } + } + + protected void onAfterProcessing(final VirtualFile[] file) throws VcsException { + } + + protected void onSVNException(final SVNException e) { + myFilesOk = false; + myExtensionOk = false; + } + + public boolean filesOk() { + return myFilesOk; + } + + public boolean extensionOk() { + return myExtensionOk; + } + } + + private abstract static class IgnorePropertyAddRemoveTemplate extends IgnorePropertyWorkTemplate { + private final Collection exceptions; + private final VcsDirtyScopeManager dirtyScopeManager; + + private IgnorePropertyAddRemoveTemplate(final SvnVcs17 activeVcs, final Project project, final boolean useCommonExtension) { + super(activeVcs, project, useCommonExtension, false); + exceptions = new ArrayList(); + dirtyScopeManager = VcsDirtyScopeManager.getInstance(project); + } + + protected boolean stopIteration() { + return false; + } + + protected abstract String getNewPropertyValue(final Set data, final SVNPropertyValue propertyValue); + + protected void processFolder(final VirtualFile folder, final File folderDir, final Set data, final SVNPropertyValue propertyValue) + throws SVNException { + String newValue = getNewPropertyValue(data, propertyValue); + newValue = (newValue.trim().isEmpty()) ? null : newValue; + myClient.doSetProperty(folderDir, SvnPropertyKeys.SVN_IGNORE, SVNPropertyValue.create(newValue), false, false, null); + + if (myUseCommonExtension) { + dirtyScopeManager.dirDirtyRecursively(folder); + } + } + + protected void onAfterProcessing(final VirtualFile[] file) throws VcsException { + if (! myUseCommonExtension) { + for (VirtualFile virtualFile : file) { + VcsDirtyScopeManager.getInstance(myProject).fileDirty(virtualFile); + } + } + + if (!exceptions.isEmpty()) { + throw new VcsException(exceptions); + } + } + + protected void onSVNException(final SVNException e) { + exceptions.add(e.getMessage()); + } + } + + private static class IgnorePropertyRemover extends IgnorePropertyAddRemoveTemplate { + private IgnorePropertyRemover(final SvnVcs17 activeVcs, final Project project, final boolean useCommonExtension) { + super(activeVcs, project, useCommonExtension); + } + + protected String getNewPropertyValue(final Set data, final SVNPropertyValue propertyValue) { + if (propertyValue != null) { + return getNewPropertyValueForRemove(data, SVNPropertyValue.getPropertyAsString(propertyValue)); + } + return ""; + } + } + + public static void setIgnores(final SvnVcs17 vcs, final Collection patterns, final File file) + throws SVNException { + final SVNWCClient client = vcs.createWCClient(); + final StringBuilder sb = new StringBuilder(); + for (String pattern : patterns) { + sb.append(pattern).append('\n'); + } + if (! patterns.isEmpty()) { + final String value = sb.toString(); + client.doSetProperty(file, SvnPropertyKeys.SVN_IGNORE, SVNPropertyValue.create(value), false, SVNDepth.EMPTY, null, null); + } else { + client.doSetProperty(file, SvnPropertyKeys.SVN_IGNORE, null, false, SVNDepth.EMPTY, null, null); + } + } + + private static String getNewPropertyValueForRemove(final Collection data, @NotNull final String propertyValue) { + final StringBuilder sb = new StringBuilder(); + final StringTokenizer st = new StringTokenizer(propertyValue, "\r\n "); + while (st.hasMoreElements()) { + final String ignorePattern = (String)st.nextElement(); + if (! data.contains(ignorePattern)) { + sb.append(ignorePattern).append('\n'); + } + } + return sb.toString(); + } + + private static class IgnorePropertyAdder extends IgnorePropertyAddRemoveTemplate { + private IgnorePropertyAdder(final SvnVcs17 activeVcs, final Project project, final boolean useCommonExtension) { + super(activeVcs, project, useCommonExtension); + } + + protected String getNewPropertyValue(final Set data, final SVNPropertyValue propertyValue) { + final String ignoreString; + if (data.size() == 1) { + ignoreString = data.iterator().next(); + } else { + final StringBuilder sb = new StringBuilder(); + for (final String name : data) { + sb.append(name).append('\n'); + } + ignoreString = sb.toString(); + } + return (propertyValue == null) ? ignoreString : (SVNPropertyValue.getPropertyAsString(propertyValue) + '\n' + ignoreString); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/AlienDirtyScope.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/AlienDirtyScope.java new file mode 100644 index 000000000000..b003c9d43b95 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/AlienDirtyScope.java @@ -0,0 +1,95 @@ +/* + * 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.integrate; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.changes.VcsDirtyScope; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Processor; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +public class AlienDirtyScope extends VcsDirtyScope { + private final Set myFiles; + private final Set myDirs; + + public AlienDirtyScope() { + myFiles = new HashSet(); + myDirs = new HashSet(); + } + + public Collection getAffectedContentRoots() { + return null; + } + + public Project getProject() { + return null; + } + + public AbstractVcs getVcs() { + return null; + } + + public Set getDirtyFiles() { + return myFiles; + } + + public Set getDirtyFilesNoExpand() { + return myFiles; + } + + public Set getRecursivelyDirtyDirectories() { + return myDirs; + } + + @Override + public boolean isRecursivelyDirty(final VirtualFile vf) { + for (FilePath dir : myDirs) { + final VirtualFile dirVf = dir.getVirtualFile(); + if (dirVf != null) { + if (VfsUtil.isAncestor(dirVf, vf, false)) { + return true; + } + } + } + return false; + } + + public void iterate(final Processor iterator) { + } + + @Override + public boolean isEmpty() { + return myFiles.isEmpty() && myDirs.isEmpty(); + } + + public boolean belongsTo(final FilePath path) { + return false; + } + + public void addFile(final FilePath path) { + myFiles.add(path); + } + + public void addDir(final FilePath dir) { + myDirs.add(dir); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/ByNumberChangeListComparator.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/ByNumberChangeListComparator.java new file mode 100644 index 000000000000..7bbaddb191b1 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/ByNumberChangeListComparator.java @@ -0,0 +1,39 @@ +/* + * 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.integrate; + +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; + +import java.util.Comparator; + +/** +* Created by IntelliJ IDEA. +* User: Irina.Chernushina +* Date: 02.07.2010 +* Time: 17:26:50 +* To change this template use File | Settings | File Templates. +*/ +public class ByNumberChangeListComparator implements Comparator { + private final static ByNumberChangeListComparator ourInstance = new ByNumberChangeListComparator(); + + public static ByNumberChangeListComparator getInstance() { + return ourInstance; + } + + public int compare(final CommittedChangeList o1, final CommittedChangeList o2) { + return (int) (o1.getNumber() - o2.getNumber()); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/GatheringChangelistBuilder.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/GatheringChangelistBuilder.java new file mode 100644 index 000000000000..5a69527e0c1b --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/GatheringChangelistBuilder.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.integrate; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsKey; +import com.intellij.openapi.vcs.changes.*; +import com.intellij.openapi.vcs.update.UpdatedFilesReverseSide; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNPropertyValue; +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; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class GatheringChangelistBuilder implements ChangelistBuilder { + private final Set myCheckSet; + private final List myChanges; + private final UpdatedFilesReverseSide myFiles; + private final VirtualFile myMergeRoot; + private final SvnVcs17 myVcs; + + public GatheringChangelistBuilder(final Project project, final UpdatedFilesReverseSide files, final VirtualFile mergeRoot) { + myVcs = SvnVcs17.getInstance(project); + myFiles = files; + myMergeRoot = mergeRoot; + myChanges = new ArrayList(); + myCheckSet = new HashSet(); + } + + public void processChange(final Change change, VcsKey vcsKey) { + addChange(change); + } + + public void processChangeInList(final Change change, @Nullable final ChangeList changeList, VcsKey vcsKey) { + addChange(change); + } + + public void processChangeInList(final Change change, final String changeListName, VcsKey vcsKey) { + addChange(change); + } + + private void addChange(final Change change) { + final FilePath path = ChangesUtil.getFilePath(change); + final VirtualFile vf = path.getVirtualFile(); + if ((mergeinfoChanged(path.getIOFile()) || ((vf != null) && myFiles.containsFile(vf))) && (! myCheckSet.contains(vf))) { + myCheckSet.add(vf); + myChanges.add(change); + } + } + + private boolean mergeinfoChanged(final File file) { + final SVNWCClient client = myVcs.createWCClient(); + try { + final SVNPropertyData current = client.doGetProperty(file, "svn:mergeinfo", SVNRevision.UNDEFINED, SVNRevision.WORKING); + final SVNPropertyData base = client.doGetProperty(file, "svn:mergeinfo", SVNRevision.UNDEFINED, SVNRevision.BASE); + if (current != null) { + if (base == null) { + return true; + } else { + final SVNPropertyValue currentValue = current.getValue(); + final SVNPropertyValue baseValue = base.getValue(); + return ! Comparing.equal(currentValue, baseValue); + } + } + } + catch (SVNException e) { + // + } + return false; + } + + public void processUnversionedFile(final VirtualFile file) { + + } + + public void processLocallyDeletedFile(final FilePath file) { + + } + + public void processLocallyDeletedFile(LocallyDeletedChange locallyDeletedChange) { + + } + + public void processModifiedWithoutCheckout(final VirtualFile file) { + + } + + public void processIgnoredFile(final VirtualFile file) { + + } + + public void processLockedFolder(final VirtualFile file) { + } + + public void processLogicallyLockedFolder(VirtualFile file, LogicalLock logicalLock) { + } + + public void processSwitchedFile(final VirtualFile file, final String branch, final boolean recursive) { + + } + + public void processRootSwitch(VirtualFile file, String branch) { + } + + public boolean reportChangesOutsideProject() { + return true; + } + + public void reportWarningMessage(final String message) { + // todo maybe, use further + } + + public List getChanges() { + return myChanges; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/GroupMerger.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/GroupMerger.java new file mode 100644 index 000000000000..987d9bba5bc8 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/GroupMerger.java @@ -0,0 +1,217 @@ +/* + * 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.integrate; + +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.util.Consumer; +import com.intellij.util.messages.Topic; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnConfiguration17; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.update.UpdateEventHandler; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.SVNDiffClient; +import org.tmatesoft.svn.core.wc.SVNDiffOptions; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNRevisionRange; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class GroupMerger implements IMerger { + protected final List myChangeLists; + protected final File myTarget; + protected final SVNDiffClient myDiffClient; + private final ProgressIndicator myProgressIndicator; + protected final SVNURL myCurrentBranchUrl; + private final StringBuilder myCommitMessage; + protected final SvnConfiguration17 mySvnConfig; + private final Project myProject; + private final String myBranchName; + private final boolean myInverseRange; + private final boolean myDryRun; + private final Splitter mySplitter; + + private int myPackStart; + private int myPackEnd; + + public GroupMerger(final SvnVcs17 vcs, + final List changeLists, + final File target, + final UpdateEventHandler handler, + final SVNURL currentBranchUrl, + String branchName, boolean inverseRange, boolean dryRun, boolean stepByStep) { + myBranchName = branchName; + myInverseRange = inverseRange; + myDryRun = dryRun; + myProject = vcs.getProject(); + mySvnConfig = SvnConfiguration17.getInstance(vcs.getProject()); + myCurrentBranchUrl = currentBranchUrl; + myDiffClient = vcs.createDiffClient(); + myChangeLists = changeLists; + + Collections.sort(myChangeLists, ByNumberChangeListComparator.getInstance()); + mySplitter = stepByStep ? new StepByStepSplitter(myChangeLists) : new GroupSplitter(myChangeLists); + + myTarget = target; + myProgressIndicator = ProgressManager.getInstance().getProgressIndicator(); + myDiffClient.setEventHandler(handler); + myDiffClient.setMergeOptions(new SVNDiffOptions(mySvnConfig.IGNORE_SPACES_IN_MERGE, mySvnConfig.IGNORE_SPACES_IN_MERGE, + mySvnConfig.IGNORE_SPACES_IN_MERGE)); + myCommitMessage = new StringBuilder(); + myPackStart = myPackEnd = -1; + } + + public boolean hasNext() { + return mySplitter.hasNext(); + } + + public void mergeNext() throws SVNException { + final int step = mySplitter.step(); + myPackStart = myPackEnd + 1; + myPackEnd = myPackStart + step - 1; + + if (myProgressIndicator != null) { + final String numbersBeingMerged; + if ((myPackStart + 1) == myPackEnd) { + numbersBeingMerged = "" + myChangeLists.get(myPackStart); + } else { + numbersBeingMerged = "" + myChangeLists.get(myPackStart) + "-" + myChangeLists.get(myPackEnd); + } + myProgressIndicator.setText2("Merging changelist(s)" + numbersBeingMerged); + } + doMerge(); + + appendComment(); + } + + private void appendComment() { + if (myCommitMessage.length() == 0) { + myCommitMessage.append("Merged from ").append(myBranchName); + } + for (int i = myPackStart; i <= myPackEnd; i++) { + final CommittedChangeList list = myChangeLists.get(i); + final String nextComment = list.getComment().trim(); + myCommitMessage.append('\n'); + if (nextComment.length() > 0) { + myCommitMessage.append(nextComment); + } + myCommitMessage.append(" [from revision ").append(list.getNumber()).append("]"); + } + } + + protected SVNRevisionRange createRange() { + final SVNRevision start = SVNRevision.create(myChangeLists.get(myPackStart).getNumber() - 1); + final SVNRevision end = SVNRevision.create(myChangeLists.get(myPackEnd).getNumber()); + return myInverseRange ? new SVNRevisionRange(end, start) : new SVNRevisionRange(start, end); + } + + protected void doMerge() throws SVNException { + myDiffClient.doMerge(myCurrentBranchUrl, SVNRevision.UNDEFINED, Collections.singletonList(createRange()), + myTarget, SVNDepth.INFINITY, true, true, mySvnConfig.MERGE_DRY_RUN, myDryRun); + } + + @NonNls + private List getTail() { + return (myPackStart < myChangeLists.size()) ? + new ArrayList(myChangeLists.subList(myPackStart, myChangeLists.size())) : + Collections.emptyList(); + } + + public void getInfo(final Consumer holder, final boolean getLatest) { + if (getLatest && (myPackStart != -1)) { + final StringBuilder sb = new StringBuilder("Changelist(s) :"); + for (int i = myPackStart; i <= myPackEnd; i++) { + final CommittedChangeList list = myChangeLists.get(i); + final String nextComment = list.getComment().trim().replace('\n', '|'); + sb.append("\n" + list.getNumber()).append(" (").append(nextComment).append(")"); + } + sb.append(" merging faced problems"); + holder.consume(sb.toString()); + } + + getSkipped(holder); + } + + public void getSkipped(final Consumer holder) { + final List tail = getTail(); + if (! tail.isEmpty()) { + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < tail.size(); i++) { + CommittedChangeList list = tail.get(i); + if (i != 0) { + sb.append(','); + } + sb.append(list.getNumber()).append(" (").append(list.getComment().replace('\n', '|')).append(')'); + } + + holder.consume(SvnBundle.message("action.Subversion.integrate.changes.warning.skipped.lists.text", sb.toString())); + } + } + + public String getComment() { + return myCommitMessage.toString(); + } + + @Nullable + public File getMergeInfoHolder() { + return myTarget; + } + + public void afterProcessing() { + // bounds approximately ok + myProject.getMessageBus().syncPublisher(COMMITTED_CHANGES_MERGED_STATE).event(new ArrayList( + myChangeLists.subList(0, myPackEnd > 0 ? myPackEnd : myChangeLists.size()))); + } + + public static final Topic COMMITTED_CHANGES_MERGED_STATE = + new Topic("COMMITTED_CHANGES_MERGED_STATE", CommittedChangesMergedStateChanged.class); + + public interface CommittedChangesMergedStateChanged { + void event(final List list); + } + + private static class StepByStepSplitter extends Splitter { + private final int myTotal; + private int myCnt; + + private StepByStepSplitter(final List lists) { + super(lists); + myTotal = lists.size(); + myCnt = 0; + } + + @Override + public int step() { + ++ myCnt; + return 1; + } + + @Override + public boolean hasNext() { + return myCnt < myTotal; + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/GroupSplitter.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/GroupSplitter.java new file mode 100644 index 000000000000..813f373a04ab --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/GroupSplitter.java @@ -0,0 +1,57 @@ +/* + * 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.integrate; + +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; + +import java.util.LinkedList; +import java.util.List; + +public class GroupSplitter extends Splitter { + private final List myList; + private int myCnt; + + public GroupSplitter(final List lists) { + super(lists); + + myCnt = 0; + myList = new LinkedList(); + if (lists.isEmpty()) return; + + int currentPackSize = 1; + for (int i = 1; i < lists.size(); i++) { + final CommittedChangeList list = lists.get(i); + if (list.getNumber() != (lists.get(i - 1).getNumber() + 1)) { + myList.add(currentPackSize); + currentPackSize = 1; + } else { + ++ currentPackSize; + } + } + myList.add(currentPackSize); + } + + @Override + public boolean hasNext() { + return myCnt < myList.size(); + } + + @Override + public int step() { + ++ myCnt; + return myList.get(myCnt - 1); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/IMerger.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/IMerger.java new file mode 100644 index 000000000000..15a9bbea2265 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/IMerger.java @@ -0,0 +1,33 @@ +/* + * 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.integrate; + +import com.intellij.util.Consumer; +import org.jetbrains.annotations.Nullable; +import org.tmatesoft.svn.core.SVNException; + +import java.io.File; + +public interface IMerger { + boolean hasNext(); + void mergeNext() throws SVNException; + void getInfo(Consumer holder, boolean getLatest); + void getSkipped(final Consumer holder); + String getComment(); + @Nullable + File getMergeInfoHolder(); + void afterProcessing(); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/IntegrateEventHandler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/IntegrateEventHandler.java new file mode 100644 index 000000000000..9a74353db4eb --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/IntegrateEventHandler.java @@ -0,0 +1,44 @@ +/* + * 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.integrate; + +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.vcs.update.FileGroup; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.update.UpdateEventHandler; +import org.tmatesoft.svn.core.wc.SVNEvent; +import org.tmatesoft.svn.core.wc.SVNEventAction; +import org.tmatesoft.svn.core.wc.SVNStatusType; + +public class IntegrateEventHandler extends UpdateEventHandler { + public IntegrateEventHandler(final SvnVcs17 vcs, final ProgressIndicator progressIndicator) { + super(vcs, progressIndicator, null); + } + + protected boolean handleInDescendants(final SVNEvent event) { + if ((event.getAction() == SVNEventAction.UPDATE_UPDATE) && (event.getContentsStatus() == SVNStatusType.UNCHANGED) && + (event.getPropertiesStatus() == SVNStatusType.UNKNOWN)) { + myText2 = SvnBundle.message("progres.text2.updated", event.getFile().getName()); + return true; + } else if (event.getAction() == SVNEventAction.DELETE) { + addFileToGroup(FileGroup.REMOVED_FROM_REPOSITORY_ID, event); + myText2 = SvnBundle.message("progress.text2.deleted", event.getFile().getName()); + return true; + } + return false; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/IntegratedSelectedOptionsDialog.form b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/IntegratedSelectedOptionsDialog.form new file mode 100644 index 000000000000..2943f0b862a8 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/IntegratedSelectedOptionsDialog.form @@ -0,0 +1,115 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/IntegratedSelectedOptionsDialog.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/IntegratedSelectedOptionsDialog.java new file mode 100644 index 000000000000..7bf770fb8ed8 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/IntegratedSelectedOptionsDialog.java @@ -0,0 +1,303 @@ +/* + * 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.integrate; + +import com.intellij.lifecycle.PeriodicalTasksCloser; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.fileChooser.FileChooser; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.FileIndexFacade; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.PlatformIcons; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.*; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.SVNInfo; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import java.io.File; +import java.util.*; + +public class IntegratedSelectedOptionsDialog extends DialogWrapper { + private JPanel contentPane; + private JCheckBox myDryRunCheckbox; + private JList myWorkingCopiesList; + private JComponent myToolbar; + private JLabel mySourceInfoLabel; + private JLabel myTargetInfoLabel; + private JLabel myWcListTitleLabel; + private JCheckBox myIgnoreWhitespacesCheckBox; + + private final Project myProject; + private final String mySelectedBranchUrl; + private final SvnVcs17 myVcs; + private final String mySelectedRepositoryUUID; + + private DefaultActionGroup myGroup; + + private boolean myMustSelectBeforeOk; + + public IntegratedSelectedOptionsDialog(final Project project, final SVNURL currentBranch, final String selectedBranchUrl) { + super(project, true); + myMustSelectBeforeOk = true; + myProject = project; + mySelectedBranchUrl = selectedBranchUrl; + myVcs = SvnVcs17.getInstance(myProject); + + mySelectedRepositoryUUID = SvnUtil.getRepositoryUUID(myVcs, currentBranch); + + setTitle(SvnBundle.message("action.Subversion.integrate.changes.dialog.title")); + init(); + + myWorkingCopiesList.setModel(new DefaultListModel()); + myWorkingCopiesList.addListSelectionListener(new ListSelectionListener() { + public void valueChanged(final ListSelectionEvent e) { + setOKActionEnabled((! myMustSelectBeforeOk) || (myWorkingCopiesList.getSelectedIndex() != -1)); + } + }); + setOKActionEnabled((! myMustSelectBeforeOk) || (myWorkingCopiesList.getSelectedIndex() != -1)); + + final List workingCopyInfoList = new ArrayList(); + final Set workingCopies = SvnBranchMapperManager17.getInstance().get(mySelectedBranchUrl); + if (workingCopies != null) { + for (String workingCopy : workingCopies) { + workingCopyInfoList.add(new WorkingCopyInfo(workingCopy, underProject(new File(workingCopy)))); + } + } + Collections.sort(workingCopyInfoList, WorkingCopyInfoComparator.getInstance()); + + for (WorkingCopyInfo info : workingCopyInfoList) { + ((DefaultListModel)myWorkingCopiesList.getModel()).addElement(info); + } + if (!workingCopyInfoList.isEmpty()) { + myWorkingCopiesList.setSelectedIndex(0); + } + + SvnConfiguration17 svnConfig = SvnConfiguration17.getInstance(myVcs.getProject()); + myDryRunCheckbox.setSelected(svnConfig.MERGE_DRY_RUN); + myIgnoreWhitespacesCheckBox.setSelected(svnConfig.IGNORE_SPACES_IN_MERGE); + + mySourceInfoLabel.setText(SvnBundle.message("action.Subversion.integrate.changes.branch.info.source.label.text", currentBranch)); + myTargetInfoLabel.setText(SvnBundle.message("action.Subversion.integrate.changes.branch.info.target.label.text", selectedBranchUrl)); + + final String addText = SvnBundle.message("action.Subversion.integrate.changes.dialog.add.wc.text"); + final AnAction addAction = new AnAction(addText, addText, PlatformIcons.ADD_ICON) { + { + registerCustomShortcutSet(CommonShortcuts.INSERT, myWorkingCopiesList); + } + + public void actionPerformed(final AnActionEvent e) { + final VirtualFile vFile = FileChooser.chooseFile(myProject, FileChooserDescriptorFactory.createSingleFolderDescriptor()); + if (vFile != null) { + final File file = new File(vFile.getPath()); + if (hasDuplicate(file)) { + return; // silently do not add duplicate + } + + final String repositoryUUID = SvnUtil.getRepositoryUUID(myVcs, file); + + // local not consistent copy can not prevent us from integration: only remote local copy is really involved + if ((mySelectedRepositoryUUID != null) && (! mySelectedRepositoryUUID.equals(repositoryUUID))) { + if (OK_EXIT_CODE == Messages.showOkCancelDialog((repositoryUUID == null) ? SvnBundle.message("action.Subversion.integrate.changes.message.not.under.control.text") + : SvnBundle.message("action.Subversion.integrate.changes.message.another.wc.text"), + getTitle(), UIUtil.getWarningIcon())) { + onOkToAdd(file); + } + } + else { + onOkToAdd(file); + } + } + } + }; + + myGroup.add(addAction); + + final String removeText = SvnBundle.message("action.Subversion.integrate.changes.dialog.remove.wc.text"); + myGroup.add(new AnAction(removeText, removeText, PlatformIcons.DELETE_ICON) { + { + registerCustomShortcutSet(CommonShortcuts.DELETE, myWorkingCopiesList); + } + + public void update(final AnActionEvent e) { + final Presentation presentation = e.getPresentation(); + final int idx = (myWorkingCopiesList == null) ? -1 : myWorkingCopiesList.getSelectedIndex(); + presentation.setEnabled(idx != -1); + } + + public void actionPerformed(final AnActionEvent e) { + final int idx = myWorkingCopiesList.getSelectedIndex(); + if (idx != -1) { + final DefaultListModel model = (DefaultListModel)myWorkingCopiesList.getModel(); + final WorkingCopyInfo info = (WorkingCopyInfo)model.get(idx); + model.removeElementAt(idx); + SvnBranchMapperManager17.getInstance().remove(mySelectedBranchUrl, new File(info.getLocalPath())); + } + } + }); + } + + public void setSelectedWcPath(final String path) { + final ListModel model = myWorkingCopiesList.getModel(); + final int size = model.getSize(); + for (int i = 0; i < size; i++) { + final WorkingCopyInfo info = (WorkingCopyInfo) model.getElementAt(i); + if (info.getLocalPath().equals(path)) { + myWorkingCopiesList.setSelectedValue(info, true); + return; + } + } + } + + public void selectWcopyRootOnly() { + myMustSelectBeforeOk = false; + setTitle(SvnBundle.message("dialog.Subversion.select.working.copy.title")); + myIgnoreWhitespacesCheckBox.setVisible(false); + myDryRunCheckbox.setVisible(false); + myWcListTitleLabel.setText(SvnBundle.message("dialog.Subversion.select.working.copy.wcopy.list.title")); + } + + private void createUIComponents() { + myGroup = new DefaultActionGroup(); + final ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, myGroup, false); + myToolbar = actionToolbar.getComponent(); + } + + private boolean hasDuplicate(final File file) { + final DefaultListModel model = (DefaultListModel)myWorkingCopiesList.getModel(); + final String path = file.getAbsolutePath(); + + for (int i = 0; i < model.getSize(); i++) { + final WorkingCopyInfo info = (WorkingCopyInfo)model.getElementAt(i); + if (path.equals(info.getLocalPath())) { + return true; + } + } + return false; + } + + private void onOkToAdd(final File file) { + final WorkingCopyInfo info = new WorkingCopyInfo(file.getAbsolutePath(), underProject(file)); + final DefaultListModel model = (DefaultListModel) myWorkingCopiesList.getModel(); + model.addElement(info); + myWorkingCopiesList.setSelectedValue(info, true); + SvnBranchMapperManager17.getInstance().put(mySelectedBranchUrl, file.getAbsolutePath()); + } + + private boolean underProject(final File file) { + final VirtualFile vf = SvnUtil.getVirtualFile(file.getAbsolutePath()); + return (vf == null) || PeriodicalTasksCloser.getInstance().safeGetService(myProject, FileIndexFacade.class).isInContent(vf); + } + + public WorkingCopyInfo getSelectedWc() { + return (WorkingCopyInfo)myWorkingCopiesList.getSelectedValue(); + } + + public void saveOptions() { + SvnConfiguration17 svnConfig = SvnConfiguration17.getInstance(myVcs.getProject()); + svnConfig.MERGE_DRY_RUN = myDryRunCheckbox.isSelected(); + svnConfig.IGNORE_SPACES_IN_MERGE = myIgnoreWhitespacesCheckBox.isSelected(); + } + + protected JComponent createCenterPanel() { + return contentPane; + } + + public static class WorkingCopyInfoComparator implements Comparator { + private static final WorkingCopyInfoComparator instance = new WorkingCopyInfoComparator(); + + public static WorkingCopyInfoComparator getInstance() { + return instance; + } + + private WorkingCopyInfoComparator() { + } + + public int compare(final WorkingCopyInfo o1, final WorkingCopyInfo o2) { + return o1.getLocalPath().compareTo(o2.getLocalPath()); + } + } + + @Nullable + private static SVNURL realTargetUrl(final SvnVcs17 vcs, final WorkingCopyInfo info, final String targetBranchUrl) { + final SVNWCClient client = vcs.createWCClient(); + try { + final SVNInfo svnInfo = client.doInfo(new File(info.getLocalPath()), SVNRevision.UNDEFINED); + final SVNURL svnurl = svnInfo.getURL(); + + if ((svnurl != null) && (svnurl.toString().startsWith(targetBranchUrl))) { + return svnurl; + } + } + catch (SVNException e) { + // tracked by return value + } + return null; + } + + @Nullable + public static Pair selectWorkingCopy(final Project project, final SVNURL currentBranch, final String targetBranch, + final boolean showIntegrationParameters, + final String selectedLocalBranchPath, final String dialogTitle) { + final IntegratedSelectedOptionsDialog dialog = new IntegratedSelectedOptionsDialog(project, currentBranch, targetBranch); + if (! showIntegrationParameters) { + dialog.selectWcopyRootOnly(); + } + if (selectedLocalBranchPath != null) { + dialog.setSelectedWcPath(selectedLocalBranchPath); + } + if (dialogTitle != null) { + dialog.setTitle(dialogTitle); + } + dialog.show(); + + if (dialog.isOK()) { + ApplicationManager.getApplication().saveAll(); + dialog.saveOptions(); + + final WorkingCopyInfo info = dialog.getSelectedWc(); + if (info != null) { + final File file = new File(info.getLocalPath()); + if ((! file.exists()) || (! file.isDirectory())) { + Messages.showErrorDialog(SvnBundle.message("action.Subversion.integrate.changes.error.target.not.dir.text"), + SvnBundle.message("action.Subversion.integrate.changes.messages.title")); + return null; + } + + final SVNURL targetUrl = realTargetUrl(SvnVcs17.getInstance(project), info, targetBranch); + + if (targetUrl == null) { + Messages.showErrorDialog(SvnBundle.message("action.Subversion.integrate.changes.error.not.versioned.text"), + SvnBundle.message("action.Subversion.integrate.changes.messages.title")); + return null; + } + return new Pair(info, targetUrl); + } + } + return null; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/Merger.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/Merger.java new file mode 100644 index 000000000000..f1b67ae31616 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/Merger.java @@ -0,0 +1,184 @@ +/* + * 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.integrate; + +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.util.Consumer; +import com.intellij.util.messages.Topic; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnConfiguration17; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.update.UpdateEventHandler; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.SVNDiffClient; +import org.tmatesoft.svn.core.wc.SVNDiffOptions; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNRevisionRange; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class Merger implements IMerger { + protected final List myChangeLists; + protected final File myTarget; + protected final SVNDiffClient myDiffClient; + protected int myCount; + private final ProgressIndicator myProgressIndicator; + protected CommittedChangeList myLatestProcessed; + protected final SVNURL myCurrentBranchUrl; + private final StringBuilder myCommitMessage; + protected final SvnConfiguration17 mySvnConfig; + private final Project myProject; + private final String myBranchName; + + public Merger(final SvnVcs17 vcs, + final List changeLists, + final File target, + final UpdateEventHandler handler, + final SVNURL currentBranchUrl, + String branchName) { + myBranchName = branchName; + myProject = vcs.getProject(); + mySvnConfig = SvnConfiguration17.getInstance(vcs.getProject()); + myCurrentBranchUrl = currentBranchUrl; + myDiffClient = vcs.createDiffClient(); + myChangeLists = changeLists; + + Collections.sort(myChangeLists, ByNumberChangeListComparator.getInstance()); + + myTarget = target; + myCount = 0; + myProgressIndicator = ProgressManager.getInstance().getProgressIndicator(); + myDiffClient.setEventHandler(handler); + myDiffClient.setMergeOptions(new SVNDiffOptions(mySvnConfig.IGNORE_SPACES_IN_MERGE, mySvnConfig.IGNORE_SPACES_IN_MERGE, + mySvnConfig.IGNORE_SPACES_IN_MERGE)); + myCommitMessage = new StringBuilder(); + } + + private static class ByNumberChangeListComparator implements Comparator{ + private final static ByNumberChangeListComparator ourInstance = new ByNumberChangeListComparator(); + + public static ByNumberChangeListComparator getInstance() { + return ourInstance; + } + + public int compare(final CommittedChangeList o1, final CommittedChangeList o2) { + return (int) (o1.getNumber() - o2.getNumber()); + } + } + + public boolean hasNext() { + return myCount < myChangeLists.size(); + } + + public void mergeNext() throws SVNException { + myLatestProcessed = myChangeLists.get(myCount); + ++ myCount; + + if (myProgressIndicator != null) { + myProgressIndicator.setText2(SvnBundle.message("action.Subversion.integrate.changes.progress.integrating.details.text", + myLatestProcessed.getNumber())); + } + doMerge(); + + appendComment(); + } + + private void appendComment() { + if (myCommitMessage.length() == 0) { + myCommitMessage.append("Merged from ").append(myBranchName); + } + final String nextComment = myLatestProcessed.getComment(); + if (nextComment.trim().length() > 0) { + myCommitMessage.append('\n').append(nextComment).append(" [from revision ").append(myLatestProcessed.getNumber()).append("]"); + } + } + + protected SVNRevisionRange createRange() { + return new SVNRevisionRange(SVNRevision.create(myLatestProcessed.getNumber() - 1), SVNRevision.create(myLatestProcessed.getNumber())); + } + + protected boolean isRecordOnly() { + return false; + } + + protected void doMerge() throws SVNException { + myDiffClient.doMerge(myCurrentBranchUrl, SVNRevision.UNDEFINED, Collections.singletonList(createRange()), + myTarget, SVNDepth.INFINITY, true, true, mySvnConfig.MERGE_DRY_RUN, isRecordOnly()); + } + + @NonNls + private List getTail() { + return (myCount < myChangeLists.size()) ? + new ArrayList(myChangeLists.subList(myCount, myChangeLists.size())) : + Collections.emptyList(); + } + + public void getInfo(final Consumer holder, final boolean getLatest) { + if (getLatest && (myLatestProcessed != null)) { + holder.consume(SvnBundle.message("action.Subversion.integrate.changes.warning.failed.list.text", myLatestProcessed.getNumber(), + myLatestProcessed.getComment().replace('\n', '|'))); + } + + getSkipped(holder); + } + + public void getSkipped(final Consumer holder) { + final List tail = getTail(); + if (! tail.isEmpty()) { + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < tail.size(); i++) { + CommittedChangeList list = tail.get(i); + if (i != 0) { + sb.append(','); + } + sb.append(list.getNumber()).append(" (").append(list.getComment().replace('\n', '|')).append(')'); + } + + holder.consume(SvnBundle.message("action.Subversion.integrate.changes.warning.skipped.lists.text", sb.toString())); + } + } + + public String getComment() { + return myCommitMessage.toString(); + } + + @Nullable + public File getMergeInfoHolder() { + return myTarget; + } + + public void afterProcessing() { + myProject.getMessageBus().syncPublisher(COMMITTED_CHANGES_MERGED_STATE).event(new ArrayList(myChangeLists.subList(0, myCount))); + } + + public static final Topic COMMITTED_CHANGES_MERGED_STATE = + new Topic("COMMITTED_CHANGES_MERGED_STATE", CommittedChangesMergedStateChanged.class); + + public interface CommittedChangesMergedStateChanged { + void event(final List list); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/MergerFactory.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/MergerFactory.java new file mode 100644 index 000000000000..4324295d9ebf --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/MergerFactory.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.integrate; + +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.update.UpdateEventHandler; +import org.tmatesoft.svn.core.SVNURL; + +import java.io.File; +import java.util.List; + +public interface MergerFactory { + IMerger createMerger(final SvnVcs17 vcs, + final File target, + final UpdateEventHandler handler, + final SVNURL currentBranchUrl, + String branchName); + boolean isMergeAll(); + @Nullable + List getListsToMerge(); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/PointMerger.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/PointMerger.java new file mode 100644 index 000000000000..911724a9f532 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/PointMerger.java @@ -0,0 +1,157 @@ +/* + * 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.integrate; + +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnRevisionNumber; +import org.jetbrains.idea.svn17.SvnUtil; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.history.SvnRepositoryContentRevision; +import org.jetbrains.idea.svn17.update.UpdateEventHandler; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; +import org.tmatesoft.svn.core.wc.SVNCopyClient; +import org.tmatesoft.svn.core.wc.SVNCopySource; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.io.File; +import java.util.*; + +public class PointMerger extends Merger { + private final List mySelectedChanges; + private SVNWCClient myWcClient; + private SVNCopyClient myCopyClient; + private final SvnVcs17 myVcs; + private final UpdateEventHandler myHandler; + + public PointMerger(final SvnVcs17 vcs, + CommittedChangeList selectedChangeList, + final File target, + final UpdateEventHandler handler, + final SVNURL currentBranchUrl, + final List selectedChanges, + String branchName) { + super(vcs, new ArrayList(Arrays.asList(selectedChangeList)), + target, handler, currentBranchUrl, branchName); + myHandler = handler; + myVcs = vcs; + mySelectedChanges = selectedChanges; + Collections.sort(mySelectedChanges, ChangesComparator.getInstance()); + myLatestProcessed = selectedChangeList; + } + + public boolean hasNext() { + return myCount == 0; + } + + protected void doMerge() throws SVNException { + for (Change change : mySelectedChanges) { + if (change.getBeforeRevision() == null) { + add(change); + } else if (change.getAfterRevision() == null) { + delete(change); + } else { + merge(change); + } + } + } + + private void merge(final Change change) throws SVNException { + final SvnRepositoryContentRevision before = (SvnRepositoryContentRevision) change.getBeforeRevision(); + final SvnRepositoryContentRevision after = (SvnRepositoryContentRevision) change.getAfterRevision(); + + final String path = myCurrentBranchUrl.toString(); + final String beforeUrl = before.getFullPath(); + final String afterUrl = after.getFullPath(); + + final File afterPath = SvnUtil.fileFromUrl(myTarget, path, afterUrl); + + myDiffClient.doMerge(SVNURL.parseURIEncoded(beforeUrl), ((SvnRevisionNumber) before.getRevisionNumber()).getRevision(), + SVNURL.parseURIEncoded(afterUrl), ((SvnRevisionNumber) after.getRevisionNumber()).getRevision(), + afterPath, false, true, false, mySvnConfig.MERGE_DRY_RUN); + } + + private void delete(final Change change) throws SVNException { + if (myWcClient == null) { + myWcClient = myVcs.createWCClient(); + myWcClient.setEventHandler(myHandler); + } + final SvnRepositoryContentRevision before = (SvnRepositoryContentRevision) change.getBeforeRevision(); + final String path = myCurrentBranchUrl.toString(); + final String beforeUrl = before.getFullPath(); + final File beforePath = SvnUtil.fileFromUrl(myTarget, path, beforeUrl); + + myWcClient.doDelete(beforePath, false, mySvnConfig.MERGE_DRY_RUN); + } + + private void add(final Change change) throws SVNException { + if (myCopyClient == null) { + myCopyClient = myVcs.createCopyClient(); + myCopyClient.setEventHandler(myHandler); + } + final SvnRepositoryContentRevision after = (SvnRepositoryContentRevision) change.getAfterRevision(); + final String path = myCurrentBranchUrl.toString(); + final String afterUrl = after.getFullPath(); + final File afterPath = SvnUtil.fileFromUrl(myTarget, path, afterUrl); + + final SVNRevision revision = ((SvnRevisionNumber)after.getRevisionNumber()).getRevision(); + final SVNCopySource[] copySource = new SVNCopySource[]{new SVNCopySource(revision, revision, SVNURL.parseURIEncoded(afterUrl))}; + // todo dry run + myCopyClient.doCopy(copySource, afterPath, false, true, true); + } + + private static class ChangesComparator implements Comparator { + private final static ChangesComparator ourInstance = new ChangesComparator(); + + public static ChangesComparator getInstance() { + return ourInstance; + } + + public int compare(final Change o1, final Change o2) { + final SvnRepositoryContentRevision after1 = (SvnRepositoryContentRevision) o1.getAfterRevision(); + final SvnRepositoryContentRevision after2 = (SvnRepositoryContentRevision) o2.getAfterRevision(); + + if (after1 == null) { + return 1; + } + if (after2 == null) { + return -1; + } + + final String path1 = after1.getFullPath(); + final String path2 = after2.getFullPath(); + if (path1 == null) { + return 1; + } + if (path2 == null) { + return -1; + } + + final String ancestor = SVNPathUtil.getCommonPathAncestor(path1, path2); + return (path1.equals(ancestor)) ? -1 : 1; + } + } + + @Nullable + @Override + public File getMergeInfoHolder() { + return null; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/ResolveWorker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/ResolveWorker.java new file mode 100644 index 000000000000..d9d302b9c489 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/ResolveWorker.java @@ -0,0 +1,118 @@ +/* + * 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.integrate; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.AbstractVcsHelper; +import com.intellij.openapi.vcs.VcsKey; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vcs.update.FileGroup; +import com.intellij.openapi.vcs.update.UpdateFilesHelper; +import com.intellij.openapi.vcs.update.UpdatedFiles; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn17.SvnConfiguration17; +import org.jetbrains.idea.svn17.SvnUtil; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.actions.SvnMergeProvider; + +import java.util.ArrayList; +import java.util.List; + +public class ResolveWorker { + private final boolean myChangesUnderProjectRoot; + private final Project myProject; + private final VcsDirtyScopeManager myDirtyScopeManager; + private List myConflictedVirtualFiles; + + public ResolveWorker(final boolean changesUnderProjectRoot, final Project project) { + myChangesUnderProjectRoot = changesUnderProjectRoot; + myProject = project; + myDirtyScopeManager = VcsDirtyScopeManager.getInstance(project); + myConflictedVirtualFiles = new ArrayList(); + } + + private void refreshChangeListsFindConflicts(final UpdatedFiles updatedFiles) { + UpdateFilesHelper.iterateFileGroupFiles(updatedFiles, + new UpdateFilesHelper.Callback() { + public void onFile(final String filePath, final String groupId) { + final VirtualFile vf = SvnUtil.getVirtualFile(filePath); + if (vf != null) { + // refresh base directory so that conflict files should be detected + // file itself is already refreshed + vf.getParent().refresh(false, false); + myDirtyScopeManager.fileDirty(vf); + } + if (FileGroup.MERGED_WITH_CONFLICT_ID.equals(groupId)) { + myConflictedVirtualFiles.add(vf); + } + } + }); + } + + public boolean needsInteraction(final UpdatedFiles updatedFiles) { + if (myChangesUnderProjectRoot) { + refreshChangeListsFindConflicts(updatedFiles); + } else { + final FileGroup conflictedGroup = updatedFiles.getGroupById(FileGroup.MERGED_WITH_CONFLICT_ID); + for (String filename : conflictedGroup.getFiles()) { + final VirtualFile vf = SvnUtil.getVirtualFile(filename); + myConflictedVirtualFiles.add(vf); + } + } + + return ((! myConflictedVirtualFiles.isEmpty()) || (! haveUnresolvedConflicts(updatedFiles))) && + (! SvnConfiguration17.getInstance(myProject).MERGE_DRY_RUN); + } + + public static boolean haveUnresolvedConflicts(final UpdatedFiles updatedFiles) { + final String[] ids = new String[] {FileGroup.MERGED_WITH_CONFLICT_ID, FileGroup.MERGED_WITH_PROPERTY_CONFLICT_ID, + FileGroup.MERGED_WITH_TREE_CONFLICT, FileGroup.SKIPPED_ID}; + for (String id : ids) { + final FileGroup group = updatedFiles.getGroupById(id); + if ((group != null) && (! group.isEmpty())) return true; + } + return false; + } + + // on EDT, dispose checked + public void execute(final UpdatedFiles updatedFiles) { + if (myConflictedVirtualFiles.isEmpty()) { + return; + } + final AbstractVcsHelper vcsHelper = AbstractVcsHelper.getInstance(myProject); + + List mergedFiles = vcsHelper.showMergeDialog(myConflictedVirtualFiles, new SvnMergeProvider(myProject)); + + final FileGroup mergedGroup = updatedFiles.getGroupById(FileGroup.MERGED_ID); + final FileGroup conflictedGroup = updatedFiles.getGroupById(FileGroup.MERGED_WITH_CONFLICT_ID); + final VcsKey vcsKey = SvnVcs17.getKey(); + + for (final VirtualFile mergedFile : mergedFiles) { + String path = FileUtil.toSystemDependentName(mergedFile.getPresentableUrl()); + conflictedGroup.remove(path); + mergedGroup.add(path, vcsKey, null); + + mergedFile.refresh(false, false); + // for additionally created files removal to be detected + mergedFile.getParent().refresh(false, false); + + if (myChangesUnderProjectRoot) { + myDirtyScopeManager.fileDirty(mergedFile); + } + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/SelectedChangeListsChecker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/SelectedChangeListsChecker.java new file mode 100644 index 000000000000..41bdd2c33667 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/SelectedChangeListsChecker.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.integrate; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.vcs.CheckSamePattern; +import com.intellij.openapi.vcs.VcsDataKeys; +import com.intellij.openapi.vcs.changes.ChangeList; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.history.SvnChangeList; +import org.tmatesoft.svn.core.SVNURL; + +import java.util.ArrayList; +import java.util.List; + +public class SelectedChangeListsChecker implements SelectedCommittedStuffChecker { + protected final List myChangeListsList; + private boolean isValid; + private SVNURL mySameBranch; + private VirtualFile myVcsRoot; + + public SelectedChangeListsChecker() { + myChangeListsList = new ArrayList(); + } + + public void execute(final AnActionEvent event) { + isValid = false; + myChangeListsList.clear(); + + getSelectedSvnCls(event); + if (! myChangeListsList.isEmpty()) { + checkSame(); + } + } + + public boolean isValid() { + return isValid; + } + + public List getSelectedLists() { + return myChangeListsList; + } + + private void checkSame() { + final CheckSamePattern sameBranch = new CheckSamePattern(); + final CheckSamePattern sameRoot = new CheckSamePattern(); + + for (ChangeList changeList : myChangeListsList) { + final SvnChangeList svnChangeList = (SvnChangeList) changeList; + sameBranch.iterate(svnChangeList.getBranchUrl()); + sameRoot.iterate(svnChangeList.getRoot()); + + if ((! sameBranch.isSame()) || (! sameRoot.isSame())) { + break; + } + } + isValid = sameBranch.isSame() && sameRoot.isSame(); + mySameBranch = sameBranch.getSameValue(); + myVcsRoot = sameRoot.getSameValue(); + } + + public SVNURL getSameBranch() { + return mySameBranch; + } + + public VirtualFile getRoot() { + return myVcsRoot; + } + + private void getSelectedSvnCls(final AnActionEvent event) { + final ChangeList[] cls = event.getData(VcsDataKeys.CHANGE_LISTS); + + if (cls != null) { + for (ChangeList cl : cls) { + final CommittedChangeList committed = ((CommittedChangeList) cl); + if ((committed != null) && (committed.getVcs() != null) && (SvnVcs17.VCS_NAME.equals(committed.getVcs().getName()))) { + myChangeListsList.add(committed); + } + } + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/SelectedChangeSetChecker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/SelectedChangeSetChecker.java new file mode 100644 index 000000000000..7bc5b07abb3b --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/SelectedChangeSetChecker.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.integrate; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.vcs.VcsDataKeys; +import com.intellij.openapi.vcs.changes.Change; +import org.jetbrains.idea.svn17.actions.ChangeSetMergerFactory; + +import java.util.ArrayList; +import java.util.List; + +public class SelectedChangeSetChecker extends SelectedChangeListsChecker { + private final List mySelectedChanges; + + public SelectedChangeSetChecker() { + super(); + mySelectedChanges = new ArrayList(); + } + + private void fillChanges(final AnActionEvent event) { + mySelectedChanges.clear(); + + final Change[] changes = event.getData(VcsDataKeys.CHANGES); + if (changes != null) { + // bugfix: event.getData(VcsDataKeys.CHANGES) return list with duplicates. + // so the check is added here + for (Change change : changes) { + if (! mySelectedChanges.contains(change)) { + mySelectedChanges.add(change); + } + } + } + } + + public void execute(final AnActionEvent event) { + super.execute(event); + fillChanges(event); + } + + public boolean isValid() { + return super.isValid() && (myChangeListsList.size() == 1) && (! mySelectedChanges.isEmpty()); + } + + public MergerFactory createFactory() { + return new ChangeSetMergerFactory(myChangeListsList.get(0), mySelectedChanges); + } + + public List getSelectedChanges() { + return mySelectedChanges; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/SelectedCommittedStuffChecker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/SelectedCommittedStuffChecker.java new file mode 100644 index 000000000000..9b478d56a486 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/SelectedCommittedStuffChecker.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.integrate; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.openapi.vfs.VirtualFile; +import org.tmatesoft.svn.core.SVNURL; + +import java.util.List; + +public interface SelectedCommittedStuffChecker { + void execute(final AnActionEvent event); + + boolean isValid(); + + SVNURL getSameBranch(); + + VirtualFile getRoot(); + + List getSelectedLists(); + +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/Splitter.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/Splitter.java new file mode 100644 index 000000000000..609d5d05ef9e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/Splitter.java @@ -0,0 +1,27 @@ +/* + * 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.integrate; + +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; + +import java.util.List; + +public abstract class Splitter { + // ordered + Splitter(final List lists) {} + public abstract boolean hasNext(); + public abstract int step(); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/SvnBranchItem.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/SvnBranchItem.java new file mode 100644 index 000000000000..cd23e24bf700 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/SvnBranchItem.java @@ -0,0 +1,67 @@ +/* + * 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.integrate; + +import java.util.Date; + +/** + * @author yole + */ +public class SvnBranchItem implements Comparable { + private String myUrl; + private long myCreationDateMillis; + private long myRevision; + + // to be serializable + public SvnBranchItem() { + } + + public SvnBranchItem(final String url, final Date creationDate, final long revision) { + myUrl = url; + // descendant can be passed (and is passed) (java.util.Date is not final) + myCreationDateMillis = creationDate.getTime(); + myRevision = revision; + } + + public void setUrl(final String url) { + myUrl = url; + } + + public void setCreationDateMillis(final long creationDate) { + myCreationDateMillis = creationDate; + } + + public void setRevision(final long revision) { + myRevision = revision; + } + + public String getUrl() { + return myUrl; + } + + public long getCreationDateMillis() { + return myCreationDateMillis; + } + + public long getRevision() { + return myRevision; + } + + public int compareTo(SvnBranchItem o) { + // === -compare() + return myCreationDateMillis < o.myCreationDateMillis ? 1 : ((myCreationDateMillis == o.myCreationDateMillis) ? 0 : -1); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/SvnIntegrateChangesActionPerformer.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/SvnIntegrateChangesActionPerformer.java new file mode 100644 index 000000000000..2a47fbb0d5b6 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/SvnIntegrateChangesActionPerformer.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.integrate; + +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Pair; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnConfiguration17; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.actions.SelectBranchPopup; +import org.jetbrains.idea.svn17.branchConfig.SvnBranchConfigurationNew; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; + +public class SvnIntegrateChangesActionPerformer implements SelectBranchPopup.BranchSelectedCallback { + private final SvnVcs17 myVcs; + private final MergerFactory myMergerFactory; + + private final SVNURL myCurrentBranch; + + public SvnIntegrateChangesActionPerformer(final Project project, final SVNURL currentBranchUrl, final MergerFactory mergerFactory) { + myVcs = SvnVcs17.getInstance(project); + myCurrentBranch = currentBranchUrl; + myMergerFactory = mergerFactory; + } + + public void branchSelected(final Project project, final SvnBranchConfigurationNew configuration, final String url, final long revision) { + onBranchSelected(url, null, null); + } + + public void onBranchSelected(final String url, final String selectedLocalBranchPath, final String dialogTitle) { + if (myCurrentBranch.toString().equals(url)) { + Messages.showErrorDialog(SvnBundle.message("action.Subversion.integrate.changes.error.source.and.target.same.text"), + SvnBundle.message("action.Subversion.integrate.changes.messages.title")); + return; + } + + // from name + final String name = SVNPathUtil.tail(myCurrentBranch.toString()); + final Pair pair = IntegratedSelectedOptionsDialog.selectWorkingCopy(myVcs.getProject(), myCurrentBranch, url, true, + selectedLocalBranchPath, dialogTitle); + if (pair == null) { + return; + } + + final WorkingCopyInfo info = pair.first; + final SVNURL realTargetUrl = pair.second; + + final SVNURL sourceUrl = correctSourceUrl(url, realTargetUrl.toString()); + if (sourceUrl == null) { + // should not occur + return; + } + final SvnIntegrateChangesTask task = new SvnIntegrateChangesTask(myVcs, info, myMergerFactory, sourceUrl, SvnBundle.message("action.Subversion.integrate.changes.messages.title"), + SvnConfiguration17.getInstance(myVcs.getProject()).MERGE_DRY_RUN, name); + ProgressManager.getInstance().run(task); + } + + @Nullable + private SVNURL correctSourceUrl(final String targetUrl, final String realTargetUrl) { + try { + if (realTargetUrl.length() > targetUrl.length()) { + if (realTargetUrl.startsWith(targetUrl)) { + return myCurrentBranch.appendPath(realTargetUrl.substring(targetUrl.length()), true); + } + } else if (realTargetUrl.equals(targetUrl)) { + return myCurrentBranch; + } + } + catch (SVNException e) { + // tracked by return value + } + return null; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/SvnIntegrateChangesTask.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/SvnIntegrateChangesTask.java new file mode 100644 index 000000000000..4a6462bd2cf0 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/SvnIntegrateChangesTask.java @@ -0,0 +1,344 @@ +/* + * 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.integrate; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.*; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.changes.InvokeAfterUpdateMode; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vcs.changes.ui.CommitChangeListDialog; +import com.intellij.openapi.vcs.ex.ProjectLevelVcsManagerEx; +import com.intellij.openapi.vcs.update.*; +import com.intellij.util.Consumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnChangeProvider; +import org.jetbrains.idea.svn17.SvnUtil; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.update.UpdateEventHandler; +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.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +public class SvnIntegrateChangesTask extends Task.Backgroundable { + private final ProjectLevelVcsManagerEx myProjectLevelVcsManager; + private final SvnVcs17 myVcs; + private final WorkingCopyInfo myInfo; + + private final UpdatedFilesReverseSide myAccomulatedFiles; + private UpdatedFiles myRecentlyUpdatedFiles; + + private final List myExceptions; + + private UpdateEventHandler myHandler; + private IMerger myMerger; + private ResolveWorker myResolveWorker; + private FilePathImpl myMergeTarget; + private final String myTitle; + private final String myBranchName; + private final MergerFactory myMergerFactory; + private final SVNURL myCurrentBranchUrl; + private boolean myDryRun; + + public SvnIntegrateChangesTask(final SvnVcs17 vcs, final WorkingCopyInfo info, final MergerFactory mergerFactory, + final SVNURL currentBranchUrl, final String title, final boolean dryRun, String branchName) { + super(vcs.getProject(), title, true, VcsConfiguration.getInstance(vcs.getProject()).getUpdateOption()); + myMergerFactory = mergerFactory; + myCurrentBranchUrl = currentBranchUrl; + myDryRun = dryRun; + myTitle = title; + myBranchName = branchName; + + myProjectLevelVcsManager = ProjectLevelVcsManagerEx.getInstanceEx(myProject); + myVcs = vcs; + + myInfo = info; + + myAccomulatedFiles = new UpdatedFilesReverseSide(UpdatedFiles.create()); + myExceptions = new ArrayList(); + myHandler = new IntegrateEventHandler(myVcs, ProgressManager.getInstance().getProgressIndicator()); + myMerger = myMergerFactory.createMerger(myVcs, new File(myInfo.getLocalPath()), myHandler, myCurrentBranchUrl, myBranchName); + } + + private void indicatorOnStart() { + final ProgressIndicator ind = ProgressManager.getInstance().getProgressIndicator(); + if (ind != null) { + ind.setIndeterminate(true); + } + if (ind != null) { + ind.setText(SvnBundle.message("action.Subversion.integrate.changes.progress.integrating.text")); + } + } + + public void run(@NotNull final ProgressIndicator indicator) { + myHandler.setProgressIndicator(ProgressManager.getInstance().getProgressIndicator()); + myResolveWorker = new ResolveWorker(myInfo.isUnderProjectRoot(), myProject); + + BlockReloadingUtil.block(); + myProjectLevelVcsManager.startBackgroundVcsOperation(); + + try { + myRecentlyUpdatedFiles = UpdatedFiles.create(); + myHandler.setUpdatedFiles(myRecentlyUpdatedFiles); + + indicatorOnStart(); + + // try to do multiple under single progress + while (true) { + doMerge(); + + RefreshVFsSynchronously.updateAllChanged(myRecentlyUpdatedFiles); + indicator.setText(VcsBundle.message("progress.text.updating.done")); + + if (myResolveWorker.needsInteraction(myRecentlyUpdatedFiles) || (! myMerger.hasNext()) || + (! myExceptions.isEmpty()) || UpdatedFilesReverseSide.containErrors(myRecentlyUpdatedFiles)) { + break; + } + accomulate(); + } + } finally { + myProjectLevelVcsManager.stopBackgroundVcsOperation(); + } + } + + private void createMessage(final boolean getLatest, final boolean warning, final String firstString) { + final List messages = new ArrayList(); + messages.add(firstString); + myMerger.getInfo(new Consumer() { + public void consume(final String s) { + messages.add(s); + } + }, getLatest); + final VcsException result = new VcsException(messages); + result.setIsWarning(warning); + myExceptions.add(result); + } + + private void doMerge() { + try { + myMerger.mergeNext(); + } catch (SVNException e) { + createMessage(true, false, e.getMessage()); + } + } + + public void onCancel() { + try { + if (myProject.isDisposed()) return; + afterExecution(true); + } finally { + BlockReloadingUtil.unblock(); + } + } + + public void onSuccess() { + try { + if (myProject.isDisposed()) return; + afterExecution(false); + } finally { + BlockReloadingUtil.unblock(); + } + } + + private void accomulate() { + myAccomulatedFiles.accomulateFiles(myRecentlyUpdatedFiles, UpdatedFilesReverseSide.DuplicateLevel.DUPLICATE_ERRORS); + } + + private void afterExecution(final boolean wasCanceled) { + if (! myRecentlyUpdatedFiles.isEmpty()) { + myResolveWorker.execute(myRecentlyUpdatedFiles); + } + final boolean haveConflicts = ResolveWorker.haveUnresolvedConflicts(myRecentlyUpdatedFiles); + + accomulate(); + + if ((! myMerger.hasNext()) || haveConflicts || (! myExceptions.isEmpty()) || myAccomulatedFiles.containErrors() || wasCanceled) { + initMergeTarget(); + if (myAccomulatedFiles.isEmpty() && myExceptions.isEmpty() && (myMergeTarget == null) && (! wasCanceled)) { + Messages.showMessageDialog(SvnBundle.message("action.Subversion.integrate.changes.message.files.up.to.date.text"), myTitle, + Messages.getInformationIcon()); + } else { + if (haveConflicts) { + final VcsException exception = new VcsException(SvnBundle.message("svn.integrate.changelist.warning.unresolved.conflicts.text")); + exception.setIsWarning(true); + myExceptions.add(exception); + } + if (wasCanceled) { + final List details = new LinkedList(); + details.add("Integration was canceled"); + myMerger.getSkipped(new Consumer() { + public void consume(String s) { + if (! StringUtil.isEmptyOrSpaces(s)) { + details.add(s); + } + } + }); + final VcsException exception = new VcsException(details); + exception.setIsWarning(true); + myExceptions.add(exception); + } + finishActions(wasCanceled); + } + myMerger.afterProcessing(); + } else { + stepToNextChangeList(); + } + } + + private void finishActions(final boolean wasCanceled) { + if (! wasCanceled) { + if ((! myDryRun) && (myExceptions.isEmpty()) && (! myAccomulatedFiles.containErrors()) && + ((! myAccomulatedFiles.isEmpty()) || (myMergeTarget != null))) { + if (myInfo.isUnderProjectRoot()) { + showLocalCommit(); + } else { + showAlienCommit(); + } + return; + } + } + + final Collection files = gatherChangedPaths(); + VcsDirtyScopeManager.getInstance(myProject).filePathsDirty(files, null); + prepareAndShowResults(); + } + + // no remote operations + private void prepareAndShowResults() { + // todo unite into one window?? + if (! myAccomulatedFiles.isEmpty()) { + showUpdateTree(); + } + if (! myExceptions.isEmpty()) { + AbstractVcsHelper.getInstance(myProject).showErrors(myExceptions, VcsBundle.message("message.title.vcs.update.errors", myExceptions.size())); + } + } + + private void showUpdateTree() { + RestoreUpdateTree restoreUpdateTree = RestoreUpdateTree.getInstance(myProject); + // action info is actually NOT used + restoreUpdateTree.registerUpdateInformation(myAccomulatedFiles.getUpdatedFiles(), ActionInfo.INTEGRATE); + myProjectLevelVcsManager.showUpdateProjectInfo(myAccomulatedFiles.getUpdatedFiles(), myTitle, ActionInfo.INTEGRATE, false); + } + + private void stepToNextChangeList() { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + ProgressManager.getInstance().run(SvnIntegrateChangesTask.this); + } + }); + } + + /** + * folder that is going to keep merge info record should also be changed + */ + @Nullable + private void initMergeTarget() { + final File mergeInfoHolder = myMerger.getMergeInfoHolder(); + if (mergeInfoHolder != null) { + final SVNStatus svnStatus = SvnUtil.getStatus(myVcs, mergeInfoHolder); + if ((svnStatus != null) && (SVNStatusType.STATUS_MODIFIED.equals(svnStatus.getPropertiesStatus()))) { + + myMergeTarget = FilePathImpl.create(mergeInfoHolder, mergeInfoHolder.isDirectory()); + } + } + } + + private void showLocalCommit() { + final Collection files = gatherChangedPaths(); + + // for changes to be detected, we need switch to background change list manager update thread and back to dispatch thread + // so callback is used; ok to be called after VCS update markup closed: no remote operations + final ChangeListManager clManager = ChangeListManager.getInstance(myProject); + clManager.invokeAfterUpdate(new Runnable() { + public void run() { + final Collection changes = new ArrayList(); + for (FilePath file : files) { + final Change change = clManager.getChange(file); + if (change != null) { + changes.add(change); + } + } + CommitChangeListDialog.commitChanges(myProject, changes, null, null, myMerger.getComment()); + prepareAndShowResults(); + } + }, InvokeAfterUpdateMode.SYNCHRONOUS_CANCELLABLE, myTitle, + new Consumer() { + public void consume(final VcsDirtyScopeManager vcsDirtyScopeManager) { + vcsDirtyScopeManager.filePathsDirty(files, null); + } + }, null); + } + + private Collection gatherChangedPaths() { + final Collection files = new ArrayList(); + + UpdateFilesHelper.iterateFileGroupFiles(myAccomulatedFiles.getUpdatedFiles(), new UpdateFilesHelper.Callback() { + public void onFile(final String filePath, final String groupId) { + final FilePath file = FilePathImpl.create(new File(filePath)); + files.add(file); + } + }); + if (myMergeTarget != null) { + files.add(myMergeTarget); + } + return files; + } + + private void showAlienCommit() { + final AlienDirtyScope dirtyScope = new AlienDirtyScope(); + + if (myMergeTarget != null) { + dirtyScope.addDir(myMergeTarget); + } else { + UpdateFilesHelper.iterateFileGroupFiles(myAccomulatedFiles.getUpdatedFiles(), new UpdateFilesHelper.Callback() { + public void onFile(final String filePath, final String groupId) { + final FilePath file = FilePathImpl.create(new File(filePath)); + dirtyScope.addFile(file); + } + }); + } + + final SvnChangeProvider provider = new SvnChangeProvider(myVcs); + final GatheringChangelistBuilder clb = new GatheringChangelistBuilder(myProject, myAccomulatedFiles, myMergeTarget == null ? null : myMergeTarget.getVirtualFile()); + try { + provider.getChanges(dirtyScope, clb, ProgressManager.getInstance().getProgressIndicator(), null); + } catch (VcsException e) { + Messages.showErrorDialog(SvnBundle.message("action.Subversion.integrate.changes.error.unable.to.collect.changes.text", + e.getMessage()), myTitle); + return; + } + + if (! clb.getChanges().isEmpty()) { + CommitChangeListDialog.commitAlienChanges(myProject, clb.getChanges(), myVcs, myMerger.getComment(), myMerger.getComment()); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/WorkingCopyInfo.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/WorkingCopyInfo.java new file mode 100644 index 000000000000..7b66c46c6c8a --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/integrate/WorkingCopyInfo.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.integrate; + +public class WorkingCopyInfo { + private final String myLocalPath; + private final boolean myUnderProjectRoot; + + public WorkingCopyInfo(final String localPath, final boolean underProjectRoot) { + myLocalPath = localPath; + myUnderProjectRoot = underProjectRoot; + } + + public String getLocalPath() { + return myLocalPath; + } + + public boolean isUnderProjectRoot() { + return myUnderProjectRoot; + } + + public String toString() { + return myLocalPath; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/BranchInfo.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/BranchInfo.java new file mode 100644 index 000000000000..b6c1a996499e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/BranchInfo.java @@ -0,0 +1,397 @@ +/* + * 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.mergeinfo; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Comparing; +import com.intellij.util.containers.MultiMap; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.history.SvnChangeList; +import org.tmatesoft.svn.core.*; +import org.tmatesoft.svn.core.internal.util.SVNMergeInfoUtil; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; +import org.tmatesoft.svn.core.wc.SVNInfo; +import org.tmatesoft.svn.core.wc.SVNPropertyData; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.io.File; +import java.util.*; + +public class BranchInfo { + private final static Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.mergeinfo.BranchInfo"); + // repo path in branch in format path@revision -> merged revisions + private final Map> myPathMergedMap; + private final Map> myNonInheritablePathMergedMap; + + private boolean myMixedRevisionsFound; + + // revision in trunk -> whether merged into branch + private final Map myAlreadyCalculatedMap; + private final Object myCalculatedLock = new Object(); + + private final String myRepositoryRoot; + private final String myBranchUrl; + private final String myTrunkUrl; + private final String myTrunkCorrected; + private final SVNWCClient myClient; + private final SvnVcs17 myVcs; + + private SvnMergeInfoCache17.CopyRevison myCopyRevison; + private final MultiMap myPartlyMerged; + + public BranchInfo(final SvnVcs17 vcs, final String repositoryRoot, final String branchUrl, final String trunkUrl, + final String trunkCorrected, final SVNWCClient client) { + myVcs = vcs; + myRepositoryRoot = repositoryRoot; + myBranchUrl = branchUrl; + myTrunkUrl = trunkUrl; + myTrunkCorrected = trunkCorrected; + myClient = client; + + myPathMergedMap = new HashMap>(); + myPartlyMerged = new MultiMap(); + myNonInheritablePathMergedMap = new HashMap>(); + + myAlreadyCalculatedMap = new HashMap(); + } + + private long calculateCopyRevision(final String branchPath) { + if (myCopyRevison != null && Comparing.equal(myCopyRevison.getPath(), branchPath)) { + return myCopyRevison.getRevision(); + } + myCopyRevison = new SvnMergeInfoCache17.CopyRevison(myVcs, branchPath, myRepositoryRoot, myBranchUrl, myTrunkUrl); + return -1; + } + + public void clear() { + myPathMergedMap.clear(); + synchronized (myCalculatedLock) { + myAlreadyCalculatedMap.clear(); + } + myMixedRevisionsFound = false; + } + + public void halfClear(final long listNumber) { + myPathMergedMap.clear(); + synchronized (myCalculatedLock) { + myAlreadyCalculatedMap.remove(listNumber); + } + myMixedRevisionsFound = false; + } + + public MergeinfoCached getCached() { + synchronized (myCalculatedLock) { + final long revision; + if (myCopyRevison != null && myCopyRevison.getRevision() != -1) { + revision = myCopyRevison.getRevision(); + } else { + revision = -1; + } + return new MergeinfoCached(Collections.unmodifiableMap(myAlreadyCalculatedMap), revision); + } + } + + public SvnMergeInfoCache17.MergeCheckResult checkList(final SvnChangeList list, final String branchPath) { + synchronized (myCalculatedLock) { + final long revision = calculateCopyRevision(branchPath); + if (revision != -1 && revision >= list.getNumber()) { + return SvnMergeInfoCache17.MergeCheckResult.COMMON; + } + + final SvnMergeInfoCache17.MergeCheckResult calculated = myAlreadyCalculatedMap.get(list.getNumber()); + if (calculated != null) { + return calculated; + } + + final SvnMergeInfoCache17.MergeCheckResult result = checkAlive(list, branchPath); + myAlreadyCalculatedMap.put(list.getNumber(), result); + return result; + } + } + + private SvnMergeInfoCache17.MergeCheckResult checkAlive(final SvnChangeList list, final String branchPath) { + final SVNInfo info = getInfo(new File(branchPath)); + if (info == null || info.getURL() == null || (! SVNPathUtil.isAncestor(myBranchUrl, info.getURL().toString()))) { + return SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED; + } + final String subPathUnderBranch = SVNPathUtil.getRelativePath(myBranchUrl, info.getURL().toString()); + + final MultiMap result = new MultiMap(); + checkPaths(list.getNumber(), list.getAddedPaths(), branchPath, subPathUnderBranch, result); + if (result.containsKey(SvnMergeInfoCache17.MergeCheckResult.NOT_EXISTS)) { + return SvnMergeInfoCache17.MergeCheckResult.NOT_EXISTS; + } + checkPaths(list.getNumber(), list.getDeletedPaths(), branchPath, subPathUnderBranch, result); + if (result.containsKey(SvnMergeInfoCache17.MergeCheckResult.NOT_EXISTS)) { + return SvnMergeInfoCache17.MergeCheckResult.NOT_EXISTS; + } + checkPaths(list.getNumber(), list.getChangedPaths(), branchPath, subPathUnderBranch, result); + + if (result.containsKey(SvnMergeInfoCache17.MergeCheckResult.NOT_EXISTS)) { + return SvnMergeInfoCache17.MergeCheckResult.NOT_EXISTS; + } else if (result.containsKey(SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED)) { + myPartlyMerged.put(list.getNumber(), result.get(SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED)); + return SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED; + } + return SvnMergeInfoCache17.MergeCheckResult.MERGED; + } + + private void checkPaths(final long number, final Collection paths, final String branchPath, final String subPathUnderBranch, + final MultiMap result) { + final String myTrunkPathCorrespondingToLocalBranchPath = SVNPathUtil.append(myTrunkCorrected, subPathUnderBranch); + for (String path : paths) { + final String absoluteInTrunkPath = SVNPathUtil.append(myRepositoryRoot, path); + if (! absoluteInTrunkPath.startsWith(myTrunkPathCorrespondingToLocalBranchPath)) { + result.putValue(SvnMergeInfoCache17.MergeCheckResult.NOT_EXISTS, path); + continue; + } + final String relativeToTrunkPath = absoluteInTrunkPath.substring(myTrunkPathCorrespondingToLocalBranchPath.length()); + final String localPathInBranch = new File(branchPath, relativeToTrunkPath).getAbsolutePath(); + + final SvnMergeInfoCache17.MergeCheckResult pathResult = checkPathGoingUp(number, -1, branchPath, localPathInBranch, path, true); + result.putValue(pathResult, path); + } + } + + private SvnMergeInfoCache17.MergeCheckResult goUp(final long revisionAsked, final long targetRevision, final String branchRootPath, + final String path, final String trunkUrl) { + final String newTrunkUrl = SVNPathUtil.removeTail(trunkUrl).trim(); + if (newTrunkUrl.length() == 0 || "/".equals(newTrunkUrl)) { + return SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED; + } + final String newPath = new File(path).getParent(); + if (newPath.length() < branchRootPath.length()) { + // we are higher than WC root -> go into repo only + if (targetRevision == -1) { + // no paths in local copy + return SvnMergeInfoCache17.MergeCheckResult.NOT_EXISTS; + } + final SVNInfo svnInfo = getInfo(new File(branchRootPath)); + if (svnInfo == null || svnInfo.getRevision() == null || svnInfo.getURL() == null) { + return SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED; + } + try { + return goUpInRepo(revisionAsked, targetRevision, svnInfo.getURL().removePathTail(), newTrunkUrl); + } + catch (SVNException e) { + LOG.info(e); + return SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED; + } + } + + return checkPathGoingUp(revisionAsked, targetRevision, branchRootPath, newPath, newTrunkUrl, false); + } + + private SvnMergeInfoCache17.MergeCheckResult goUpInRepo(final long revisionAsked, final long targetRevision, final SVNURL branchUrl, + final String trunkUrl) { + final String branchAsString = branchUrl.toString(); + final String keyString = branchAsString + "@" + targetRevision; + final Set mergeInfo = myPathMergedMap.get(keyString); + if (mergeInfo != null) { + // take from self or first parent with info; do not go further + return SvnMergeInfoCache17.MergeCheckResult.getInstance(mergeInfo.contains(revisionAsked)); + } + + final SVNPropertyData mergeinfoProperty; + try { + mergeinfoProperty = myClient.doGetProperty(branchUrl, SVNProperty.MERGE_INFO, SVNRevision.UNDEFINED, SVNRevision.create(targetRevision)); + } + catch (SVNException e) { + LOG.info(e); + return SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED; + } + + if (mergeinfoProperty == null) { + final String newTrunkUrl = SVNPathUtil.removeTail(trunkUrl).trim(); + final SVNURL newBranchUrl; + try { + newBranchUrl = branchUrl.removePathTail(); + } + catch (SVNException e) { + LOG.info(e); + return SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED; + } + final String absoluteTrunk = SVNPathUtil.append(myRepositoryRoot, newTrunkUrl); + if ((1 >= newTrunkUrl.length()) || (myRepositoryRoot.length() >= newBranchUrl.toString().length()) || + (newBranchUrl.toString().equals(absoluteTrunk))) { + return SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED; + } + // go up + return goUpInRepo(revisionAsked, targetRevision, newBranchUrl, newTrunkUrl); + } + // process + return processMergeinfoProperty(keyString, revisionAsked, mergeinfoProperty.getValue(), trunkUrl, false); + } + + private SVNInfo getInfo(final File pathFile) { + try { + return myClient.doInfo(pathFile, SVNRevision.UNDEFINED); + } catch (SVNException e) { + // + } + return null; + } + + private SvnMergeInfoCache17.MergeCheckResult checkPathGoingUp(final long revisionAsked, final long targetRevision, final String branchRootPath, + final String path, final String trunkUrl, final boolean self) { + final File pathFile = new File(path); + + if (targetRevision == -1) { + // we didn't find existing item on the path jet + // check whether we locally have path + if (! pathFile.exists()) { + // go into parent + return goUp(revisionAsked, targetRevision, branchRootPath, path, trunkUrl); + } + } + + final SVNInfo svnInfo = getInfo(pathFile); + if (svnInfo == null || svnInfo.getRevision() == null || svnInfo.getURL() == null) { + LOG.info("Svninfo for " + pathFile + " is null or not full."); + return SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED; + } + + final long actualRevision = svnInfo.getRevision().getNumber(); + final long targetRevisionCorrected = (targetRevision == -1) ? actualRevision : targetRevision; + + // here we know local URL and revision + + // check existing info + final String keyString = path + "@" + targetRevisionCorrected; + final Set selfInfo = self ? myNonInheritablePathMergedMap.get(keyString) : null; + final Set mergeInfo = myPathMergedMap.get(keyString); + if (mergeInfo != null || selfInfo != null) { + final boolean merged = ((mergeInfo != null) && mergeInfo.contains(revisionAsked)) || + ((selfInfo != null) && selfInfo.contains(revisionAsked)); + // take from self or first parent with info; do not go further + return SvnMergeInfoCache17.MergeCheckResult.getInstance(merged); + } + + final SVNPropertyData mergeinfoProperty; + try { + if (actualRevision == targetRevisionCorrected) { + // look in WC + mergeinfoProperty = myClient.doGetProperty(pathFile, SVNProperty.MERGE_INFO, SVNRevision.WORKING, SVNRevision.WORKING); + } else { + // in repo + myMixedRevisionsFound = true; + mergeinfoProperty = myClient.doGetProperty(svnInfo.getURL(), SVNProperty.MERGE_INFO, SVNRevision.UNDEFINED, SVNRevision.create(targetRevisionCorrected)); + } + } + catch (SVNException e) { + LOG.info(e); + return SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED; + } + + if (mergeinfoProperty == null) { + // go up + return goUp(revisionAsked, targetRevisionCorrected, branchRootPath, path, trunkUrl); + } + // process + return processMergeinfoProperty(keyString, revisionAsked, mergeinfoProperty.getValue(), trunkUrl, self); + } + + private SvnMergeInfoCache17.MergeCheckResult processMergeinfoProperty(final String pathWithRevisionNumber, final long revisionAsked, + final SVNPropertyValue value, final String trunkRelativeUrl, + final boolean self) { + final String valueAsString = value.toString().trim(); + + // empty mergeinfo + if (valueAsString.length() == 0) { + myPathMergedMap.put(pathWithRevisionNumber, Collections.emptySet()); + return SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED; + } + + final Map map; + try { + map = SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(replaceSeparators(value.getString())), null); + } + catch (SVNException e) { + LOG.info(e); + return SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED; + } + + for (String key : map.keySet()) { + if ((key != null) && (trunkRelativeUrl.startsWith(key))) { + final Set revisions = new HashSet(); + final Set nonInheritableRevisions = new HashSet(); + + final SVNMergeRangeList rangesList = map.get(key); + + boolean result = false; + for (SVNMergeRange range : rangesList.getRanges()) { + // SVN does not include start revision in range + final long startRevision = range.getStartRevision() + 1; + final long endRevision = range.getEndRevision(); + final boolean isInheritable = range.isInheritable(); + final boolean inInterval = (revisionAsked >= startRevision) && (revisionAsked <= endRevision); + + if ((isInheritable || self) && inInterval) { + result = true; + } + + for (long i = startRevision; i <= endRevision; i++) { + if (isInheritable) { + revisions.add(i); + } else { + nonInheritableRevisions.add(i); + } + } + } + myPathMergedMap.put(pathWithRevisionNumber, revisions); + if (! nonInheritableRevisions.isEmpty()) { + myNonInheritablePathMergedMap.put(pathWithRevisionNumber, nonInheritableRevisions); + } + + return SvnMergeInfoCache17.MergeCheckResult.getInstance(result); + } + } + myPathMergedMap.put(pathWithRevisionNumber, Collections.emptySet()); + return SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED; + } + + private String replaceSeparators(final String s) { + return s.replace('\r', '\n').replace("\n\n", "\n"); + } + + public boolean isMixedRevisionsFound() { + return myMixedRevisionsFound; + } + + // if nothing, maybe all not merged or merged: here only partly not merged + public Collection getNotMergedPaths(final long number) { + return myPartlyMerged.get(number); + } + + public static class MyMergeCheckerWrapper implements MergeChecker { + private final BranchInfo myInfo; + private final String myBranchPath; + + public MyMergeCheckerWrapper(String branchPath, BranchInfo info) { + myBranchPath = branchPath; + myInfo = info; + } + + public SvnMergeInfoCache17.MergeCheckResult checkList(SvnChangeList list) { + return myInfo.checkList(list, myBranchPath); + } + + public Collection getNotMergedPaths(long number) { + return myInfo.getNotMergedPaths(number); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/MergeChecker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/MergeChecker.java new file mode 100644 index 000000000000..5ecbb5d425ae --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/MergeChecker.java @@ -0,0 +1,12 @@ +package org.jetbrains.idea.svn17.mergeinfo; + +import org.jetbrains.idea.svn17.history.SvnChangeList; + +import java.util.Collection; + +public interface MergeChecker { + SvnMergeInfoCache17.MergeCheckResult checkList(SvnChangeList list); + + // if nothing, maybe all not merged or merged: here only partly not merged + Collection getNotMergedPaths(long number); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/MergeInfoHolder.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/MergeInfoHolder.java new file mode 100644 index 000000000000..432287585c4b --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/MergeInfoHolder.java @@ -0,0 +1,246 @@ +/* + * 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.mergeinfo; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Getter; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vcs.changes.committed.CommittedChangeListsListener; +import com.intellij.openapi.vcs.changes.committed.DecoratorManager; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.util.Consumer; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.dialogs.WCInfoWithBranches; +import org.jetbrains.idea.svn17.dialogs.WCPaths; +import org.jetbrains.idea.svn17.history.SvnChangeList; + +import javax.swing.*; +import java.awt.*; +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +public class MergeInfoHolder { + private final DecoratorManager myManager; + private final Consumer myMixedRevisionsConsumer; + private final SvnMergeInfoCache17 myMergeInfoCache; + + private final static String ourIntegratedText = SvnBundle.message("committed.changes.merge.status.integrated.text"); + private final static String ourNotIntegratedText = SvnBundle.message("committed.changes.merge.status.not.integrated.text"); + private final static SimpleTextAttributes ourNotIntegratedAttributes = new SimpleTextAttributes(SimpleTextAttributes.STYLE_BOLD, Color.RED); + private final static SimpleTextAttributes ourIntegratedAttributes = new SimpleTextAttributes(SimpleTextAttributes.STYLE_BOLD, Color.GREEN); + private final static SimpleTextAttributes ourRefreshAttributes = new SimpleTextAttributes(SimpleTextAttributes.STYLE_BOLD, Color.GRAY); + + // used ONLY when refresh is triggered + private final Map, MergeinfoCached> myCachedMap; + + private final Getter myRootGetter; + private final Getter myBranchGetter; + private final Getter myWcPathGetter; + private final Getter myEnabledHolder; + private final MyDecorator myDecorator; + + public MergeInfoHolder(final Project project, final DecoratorManager manager, final Getter rootGetter, + final Getter branchGetter, + final Getter wcPathGetter, Getter enabledHolder, final Consumer mixedRevisionsConsumer) { + myRootGetter = rootGetter; + myBranchGetter = branchGetter; + myWcPathGetter = wcPathGetter; + myEnabledHolder = enabledHolder; + myManager = manager; + myMixedRevisionsConsumer = mixedRevisionsConsumer; + myMergeInfoCache = SvnMergeInfoCache17.getInstance(project); + myCachedMap = new HashMap, MergeinfoCached>(); + + myDecorator = new MyDecorator(); + } + + private MergeinfoCached getCurrentCache() { + return myCachedMap.get(createKey(myRootGetter.get(), myBranchGetter.get())); + } + + private boolean enabledAndGettersFilled(final boolean ignoreEnabled) { + if ((! ignoreEnabled) && (! Boolean.TRUE.equals(myEnabledHolder.get()))) { + return false; + } + return (myRootGetter.get() != null) && (myBranchGetter.get() != null) && (myWcPathGetter.get() != null); + } + + public boolean refreshEnabled(final boolean ignoreEnabled) { + return enabledAndGettersFilled(ignoreEnabled) && (getCurrentCache() == null); + } + + private static Pair createKey(final WCPaths root, final WCInfoWithBranches.Branch branch) { + return new Pair(root.getPath(), branch.getUrl()); + } + + public void refresh(final boolean ignoreEnabled) { + final CommittedChangeListsListener refresher = createRefresher(ignoreEnabled); + if (refresher != null) { + myManager.reportLoadedLists(new MyRefresher()); + } + myManager.repaintTree(); + } + + @Nullable + public CommittedChangeListsListener createRefresher(final boolean ignoreEnabled) { + if (refreshEnabled(ignoreEnabled)) { + // on awt thread + final MergeinfoCached state = myMergeInfoCache.getCachedState(myRootGetter.get(), myWcPathGetter.get()); + myCachedMap.put(createKey(myRootGetter.get(), myBranchGetter.get()), (state == null) ? new MergeinfoCached() : + new MergeinfoCached(new HashMap(state.getMap()), state.getCopyRevision())); + myMergeInfoCache.clear(myRootGetter.get(), myWcPathGetter.get()); + + return new MyRefresher(); + } + return null; + } + + private class MyRefresher implements CommittedChangeListsListener { + private final WCInfoWithBranches myRefreshedRoot; + private final WCInfoWithBranches.Branch myRefreshedBranch; + private final String myBranchPath; + + private MyRefresher() { + myRefreshedRoot = myRootGetter.get(); + myRefreshedBranch = myBranchGetter.get(); + myBranchPath = myWcPathGetter.get(); + } + + public void onBeforeStartReport() { + } + + public boolean report(final CommittedChangeList list) { + if (list instanceof SvnChangeList) { + final SvnChangeList svnList = (SvnChangeList) list; + final String wcPath = svnList.getWcPath() + File.separator; + // todo check if this needed + /*if (! myRefreshedRoot.getPath().equals(wcPath)) { + return true; + } */ + + // prepare state. must be in non awt thread + final SvnMergeInfoCache17.MergeCheckResult checkState = myMergeInfoCache.getState(myRefreshedRoot, (SvnChangeList)list, myRefreshedBranch, + myBranchPath); + // todo make batches - by 10 + final long number = list.getNumber(); + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + final MergeinfoCached cachedState = myCachedMap.get(createKey(myRefreshedRoot, myRefreshedBranch)); + if (cachedState != null) { + cachedState.getMap().put(number, checkState); + } + myManager.repaintTree(); + } + }); + } + return true; + } + + public void onAfterEndReport() { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + myCachedMap.remove(createKey(myRefreshedRoot, myRefreshedBranch)); + updateMixedRevisionsForPanel(); + myManager.repaintTree(); + } + }); + } + } + + public static enum ListMergeStatus { + COMMON(IconLoader.getIcon("/icons/Common.png")), + MERGED(IconLoader.getIcon("/icons/Integrated.png")), + NOT_MERGED(IconLoader.getIcon("/icons/Notintegrated.png")), + //ALIEN(IconLoader.getIcon("/icons/OnDefault.png")), + ALIEN(null), + REFRESHING(IconLoader.getIcon("/icons/IntegrationStatusUnknown.png")); + + private final Icon myIcon; + + private ListMergeStatus(final Icon icon) { + myIcon = icon; + } + + public Icon getIcon() { + return myIcon; + } + } + + public static interface ListChecker { + ListMergeStatus check(final CommittedChangeList list, final boolean ignoreEnabled); + } + + class MyDecorator implements ListChecker { + private ListMergeStatus convert(SvnMergeInfoCache17.MergeCheckResult result, final boolean refreshing) { + if (result != null) { + if (SvnMergeInfoCache17.MergeCheckResult.MERGED.equals(result)) { + return ListMergeStatus.MERGED; + } else if (SvnMergeInfoCache17.MergeCheckResult.COMMON.equals(result)) { + return ListMergeStatus.COMMON; + } else { + return ListMergeStatus.NOT_MERGED; + } + } + if (refreshing) { + return ListMergeStatus.REFRESHING; + } + return ListMergeStatus.ALIEN; + } + + public ListMergeStatus check(final CommittedChangeList list, final boolean ignoreEnabled) { + if (! enabledAndGettersFilled(ignoreEnabled)) { + return ListMergeStatus.ALIEN; + } + + if (! (list instanceof SvnChangeList)) { + return ListMergeStatus.ALIEN; + } + + final MergeinfoCached cachedState = getCurrentCache(); + if (cachedState != null) { + if (cachedState.getCopyRevision() != -1 && cachedState.getCopyRevision() >= list.getNumber()) { + return ListMergeStatus.COMMON; + } + final SvnMergeInfoCache17.MergeCheckResult result = cachedState.getMap().get(list.getNumber()); + return convert(result, true); + } else { + final MergeinfoCached state = myMergeInfoCache.getCachedState(myRootGetter.get(), myWcPathGetter.get()); + if (state == null) { + refresh(ignoreEnabled); + return ListMergeStatus.REFRESHING; + } else { + if (state.getCopyRevision() != -1 && state.getCopyRevision() >= list.getNumber()) { + return ListMergeStatus.COMMON; + } + return convert(state.getMap().get(list.getNumber()), false); + } + } + } + } + + public ListChecker getDecorator() { + return myDecorator; + } + + public void updateMixedRevisionsForPanel() { + myMixedRevisionsConsumer.consume(myMergeInfoCache.isMixedRevisions(myRootGetter.get(), myWcPathGetter.get())); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/MergeInfoWorker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/MergeInfoWorker.java new file mode 100644 index 000000000000..297db58eaa0b --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/MergeInfoWorker.java @@ -0,0 +1,21 @@ +/* + * 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.mergeinfo; + +// about a single path in WC +public interface MergeInfoWorker { + SvnMergeInfoCache17.MergeCheckResult isMerged(final String relativeToRepoURLPath, final long revisionNumber); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/MergeinfoCached.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/MergeinfoCached.java new file mode 100644 index 000000000000..5493bdf4ccad --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/MergeinfoCached.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.mergeinfo; + +import java.util.HashMap; +import java.util.Map; + +public class MergeinfoCached { + private final Map myMap; + private final long myCopyRevision; + + public MergeinfoCached() { + myMap = new HashMap(); + myCopyRevision = -1; + } + + public MergeinfoCached(final Map map, final long copyRevision) { + myMap = map; + myCopyRevision = copyRevision; + } + + public Map getMap() { + return myMap; + } + + public long getCopyRevision() { + return myCopyRevision; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/OneRecursiveShotMergeInfoWorker.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/OneRecursiveShotMergeInfoWorker.java new file mode 100644 index 000000000000..86139b25bb9f --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/OneRecursiveShotMergeInfoWorker.java @@ -0,0 +1,163 @@ +/* + * 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.mergeinfo; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.AreaMap; +import com.intellij.util.PairProcessor; +import org.jetbrains.idea.svn17.SvnConfiguration17; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.dialogs.WCInfo; +import org.tmatesoft.svn.core.*; +import org.tmatesoft.svn.core.internal.util.SVNMergeInfoUtil; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; +import org.tmatesoft.svn.core.wc.ISVNPropertyHandler; +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; +import java.util.Map; + +public class OneRecursiveShotMergeInfoWorker implements MergeInfoWorker { + private final Project myProject; + private final WCInfo myWCInfo; + // subpath [file] (local) to (subpathURL - merged FROM - to ranges list) + private final AreaMap> myDataMap; + private String myFromUrlRelative; + + public OneRecursiveShotMergeInfoWorker(final Project project, final WCInfo WCInfo, final String fromUrl) { + myProject = project; + myWCInfo = WCInfo; + + myDataMap = AreaMap.create(new PairProcessor() { + public boolean process(String parentUrl, String childUrl) { + if (".".equals(parentUrl)) return true; + return SVNPathUtil.isAncestor(ensureUrlFromSlash(parentUrl), ensureUrlFromSlash(childUrl)); + } + }); + final String url = SVNPathUtil.getRelativePath(myWCInfo.getRepositoryRoot(), fromUrl); + myFromUrlRelative = ensureUrlFromSlash(url); + } + + private String ensureUrlFromSlash(final String url) { + return url.startsWith("/") ? url : "/" + url; + } + + public void prepare() throws SVNException { + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final SVNWCClient client = vcs.createWCClient(); + + final SVNDepth depth = SvnConfiguration17.getInstance(myProject).CHECK_NESTED_FOR_QUICK_MERGE ? SVNDepth.INFINITY : SVNDepth.EMPTY; + client.doGetProperty(new File(myWCInfo.getPath()), SVNProperty.MERGE_INFO, SVNRevision.UNDEFINED, SVNRevision.WORKING, + depth, new ISVNPropertyHandler() { + public void handleProperty(File path, SVNPropertyData property) throws SVNException { + final String key = keyFromFile(path); + myDataMap.put(key, SVNMergeInfoUtil.parseMergeInfo(new StringBuffer(replaceSeparators(property.getValue().getString())), null)); + } + + public void handleProperty(SVNURL url, SVNPropertyData property) throws SVNException { + } + public void handleProperty(long revision, SVNPropertyData property) throws SVNException { + } + }, null); + } + + public SvnMergeInfoCache17.MergeCheckResult isMerged(final String relativeToRepoURLPath, final long revisionNumber) { + // should make relative to wc root + final String relativeToWc = SVNPathUtil.getRelativePath(myFromUrlRelative, ensureUrlFromSlash(relativeToRepoURLPath)); + if (relativeToWc == null) return SvnMergeInfoCache17.MergeCheckResult.NOT_EXISTS; + + final InfoProcessor processor = new InfoProcessor(relativeToWc, myFromUrlRelative, revisionNumber); + myDataMap.getSimiliar(keyFromPath(relativeToWc), processor); + return SvnMergeInfoCache17.MergeCheckResult.getInstance(processor.isMerged()); + } + + private static class InfoProcessor implements PairProcessor> { + private final String myWcLevelRelativeSourceUrl; + private boolean myMerged; + private final String myFilePathAsked; + private final long myRevisionAsked; + + public InfoProcessor(final String filePathAsked, final String wcLevelRelativeSourceUrl, final long revisionAsked) { + myFilePathAsked = filePathAsked; + myRevisionAsked = revisionAsked; + myWcLevelRelativeSourceUrl = wcLevelRelativeSourceUrl.startsWith("/") ? wcLevelRelativeSourceUrl : "/" + wcLevelRelativeSourceUrl; + } + + public boolean isMerged() { + return myMerged; + } + + public boolean process(final String relativeFileSubpath, Map map) { + boolean processed = false; + final boolean self = relativeFileSubpath.equals(myFilePathAsked); + + if (map.isEmpty()) { + myMerged = false; + return true; + } + for (Map.Entry entry : map.entrySet()) { + String relativeUrl = entry.getKey(); + + boolean urlMatches = false; + if (".".equals(relativeUrl) || "".equals(relativeUrl)) { + urlMatches = true; + } else { + relativeUrl = (relativeUrl.startsWith("/")) ? relativeUrl : "/" + relativeUrl; + urlMatches = SVNPathUtil.isAncestor(myWcLevelRelativeSourceUrl, relativeUrl); + } + + if (! urlMatches) continue; + processed = true; + + final SVNMergeRangeList rangesList = entry.getValue(); + + for (SVNMergeRange range : rangesList.getRanges()) { + // SVN does not include start revision in range + final long startRevision = range.getStartRevision() + 1; + final long endRevision = range.getEndRevision(); + final boolean isInheritable = range.isInheritable(); + final boolean inInterval = (myRevisionAsked >= startRevision) && (myRevisionAsked <= endRevision); + + if ((isInheritable || self) && inInterval) { + myMerged = true; + break; + } + } + break; + } + + return processed; + } + } + + private String keyFromFile(final File file) { + final String path = + FileUtil.getRelativePath(myWCInfo.getPath(), file.getAbsolutePath(), File.separatorChar).replace(File.separatorChar, '/'); + return keyFromPath(path); + } + + private static String keyFromPath(final String path) { + return SystemInfo.isFileSystemCaseSensitive ? path : path.toUpperCase(); + } + + private static String replaceSeparators(final String s) { + return s.replace('\r', '\n').replace("\n\n", "\n"); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/OneShotMergeInfoHelper.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/OneShotMergeInfoHelper.java new file mode 100644 index 000000000000..af8d215a9317 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/OneShotMergeInfoHelper.java @@ -0,0 +1,66 @@ +/* + * 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.mergeinfo; + +import com.intellij.openapi.project.Project; +import org.jetbrains.idea.svn17.dialogs.WCInfo; +import org.jetbrains.idea.svn17.history.SvnChangeList; +import org.tmatesoft.svn.core.SVNException; + +import java.util.*; + +public class OneShotMergeInfoHelper implements MergeChecker { + private OneRecursiveShotMergeInfoWorker myWorker; + private final Map> myPartiallyMerged; + + public OneShotMergeInfoHelper(final Project project, final WCInfo wcInfo, final String branchPath) throws SVNException { + myWorker = new OneRecursiveShotMergeInfoWorker(project, wcInfo, branchPath); + myPartiallyMerged = new HashMap>(); + } + + public void prepare() throws SVNException { + myWorker.prepare(); + } + + public Collection getNotMergedPaths(long number) { + return myPartiallyMerged.get(number); + } + + public SvnMergeInfoCache17.MergeCheckResult checkList(final SvnChangeList list) { + final Set notMerged = new HashSet(); + boolean somethingMerged = false; + + final long number = list.getNumber(); + final Set paths = new HashSet(list.getAddedPaths()); + paths.addAll(list.getDeletedPaths()); + paths.addAll(list.getChangedPaths()); + + for (String path : paths) { + final SvnMergeInfoCache17.MergeCheckResult pathResult = myWorker.isMerged(path, number); + if (SvnMergeInfoCache17.MergeCheckResult.MERGED.equals(pathResult)) { + somethingMerged = true; + } else if (SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED.equals(pathResult)) { + notMerged.add(path); + } + } + + if (somethingMerged && (! notMerged.isEmpty())) { + myPartiallyMerged.put(number, notMerged); + } + if ((! somethingMerged) && notMerged.isEmpty()) return SvnMergeInfoCache17.MergeCheckResult.NOT_EXISTS; + return SvnMergeInfoCache17.MergeCheckResult.getInstance(somethingMerged && notMerged.isEmpty()); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/SelectBranchAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/SelectBranchAction.java new file mode 100644 index 000000000000..7bfea1c51125 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/SelectBranchAction.java @@ -0,0 +1,109 @@ +/* + * 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.mergeinfo; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Getter; +import com.intellij.openapi.vcs.changes.committed.DecoratorManager; +import com.intellij.openapi.vcs.changes.committed.LabeledComboBoxAction; +import com.intellij.util.Consumer; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.dialogs.WCInfoWithBranches; + +import javax.swing.*; + +public class SelectBranchAction extends LabeledComboBoxAction implements SelectRootListener, Getter { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.mergeinfo.SelectBranchAction"); + private final DecoratorManager myManager; + private WCInfoWithBranches mySelectedRoot; + public static final DefaultComboBoxModel EMPTY = new DefaultComboBoxModel(); + private final Consumer mySelectionListener; + + public SelectBranchAction(final DecoratorManager manager, final Consumer selectionListener) { + super(SvnBundle.message("committed.changes.action.merge.highlighting.select.branch")); + myManager = manager; + mySelectionListener = selectionListener; + } + + protected void selectionChanged(final Object selection) { + mySelectionListener.consume(((WCInfoWithBranches.Branch) selection).getUrl()); + myManager.repaintTree(); + } + + public void selectionChanged(final WCInfoWithBranches wcInfoWithBranches) { + final boolean valueChanges = (wcInfoWithBranches != null) && (!wcInfoWithBranches.equals(mySelectedRoot)); + mySelectedRoot = wcInfoWithBranches; + + if (valueChanges) { + final ComboBoxModel model = createModel(); + setModel(model); + myManager.repaintTree(); + } + } + + public void force(final WCInfoWithBranches info) { + mySelectedRoot = info; + + if (info == null) { + setModel(EMPTY); + } else { + + final WCInfoWithBranches.Branch selected = (WCInfoWithBranches.Branch) getSelected(); + final ComboBoxModel model = createModel(); + setModel(model); + + if (selected != null) { + boolean selectedSet = false; + for (int i = 0; i < model.getSize(); i++) { + final WCInfoWithBranches.Branch element = (WCInfoWithBranches.Branch) model.getElementAt(i); + if (selected.equals(element)) { + model.setSelectedItem(element); + selectedSet = true; + } + } + if (! selectedSet) { + model.setSelectedItem(model.getElementAt(0)); + } + } else { + if (model.getSize() > 0) { + model.setSelectedItem(model.getElementAt(0)); + } + } + } + + myManager.repaintTree(); + } + + protected ComboBoxModel createModel() { + if (mySelectedRoot == null) { + return EMPTY; + } + + final DefaultComboBoxModel model = new DefaultComboBoxModel(mySelectedRoot.getBranches().toArray()); + if (model.getSize() > 0) { + selectionChanged(model.getElementAt(0)); + } + return model; + } + + public void enable(final boolean value) { + myManager.repaintTree(); + } + + public WCInfoWithBranches.Branch get() { + return (WCInfoWithBranches.Branch) getSelected(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/SelectRootListener.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/SelectRootListener.java new file mode 100644 index 000000000000..b0e6d57b477e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/SelectRootListener.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.mergeinfo; + +import org.jetbrains.idea.svn17.dialogs.WCInfoWithBranches; + +public interface SelectRootListener { + void selectionChanged(final WCInfoWithBranches info); + void force(final WCInfoWithBranches info); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/SvnMergeInfoCache17.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/SvnMergeInfoCache17.java new file mode 100644 index 000000000000..b9ab698ac55f --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/mergeinfo/SvnMergeInfoCache17.java @@ -0,0 +1,215 @@ +/* + * 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.mergeinfo; + +import com.intellij.lifecycle.PeriodicalTasksCloser; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.util.Consumer; +import com.intellij.util.containers.SoftHashMap; +import com.intellij.util.messages.Topic; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.dialogs.WCInfoWithBranches; +import org.jetbrains.idea.svn17.dialogs.WCPaths; +import org.jetbrains.idea.svn17.history.CopyData; +import org.jetbrains.idea.svn17.history.FirstInBranch; +import org.jetbrains.idea.svn17.history.SvnChangeList; +import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.util.HashMap; +import java.util.Map; + +public class SvnMergeInfoCache17 { + private final static Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.mergeinfo.SvnMergeInfoCache17"); + + private final Project myProject; + private final MyState myState; + private final SVNWCClient myClient; + + public static Topic SVN_MERGE_INFO_CACHE = new Topic("SVN_MERGE_INFO_CACHE", + SvnMergeInfoCacheListener.class); + + private SvnMergeInfoCache17(final Project project) { + myProject = project; + myState = new MyState(); + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + myClient = vcs.createWCClient(); + myClient.setOptions(new DefaultSVNOptions() { + @Override + public byte[] getNativeEOL() { + return new byte[]{'\n'}; + } + }); + } + + public static SvnMergeInfoCache17 getInstance(final Project project) { + return PeriodicalTasksCloser.getInstance().safeGetService(project, SvnMergeInfoCache17.class); + } + + public void clear(final WCPaths info, final String branchPath) { + final String currentUrl = info.getRootUrl(); + + final MyCurrentUrlData rootMapping = myState.getCurrentUrlMapping().get(currentUrl); + if (rootMapping != null) { + final BranchInfo mergeChecker = rootMapping.getBranchInfo(branchPath); + if (mergeChecker != null) { + mergeChecker.clear(); + } + } + } + + @Nullable + public MergeinfoCached getCachedState(final WCPaths info, final String branchPath) { + final String currentUrl = info.getRootUrl(); + + MyCurrentUrlData rootMapping = myState.getCurrentUrlMapping().get(currentUrl); + if (rootMapping != null) { + final BranchInfo branchInfo = rootMapping.getBranchInfo(branchPath); + if (branchInfo != null) { + return branchInfo.getCached(); + } + } + return null; + } + + // only refresh might have changed; for branches/roots change, another method is used + public MergeCheckResult getState(final WCInfoWithBranches info, final SvnChangeList list, final WCInfoWithBranches.Branch selectedBranch) { + return getState(info, list, selectedBranch, null); + } + + // only refresh might have changed; for branches/roots change, another method is used + public MergeCheckResult getState(final WCInfoWithBranches info, final SvnChangeList list, final WCInfoWithBranches.Branch selectedBranch, + final String branchPath) { + final String currentUrl = info.getRootUrl(); + final String branchUrl = selectedBranch.getUrl(); + + MyCurrentUrlData rootMapping = myState.getCurrentUrlMapping().get(currentUrl); + BranchInfo mergeChecker = null; + if (rootMapping == null) { + rootMapping = new MyCurrentUrlData(); + myState.getCurrentUrlMapping().put(currentUrl, rootMapping); + } else { + mergeChecker = rootMapping.getBranchInfo(branchPath); + } + if (mergeChecker == null) { + mergeChecker = new BranchInfo(SvnVcs17.getInstance(myProject), info.getRepoUrl(), branchUrl, currentUrl, info.getTrunkRoot(), myClient); + rootMapping.addBranchInfo(branchPath, mergeChecker); + } + + return mergeChecker.checkList(list, branchPath); + } + + public boolean isMixedRevisions(final WCInfoWithBranches info, final String branchPath) { + final String currentUrl = info.getRootUrl(); + final MyCurrentUrlData rootMapping = myState.getCurrentUrlMapping().get(currentUrl); + if (rootMapping != null) { + final BranchInfo branchInfo = rootMapping.getBranchInfo(branchPath); + if (branchInfo != null) { + return branchInfo.isMixedRevisionsFound(); + } + } + return false; + } + + private static class MyState { + private Map myCurrentUrlMapping; + + private MyState() { + myCurrentUrlMapping = new HashMap(); + } + + public Map getCurrentUrlMapping() { + return myCurrentUrlMapping; + } + + public void setCurrentUrlMapping(final Map currentUrlMapping) { + myCurrentUrlMapping = currentUrlMapping; + } + } + + public static enum MergeCheckResult { + COMMON, + MERGED, + NOT_MERGED, + NOT_EXISTS, + NOT_EXISTS_PARTLY_MERGED; + + public static MergeCheckResult getInstance(final boolean merged) { + // not exists assumed to be already checked + if (merged) { + return MERGED; + } + return NOT_MERGED; + } + } + + static class CopyRevison { + private final String myPath; + private volatile long myRevision; + + CopyRevison(final SvnVcs17 vcs, final String path, final String repositoryRoot, final String branchUrl, final String trunkUrl) { + myPath = path; + myRevision = -1; + + ApplicationManager.getApplication().executeOnPooledThread(new FirstInBranch(vcs, repositoryRoot, branchUrl, trunkUrl, + new Consumer() { + public void consume(CopyData copyData) { + if (copyData == null) return; + myRevision = copyData.getCopySourceRevision(); + if (myRevision != -1) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + if (vcs.getProject().isDisposed()) return; + vcs.getProject().getMessageBus().syncPublisher(SVN_MERGE_INFO_CACHE).copyRevisionUpdated(); + } + }); + } + } + })); + } + + public String getPath() { + return myPath; + } + + public long getRevision() { + return myRevision; + } + } + + private static class MyCurrentUrlData { + private final Map myBranchInfo; + + private MyCurrentUrlData() { + myBranchInfo = new SoftHashMap(); + } + + public BranchInfo getBranchInfo(final String branchUrl) { + return myBranchInfo.get(branchUrl); + } + + public void addBranchInfo(final String branchUrl, final BranchInfo mergeChecker) { + myBranchInfo.put(branchUrl, mergeChecker); + } + } + + public interface SvnMergeInfoCacheListener { + void copyRevisionUpdated(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/revision/SvnSelectRevisionPanel.form b/plugins/svn4idea/src/org/jetbrains/idea/svn17/revision/SvnSelectRevisionPanel.form new file mode 100644 index 000000000000..fcdc62ebe3bf --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/revision/SvnSelectRevisionPanel.form @@ -0,0 +1,26 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/revision/SvnSelectRevisionPanel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/revision/SvnSelectRevisionPanel.java new file mode 100644 index 000000000000..04825cd698e3 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/revision/SvnSelectRevisionPanel.java @@ -0,0 +1,52 @@ +/* + * 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.revision; + +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn17.update.SvnRevisionPanel; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import javax.swing.*; +import java.awt.*; + +public class SvnSelectRevisionPanel extends JPanel { + private SvnRevisionPanel mySvnRevisionPanel; + private JPanel myPanel; + + public SvnSelectRevisionPanel() { + super(new BorderLayout()); + } + + public void setProject(final Project project) { + mySvnRevisionPanel.setProject(project); + } + + public void setUrlProvider(final SvnRevisionPanel.UrlProvider urlProvider) { + mySvnRevisionPanel.setUrlProvider(urlProvider); + } + + public void setRoot(final VirtualFile root) { + mySvnRevisionPanel.setRoot(root); + } + + @NotNull + public SVNRevision getRevision() throws ConfigurationException { + return mySvnRevisionPanel.getRevision(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/rollback/SvnRollbackEnvironment17.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/rollback/SvnRollbackEnvironment17.java new file mode 100644 index 000000000000..5cb0dfee0091 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/rollback/SvnRollbackEnvironment17.java @@ -0,0 +1,321 @@ +/* + * 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.rollback; + +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Trinity; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangesUtil; +import com.intellij.openapi.vcs.changes.ContentRevision; +import com.intellij.openapi.vcs.changes.EmptyChangelistBuilder; +import com.intellij.openapi.vcs.rollback.DefaultRollbackEnvironment; +import com.intellij.openapi.vcs.rollback.RollbackProgressListener; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.MoveRenameReplaceCheck; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnChangeProvider; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNErrorCode; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNNodeKind; +import org.tmatesoft.svn.core.wc.*; + +import java.io.File; +import java.util.*; + +/** + * @author yole + */ +public class SvnRollbackEnvironment17 extends DefaultRollbackEnvironment { + private final SvnVcs17 mySvnVcs; + + public SvnRollbackEnvironment17(SvnVcs17 svnVcs) { + mySvnVcs = svnVcs; + } + + @Override + public String getRollbackOperationName() { + return SvnBundle.message("action.name.revert"); + } + + public void rollbackChanges(List changes, final List exceptions, @NotNull final RollbackProgressListener listener) { + listener.indeterminate(); + final SvnChangeProvider changeProvider = (SvnChangeProvider) mySvnVcs.getChangeProvider(); + + final UnversionedFilesGroupCollector collector = new UnversionedFilesGroupCollector(); + + final ChangesChecker checker = new ChangesChecker(changeProvider, collector); + checker.gather(changes); + exceptions.addAll(checker.getExceptions()); + + final SVNWCClient client = mySvnVcs.createWCClient(); + client.setEventHandler(new ISVNEventHandler() { + public void handleEvent(SVNEvent event, double progress) { + if (event.getAction() == SVNEventAction.REVERT) { + final File file = event.getFile(); + if (file != null) { + listener.accept(file); + } + } + if (event.getAction() == SVNEventAction.FAILED_REVERT) { + exceptions.add(new VcsException("Revert failed")); + } + } + + public void checkCancelled() { + listener.checkCanceled(); + } + }); + + // adds (deletes) + // deletes (adds) + // modifications + final Reverter reverter = new Reverter(client, exceptions); + reverter.revert(checker.getForAdds(), true); + reverter.revert(checker.getForDeletes(), true); + final List edits = checker.getForEdits(); + reverter.revert(edits.toArray(new File[edits.size()]), false); + + final List> fromTo = collector.getFromTo(); + for (Trinity trinity : fromTo) { + if (trinity.getFirst().exists()) { + // parent successfully renamed/moved + trinity.getSecond().renameTo(trinity.getThird()); + } + } + final List> toBeDeleted = collector.getToBeDeleted(); + for (Pair pair : toBeDeleted) { + if (pair.getFirst().exists()) { + FileUtil.delete(pair.getSecond()); + } + } + } + + private static class Reverter { + private final SVNWCClient myClient; + private final List myExceptions; + + private Reverter(SVNWCClient client, List exceptions) { + myClient = client; + myExceptions = exceptions; + } + + public void revert(final File[] files, final boolean recursive) { + if (files.length == 0) return; + try { + myClient.doRevert(files, recursive); + } + catch (SVNException e) { + if (e.getErrorMessage().getErrorCode() != SVNErrorCode.WC_NOT_DIRECTORY) { + // skip errors on unversioned resources. + myExceptions.add(new VcsException(e)); + } + } + } + } + + public void rollbackMissingFileDeletion(List filePaths, final List exceptions, + final RollbackProgressListener listener) { + final SVNWCClient wcClient = mySvnVcs.createWCClient(); + + List files = ChangesUtil.filePathsToFiles(filePaths); + for (File file : files) { + listener.accept(file); + try { + SVNInfo info = wcClient.doInfo(file, SVNRevision.UNDEFINED); + if (info != null && info.getKind() == SVNNodeKind.FILE) { + wcClient.doRevert(file, false); + } else { + // do update to restore missing directory. + mySvnVcs.createUpdateClient().doUpdate(file, SVNRevision.HEAD, true); + } + } + catch (SVNException e) { + exceptions.add(new VcsException(e)); + } + } + } + + private static class UnversionedFilesGroupCollector extends EmptyChangelistBuilder { + private File myCurrentBeforeFile; + private final List> myToBeDeleted; + private final List> myFromTo; + + private UnversionedFilesGroupCollector() { + myFromTo = new ArrayList>(); + myToBeDeleted = new ArrayList>(); + } + + @Override + public void processUnversionedFile(final VirtualFile file) { + final File to = new File(myCurrentBeforeFile, file.getName()); + myFromTo.add(new Trinity(myCurrentBeforeFile, new File(file.getPath()), to)); + } + + public void setBefore(@NotNull final File beforeFile, @NotNull final File afterFile) { + myCurrentBeforeFile = beforeFile; + myToBeDeleted.add(new Pair(beforeFile, afterFile)); + } + + public List> getToBeDeleted() { + return myToBeDeleted; + } + + public List> getFromTo() { + return myFromTo; + } + } + + // both adds and deletes + private static abstract class SuperfluousRemover { + private final Set myParentPaths; + + private SuperfluousRemover() { + myParentPaths = new HashSet(); + } + + @Nullable + protected abstract File accept(final Change change); + + public void check(final File file) { + for (Iterator iterator = myParentPaths.iterator(); iterator.hasNext();) { + final File parentPath = iterator.next(); + if (VfsUtil.isAncestor(parentPath, file, true)) { + return; + } else if (VfsUtil.isAncestor(file, parentPath, true)) { + iterator.remove(); + // remove others; dont check for 1st variant any more + for (; iterator.hasNext();) { + final File innerParentPath = iterator.next(); + if (VfsUtil.isAncestor(file, innerParentPath, true)) { + iterator.remove(); + } + } + // will be added in the end + } + } + myParentPaths.add(file); + } + + public Set getParentPaths() { + return myParentPaths; + } + } + + private static class ChangesChecker { + private final SuperfluousRemover myForAdds; + private final SuperfluousRemover myForDeletes; + private final List myForEdits; + + private final SvnChangeProvider myChangeProvider; + private final UnversionedFilesGroupCollector myCollector; + + private final List myExceptions; + + private ChangesChecker(SvnChangeProvider changeProvider, UnversionedFilesGroupCollector collector) { + myChangeProvider = changeProvider; + myCollector = collector; + + myForAdds = new SuperfluousRemover() { + @Nullable + @Override + protected File accept(Change change) { + final ContentRevision beforeRevision = change.getBeforeRevision(); + final ContentRevision afterRevision = change.getAfterRevision(); + if (beforeRevision == null || MoveRenameReplaceCheck.check(change)) { + return afterRevision.getFile().getIOFile(); + } + return null; + } + }; + + myForDeletes = new SuperfluousRemover() { + @Nullable + @Override + protected File accept(Change change) { + final ContentRevision beforeRevision = change.getBeforeRevision(); + final ContentRevision afterRevision = change.getAfterRevision(); + if (afterRevision == null || MoveRenameReplaceCheck.check(change)) { + return beforeRevision.getFile().getIOFile(); + } + return null; + } + }; + + myForEdits = new ArrayList(); + myExceptions = new ArrayList(); + } + + public void gather(final List changes) { + for (Change change : changes) { + final ContentRevision beforeRevision = change.getBeforeRevision(); + final ContentRevision afterRevision = change.getAfterRevision(); + + if (MoveRenameReplaceCheck.check(change)) { + myCollector.setBefore(beforeRevision.getFile().getIOFile(), afterRevision.getFile().getIOFile()); + try { + myChangeProvider.getChanges(afterRevision.getFile(), false, myCollector); + } + catch (SVNException e) { + myExceptions.add(new VcsException(e)); + } + } + + boolean checked = getAddDelete(myForAdds, change); + checked |= getAddDelete(myForDeletes, change); + + if (! checked) { + myForEdits.add(afterRevision.getFile().getIOFile()); + } + } + } + + private boolean getAddDelete(final SuperfluousRemover superfluousRemover, final Change change) { + final File file = superfluousRemover.accept(change); + if (file != null) { + superfluousRemover.check(file); + return true; + } + return false; + } + + public File[] getForAdds() { + return convert(myForAdds.getParentPaths()); + } + + public File[] getForDeletes() { + return convert(myForDeletes.getParentPaths()); + } + + private File[] convert(final Collection paths) { + return paths.toArray(new File[paths.size()]); + } + + public List getExceptions() { + return myExceptions; + } + + public List getForEdits() { + return myForEdits; + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/status/DiffContentRevision.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/status/DiffContentRevision.java new file mode 100644 index 000000000000..50b78bfb02db --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/status/DiffContentRevision.java @@ -0,0 +1,76 @@ +/* + * 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.status; + +import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream; +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.vfs.CharsetToolkit; +import com.intellij.vcsUtil.VcsUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.io.SVNRepository; + +import java.nio.charset.Charset; + +public class DiffContentRevision implements ContentRevision { + private String myPath; + private SVNRepository myRepository; + private String myContents; + private FilePath myFilePath; + private long myRevision; + + public DiffContentRevision(String path, @NotNull SVNRepository repos, long revision) { + this(path, repos, revision, VcsUtil.getFilePath(path)); + } + + public DiffContentRevision(final String path, final SVNRepository repository, final long revision, final FilePath filePath) { + myPath = path; + myRepository = repository; + myFilePath = filePath; + myRevision = revision; + } + + @Nullable + public String getContent() throws VcsException { + if (myContents == null) { + BufferExposingByteArrayOutputStream bos = new BufferExposingByteArrayOutputStream(2048); + try { + myRepository.getFile(myPath, -1, null, bos); + myRepository.closeSession(); + } catch (SVNException e) { + throw new VcsException(e); + } + final byte[] bytes = bos.toByteArray(); + final Charset charset = myFilePath.getCharset(); + myContents = charset == null ? CharsetToolkit.bytesToString(bytes) : CharsetToolkit.bytesToString(bytes, charset); + } + return myContents; + } + + @NotNull + public FilePath getFile() { + return myFilePath; + } + + @NotNull + public VcsRevisionNumber getRevisionNumber() { + return new VcsRevisionNumber.Long(myRevision); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/status/SvnDiffEditor.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/status/SvnDiffEditor.java new file mode 100644 index 000000000000..6d98971d7a29 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/status/SvnDiffEditor.java @@ -0,0 +1,182 @@ +/* + * 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. + */ + +/* + * Created by IntelliJ IDEA. + * User: Alexander.Kitaev + * Date: 20.07.2006 + * Time: 19:09:29 + */ +package org.jetbrains.idea.svn17.status; + +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vcs.actions.VcsContextFactory; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ContentRevision; +import com.intellij.openapi.vcs.changes.CurrentContentRevision; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.containers.HashMap; +import org.jetbrains.annotations.NotNull; +import org.tmatesoft.svn.core.SVNCommitInfo; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNPropertyValue; +import org.tmatesoft.svn.core.io.ISVNEditor; +import org.tmatesoft.svn.core.io.SVNRepository; +import org.tmatesoft.svn.core.io.diff.SVNDiffWindow; + +import java.io.File; +import java.io.OutputStream; +import java.util.Map; + +public class SvnDiffEditor implements ISVNEditor { + private VirtualFile mySourceRoot; + private SVNRepository mySource; + private final SVNRepository myTarget; + private final long myTargetRevision; + private final boolean myReverse; + + private final Map myChanges; + + public SvnDiffEditor(@NotNull SVNRepository source, SVNRepository target, long targetRevision, boolean reverse) { + mySource = source; + myTarget = target; + myTargetRevision = targetRevision; + myChanges = new HashMap(); + myReverse = reverse; + } + + public SvnDiffEditor(@NotNull final VirtualFile sourceRoot, final SVNRepository target, long targetRevision, + boolean reverse) { + mySourceRoot = sourceRoot; + myTarget = target; + myTargetRevision = targetRevision; + myChanges = new HashMap(); + myReverse = reverse; + } + + public Map getChangesMap() { + return myChanges; + } + + private ContentRevision createBeforeRevision(final String path) { + if (mySource != null) { + return new DiffContentRevision(path, mySource, -1); + } + // 'path' includes the first component of the root local path + File f = new File(mySourceRoot.getPath(), path); + FilePath filePath = VcsContextFactory.SERVICE.getInstance().createFilePathOn(f); + return CurrentContentRevision.create(filePath); + } + + private DiffContentRevision createAfterRevision(final String path) { + if (mySourceRoot != null) { + File f = new File(mySourceRoot.getPath(), path); + FilePath filePath = VcsContextFactory.SERVICE.getInstance().createFilePathOn(f); + return new DiffContentRevision(path, myTarget, myTargetRevision, filePath); + } + return new DiffContentRevision(path, myTarget, myTargetRevision); + } + + public void targetRevision(long revision) throws SVNException { + } + public void openRoot(long revision) throws SVNException { + } + + public void deleteEntry(String path, long revision) throws SVNException { + // deleted - null for target, existing for source. + Change change = createChange(path, FileStatus.DELETED); + myChanges.put(path, change); + } + + public void absentDir(String path) throws SVNException { + } + public void absentFile(String path) throws SVNException { + } + public void addDir(String path, String copyFromPath, long copyFromRevision) throws SVNException { + FileStatus status = FileStatus.ADDED; + if (myChanges.containsKey(path) && myChanges.get(path).getFileStatus() == FileStatus.DELETED) { + // replaced file + myChanges.remove(path); + status = FileStatus.MODIFIED; + } + Change change = createChange(path, status); + myChanges.put(path, change); + } + + private Change createChange(final String path, final FileStatus status) { + final ContentRevision beforeRevision = createBeforeRevision(path); + final DiffContentRevision afterRevision = createAfterRevision(path); + if (myReverse) { + if (status == FileStatus.ADDED) { + return new Change(afterRevision, null); + } + if (status == FileStatus.DELETED) { + return new Change(null, beforeRevision); + } + return new Change(afterRevision, beforeRevision, status); + } + return new Change(status == FileStatus.ADDED ? null : beforeRevision, + status == FileStatus.DELETED ? null : afterRevision, + status); + } + + public void openDir(String path, long revision) throws SVNException { + } + public void changeDirProperty(final String name, final SVNPropertyValue value) throws SVNException { + } + public void closeDir() throws SVNException { + } + + public void addFile(String path, String copyFromPath, long copyFromRevision) throws SVNException { + FileStatus status = FileStatus.ADDED; + if (myChanges.containsKey(path) && myChanges.get(path).getFileStatus() == FileStatus.DELETED) { + // replaced file + myChanges.remove(path); + status = FileStatus.MODIFIED; + } + Change change = createChange(path, status); + myChanges.put(path, change); + } + + public void openFile(String path, long revision) throws SVNException { + Change change = createChange(path, FileStatus.MODIFIED); + myChanges.put(path, change); + } + + public void changeFileProperty(final String path, final String propertyName, final SVNPropertyValue propertyValue) throws SVNException { + } + public void closeFile(String path, String textChecksum) throws SVNException { + } + + public void abortEdit() throws SVNException { + } + + public void applyTextDelta(String path, String baseChecksum) throws SVNException { + } + + public OutputStream textDeltaChunk(String path, SVNDiffWindow diffWindow) throws SVNException { + return null; + } + + public void textDeltaEnd(String path) throws SVNException { + } + + public SVNCommitInfo closeEdit() throws SVNException { + return null; + } + +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/AbstractSvnUpdateIntegrateEnvironment17.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/AbstractSvnUpdateIntegrateEnvironment17.java new file mode 100644 index 000000000000..459eaf3dda02 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/AbstractSvnUpdateIntegrateEnvironment17.java @@ -0,0 +1,326 @@ +/* + * 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.update; + +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.*; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import com.intellij.openapi.vcs.update.*; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.ReadonlyStatusHandler; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.newvfs.RefreshQueue; +import com.intellij.openapi.vfs.newvfs.RefreshSession; +import com.intellij.util.WaitForProgressToShow; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnUtil; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.actions.SvnMergeProvider; +import org.tmatesoft.svn.core.wc.ISVNEventHandler; + +import java.io.File; +import java.util.*; + +public abstract class AbstractSvnUpdateIntegrateEnvironment17 implements UpdateEnvironment { + protected final SvnVcs17 myVcs; + private final ProjectLevelVcsManager myVcsManager; + @NonNls public static final String REPLACED_ID = "replaced"; + @NonNls public static final String EXTERNAL_ID = "external"; + + protected AbstractSvnUpdateIntegrateEnvironment17(final SvnVcs17 vcs) { + myVcs = vcs; + myVcsManager = ProjectLevelVcsManager.getInstance(vcs.getProject()); + } + + public void fillGroups(UpdatedFiles updatedFiles) { + updatedFiles.registerGroup(new FileGroup(VcsBundle.message("update.group.name.merged.with.property.conflicts"), + VcsBundle.message("status.group.name.will.be.merged.with.property.conflicts"), false, + FileGroup.MERGED_WITH_PROPERTY_CONFLICT_ID, false)); + updatedFiles.registerGroup(new FileGroup(VcsBundle.message("update.group.name.merged.with.tree.conflicts"), + VcsBundle.message("status.group.name.will.be.merged.with.tree.conflicts"), false, + FileGroup.MERGED_WITH_TREE_CONFLICT, false)); + } + + @NotNull + public UpdateSession updateDirectories(@NotNull final FilePath[] contentRoots, + final UpdatedFiles updatedFiles, + final ProgressIndicator progressIndicator, @NotNull final Ref context) + throws ProcessCanceledException { + + if (context.isNull()) { + context.set(new SvnUpdateContext(myVcs, contentRoots)); + } + + final ArrayList exceptions = new ArrayList(); + UpdateEventHandler eventHandler = new UpdateEventHandler(myVcs, progressIndicator, (SvnUpdateContext) context.get()); + eventHandler.setUpdatedFiles(updatedFiles); + + boolean totalUpdate = true; + AbstractUpdateIntegrateCrawler crawler = createCrawler(eventHandler, totalUpdate, exceptions, updatedFiles); + + Collection updatedRoots = new HashSet(); + Arrays.sort(contentRoots, new Comparator() { + public int compare(FilePath o1, FilePath o2) { + return SystemInfo.isFileSystemCaseSensitive ? o1.getPath().replace("/", "\\").compareTo(o2.getPath().replace("/", "\\")) : + o1.getPath().replace("/", "\\").compareToIgnoreCase(o2.getPath().replace("/", "\\")); + } + }); + for (FilePath contentRoot : contentRoots) { + if (progressIndicator != null) { + progressIndicator.checkCanceled(); + } + final File ioRoot = contentRoot.getIOFile(); + if (! ((SvnUpdateContext)context.get()).shouldRunFor(ioRoot)) continue; + + Collection roots = SvnUtil.crawlWCRoots(ioRoot, crawler, progressIndicator); + updatedRoots.addAll(roots); + } + if (updatedRoots.isEmpty()) { + WaitForProgressToShow.runOrInvokeLaterAboveProgress(new Runnable() { + public void run() { + Messages.showErrorDialog(myVcs.getProject(), SvnBundle.message("message.text.update.no.directories.found"), + SvnBundle.message("messate.text.update.error")); + } + }, null, myVcs.getProject()); + return new UpdateSessionAdapter(Collections.emptyList(), true); + } + + return new MyUpdateSessionAdapter(contentRoots, updatedFiles, exceptions); + } + + private class MyUpdateSessionAdapter extends UpdateSessionAdapter { + private final FilePath[] myContentRoots; + private final UpdatedFiles myUpdatedFiles; + private final VcsDirtyScopeManager myDirtyScopeManager; + private final List myGroupWorkers; + + private MyUpdateSessionAdapter(@NotNull final FilePath[] contentRoots, final UpdatedFiles updatedFiles, final List exceptions) { + super(exceptions, false); + myContentRoots = contentRoots; + myUpdatedFiles = updatedFiles; + myDirtyScopeManager = VcsDirtyScopeManager.getInstance(myVcs.getProject()); + + if (! isDryRun()) { + myGroupWorkers = Arrays.asList(new MyTextConflictWorker(), new MyConflictWorker(FileGroup.MERGED_WITH_PROPERTY_CONFLICT_ID) { + protected List merge() { + return null; + } + }, new MyTreeConflictWorker(), new MyReplacedWorker()); + } else { + myGroupWorkers = Collections.emptyList(); + } + } + + // update switched/ignored status of directories + private void dirtyRoots() { + final Collection vfColl = new ArrayList(myContentRoots.length); + for (FilePath contentRoot: myContentRoots) { + final VirtualFile vf = contentRoot.getVirtualFile(); + if (vf != null) { + vfColl.add(vf); + } + } + myDirtyScopeManager.filesDirty(vfColl, null); + } + + public void onRefreshFilesCompleted() { + dirtyRoots(); + + for (Runnable groupWorker : myGroupWorkers) { + groupWorker.run(); + } + } + + // not a conflict worker; to correctly show replaced items + private class MyReplacedWorker implements Runnable { + public void run() { + final FileGroup replacedGroup = myUpdatedFiles.getGroupById(REPLACED_ID); + final FileGroup deletedGroup = myUpdatedFiles.getGroupById(FileGroup.REMOVED_FROM_REPOSITORY_ID); + if ((deletedGroup != null) && (replacedGroup != null) && (! deletedGroup.isEmpty()) && (! replacedGroup.isEmpty())) { + final Set replacedFiles = new HashSet(replacedGroup.getFiles()); + final Collection deletedFiles = new HashSet(deletedGroup.getFiles()); + + for (String deletedFile : deletedFiles) { + if (replacedFiles.contains(deletedFile)) { + deletedGroup.remove(deletedFile); + } + } + } + } + } + + // at the moment no resolve, only refresh files & statuses + private class MyTreeConflictWorker implements Runnable { + public void run() { + final LocalFileSystem lfs = LocalFileSystem.getInstance(); + final FileGroup conflictedGroup = myUpdatedFiles.getGroupById(FileGroup.MERGED_WITH_TREE_CONFLICT); + final Collection conflictedFiles = conflictedGroup.getFiles(); + final Collection parents = new ArrayList(); + + if ((conflictedFiles != null) && (! conflictedFiles.isEmpty())) { + for (final String conflictedFile : conflictedFiles) { + final File file = new File(conflictedFile); + final File parent = file.getParentFile(); + + VirtualFile vf = lfs.findFileByIoFile(parent); + if (vf == null) { + vf = lfs.refreshAndFindFileByIoFile(parent); + } + if (vf != null) { + parents.add(vf); + } + } + } + + if (! parents.isEmpty()) { + final RefreshQueue refreshQueue = RefreshQueue.getInstance(); + final RefreshSession session = refreshQueue.createSession(true, true, new Runnable() { + public void run() { + myDirtyScopeManager.filesDirty(parents, null); + } + }); + session.addAllFiles(parents); + session.launch(); + } + } + } + + private class MyTextConflictWorker extends MyConflictWorker { + private MyTextConflictWorker() { + super(FileGroup.MERGED_WITH_CONFLICT_ID); + } + + protected List merge() { + final List writeable = prepareWriteable(myFiles); + final AbstractVcsHelper vcsHelper = AbstractVcsHelper.getInstance(myVcs.getProject()); + return vcsHelper.showMergeDialog(writeable, new SvnMergeProvider(myVcs.getProject())); + } + } + + private abstract class MyConflictWorker implements Runnable { + private final String groupId; + protected final List myFiles; + private final LocalFileSystem myLfs; + private final ProjectLevelVcsManager myPlVcsManager; + + protected MyConflictWorker(final String groupId) { + this.groupId = groupId; + myFiles = new ArrayList(); + myLfs = LocalFileSystem.getInstance(); + myPlVcsManager = ProjectLevelVcsManager.getInstance(myVcs.getProject()); + } + + // for reuse + protected List prepareWriteable(final Collection files) { + final List writeable = new ArrayList(); + for (VirtualFile vf : files) { + if (myVcs.equals(myPlVcsManager.getVcsFor(vf))) { + writeable.add(vf); + } + } + final ReadonlyStatusHandler.OperationStatus operationStatus = + ReadonlyStatusHandler.getInstance(myVcs.getProject()).ensureFilesWritable(writeable); + writeable.removeAll(Arrays.asList(operationStatus.getReadonlyFiles())); + + return writeable; + } + + @Nullable + protected abstract List merge(); + + public void run() { + fillAndRefreshFiles(); + if (! myFiles.isEmpty()) { + final List merged = merge(); + if (merged != null && (! merged.isEmpty())) { + moveToMergedGroup(merged); + + // do we need this + myDirtyScopeManager.filesDirty(merged, null); + } + } + } + + protected void moveToMergedGroup(final List merged) { + final FileGroup conflictedGroup = myUpdatedFiles.getGroupById(groupId); + FileGroup mergedGroup = myUpdatedFiles.getGroupById(FileGroup.MERGED_ID); + for (VirtualFile mergedFile: merged) { + final String path = FileUtil.toSystemDependentName(mergedFile.getPresentableUrl()); + final VcsRevisionNumber revision = conflictedGroup.getRevision(myVcsManager, path); + conflictedGroup.remove(path); + mergedGroup.add(path, myVcs.getKeyInstanceMethod(), revision); + } + } + + protected void fillAndRefreshFiles() { + final FileGroup conflictedGroup = myUpdatedFiles.getGroupById(groupId); + final Collection conflictedFiles = conflictedGroup.getFiles(); + final Collection parents = new ArrayList(); + + if ((conflictedFiles != null) && (! conflictedFiles.isEmpty())) { + for (final String conflictedFile : conflictedFiles) { + final File file = new File(conflictedFile); + VirtualFile vf = myLfs.findFileByIoFile(file); + if (vf == null) { + vf = myLfs.refreshAndFindFileByIoFile(file); + } + if (vf != null) { + myFiles.add(vf); + final VirtualFile parent = vf.getParent(); + if (parent != null) { + parents.add(parent); + } + } + } + } + + if (! myFiles.isEmpty()) { + final RefreshQueue refreshQueue = RefreshQueue.getInstance(); + final RefreshSession session = refreshQueue.createSession(true, true, null); + session.addAllFiles(parents); + session.launch(); + + myDirtyScopeManager.filesDirty(myFiles, null); + } + } + + public String getGroupId() { + return groupId; + } + } + } + + protected boolean isDryRun() { + return false; + } + + protected abstract AbstractUpdateIntegrateCrawler createCrawler(ISVNEventHandler eventHandler, + boolean totalUpdate, + ArrayList exceptions, UpdatedFiles updatedFiles); + + @Nullable + public abstract Configurable createConfigurable(Collection collection); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/AbstractSvnUpdatePanel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/AbstractSvnUpdatePanel.java new file mode 100644 index 000000000000..e55593f7f7d8 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/AbstractSvnUpdatePanel.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.update; + +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.util.FilePathByPathComparator; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.DepthCombo; +import org.jetbrains.idea.svn17.SvnConfiguration17; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.SVNInfo; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import javax.swing.*; +import java.awt.*; +import java.util.*; + +public abstract class AbstractSvnUpdatePanel { + protected final SvnVcs17 myVCS; + + private final Map myRootToPanel = new LinkedHashMap(); + + public AbstractSvnUpdatePanel(final SvnVcs17 vcs) { + myVCS = vcs; + } + + protected void init(final Collection roots) { + final JPanel configureRootsPanel = getRootsPanel(); + configureRootsPanel.setLayout(new BorderLayout()); + + final ArrayList rootsCopy = new ArrayList(roots); + Collections.sort(rootsCopy, FilePathByPathComparator.getInstance()); + for (FilePath root : rootsCopy) { + SVNURL url = getUrlFor(root); + if (url != null) { + myRootToPanel.put(root, createRootPanel(root, myVCS, roots)); + } + + if (myRootToPanel.size() == 1) { + + configureRootsPanel.add(myRootToPanel.values().iterator().next().getPanel(), BorderLayout.CENTER); + } + else { + final MultipleRootEditorWithSplitter multipleRootsEditor = new MultipleRootEditorWithSplitter(myRootToPanel, myVCS.getProject()); + configureRootsPanel.add(multipleRootsEditor, BorderLayout.CENTER); + } + } + } + + protected abstract SvnPanel createRootPanel(final FilePath root, final SvnVcs17 p1, Collection roots); + + protected abstract JPanel getRootsPanel(); + + public void reset(final SvnConfiguration17 configuration) { + getDepthBox().setSelectedItem(configuration.UPDATE_DEPTH); + + for (FilePath filePath : myRootToPanel.keySet()) { + myRootToPanel.get(filePath).reset(configuration); + } + + } + + public void apply(final SvnConfiguration17 configuration) throws ConfigurationException { + configuration.UPDATE_DEPTH = getDepthBox().getDepth(); + + for (FilePath filePath : myRootToPanel.keySet()) { + final SvnPanel svnPanel = myRootToPanel.get(filePath); + if (svnPanel.canApply()) { + svnPanel.apply(configuration); + } + } + } + + @Nullable + private SVNURL getUrlFor(@NotNull final FilePath root) { + try { + SVNWCClient wcClient = myVCS.createWCClient(); + final SVNInfo info = wcClient.doInfo(root.getIOFile(), SVNRevision.UNDEFINED); + if (info != null) { + return info.getURL(); + } + return null; + } + catch (SVNException e) { + return null; + } + } + + protected abstract JComponent getPanel(); + + protected abstract DepthCombo getDepthBox(); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/AbstractUpdateIntegrateCrawler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/AbstractUpdateIntegrateCrawler.java new file mode 100644 index 000000000000..6b1c52d6bb9e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/AbstractUpdateIntegrateCrawler.java @@ -0,0 +1,79 @@ +/* + * 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.update; + +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.update.UpdatedFiles; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.SvnWCRootCrawler; +import org.tmatesoft.svn.core.SVNErrorCode; +import org.tmatesoft.svn.core.SVNErrorMessage; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.ISVNEventHandler; +import org.tmatesoft.svn.core.wc.SVNUpdateClient; + +import java.io.File; +import java.util.Collection; + +public abstract class AbstractUpdateIntegrateCrawler implements SvnWCRootCrawler { + protected final SvnVcs17 myVcs; + protected final ISVNEventHandler myHandler; + protected final Collection myExceptions; + protected final UpdatedFiles myPostUpdateFiles; + protected final boolean myIsTotalUpdate; + + protected AbstractUpdateIntegrateCrawler( + final boolean isTotalUpdate, + final UpdatedFiles postUpdateFiles, + final Collection exceptions, + final ISVNEventHandler handler, + final SvnVcs17 vcs) { + myIsTotalUpdate = isTotalUpdate; + myPostUpdateFiles = postUpdateFiles; + myExceptions = exceptions; + myHandler = handler; + myVcs = vcs; + } + + public void handleWorkingCopyRoot(File root, ProgressIndicator progress) { + if (progress != null) { + showProgressMessage(progress, root); + } + try { + SVNUpdateClient client = myVcs.createUpdateClient(); + client.setEventHandler(myHandler); + + long rev = doUpdate(root, client); + + if (rev < 0 && !isMerge()) { + throw new SVNException(SVNErrorMessage.create(SVNErrorCode.UNKNOWN, SvnBundle.message("exception.text.root.was.not.properly.updated", root))); + } + } + catch (SVNException e) { + myExceptions.add(new VcsException(e)); + } + } + + protected abstract void showProgressMessage(ProgressIndicator progress, File root); + + protected abstract long doUpdate( + File root, + SVNUpdateClient client) throws SVNException; + + protected abstract boolean isMerge(); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/MergeRootInfo.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/MergeRootInfo.java new file mode 100644 index 000000000000..07b284ac1e6c --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/MergeRootInfo.java @@ -0,0 +1,103 @@ +/* + * 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.update; + +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.io.File; + +public class MergeRootInfo { + private String myUrl1; + private SVNRevision myRevision1; + private String myUrl2; + private SVNRevision myRevision2; + + public MergeRootInfo(File file, SvnVcs17 vcs) { + myRevision1 = SVNRevision.HEAD; + myRevision2 = SVNRevision.HEAD; + + try { + SVNWCClient wcClient = vcs.createWCClient(); + final SVNURL url = wcClient.doInfo(file, SVNRevision.UNDEFINED).getURL(); + myUrl1 = url.toString(); + myUrl2 = url.toString(); + } + catch (SVNException e) { + myUrl1 = ""; + myUrl2 = ""; + } + + } + + public SVNURL getUrl1() { + try { + return SVNURL.parseURIDecoded(myUrl1); + } + catch (SVNException e) { + return null; + } + } + + public SVNURL getUrl2() { + try { + return SVNURL.parseURIDecoded(myUrl2); + } + catch (SVNException e) { + return null; + } + } + + public SVNRevision getRevision2() { + return myRevision2; + } + + public SVNRevision getRevision1() { + return myRevision1; + } + + public long getResultRevision() { + return myRevision2.getNumber(); + } + + public void setUrl1(final String text) { + myUrl1 = text; + } + + public void setUrl2(final String text) { + myUrl2 = text; + } + + public void setRevision1(final SVNRevision rev) { + myRevision1 = rev; + } + + public void setRevision2(final SVNRevision rev) { + myRevision2 = rev; + } + + public String getUrlString1() { + return myUrl1; + } + + public String getUrlString2() { + return myUrl2; + } + +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/MultipleRootEditorWithSplitter.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/MultipleRootEditorWithSplitter.java new file mode 100644 index 000000000000..9afcce2955e9 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/MultipleRootEditorWithSplitter.java @@ -0,0 +1,137 @@ +/* + * 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.update; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Splitter; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.impl.VcsPathPresenter; +import com.intellij.ui.ColoredListCellRenderer; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.ui.components.JBList; +import com.intellij.util.ui.AdjustComponentWhenShown; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NonNls; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import java.awt.*; +import java.io.File; +import java.util.Map; + +/** + * @author irengrig + * Date: 1/14/11 + * Time: 1:17 PM + */ +public class MultipleRootEditorWithSplitter extends JPanel { + private JList myList; + private JPanel myConfigureRootPanel; + @NonNls private static final String EMPTY = "empty"; + + public MultipleRootEditorWithSplitter(final Map rootToPanel, final Project project) { + super(new BorderLayout()); + + final Splitter splitter = new Splitter(false, 0.5f); + splitter.setShowDividerControls(true); + add(splitter, BorderLayout.CENTER); + + myList = new JBList(); + final Color borderColor = UIUtil.getBorderColor(); + myList.setBorder(BorderFactory.createLineBorder(borderColor)); + myConfigureRootPanel = new JPanel(); + myConfigureRootPanel.setBorder(BorderFactory.createLineBorder(borderColor)); + splitter.setFirstComponent(myList); + splitter.setSecondComponent(myConfigureRootPanel); + + final CardLayout layout = new CardLayout(); + myConfigureRootPanel.setLayout(layout); + + final DefaultListModel listModel = new DefaultListModel(); + + layout.addLayoutComponent(new JPanel(), EMPTY); + + int minimumRightSize = 320; + for (FilePath root : rootToPanel.keySet()) { + final JPanel panel = rootToPanel.get(root).getPanel(); + myConfigureRootPanel.add(panel, root.getPath()); + if (panel.getMinimumSize().width > 0) { + minimumRightSize = Math.max(minimumRightSize, panel.getPreferredSize().width); + } + listModel.addElement(root); + } + + myConfigureRootPanel.revalidate(); + + myList.setModel(listModel); + + myList.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + myList.setCellRenderer(new ColoredListCellRenderer(){ + protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) { + if (value instanceof FilePath) { + final FilePath path = ((FilePath)value); + if (path.getVirtualFile() != null) { + append(VcsPathPresenter.getInstance(project).getPresentableRelativePathFor(path.getVirtualFile()), + SimpleTextAttributes.REGULAR_ATTRIBUTES); + } else { + append(VcsPathPresenter.getInstance(project).getPresentableRelativePathFor(path.getVirtualFileParent()) + File.separator + path.getName(), + SimpleTextAttributes.REGULAR_ATTRIBUTES); + + } + } + } + }); + + myList.addListSelectionListener(new ListSelectionListener() { + public void valueChanged(ListSelectionEvent e) { + final FilePath root = ((FilePath)myList.getSelectedValue()); + if (root != null) { + layout.show(myConfigureRootPanel, root.getPath()); + } else { + layout.show(myConfigureRootPanel, EMPTY); + } + } + }); + + myList.setSelectedIndex(0); + myList.requestFocus(); + + final int finalMinimumRightSize = minimumRightSize; + new AdjustComponentWhenShown() { + @Override + protected boolean init() { + if (getWidth() < finalMinimumRightSize * 2) { + final int left = getWidth() - finalMinimumRightSize; + final float newProportion; + if (left < 0) { + newProportion = 0.2f; + } else { + newProportion = ((float) left / getWidth()); + } + splitter.setProportion(newProportion); + } + return true; + } + + @Override + protected boolean canExecute() { + return getWidth() > 0; + } + }.install(splitter); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnIntegrateEnvironment17.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnIntegrateEnvironment17.java new file mode 100644 index 000000000000..d50242a391c1 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnIntegrateEnvironment17.java @@ -0,0 +1,154 @@ +/* + * 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.update; + +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.update.UpdatedFiles; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnConfiguration17; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.io.SVNRepository; +import org.tmatesoft.svn.core.wc.ISVNEventHandler; +import org.tmatesoft.svn.core.wc.SVNDiffClient; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNUpdateClient; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; + +public class SvnIntegrateEnvironment17 extends AbstractSvnUpdateIntegrateEnvironment17 { + public SvnIntegrateEnvironment17(final SvnVcs17 vcs) { + super(vcs); + } + + protected AbstractUpdateIntegrateCrawler createCrawler(ISVNEventHandler eventHandler, + boolean totalUpdate, + ArrayList exceptions, + UpdatedFiles updatedFiles) { + return new IntegrateCrawler(myVcs, eventHandler, totalUpdate, exceptions, updatedFiles); + } + + public Configurable createConfigurable(final Collection collection) { + if (collection.isEmpty()) return null; + return new SvnUpdateConfigurable(myVcs.getProject()){ + protected AbstractSvnUpdatePanel createPanel() { + return new SvnIntegratePanel(myVcs, collection); + } + + public String getDisplayName() { + return SvnBundle.message("integrate.display.name"); + } + + public String getHelpTopic() { + return "reference.dialogs.versionControl.integrate.project.subversion"; + } + }; + } + + @Override + protected boolean isDryRun() { + return SvnConfiguration17.getInstance(myVcs.getProject()).MERGE_DRY_RUN; + } + + private class IntegrateCrawler extends AbstractUpdateIntegrateCrawler { + + public IntegrateCrawler(SvnVcs17 vcs, + ISVNEventHandler handler, + boolean totalUpdate, + Collection exceptions, + UpdatedFiles postUpdateFiles) { + super(totalUpdate, + postUpdateFiles, + exceptions, + handler, vcs); + } + + protected void showProgressMessage(final ProgressIndicator progress, final File root) { + if (SvnConfiguration17.getInstance(myVcs.getProject()).MERGE_DRY_RUN) { + progress.setText(SvnBundle.message("progress.text.merging.dry.run.changes", root.getAbsolutePath())); + } + else { + progress.setText(SvnBundle.message("progress.text.merging.changes", root.getAbsolutePath())); + } + } + + protected long doUpdate( + final File root, + final SVNUpdateClient client) throws + SVNException { + final SvnConfiguration17 svnConfig = SvnConfiguration17.getInstance(myVcs.getProject()); + + MergeRootInfo info = svnConfig.getMergeRootInfo(root, myVcs); + if (info.getUrlString1().equals(info.getUrlString2()) && + info.getRevision1().equals(info.getRevision2())) { + return 0; + } + + SVNDiffClient diffClient = myVcs.createDiffClient(); + diffClient.setEventHandler(myHandler); + diffClient.doMerge(info.getUrl1(), info.getRevision1(), + info.getUrl2(), info.getRevision2(), root, + svnConfig.UPDATE_DEPTH, svnConfig.MERGE_DIFF_USE_ANCESTRY, false, svnConfig.MERGE_DRY_RUN, false); + + svnConfig.LAST_MERGED_REVISION = getLastMergedRevision(info.getRevision2(), info.getUrl2()); + return info.getResultRevision(); + } + + protected boolean isMerge() { + return true; + } + } + + @Nullable + private String getLastMergedRevision(final SVNRevision rev2, final SVNURL svnURL2) { + if (!rev2.isValid() || rev2.isLocal()) { + return null; + } + else { + final long number = rev2.getNumber(); + if (number > 0) { + return String.valueOf(number); + } + else { + + SVNRepository repos = null; + try { + repos = myVcs.createRepository(svnURL2.toString()); + final long latestRev = repos.getLatestRevision(); + return String.valueOf(latestRev); + } + catch (SVNException e) { + return null; + } finally { + if (repos != null) { + repos.closeSession(); + } + } + } + } + } + + public boolean validateOptions(final Collection roots) { + return true; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnIntegratePanel.form b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnIntegratePanel.form new file mode 100644 index 000000000000..ba07b908593d --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnIntegratePanel.form @@ -0,0 +1,53 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnIntegratePanel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnIntegratePanel.java new file mode 100644 index 000000000000..d889a93347d0 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnIntegratePanel.java @@ -0,0 +1,82 @@ +/* + * 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.update; + +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.vcs.FilePath; +import org.jetbrains.idea.svn17.DepthCombo; +import org.jetbrains.idea.svn17.SvnConfiguration17; +import org.jetbrains.idea.svn17.SvnVcs17; + +import javax.swing.*; +import java.util.Collection; + +public class SvnIntegratePanel extends AbstractSvnUpdatePanel{ + + private JCheckBox myDryRunCheckbox; + private JPanel myRootOptionsPanel; + private JPanel myPanel; + private JCheckBox myUseAncestry; + private DepthCombo myDepthCombo; + private JLabel myDepthLabel; + + public SvnIntegratePanel(final SvnVcs17 vcs, Collection roots) { + super(vcs); + init(roots); + + boolean descend = false; + for (FilePath root : roots) { + if (root.isDirectory()) { + descend = true; + break; + } + } + myDepthCombo.setVisible(descend); + myDepthLabel.setVisible(descend); + myDepthLabel.setLabelFor(myDepthCombo); + } + + protected SvnPanel createRootPanel(final FilePath root, final SvnVcs17 p1, Collection roots) { + return new SvnIntegrateRootOptionsPanel(myVCS, root); + } + + protected JPanel getRootsPanel() { + return myRootOptionsPanel; + } + + public void reset(final SvnConfiguration17 configuration) { + super.reset(configuration); + myDryRunCheckbox.setSelected(configuration.MERGE_DRY_RUN); + myUseAncestry.setSelected(configuration.MERGE_DIFF_USE_ANCESTRY); + } + public void apply(final SvnConfiguration17 configuration) throws ConfigurationException { + super.apply(configuration); + configuration.MERGE_DRY_RUN = myDryRunCheckbox.isSelected(); + configuration.MERGE_DIFF_USE_ANCESTRY = myUseAncestry.isSelected(); + } + + protected JComponent getPanel() { + return myPanel; + } + + protected DepthCombo getDepthBox() { + return myDepthCombo; + } + + private void createUIComponents() { + myDepthCombo = new DepthCombo(true); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnIntegrateRootOptionsPanel.form b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnIntegrateRootOptionsPanel.form new file mode 100644 index 000000000000..d71684c7f883 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnIntegrateRootOptionsPanel.form @@ -0,0 +1,116 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnIntegrateRootOptionsPanel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnIntegrateRootOptionsPanel.java new file mode 100644 index 000000000000..1fff6b1f7331 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnIntegrateRootOptionsPanel.java @@ -0,0 +1,147 @@ +/* + * 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.update; + +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.vcs.FilePath; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnConfiguration17; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.dialogs.SelectLocationDialog; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class SvnIntegrateRootOptionsPanel implements SvnPanel{ + private TextFieldWithBrowseButton myMergeText1; + private TextFieldWithBrowseButton myMergeText2; + + private JPanel myPanel; + private final FilePath myRoot; + private final SvnVcs17 myVcs; + private SvnRevisionPanel myRevision2; + private SvnRevisionPanel myRevision1; + private JLabel myUrlLabel1; + + public SvnIntegrateRootOptionsPanel(final SvnVcs17 vcs, FilePath root) { + myRoot = root; + myVcs = vcs; + + //myPanel.setMinimumSize(new Dimension(670, 70)); + myMergeText1.setEditable(true); + + myUrlLabel1.setLabelFor(myMergeText1); + + myMergeText2.setEditable(true); + + myMergeText1.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + chooseUrl(myMergeText1, vcs); + } + }); + myMergeText2.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + chooseUrl2(vcs); + } + }); + + myRevision1.setProject(vcs.getProject()); + myRevision2.setProject(vcs.getProject()); + + myRevision1.setRoot(myRoot.getVirtualFile()); + myRevision2.setRoot(myRoot.getVirtualFile()); + + myRevision1.setUrlProvider(new SvnRevisionPanel.UrlProvider() { + public String getUrl() { + return myMergeText1.getText(); + } + }); + + myRevision2.setUrlProvider(new SvnRevisionPanel.UrlProvider() { + public String getUrl() { + return myMergeText2.getText(); + } + }); + + } + + private boolean chooseUrl2(final SvnVcs17 vcs) { + return chooseUrl(myMergeText2, vcs); + } + + private boolean chooseUrl(final TextFieldWithBrowseButton textField, final SvnVcs17 vcs) { + String url = textField.getText(); + final String selectedUrl = SelectLocationDialog.selectLocation(vcs.getProject(), url); + if (selectedUrl != null) { + textField.setText(selectedUrl); + return true; + } else { + return false; + } + } + + public void apply(SvnConfiguration17 conf) throws ConfigurationException { + + if (myMergeText1.getText().trim().length() == 0) { + myMergeText1.getTextField().requestFocus(); + throw new ConfigurationException(SvnBundle.message("source.url.could.not.be.empty.error.message")); + } + + if (myMergeText2.getText().trim().length() == 0) { + myMergeText2.getTextField().requestFocus(); + throw new ConfigurationException(SvnBundle.message("source.url.could.not.be.empty.error.message")); + } + + if (myMergeText1.getText().equals(myMergeText2.getText()) && myRevision1.getRevisionText().equals(myRevision2.getRevisionText())) { + throw new ConfigurationException(SvnBundle.message("no.differences.between.sources.error.message")); + } + + final MergeRootInfo rootInfo = conf.getMergeRootInfo(myRoot.getIOFile(), myVcs); + rootInfo.setUrl1(myMergeText1.getText()); + rootInfo.setUrl2(myMergeText2.getText()); + rootInfo.setRevision1(myRevision1.getRevision()); + rootInfo.setRevision2(myRevision2.getRevision()); + + } + + public boolean canApply() { + return !myMergeText1.getText().equals(myMergeText2.getText()) || !myRevision1.getRevisionText().equals(myRevision2.getRevisionText()); + } + + public JPanel getPanel() { + return myPanel; + } + + public void reset(SvnConfiguration17 config) { + final MergeRootInfo rootInfo = config.getMergeRootInfo(myRoot.getIOFile(), myVcs); + myRevision1.setRevision(rootInfo.getRevision1()); + myRevision2.setRevision(rootInfo.getRevision2()); + myMergeText1.setText(rootInfo.getUrlString1()); + myMergeText2.setText(rootInfo.getUrlString2()); + } + + public void setData(SvnRevisionPanel data) { + } + + public void getData(SvnRevisionPanel data) { + } + + public boolean isModified(SvnRevisionPanel data) { + return false; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnPanel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnPanel.java new file mode 100644 index 000000000000..d9e07345ddd4 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnPanel.java @@ -0,0 +1,28 @@ +/* + * 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.update; + +import com.intellij.openapi.options.ConfigurationException; +import org.jetbrains.idea.svn17.SvnConfiguration17; + +import javax.swing.*; + +public interface SvnPanel { + void reset(SvnConfiguration17 conf); + void apply(SvnConfiguration17 conf) throws ConfigurationException; + boolean canApply(); + JPanel getPanel(); +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnRevisionPanel.form b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnRevisionPanel.form new file mode 100644 index 000000000000..5aa3f20a5307 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnRevisionPanel.form @@ -0,0 +1,41 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnRevisionPanel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnRevisionPanel.java new file mode 100644 index 000000000000..8bd02eac25e7 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnRevisionPanel.java @@ -0,0 +1,177 @@ +/* + * 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.update; + +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.DocumentAdapter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.history.SvnChangeList; +import org.jetbrains.idea.svn17.history.SvnRepositoryLocation; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; + +public class SvnRevisionPanel extends JPanel { + private JRadioButton mySpecified; + private JRadioButton myHead; + private JPanel myPanel; + private TextFieldWithBrowseButton myRevisionField; + private Project myProject; + private UrlProvider myUrlProvider; + private final ArrayList myChangeListeners = new ArrayList(); + private VirtualFile myRoot; + + public SvnRevisionPanel() { + super(new BorderLayout()); + add(myPanel); + myHead.setSelected(true); + myRevisionField.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + chooseRevision(); + } + }); + +// myRevisionField.setEditable(false); + myRevisionField.setEnabled(false); + + mySpecified.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (mySpecified.isSelected()) { + if (myRevisionField.getText().trim().length() == 0) { + myRevisionField.setText("HEAD"); + } + myRevisionField.setEnabled(true); + } else { + myRevisionField.setEnabled(false); + } + notifyChangeListeners(); + } + }); + + myHead.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + myRevisionField.setEnabled(false); + notifyChangeListeners(); + } + }); + + myRevisionField.getTextField().setColumns(10); + myRevisionField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() { + protected void textChanged(final DocumentEvent e) { + notifyChangeListeners(); + } + }); + } + + private void chooseRevision() { + if (myProject != null && myUrlProvider != null) { + final SvnRepositoryLocation location = new SvnRepositoryLocation(myUrlProvider.getUrl()); + + final SvnChangeList version = SvnSelectRevisionUtil.chooseCommittedChangeList(myProject, location, myRoot); + if (version != null) { + myRevisionField.setText(String.valueOf(version.getNumber())); + } + } + } + + public void setProject(final Project project) { + myProject = project; + } + + public void setRoot(final VirtualFile root) { + myRoot = root; + } + + public void setUrlProvider(final UrlProvider urlProvider) { + myUrlProvider = urlProvider; + } + + public String getRevisionText() { + return myHead.isSelected() ? SVNRevision.HEAD.toString() : myRevisionField.getText(); + } + + @NotNull + public SVNRevision getRevision() throws ConfigurationException { + + if (myHead.isSelected()) return SVNRevision.HEAD; + + final SVNRevision result = SVNRevision.parse(myRevisionField.getText()); + if (!result.isValid()) { + throw new ConfigurationException(SvnBundle.message("invalid.svn.revision.error.message", myRevisionField.getText())); + } + + return result; + } + + public void setRevisionText(String text) { + myRevisionField.setText(text); + } + + public void setRevision(final SVNRevision revision) { + if (revision == SVNRevision.HEAD) { + myHead.setSelected(true); + myRevisionField.setEnabled(false); + } else { + myRevisionField.setText(String.valueOf(revision.getNumber())); + mySpecified.setSelected(true); + myRevisionField.setEnabled(true); + } + } + + @Override + public void setEnabled(final boolean enabled) { + super.setEnabled(enabled); + if (!enabled) { + myHead.setEnabled(false); + mySpecified.setEnabled(false); + myRevisionField.setEnabled(false); + } + else { + myHead.setEnabled(true); + mySpecified.setEnabled(true); + myRevisionField.setEnabled(mySpecified.isSelected()); + } + } + + public void addChangeListener(ChangeListener listener) { + myChangeListeners.add(listener); + } + + public void removeChangeListener(ChangeListener listener) { + myChangeListeners.remove(listener); + } + + private void notifyChangeListeners() { + for(ChangeListener listener: myChangeListeners) { + listener.stateChanged(new ChangeEvent(this)); + } + } + + public interface UrlProvider { + String getUrl(); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnSelectRevisionUtil.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnSelectRevisionUtil.java new file mode 100644 index 000000000000..9ba7351b1694 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnSelectRevisionUtil.java @@ -0,0 +1,52 @@ +/* + * 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.update; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.committed.ChangesBrowserDialog; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.jetbrains.idea.svn17.history.SvnChangeList; +import org.jetbrains.idea.svn17.history.SvnCommittedChangesTableModel; +import org.jetbrains.idea.svn17.history.SvnRepositoryLocation; + +public class SvnSelectRevisionUtil { + private SvnSelectRevisionUtil() { + } + + @Nullable + public static SvnChangeList chooseCommittedChangeList(final Project project, final SvnRepositoryLocation location, + final VirtualFile root) { + try { + final SvnCommittedChangesTableModel model = new SvnCommittedChangesTableModel(location, project, root, + SvnVcs17.getInstance(project).getCommittedChangesProvider().getColumns()); + final ChangesBrowserDialog dlg = new ChangesBrowserDialog(project, model, ChangesBrowserDialog.Mode.Choose, null); + dlg.show(); + if (dlg.isOK()) { + return (SvnChangeList) dlg.getSelectedChangeList(); + } + model.onBeforeClose(); + } + catch (VcsException e) { + Messages.showErrorDialog(e.getMessage(), SvnBundle.message("error.cannot.load.revisions")); + } + return null; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdateConfigurable.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdateConfigurable.java new file mode 100644 index 000000000000..3a2a55481563 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdateConfigurable.java @@ -0,0 +1,72 @@ +/* + * 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.update; + +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.idea.svn17.SvnConfiguration17; + +import javax.swing.*; + +public abstract class SvnUpdateConfigurable implements Configurable { + @NonNls private static final String HELP_ID = "vcs.subversion.updateProject"; + + private AbstractSvnUpdatePanel myPanel; + + private final Project myProject; + + public SvnUpdateConfigurable(Project project) { + myProject = project; + } + + public String getHelpTopic() { + return HELP_ID; + } + + + public void apply() throws ConfigurationException { + myPanel.apply(SvnConfiguration17.getInstance(myProject)); + } + + public Icon getIcon() { + return null; + } + + public JComponent createComponent() { + myPanel = createPanel(); + return myPanel.getPanel(); + } + + protected abstract AbstractSvnUpdatePanel createPanel(); + + public boolean isModified() { + return false; + } + + public void reset() { + myPanel.reset(SvnConfiguration17.getInstance(myProject)); + } + + public void disposeUIResources() { + myPanel = null; + } + + protected Project getProject() { + return myProject; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdateContext.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdateContext.java new file mode 100644 index 000000000000..be16cee86491 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdateContext.java @@ -0,0 +1,66 @@ +/* + * 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.update; + +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.update.SequentialUpdatesContext; +import com.intellij.util.FilePathByPathComparator; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.svn17.FilePathUtil; +import org.jetbrains.idea.svn17.NestedCopyType; +import org.jetbrains.idea.svn17.RootUrlInfo; +import org.jetbrains.idea.svn17.SvnVcs17; + +import java.io.File; +import java.util.*; + +public class SvnUpdateContext implements SequentialUpdatesContext { + private final Set myUpdatedExternals; + private final SvnVcs17 myVcs; + private final List myContentRoots; + + public SvnUpdateContext(final SvnVcs17 vcs, FilePath[] contentRoots) { + myVcs = vcs; + myContentRoots = Arrays.asList(contentRoots); + Collections.sort(myContentRoots, FilePathByPathComparator.getInstance()); + myUpdatedExternals = new HashSet(); + } + + @NotNull + public String getMessageWhenInterruptedBeforeStart() { + // never + return null; + } + + public boolean shouldFail() { + return false; + } + + public void registerExternalRootBeingUpdated(final File root) { + myUpdatedExternals.add(root); + } + + public boolean shouldRunFor(final File ioRoot) { + if (myUpdatedExternals.contains(ioRoot)) return false; + if (FilePathUtil.isNested(myContentRoots, ioRoot)) { + final RootUrlInfo info = myVcs.getSvnFileUrlMapping().getWcRootForFilePath(ioRoot); + if (info != null) { + return ! NestedCopyType.switched.equals(info.getType()); + } + } + return true; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdateEnvironment17.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdateEnvironment17.java new file mode 100644 index 000000000000..1f9155c6719c --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdateEnvironment17.java @@ -0,0 +1,204 @@ +/* + * 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.update; + +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.update.UpdatedFiles; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnConfiguration17; +import org.jetbrains.idea.svn17.SvnRevisionNumber; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.io.SVNRepository; +import org.tmatesoft.svn.core.wc.*; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; + +public class SvnUpdateEnvironment17 extends AbstractSvnUpdateIntegrateEnvironment17 { + + public SvnUpdateEnvironment17(SvnVcs17 vcs) { + super(vcs); + } + + protected AbstractUpdateIntegrateCrawler createCrawler(final ISVNEventHandler eventHandler, + final boolean totalUpdate, + final ArrayList exceptions, final UpdatedFiles updatedFiles) { + return new UpdateCrawler(myVcs, eventHandler, totalUpdate, exceptions, updatedFiles); + } + + public Configurable createConfigurable(final Collection collection) { + if (collection.isEmpty()) return null; + return new SvnUpdateConfigurable(myVcs.getProject()){ + + public String getDisplayName() { + return SvnBundle.message("update.switch.configurable.name"); + } + + protected AbstractSvnUpdatePanel createPanel() { + + return new SvnUpdatePanel(myVcs, collection); + } + }; + } + + protected static class UpdateCrawler extends AbstractUpdateIntegrateCrawler { + public UpdateCrawler(SvnVcs17 vcs, ISVNEventHandler handler, boolean totalUpdate, + Collection exceptions, UpdatedFiles postUpdateFiles) { + super(totalUpdate, postUpdateFiles, exceptions, handler, vcs); + } + + protected void showProgressMessage(final ProgressIndicator progress, final File root) { + progress.setText(SvnBundle.message("progress.text.updating", root.getAbsolutePath())); + } + + protected long doUpdate( + final File root, + final SVNUpdateClient client) throws + SVNException { + final long rev; + + final SvnConfiguration17 configuration = SvnConfiguration17.getInstance(myVcs.getProject()); + final UpdateRootInfo rootInfo = configuration.getUpdateRootInfo(root, myVcs); + + final SVNUpdateClient updateClient = myVcs.createUpdateClient(); + updateClient.setEventHandler(myHandler); + updateClient.setUpdateLocksOnDemand(configuration.UPDATE_LOCK_ON_DEMAND); + if (rootInfo != null) { + final SVNURL url = rootInfo.getUrl(); + if (url != null && url.equals(getSourceUrl(myVcs, root))) { + if (rootInfo.isUpdateToRevision()) { + rev = updateClient.doUpdate(root, rootInfo.getRevision(), configuration.UPDATE_DEPTH, configuration.FORCE_UPDATE, false); + } else { + rev = updateClient.doUpdate(root, SVNRevision.HEAD, configuration.UPDATE_DEPTH, configuration.FORCE_UPDATE, false); + } + + } else if (url != null) { + rev = updateClient.doSwitch(root, url, SVNRevision.UNDEFINED, rootInfo.getRevision(), configuration.UPDATE_DEPTH, configuration.FORCE_UPDATE, false); + } else { + rev = updateClient.doUpdate(root, SVNRevision.HEAD, configuration.UPDATE_DEPTH, configuration.FORCE_UPDATE, false); + } + } else { + rev = updateClient.doUpdate(root, SVNRevision.HEAD, configuration.UPDATE_DEPTH, configuration.FORCE_UPDATE, false); + } + + myPostUpdateFiles.setRevisions(root.getAbsolutePath(), myVcs, new SvnRevisionNumber(SVNRevision.create(rev))); + + return rev; + } + + protected boolean isMerge() { + return false; + } + } + + @Nullable + private static SVNURL getSourceUrl(final SvnVcs17 vcs, final File root) { + try { + SVNWCClient wcClient = vcs.createWCClient(); + final SVNInfo svnInfo = wcClient.doInfo(root, SVNRevision.UNDEFINED); + if (svnInfo != null) { + return svnInfo.getURL(); + } else { + return null; + } + } + catch (SVNException e) { + return null; + } + } + + public boolean validateOptions(final Collection roots) { + /*final SvnConfiguration configuration = SvnConfiguration.getInstance(myVcs.getProject()); + + final Map map = configuration.getUpdateInfosMap(); + try { + for (FilePath root : roots) { + final File ioFile = root.getIOFile(); + final UpdateRootInfo value = map.get(ioFile); + if (value == null) { + continue; + } + final SVNURL url = value.getUrl(); + if (url != null && (! url.equals(getSourceUrl(myVcs, root.getIOFile())))) { + // switch + final SVNRevision updateRevision = correctRevision(value, url, value.getRevision()); + return true; + // should be turned on after bugfix with copy url + //return checkAncestry(ioFile, url, updateRevision); + } + } + } + catch (SVNException e) { + Messages.showErrorDialog(myVcs.getProject(), e.getMessage(), SvnBundle.message("switch.target.problem.title")); + return false; + }*/ + + return true; + } + + private SVNRevision correctRevision(final UpdateRootInfo value, final SVNURL url, final SVNRevision updateRevision) throws SVNException { + if (SVNRevision.HEAD.equals(value.getRevision())) { + // find acual revision to update to (a bug if just say head in switch) + SVNRepository repository = null; + try { + repository = myVcs.createRepository(url); + final long longRevision = repository.getLatestRevision(); + final SVNRevision newRevision = SVNRevision.create(longRevision); + value.setRevision(newRevision); + return newRevision; + } finally { + if (repository != null) { + repository.closeSession(); + } + } + } + return updateRevision; + } + + // false - do not do update + private boolean checkAncestry(final File sourceFile, final SVNURL targetUrl, final SVNRevision targetRevision) throws SVNException { + final SVNWCClient client = myVcs.createWCClient(); + final SVNInfo sourceSvnInfo = client.doInfo(sourceFile, SVNRevision.UNDEFINED); + final SVNInfo targetSvnInfo = client.doInfo(targetUrl, SVNRevision.UNDEFINED, targetRevision); + + if (sourceSvnInfo == null || targetSvnInfo == null) { + // cannot check + return true; + } + + final SVNURL copyFromTarget = targetSvnInfo.getCopyFromURL(); + final SVNURL copyFromSource = sourceSvnInfo.getCopyFromURL(); + + if ((copyFromSource != null) || (copyFromTarget != null)) { + if (sourceSvnInfo.getURL().equals(copyFromTarget) || targetUrl.equals(copyFromSource)) { + return true; + } + } + + final int result = Messages.showYesNoDialog(myVcs.getProject(), SvnBundle.message("switch.target.not.copy.current"), + SvnBundle.message("switch.target.problem.title"), Messages.getWarningIcon()); + return (DialogWrapper.OK_EXIT_CODE == result); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdateGroups.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdateGroups.java new file mode 100644 index 000000000000..4a285ada1776 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdateGroups.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.update; + +public interface SvnUpdateGroups { +// String MERGED_WITH_PROPERTY_CONFLICT = "MERGED_WITH_PROPERTY_CONFLICT"; + String MERGED_WITH_TREE_CONFLICT = "MERGED_WITH_TREE_CONFLICT"; +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdatePanel.form b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdatePanel.form new file mode 100644 index 000000000000..1da300daef56 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdatePanel.form @@ -0,0 +1,59 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdatePanel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdatePanel.java new file mode 100644 index 000000000000..f9e4485b30da --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdatePanel.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.update; + +import com.intellij.openapi.vcs.FilePath; +import com.intellij.ui.MultiLineTooltipUI; +import org.jetbrains.idea.svn17.DepthCombo; +import org.jetbrains.idea.svn17.SvnConfiguration17; +import org.jetbrains.idea.svn17.SvnVcs17; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; + +public class SvnUpdatePanel extends AbstractSvnUpdatePanel { + private JPanel myConfigureRootsPanel; + private JCheckBox myForceBox; + private JPanel myPanel; + private JCheckBox myLockOnDemand; + private DepthCombo myDepthCombo; + private JLabel myDepthLabel; + + public SvnUpdatePanel(SvnVcs17 vcs, Collection roots) { + super(vcs); + init(roots); + + boolean descend = false; + for (FilePath root : roots) { + if (root.isDirectory()) { + descend = true; + break; + } + } + myDepthCombo.setVisible(descend); + myDepthLabel.setVisible(descend); + myDepthLabel.setLabelFor(myDepthCombo); + + final SvnConfiguration17 svnConfiguration = SvnConfiguration17.getInstance(myVCS.getProject()); + myLockOnDemand.setSelected(svnConfiguration.UPDATE_LOCK_ON_DEMAND); + myLockOnDemand.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + svnConfiguration.UPDATE_LOCK_ON_DEMAND = myLockOnDemand.isSelected(); + } + }); + myForceBox.setSelected(svnConfiguration.FORCE_UPDATE); + myForceBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + svnConfiguration.FORCE_UPDATE = myForceBox.isSelected(); + } + }); + } + + protected JPanel getRootsPanel() { + return myConfigureRootsPanel; + } + + protected SvnPanel createRootPanel(final FilePath root, final SvnVcs17 vcs, Collection roots) { + return new SvnUpdateRootOptionsPanel(root, vcs, roots); + } + + protected JComponent getPanel() { + return myPanel; + } + + protected DepthCombo getDepthBox() { + return myDepthCombo; + } + + private void createUIComponents() { + myLockOnDemand = new JCheckBox() { + @Override + public JToolTip createToolTip() { + JToolTip toolTip = new JToolTip() {{ + setUI(new MultiLineTooltipUI()); + }}; + toolTip.setComponent(this); + return toolTip; + } + }; + myDepthCombo = new DepthCombo(true); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdateRootOptionsPanel.form b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdateRootOptionsPanel.form new file mode 100644 index 000000000000..4900831847c2 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdateRootOptionsPanel.form @@ -0,0 +1,94 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdateRootOptionsPanel.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdateRootOptionsPanel.java new file mode 100644 index 000000000000..f4b3e4e25490 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/SvnUpdateRootOptionsPanel.java @@ -0,0 +1,264 @@ +/* + * 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.update; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.vcs.FilePath; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.*; +import org.jetbrains.idea.svn17.actions.SelectBranchPopup; +import org.jetbrains.idea.svn17.branchConfig.SvnBranchConfigurationNew; +import org.jetbrains.idea.svn17.dialogs.SelectLocationDialog; +import org.jetbrains.idea.svn17.history.SvnChangeList; +import org.jetbrains.idea.svn17.history.SvnRepositoryLocation; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.internal.util.SVNPathUtil; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; + +public class SvnUpdateRootOptionsPanel implements SvnPanel{ + private final static Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.update.SvnUpdateRootOptionsPanel.SvnUpdateRootOptionsPanel"); + private TextFieldWithBrowseButton myURLText; + private JCheckBox myRevisionBox; + private TextFieldWithBrowseButton myRevisionText; + + private final SvnVcs17 myVcs; + private JPanel myPanel; + private final FilePath myRoot; + private JCheckBox myUpdateToSpecificUrl; + private TextFieldWithBrowseButton myBranchField; + private JLabel myBranchLabel; + private JLabel myUrlLabel; + private JLabel myCopyType; + private String mySourceUrl; + private SVNURL myBranchUrl; + private final boolean myIsNested; + + public SvnUpdateRootOptionsPanel(FilePath root, final SvnVcs17 vcs, Collection roots) { + myRoot = root; + myVcs = vcs; + myIsNested = FilePathUtil.isNested(roots, myRoot); + + //myPanel.setMinimumSize(new Dimension(320, 70)); + myURLText.setEditable(true); + + myURLText.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + chooseUrl(); + } + }); + + myBranchField.setEditable(false); + myBranchField.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + chooseBranch(); + } + }); + myBranchLabel.setLabelFor(myBranchField); + myUrlLabel.setLabelFor(myURLText); + + myUpdateToSpecificUrl.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (myUpdateToSpecificUrl.isSelected()) { + myURLText.setEnabled(true); + myBranchField.setEnabled((myBranchUrl != null) && (mySourceUrl != null)); + //chooseBranch(); + } else { + myURLText.setEnabled(false); + myBranchField.setEnabled(false); + } + } + }); + + myRevisionBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (e.getSource() == myRevisionBox) { + myRevisionText.setEnabled(myRevisionBox.isSelected()); + if (myRevisionBox.isSelected()) { + if ("".equals(myRevisionText.getText().trim())) { + myRevisionText.setText("HEAD"); + } + myRevisionText.getTextField().selectAll(); + myRevisionText.requestFocus(); + } + } + } + }); + + myRevisionText.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + final Project project = vcs.getProject(); + // todo check whether ok; rather shoudl be used if checkbox is turned on + final SvnRepositoryLocation location = new SvnRepositoryLocation(myURLText.getText()); + final SvnChangeList repositoryVersion = SvnSelectRevisionUtil.chooseCommittedChangeList(project, location, myRoot.getVirtualFile()); + if (repositoryVersion != null) { + myRevisionText.setText(String.valueOf(repositoryVersion.getNumber())); + } + } + }); + + myRevisionText.setText(SVNRevision.HEAD.toString()); + myRevisionText.getTextField().selectAll(); + myRevisionText.setEnabled(myRevisionBox.isSelected()); + myURLText.setEnabled(myUpdateToSpecificUrl.isSelected()); + myBranchField.setEnabled(myUpdateToSpecificUrl.isSelected() && (myBranchUrl != null) && (mySourceUrl != null)); + + final boolean revisionCanBeSpecifiedForRoot = (! myIsNested) || isRevisionCanBeSpecifiedForRoot(); + myRevisionBox.setEnabled(revisionCanBeSpecifiedForRoot); + myRevisionText.setEnabled(revisionCanBeSpecifiedForRoot); + myCopyType.setVisible(! revisionCanBeSpecifiedForRoot); + myCopyType.setFont(myCopyType.getFont().deriveFont(Font.ITALIC)); + myUpdateToSpecificUrl.setEnabled(revisionCanBeSpecifiedForRoot); + } + + private boolean isRevisionCanBeSpecifiedForRoot() { + final RootUrlInfo info = myVcs.getSvnFileUrlMapping().getWcRootForFilePath(myRoot.getIOFile()); + if (info != null) { + final boolean result = (!NestedCopyType.external.equals(info.getType())) && (!NestedCopyType.switched.equals(info.getType())); + if (! result) { + myCopyType.setText(info.getType().getName() + " copy"); + } + return result; + } + return true; + } + + private void chooseBranch() { + if ((myBranchUrl == null) || (mySourceUrl == null)) { + myBranchField.setEnabled(false); + return; + } + SelectBranchPopup.show(myVcs.getProject(), myRoot.getVirtualFile(), new SelectBranchPopup.BranchSelectedCallback() { + public void branchSelected(final Project project, final SvnBranchConfigurationNew configuration, final String url, final long revision) { + recalculateUrl(url); + myBranchField.setText(SVNPathUtil.tail(url)); + } + }, SvnBundle.message("select.branch.popup.general.title"), myPanel); + } + + private void recalculateUrl(final String url) { + // recalculate fields + try { + final String newText = SVNURL.parseURIEncoded(url).appendPath(mySourceUrl.substring(myBranchUrl.toString().length()), true).toString(); + myURLText.setText(newText); + } + catch (SVNException e) { + LOG.error(e); + } + } + + private void chooseUrl() { + String selected = SelectLocationDialog.selectLocation(myVcs.getProject(), myURLText.getText()); + if (selected != null) { + myURLText.setText(selected); + } + } + + public JPanel getPanel() { + return myPanel; + } + + @Nullable + private SVNURL getBranchForUrl(final String url) { + final SvnFileUrlMapping17 urlMapping = myVcs.getSvnFileUrlMapping(); + final RootUrlInfo rootForFilePath = urlMapping.getWcRootForFilePath(myRoot.getIOFile()); + if (rootForFilePath == null) { + return null; + } + return SvnUtil.getBranchForUrl(myVcs, rootForFilePath.getVirtualFile(), url); + } + + public void reset(final SvnConfiguration17 configuration) { + final UpdateRootInfo rootInfo = configuration.getUpdateRootInfo(myRoot.getIOFile(), myVcs); + + mySourceUrl = rootInfo.getUrlAsString(); + myBranchUrl = getBranchForUrl(mySourceUrl); + if (myBranchUrl != null) { + myBranchField.setText(SVNPathUtil.tail(myBranchUrl.toString())); + } + + myURLText.setText(mySourceUrl); + myRevisionBox.setSelected(rootInfo.isUpdateToRevision()); + myRevisionText.setText(rootInfo.getRevision().toString()); + myUpdateToSpecificUrl.setSelected(false); + myRevisionText.setEnabled(myRevisionBox.isSelected()); + myURLText.setEnabled(myUpdateToSpecificUrl.isSelected()); + myBranchField.setEnabled(myUpdateToSpecificUrl.isSelected() && (myBranchUrl != null) && (mySourceUrl != null)); + } + + public void apply(final SvnConfiguration17 configuration) throws ConfigurationException { + final UpdateRootInfo rootInfo = configuration.getUpdateRootInfo(myRoot.getIOFile(), myVcs); + if (myUpdateToSpecificUrl.isSelected()) { + rootInfo.setUrl(myURLText.getText()); + } + rootInfo.setUpdateToRevision(myRevisionBox.isSelected()); + final SVNRevision revision = SVNRevision.parse(myRevisionText.getText()); + if (!revision.isValid()) { + throw new ConfigurationException(SvnBundle.message("invalid.svn.revision.error.message", myRevisionText.getText())); + } + rootInfo.setRevision(revision); + } + + public boolean canApply() { + return true; + } + +/* private class MyBranchFieldFocusListener implements FocusListener { + private SvnBranchConfiguration mySvnBranchConfiguration; + + private MyBranchFieldFocusListener() { + final VirtualFile root = ProjectLevelVcsManager.getInstance(myVcs.getProject()).getVcsRootFor(myRoot); + if (root != null) { + try { + mySvnBranchConfiguration = SvnBranchConfigurationManager.getInstance(myVcs.getProject()).get(root); + } + catch (VcsException e) { + LOG.error(e); + } + } + } + + public void focusGained(final FocusEvent e) { + } + + public void focusLost(final FocusEvent e) { + if (mySvnBranchConfiguration != null) { + String text = myBranchField.getText(); + text = (text == null) ? "" : text.trim(); + if ((myBranchUrl != null) && (mySourceUrl != null) && (text.length() > 0) && (! text.contains("/"))) { + try { + final String branch = mySvnBranchConfiguration.getBranchByName(myVcs.getProject(), text); + if (branch != null) { + recalculateUrl(branch); + } + } + catch (SVNException exc) { + LOG.error(exc); + } + } + } + } + }*/ +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/UpdateEventHandler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/UpdateEventHandler.java new file mode 100644 index 000000000000..52639ff773bb --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/UpdateEventHandler.java @@ -0,0 +1,218 @@ +/* + * 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.update; + +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.vcs.VcsBundle; +import com.intellij.openapi.vcs.update.FileGroup; +import com.intellij.openapi.vcs.update.UpdatedFiles; +import com.intellij.openapi.wm.StatusBar; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.SvnBundle; +import org.jetbrains.idea.svn17.SvnFileUrlMapping17; +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNCancelException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; +import org.tmatesoft.svn.core.wc.ISVNEventHandler; +import org.tmatesoft.svn.core.wc.SVNEvent; +import org.tmatesoft.svn.core.wc.SVNEventAction; +import org.tmatesoft.svn.core.wc.SVNStatusType; +import org.tmatesoft.svn.util.SVNLogType; + +import java.io.File; + +/** + * @author lesya +*/ +public class UpdateEventHandler implements ISVNEventHandler { + private ProgressIndicator myProgressIndicator; + private UpdatedFiles myUpdatedFiles; + private int myExternalsCount; + private final SvnVcs17 myVCS; + @Nullable private final SvnUpdateContext mySequentialUpdatesContext; + + protected String myText; + protected String myText2; + + public UpdateEventHandler(SvnVcs17 vcs, ProgressIndicator progressIndicator, + @Nullable final SvnUpdateContext sequentialUpdatesContext) { + myProgressIndicator = progressIndicator; + myVCS = vcs; + mySequentialUpdatesContext = sequentialUpdatesContext; + myExternalsCount = 1; + } + + public void setUpdatedFiles(final UpdatedFiles updatedFiles) { + myUpdatedFiles = updatedFiles; + } + + public void handleEvent(final SVNEvent event, double progress) { + if (event == null || event.getFile() == null) { + return; + } + + String path = event.getFile().getAbsolutePath(); + String displayPath = event.getFile().getName(); + myText2 = null; + myText = null; + + if (handleInDescendants(event)) { + updateProgressIndicator(); + return; + } + + if (event.getAction() == SVNEventAction.TREE_CONFLICT) { + myText2 = SvnBundle.message("progress.text2.treeconflicted", displayPath); + updateProgressIndicator(); + myUpdatedFiles.registerGroup(createFileGroup(VcsBundle.message("update.group.name.merged.with.tree.conflicts"), + FileGroup.MERGED_WITH_TREE_CONFLICT)); + addFileToGroup(FileGroup.MERGED_WITH_TREE_CONFLICT, event); + } + + if (event.getAction() == SVNEventAction.UPDATE_ADD || + event.getAction() == SVNEventAction.ADD) { + myText2 = SvnBundle.message("progress.text2.added", displayPath); + if (event.getContentsStatus() == SVNStatusType.CONFLICTED || event.getPropertiesStatus() == SVNStatusType.CONFLICTED) { + addFileToGroup(FileGroup.MERGED_WITH_CONFLICT_ID, event); + myText2 = SvnBundle.message("progress.text2.conflicted", displayPath); + } else if (myUpdatedFiles.getGroupById(FileGroup.REMOVED_FROM_REPOSITORY_ID).getFiles().contains(path)) { + myUpdatedFiles.getGroupById(FileGroup.REMOVED_FROM_REPOSITORY_ID).getFiles().remove(path); + if (myUpdatedFiles.getGroupById(AbstractSvnUpdateIntegrateEnvironment17.REPLACED_ID) == null) { + myUpdatedFiles.registerGroup(createFileGroup(SvnBundle.message("status.group.name.replaced"), + AbstractSvnUpdateIntegrateEnvironment17.REPLACED_ID)); + } + addFileToGroup(AbstractSvnUpdateIntegrateEnvironment17.REPLACED_ID, event); + } else { + addFileToGroup(FileGroup.CREATED_ID, event); + } + } + else if (event.getAction() == SVNEventAction.UPDATE_NONE) { + // skip it + return; + } + else if (event.getAction() == SVNEventAction.UPDATE_DELETE) { + myText2 = SvnBundle.message("progress.text2.deleted", displayPath); + addFileToGroup(FileGroup.REMOVED_FROM_REPOSITORY_ID, event); + } + else if (event.getAction() == SVNEventAction.UPDATE_UPDATE) { + if (event.getContentsStatus() == SVNStatusType.CONFLICTED || event.getPropertiesStatus() == SVNStatusType.CONFLICTED) { + if (event.getContentsStatus() == SVNStatusType.CONFLICTED) { + addFileToGroup(FileGroup.MERGED_WITH_CONFLICT_ID, event); + } + if (event.getPropertiesStatus() == SVNStatusType.CONFLICTED) { + addFileToGroup(FileGroup.MERGED_WITH_PROPERTY_CONFLICT_ID, event); + } + myText2 = SvnBundle.message("progress.text2.conflicted", displayPath); + } + else if (event.getContentsStatus() == SVNStatusType.MERGED || event.getPropertiesStatus() == SVNStatusType.MERGED) { + myText2 = SvnBundle.message("progres.text2.merged", displayPath); + addFileToGroup(FileGroup.MERGED_ID, event); + } + else if (event.getContentsStatus() == SVNStatusType.CHANGED || event.getPropertiesStatus() == SVNStatusType.CHANGED) { + myText2 = SvnBundle.message("progres.text2.updated", displayPath); + addFileToGroup(FileGroup.UPDATED_ID, event); + } + else if (event.getContentsStatus() == SVNStatusType.UNCHANGED && + (event.getPropertiesStatus() == SVNStatusType.UNCHANGED || event.getPropertiesStatus() == SVNStatusType.UNKNOWN)) { + myText2 = SvnBundle.message("progres.text2.updated", displayPath); + } + else { + myText2 = ""; + addFileToGroup(FileGroup.UNKNOWN_ID, event); + } + } + else if (event.getAction() == SVNEventAction.UPDATE_EXTERNAL) { + if (mySequentialUpdatesContext != null) { + mySequentialUpdatesContext.registerExternalRootBeingUpdated(event.getFile()); + } + myExternalsCount++; + if (myUpdatedFiles.getGroupById(AbstractSvnUpdateIntegrateEnvironment17.EXTERNAL_ID) == null) { + myUpdatedFiles.registerGroup(new FileGroup(SvnBundle.message("status.group.name.externals"), + SvnBundle.message("status.group.name.externals"), + false, AbstractSvnUpdateIntegrateEnvironment17.EXTERNAL_ID, true)); + } + addFileToGroup(AbstractSvnUpdateIntegrateEnvironment17.EXTERNAL_ID, event); + myText = SvnBundle.message("progress.text.updating.external.location", event.getFile().getAbsolutePath()); + } + else if (event.getAction() == SVNEventAction.RESTORE) { + myText2 = SvnBundle.message("progress.text2.restored.file", displayPath); + addFileToGroup(FileGroup.RESTORED_ID, event); + } + else if (event.getAction() == SVNEventAction.UPDATE_COMPLETED && event.getRevision() >= 0) { + myExternalsCount--; + myText2 = SvnBundle.message("progres.text2.updated.to.revision", event.getRevision()); + if (myExternalsCount == 0) { + myExternalsCount = 1; + StatusBar.Info.set(SvnBundle.message("status.text.updated.to.revision", event.getRevision()), myVCS.getProject()); + } + } + else if (event.getAction() == SVNEventAction.SKIP) { + myText2 = SvnBundle.message("progress.text2.skipped.file", displayPath); + addFileToGroup(FileGroup.SKIPPED_ID, event); + } + + updateProgressIndicator(); + } + + private boolean itemSwitched(final SVNEvent event) { + final File file = event.getFile(); + final SvnFileUrlMapping17 urlMapping = myVCS.getSvnFileUrlMapping(); + final SVNURL currentUrl = urlMapping.getUrlForFile(file); + return (currentUrl != null) && (! currentUrl.equals(event.getURL())); + } + + private void updateProgressIndicator() { + if (myProgressIndicator != null) { + if (myText != null) { + myProgressIndicator.setText(myText); + } + if (myText2 != null) { + myProgressIndicator.setText2(myText2); + } + } + } + + protected boolean handleInDescendants(final SVNEvent event) { + return false; + } + + protected void addFileToGroup(final String id, final SVNEvent event) { + final FileGroup fileGroup = myUpdatedFiles.getGroupById(id); + final String path = event.getFile().getAbsolutePath(); + fileGroup.add(path, SvnVcs17.getKey(), null); + if (event.getErrorMessage() != null) { + fileGroup.addError(path, event.getErrorMessage().getMessage()); + } + } + + public void checkCancelled() throws SVNCancelException { + if (myProgressIndicator != null) { + myProgressIndicator.checkCanceled(); + if (myProgressIndicator.isCanceled()) { + SVNErrorManager.cancel(SvnBundle.message("exception.text.update.operation.cancelled"), SVNLogType.DEFAULT); + } + } + } + + private static FileGroup createFileGroup(String name, String id) { + return new FileGroup(name, name, false, id, true); + } + + public void setProgressIndicator(ProgressIndicator progressIndicator) { + myProgressIndicator = progressIndicator; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/UpdateRootInfo.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/UpdateRootInfo.java new file mode 100644 index 000000000000..0e268813318e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn17/update/UpdateRootInfo.java @@ -0,0 +1,82 @@ +/* + * 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.update; + +import org.jetbrains.idea.svn17.SvnVcs17; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.SVNInfo; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.io.File; + +public class UpdateRootInfo { + private String myUrl; + private SVNRevision myRevision; + private boolean myUpdateToSpecifiedRevision = false; + + public UpdateRootInfo(File file, SvnVcs17 vcs) { + myRevision = SVNRevision.HEAD; + try { + SVNWCClient wcClient = vcs.createWCClient(); + SVNInfo info = wcClient.doInfo(file, SVNRevision.UNDEFINED); + if (info != null) { + final SVNURL url = info.getURL(); + myUrl = url.toString(); + } else { + myUrl = ""; + } + } + catch (SVNException e) { + myUrl = ""; + } + + } + + public SVNURL getUrl() { + try { + return SVNURL.parseURIEncoded(myUrl); + } + catch (SVNException e) { + return null; + } + } + + public String getUrlAsString() { + return myUrl; + } + + public SVNRevision getRevision() { + return myRevision; + } + + public boolean isUpdateToRevision() { + return myUpdateToSpecifiedRevision; + } + + public void setUrl(final String text) { + myUrl = text; + } + + public void setUpdateToRevision(final boolean value) { + myUpdateToSpecifiedRevision = value; + } + + public void setRevision(final SVNRevision svnRevision) { + myRevision =svnRevision; + } +} diff --git a/plugins/svn4idea/svn4idea-tests.iml b/plugins/svn4idea/svn4idea-tests.iml index 9a38eff3d508..3f4d3913d5b4 100644 --- a/plugins/svn4idea/svn4idea-tests.iml +++ b/plugins/svn4idea/svn4idea-tests.iml @@ -1,6 +1,8 @@ - + + + diff --git a/plugins/svn4idea/svn4idea.iml b/plugins/svn4idea/svn4idea.iml index 89ea2748a34e..303c74868af3 100644 --- a/plugins/svn4idea/svn4idea.iml +++ b/plugins/svn4idea/svn4idea.iml @@ -1,6 +1,8 @@ - + + + @@ -55,6 +57,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/IgnoredFilesTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/IgnoredFilesTest.java new file mode 100644 index 000000000000..ced21fe21da7 --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/IgnoredFilesTest.java @@ -0,0 +1,186 @@ +package org.jetbrains.idea.svn17; + +import com.intellij.ide.startup.impl.StartupManagerImpl; +import com.intellij.openapi.components.ProjectComponent; +import com.intellij.openapi.startup.StartupManager; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.changes.IgnoredBeanFactory; +import com.intellij.openapi.vcs.changes.IgnoredFileBean; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.testFramework.AbstractVcsTestCase; +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; +import com.intellij.testFramework.fixtures.TempDirTestFixture; +import com.intellij.util.ui.UIUtil; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; + +/** + * @author irengrig + */ +public class IgnoredFilesTest extends AbstractVcsTestCase { + private SvnVcs17 myVcs; + private ProjectLevelVcsManagerImpl myVcsManager; + private ChangeListManager myChangeListManager; + private LocalFileSystem myLocalFileSystem; + private TempDirTestFixture myTempDirFixture; + private File myClientRoot; + private VcsDirtyScopeManager myVcsDirtyScopeManager; + + @Before + public void setUp() throws Exception { + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + @Override + public void run() { + try { + final IdeaTestFixtureFactory fixtureFactory = IdeaTestFixtureFactory.getFixtureFactory(); + myTempDirFixture = fixtureFactory.createTempDirTestFixture(); + myTempDirFixture.setUp(); + + myClientRoot = new File(myTempDirFixture.getTempDirPath(), "clientroot"); + myClientRoot.mkdir(); + + initProject(myClientRoot); + + ((StartupManagerImpl)StartupManager.getInstance(myProject)).runPostStartupActivities(); + + myChangeListManager = ChangeListManager.getInstance(myProject); + myVcs = SvnVcs17.getInstance(myProject); + myVcsManager = (ProjectLevelVcsManagerImpl)ProjectLevelVcsManager.getInstance(myProject); + myVcsManager.registerVcs(myVcs); + myVcsManager.setDirectoryMapping(myWorkingCopyDir.getPath(), myVcs.getName()); + + ((ProjectComponent)myChangeListManager).projectOpened(); + myVcsDirtyScopeManager = VcsDirtyScopeManager.getInstance(myProject); + ((ProjectComponent)myVcsDirtyScopeManager).projectOpened(); + myLocalFileSystem = LocalFileSystem.getInstance(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + } + + @After + public void tearDown() throws Exception { + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + @Override + public void run() { + try { + myVcsManager.unregisterVcs(myVcs); + myVcsManager = null; + myVcs = null; + + ((ProjectComponent)myChangeListManager).projectClosed(); + ((ProjectComponent)myVcsDirtyScopeManager).projectClosed(); + + tearDownProject(); + if (myTempDirFixture != null) { + myTempDirFixture.tearDown(); + myTempDirFixture = null; + } + FileUtil.delete(myClientRoot); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + } + + // they all blink now + + @Test + public void testFileIsIgnored() throws Exception { + final String filePath1 = myClientRoot.getPath() + "/a"; + final File file = new File(filePath1); + file.createNewFile(); + + final IgnoredFileBean ignoredFileBean = IgnoredBeanFactory.ignoreFile(filePath1, myProject); + myChangeListManager.addFilesToIgnore(ignoredFileBean); + + dirty(); + + final VirtualFile vf = myLocalFileSystem.refreshAndFindFileByIoFile(file); + Assert.assertNotNull(vf); + Assert.assertTrue(myChangeListManager.isIgnoredFile(vf)); + } + + private void dirty() { + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + myChangeListManager.scheduleUpdate(true); + myChangeListManager.ensureUpToDate(false); + } + + @Test + public void testDirIsIgnored() throws Exception { + //final String dirPath1 = myClientRoot.getPath() + "/a"; + final File dir = new File(myClientRoot, "a"); + dir.mkdir(); + final File innerDir = new File(dir, "innerDir"); + innerDir.mkdir(); + final File file1 = new File(innerDir, "file1"); + final File file2 = new File(innerDir, "file2"); + file1.createNewFile(); + file2.createNewFile(); + + final VirtualFile innerVf = myLocalFileSystem.refreshAndFindFileByIoFile(innerDir); + final VirtualFile vf1 = myLocalFileSystem.refreshAndFindFileByIoFile(file1); + final VirtualFile vf2 = myLocalFileSystem.refreshAndFindFileByIoFile(file2); + + final IgnoredFileBean ignoredFileBean = IgnoredBeanFactory.ignoreUnderDirectory(FileUtil.toSystemIndependentName(dir.getPath()), myProject); + myChangeListManager.addFilesToIgnore(ignoredFileBean); + + dirty(); + + assertFoundAndIgnored(innerVf); + assertFoundAndIgnored(vf1); + assertFoundAndIgnored(vf2); + } + + @Test + public void testPatternIsIgnored() throws Exception { + final String dirPath1 = myClientRoot.getPath() + "/a"; + final File dir = new File(myClientRoot, "a"); + dir.mkdir(); + final File innerDir = new File(dir, "innerDir"); + innerDir.mkdir(); + final File file1 = new File(innerDir, "file1"); + final File file2 = new File(innerDir, "file2"); + file1.createNewFile(); + file2.createNewFile(); + + final VirtualFile innerVf = myLocalFileSystem.refreshAndFindFileByIoFile(innerDir); + final VirtualFile vf1 = myLocalFileSystem.refreshAndFindFileByIoFile(file1); + final VirtualFile vf2 = myLocalFileSystem.refreshAndFindFileByIoFile(file2); + + final IgnoredFileBean ignoredFileBean = IgnoredBeanFactory.withMask("file*"); + myChangeListManager.addFilesToIgnore(ignoredFileBean); + + dirty(); + + Assert.assertNotNull(innerVf); + Assert.assertFalse(myChangeListManager.isIgnoredFile(innerVf)); + Assert.assertEquals(FileStatus.UNKNOWN, myChangeListManager.getStatus(innerVf)); + Assert.assertTrue(myChangeListManager.isUnversioned(innerVf)); + + assertFoundAndIgnored(vf1); + assertFoundAndIgnored(vf2); + } + + void assertFoundAndIgnored(final VirtualFile vf) { + Assert.assertNotNull(vf); + Assert.assertTrue(myChangeListManager.isIgnoredFile(vf)); + Assert.assertEquals(FileStatus.IGNORED, myChangeListManager.getStatus(vf)); + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnAddTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnAddTest.java new file mode 100644 index 000000000000..ce3e12aab184 --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnAddTest.java @@ -0,0 +1,74 @@ +package org.jetbrains.idea.svn17; + +import com.intellij.execution.process.ProcessOutput; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.vcs.VcsConfiguration; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vfs.VirtualFile; +import org.junit.Assert; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * @author yole + */ +public class SvnAddTest extends SvnTestCase { + @Test + public void testCopy() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile file = createFileInCommand("a.txt", "old content"); + checkin(); + copyFileInCommand(file, "b.txt"); + verify(runSvn("status"), "A + b.txt"); + } + + // IDEADEV-16268 + @Test + public void testDirAndFileInCommand() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + new WriteCommandAction.Simple(myProject) { + @Override + public void run() { + try { + VirtualFile dir = myWorkingCopyDir.createChildDirectory(this, "child"); + dir.createChildData(this, "a.txt"); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + }.execute(); + + final ProcessOutput result = runSvn("status"); + verify(result, "A child", "A child" + File.separatorChar + "a.txt"); + } + + // IDEADEV-19308 + @Test + public void testDirAfterFile() throws Exception { + disableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile dir = createDirInCommand(myWorkingCopyDir, "dir"); + final VirtualFile file = createFileInCommand(dir, "a.txt", "content"); + + final List files = new ArrayList(); + files.add(file); + files.add(dir); + final List errors = SvnVcs17.getInstance(myProject).getCheckinEnvironment().scheduleUnversionedFilesForAddition(files); + Assert.assertEquals(0, errors.size()); + } + + @Test + public void testUndoAdd() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + createFileInCommand("a.txt", "old content"); + checkin(); + undo(); + verify(runSvn("status"), "D a.txt"); + Assert.assertFalse(new File(myWorkingCopyDir.getPath(), "a.txt").exists()); + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnAuthenticationTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnAuthenticationTest.java new file mode 100644 index 000000000000..f7cc6ca95760 --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnAuthenticationTest.java @@ -0,0 +1,1273 @@ +/* + * 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.application.ApplicationManager; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.Trinity; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.testFramework.PlatformTestCase; +import com.intellij.util.concurrency.Semaphore; +import com.intellij.util.ui.UIUtil; +import junit.framework.Assert; +import org.jetbrains.idea.svn17.auth.ProviderType; +import org.jetbrains.idea.svn17.auth.SvnAuthenticationInteraction; +import org.jetbrains.idea.svn17.auth.SvnAuthenticationListener; +import org.jetbrains.idea.svn17.dialogs.SvnAuthenticationProvider; +import org.tmatesoft.svn.core.SVNErrorMessage; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.auth.*; +import org.tmatesoft.svn.core.internal.util.jna.SVNJNAUtil; +import org.tmatesoft.svn.core.internal.wc.SVNConfigFile; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class SvnAuthenticationTest extends PlatformTestCase { + private SvnAuthenticationManager myAuthenticationManager; + private TestInteraction myTestInteraction; + private final Object mySynchObject = new Object(); + private SvnTestInteractiveAuthentication myInteractiveProvider; + private SvnConfiguration17 myConfiguration; + + @Override + protected void runBareRunnable(Runnable runnable) throws Throwable { + runnable.run(); + } + + @Override + protected void setUp() throws Exception { + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + @Override + public void run() { + try { + SvnAuthenticationTest.super.setUp(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + + + myConfiguration = SvnConfiguration17.getInstance(myProject); + final String configPath = myProject.getBaseDir().getPath() + File.separator + "Subversion"; + myConfiguration.setConfigurationDirectory(configPath); + + final File configFile = new File(configPath); + myFilesToDelete.add(configFile); + + SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + + myAuthenticationManager = new SvnAuthenticationManager(myProject, configFile); + + myInteractiveProvider = new SvnTestInteractiveAuthentication(myAuthenticationManager); + myAuthenticationManager.setAuthenticationProvider(new SvnAuthenticationProvider(vcs, myInteractiveProvider)); + myAuthenticationManager.setRuntimeStorage(SvnConfiguration17.RUNTIME_AUTH_CACHE); + + myTestInteraction = new TestInteraction(); + myAuthenticationManager.setInteraction(myTestInteraction); + + SVNConfigFile.createDefaultConfiguration(configFile); + } + + @Override + protected void tearDown() throws Exception { + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + @Override + public void run() { + try { + SvnAuthenticationTest.super.tearDown(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + + FileUtil.delete(new File(myConfiguration.getConfigurationDirectory())); + } + + public void testSavedAndRead() throws Exception { + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + + final SVNURL url = SVNURL.parseURIEncoded("http://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final boolean[] result = new boolean[]{false}; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + if (SystemInfo.isWindows) { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.save)); + } else { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.without_pasword_save)); + } + + commonScheme(url, false, null); + Assert.assertEquals(3, listener.getCnt()); + //long start = System.currentTimeMillis(); + //waitListenerStep(start, listener, 3); + + listener.reset(); + if (!SystemInfo.isWindows) savedOnceListener.reset(); + SvnConfiguration17.RUNTIME_AUTH_CACHE.clear(); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + if (! SystemInfo.isWindows) { + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.without_pasword_save)); + } + commonScheme(url, false, null); + //start = System.currentTimeMillis(); + //waitListenerStep(start, listener, 4); + Assert.assertEquals(SystemInfo.isWindows ? 1 : 3, listener.getCnt()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + Assert.assertEquals(SystemInfo.isWindows ? 0 : 2, myTestInteraction.getNumPlaintextPrompt()); + Assert.assertEquals(0, myTestInteraction.getNumAuthWarn()); + Assert.assertEquals(0, myTestInteraction.getNumPasswordsWarn()); + Assert.assertEquals(0, myTestInteraction.getNumSSLPlaintextPrompt()); + Assert.assertEquals(0, myTestInteraction.getNumSSLWarn()); + Assert.assertEquals(SystemInfo.isWindows ? 1 : 3, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + + savedOnceListener.assertSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + } + + public void testSavedAndReadUnix() throws Exception { + if (SystemInfo.isWindows) return; + + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + + final SVNURL url = SVNURL.parseURIEncoded("http://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + + final File servers = new File(myConfiguration.getConfigurationDirectory(), "servers"); + final File oldServers = new File(myConfiguration.getConfigurationDirectory(), "config_old"); + FileUtil.copy(servers, oldServers); + try { + FileUtil.appendToFile(servers, "\nstore-plaintext-passwords=yes\n"); + + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.save)); + + commonScheme(url, false, null); + Assert.assertEquals(3, listener.getCnt()); + //long start = System.currentTimeMillis(); + //waitListenerStep(start, listener, 3); + + SvnConfiguration17.RUNTIME_AUTH_CACHE.clear(); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + commonScheme(url, false, null); + //start = System.currentTimeMillis(); + //waitListenerStep(start, listener, 4); + Assert.assertEquals(4, listener.getCnt()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + } finally { + FileUtil.delete(servers); + FileUtil.rename(oldServers, servers); + } + + Assert.assertTrue(result[0]); + myTestInteraction.assertNothing(); + Assert.assertEquals(4, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + + savedOnceListener.assertSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + } + + private void waitListenerStep(long start, TestListener listener, final int stepNoNext) { + while (listener.getCnt() < stepNoNext && System.currentTimeMillis() - start < 10000) { + synchronized (mySynchObject) { + try { + mySynchObject.wait(50); + } + catch (InterruptedException e) { + // + } + } + } + Assert.assertEquals(stepNoNext, listener.getCnt()); + } + + public void testWhenNotSaved() throws Exception { + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + + myInteractiveProvider.setSaveData(false); + + final SVNURL url = SVNURL.parseURIEncoded("http://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + + commonScheme(url, false, null); + //long start = System.currentTimeMillis(); + //waitListenerStep(start, listener, 2); + Assert.assertEquals(2, listener.getCnt()); + + savedOnceListener.assertNotSaved(url, ISVNAuthenticationManager.PASSWORD); + // cause is not cleared though + savedOnceListener.reset(); + + SvnConfiguration17.RUNTIME_AUTH_CACHE.clear(); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + commonScheme(url, false, null); + Assert.assertEquals(4, listener.getCnt()); + //start = System.currentTimeMillis(); + //waitListenerStep(start, listener, 4); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + myTestInteraction.assertNothing(); + Assert.assertEquals(4, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertNotSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + } + + public void testWhenAuthCredsNoInConfig() throws Exception { + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + + final File config = new File(myConfiguration.getConfigurationDirectory(), "config"); + final String contents = FileUtil.loadFile(config); + final String auth = "[auth]"; + final int idx = contents.indexOf(auth); + Assert.assertTrue(idx != -1); + final String newContents = contents.substring(0, idx + auth.length()) + "\nstore-auth-creds=no\n" + contents.substring(idx + auth.length()); + + final File oldConfig = new File(myConfiguration.getConfigurationDirectory(), "config_old"); + FileUtil.rename(config, oldConfig); + try { + config.createNewFile(); + FileUtil.appendToFile(config, newContents); + + final SVNURL url = SVNURL.parseURIEncoded("http://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + + commonScheme(url, false, null); + Assert.assertEquals(2, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumAuthWarn()); + myTestInteraction.reset(); + savedOnceListener.assertForAwt(); + savedOnceListener.reset(); + + SvnConfiguration17.RUNTIME_AUTH_CACHE.clear(); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + commonScheme(url, false, null); + Assert.assertEquals(4, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumAuthWarn()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + Assert.assertEquals(1, myTestInteraction.getNumAuthWarn()); + Assert.assertEquals(4, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertNotSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + } finally { + FileUtil.delete(config); + FileUtil.rename(oldConfig, config); + } + } + + public void testWhenAuthCredsNoInServers() throws Exception { + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + + final File servers = new File(myConfiguration.getConfigurationDirectory(), "servers"); + + final File oldServers = new File(myConfiguration.getConfigurationDirectory(), "config_old"); + FileUtil.copy(servers, oldServers); + try { + FileUtil.appendToFile(servers, "\nstore-auth-creds=no\n"); + + final SVNURL url = SVNURL.parseURIEncoded("http://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + + commonScheme(url, false, null); + Assert.assertEquals(2, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumAuthWarn()); + myTestInteraction.reset(); + savedOnceListener.assertForAwt(); + savedOnceListener.reset(); + + SvnConfiguration17.RUNTIME_AUTH_CACHE.clear(); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + commonScheme(url, false, null); + Assert.assertEquals(4, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumAuthWarn()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + Assert.assertEquals(1, myTestInteraction.getNumAuthWarn()); + Assert.assertEquals(4, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertNotSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + } finally { + FileUtil.delete(servers); + FileUtil.rename(oldServers, servers); + } + } + + public void testWhenPassSaveNoInConfig() throws Exception { + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + + final File config = new File(myConfiguration.getConfigurationDirectory(), "config"); + final String contents = FileUtil.loadFile(config); + final String auth = "[auth]"; + final int idx = contents.indexOf(auth); + Assert.assertTrue(idx != -1); + final String newContents = contents.substring(0, idx + auth.length()) + "\nstore-passwords=no\n" + contents.substring(idx + auth.length()); + + final File oldConfig = new File(myConfiguration.getConfigurationDirectory(), "config_old"); + FileUtil.rename(config, oldConfig); + try { + config.createNewFile(); + FileUtil.appendToFile(config, newContents); + + final SVNURL url = SVNURL.parseURIEncoded("http://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.without_pasword_save)); + + commonScheme(url, false, null); + Assert.assertEquals(3, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumPasswordsWarn()); + myTestInteraction.reset(); + savedOnceListener.assertForAwt(); + savedOnceListener.reset(); + + SvnConfiguration17.RUNTIME_AUTH_CACHE.clear(); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.without_pasword_save)); + commonScheme(url, false, null); + Assert.assertEquals(6, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumPasswordsWarn()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + Assert.assertEquals(1, myTestInteraction.getNumPasswordsWarn()); + Assert.assertEquals(6, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + } finally { + FileUtil.delete(config); + FileUtil.rename(oldConfig, config); + } + } + + public void testWhenPassSaveNoInServers() throws Exception { + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + + final File servers = new File(myConfiguration.getConfigurationDirectory(), "servers"); + + final File oldServers = new File(myConfiguration.getConfigurationDirectory(), "config_old"); + FileUtil.copy(servers, oldServers); + try { + FileUtil.appendToFile(servers, "\nstore-passwords=no\n"); + + final SVNURL url = SVNURL.parseURIEncoded("http://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.without_pasword_save)); + + commonScheme(url, false, null); + Assert.assertEquals(3, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumPasswordsWarn()); + myTestInteraction.reset(); + savedOnceListener.assertForAwt(); + savedOnceListener.reset(); + + SvnConfiguration17.RUNTIME_AUTH_CACHE.clear(); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.without_pasword_save)); + commonScheme(url, false, null); + Assert.assertEquals(6, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumPasswordsWarn()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + Assert.assertEquals(1, myTestInteraction.getNumPasswordsWarn()); + Assert.assertEquals(6, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + } finally { + FileUtil.delete(servers); + FileUtil.rename(oldServers, servers); + } + } + + public void testWhenPassSaveNoForGroup() throws Exception { + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + + final File servers = new File(myConfiguration.getConfigurationDirectory(), "servers"); + final String contents = FileUtil.loadFile(servers); + final String groups = "[groups]"; + final int idx = contents.indexOf(groups); + Assert.assertTrue(idx != -1); + final String newContents = contents.substring(0, idx + groups.length()) + "\nsomegroup=some*\n" + contents.substring(idx + groups.length()) + + "\n[somegroup]\nstore-passwords=no\n"; + + final File oldServers = new File(myConfiguration.getConfigurationDirectory(), "config_old"); + FileUtil.rename(servers, oldServers); + try { + servers.createNewFile(); + FileUtil.appendToFile(servers, newContents); + + final SVNURL url = SVNURL.parseURIEncoded("http://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.without_pasword_save)); + + commonScheme(url, false, null); + Assert.assertEquals(3, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumPasswordsWarn()); + myTestInteraction.reset(); + savedOnceListener.assertForAwt(); + savedOnceListener.reset(); + + SvnConfiguration17.RUNTIME_AUTH_CACHE.clear(); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.without_pasword_save)); + commonScheme(url, false, null); + Assert.assertEquals(6, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumPasswordsWarn()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + Assert.assertEquals(1, myTestInteraction.getNumPasswordsWarn()); + Assert.assertEquals(6, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + } finally { + FileUtil.delete(servers); + FileUtil.rename(oldServers, servers); + } + } + + public void testWhenPassPhraseSaveNo() throws Exception { + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + + final File servers = new File(myConfiguration.getConfigurationDirectory(), "servers"); + + final File oldServers = new File(myConfiguration.getConfigurationDirectory(), "config_old"); + FileUtil.copy(servers, oldServers); + try { + FileUtil.appendToFile(servers, "\nstore-ssl-client-cert-pp=no\n"); + + final SVNURL url = SVNURL.parseURIEncoded("https://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.without_pasword_save)); + + commonScheme(url, false, null); + Assert.assertEquals(3, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumSSLWarn()); + myTestInteraction.reset(); + savedOnceListener.assertForAwt(); + savedOnceListener.reset(); + + SvnConfiguration17.RUNTIME_AUTH_CACHE.clear(); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.without_pasword_save)); + commonScheme(url, false, null); + Assert.assertEquals(6, listener.getCnt()); + Assert.assertEquals(1, myTestInteraction.getNumSSLWarn()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + Assert.assertEquals(1, myTestInteraction.getNumSSLWarn()); + Assert.assertEquals(6, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertSaved(url, ISVNAuthenticationManager.SSL); + + if (exception[0] != null) { + throw exception[0]; + } + } finally { + FileUtil.delete(servers); + FileUtil.rename(oldServers, servers); + } + } + + public void testPlaintextPrompt() throws Exception { + SVNJNAUtil.setJNAEnabled(false); + + // yes, no + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + myTestInteraction.setPlaintextAnswer(true); + + final SVNURL url = SVNURL.parseURIEncoded("http://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.save)); + + commonScheme(url, false, null); + long start = System.currentTimeMillis(); + waitListenerStep(start, listener, 3); + Assert.assertEquals(1, myTestInteraction.getNumPlaintextPrompt()); + savedOnceListener.assertSaved(url, ISVNAuthenticationManager.PASSWORD); + savedOnceListener.reset(); + myTestInteraction.reset(); + + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + @Override + public void run() { + try { + myConfiguration.clearAuthenticationDirectory(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + + myTestInteraction.setPlaintextAnswer(false); + SvnConfiguration17.RUNTIME_AUTH_CACHE.clear(); + + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.without_pasword_save)); + commonScheme(url, false, null); + start = System.currentTimeMillis(); + waitListenerStep(start, listener, 6); + Assert.assertEquals(1, myTestInteraction.getNumPlaintextPrompt()); + + savedOnceListener.reset(); + myTestInteraction.reset(); + SvnConfiguration17.RUNTIME_AUTH_CACHE.clear(); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.without_pasword_save)); + commonScheme(url, false, null); + start = System.currentTimeMillis(); + waitListenerStep(start, listener, 9); + Assert.assertEquals(1, myTestInteraction.getNumPlaintextPrompt()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + Assert.assertEquals(1, myTestInteraction.getNumPlaintextPrompt()); + Assert.assertEquals(9, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + SVNJNAUtil.setJNAEnabled(true); + } + + public void testPlaintextPromptAndSecondPrompt() throws Exception { + SVNJNAUtil.setJNAEnabled(false); + + // yes, no + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + myTestInteraction.setPlaintextAnswer(false); + + final SVNURL url = SVNURL.parseURIEncoded("http://some.host.com/repo"); + final SVNURL url2 = SVNURL.parseURIEncoded("http://some.other.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.without_pasword_save)); + + commonScheme(url, false, null); + long start = System.currentTimeMillis(); + waitListenerStep(start, listener, 3); + Assert.assertEquals(1, myTestInteraction.getNumPlaintextPrompt()); + // actually password not saved, but save was called + savedOnceListener.assertSaved(url, ISVNAuthenticationManager.PASSWORD); + savedOnceListener.reset(); + myTestInteraction.reset(); + + listener.addStep(new Trinity(ProviderType.persistent, url2, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url2, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url2, Type.without_pasword_save)); + commonScheme(url2, false, "anotherRealm"); + start = System.currentTimeMillis(); + waitListenerStep(start, listener, 6); + Assert.assertEquals(1, myTestInteraction.getNumPlaintextPrompt()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + Assert.assertEquals(1, myTestInteraction.getNumPlaintextPrompt()); + Assert.assertEquals(6, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + // didn't called to save for 2nd time + savedOnceListener.assertNotSaved(url, ISVNAuthenticationManager.PASSWORD); + + if (exception[0] != null) { + throw exception[0]; + } + SVNJNAUtil.setJNAEnabled(true); + } + + public void testPlaintextSSLPrompt() throws Exception { + SVNJNAUtil.setJNAEnabled(false); + + // yes, no + final TestListener listener = new TestListener(mySynchObject); + myAuthenticationManager.addListener(listener); + final SavedOnceListener savedOnceListener = new SavedOnceListener(); + myAuthenticationManager.addListener(savedOnceListener); + myTestInteraction.setSSLPlaintextAnswer(true); + + final SVNURL url = SVNURL.parseURIEncoded("https://some.host.com/repo"); + + final SVNException[] exception = new SVNException[1]; + final Boolean[] result = new Boolean[1]; + synchronousBackground(new Runnable() { + @Override + public void run() { + try { + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.save)); + + commonScheme(url, false, null); + long start = System.currentTimeMillis(); + waitListenerStep(start, listener, 3); + Assert.assertEquals(1, myTestInteraction.getNumSSLPlaintextPrompt()); + savedOnceListener.assertSaved(url, ISVNAuthenticationManager.SSL); + savedOnceListener.reset(); + myTestInteraction.reset(); + + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + @Override + public void run() { + try { + myConfiguration.clearAuthenticationDirectory(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + + myTestInteraction.setSSLPlaintextAnswer(false); + SvnConfiguration17.RUNTIME_AUTH_CACHE.clear(); + + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.without_pasword_save)); + commonScheme(url, false, null); + start = System.currentTimeMillis(); + waitListenerStep(start, listener, 6); + Assert.assertEquals(1, myTestInteraction.getNumSSLPlaintextPrompt()); + + SvnConfiguration17.RUNTIME_AUTH_CACHE.clear(); + myTestInteraction.reset(); + savedOnceListener.reset(); + + listener.addStep(new Trinity(ProviderType.persistent, url, Type.request)); + listener.addStep(new Trinity(ProviderType.interactive, url, Type.request)); + listener.addStep(new Trinity(ProviderType.persistent, url, Type.without_pasword_save)); + commonScheme(url, false, null); + start = System.currentTimeMillis(); + waitListenerStep(start, listener, 9); + Assert.assertEquals(1, myTestInteraction.getNumSSLPlaintextPrompt()); + } + catch (SVNException e) { + exception[0] = e; + } + result[0] = true; + } + }); + + Assert.assertTrue(result[0]); + Assert.assertEquals(1, myTestInteraction.getNumSSLPlaintextPrompt()); + Assert.assertEquals(9, listener.getCnt()); + listener.assertForAwt(); + savedOnceListener.assertForAwt(); + savedOnceListener.assertSaved(url, ISVNAuthenticationManager.SSL); + + if (exception[0] != null) { + throw exception[0]; + } + SVNJNAUtil.setJNAEnabled(true); + } + + private static void synchronousBackground(final Runnable runnable) throws InterruptedException { + final Semaphore semaphore = new Semaphore(); + semaphore.down(); + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + try { + runnable.run(); + } finally { + semaphore.up(); + } + } + }); + semaphore.waitFor(); + } + + private void commonScheme(final SVNURL url, final boolean username, final String realm) throws SVNException { + String kind = null; + + final String actualRealm = realm == null ? "realm" : realm; + final String protocol = url.getProtocol(); + if (username) { + kind = ISVNAuthenticationManager.USERNAME; + } else if ("svn+ssh".equals(protocol)) { + kind = ISVNAuthenticationManager.SSH; + } else if ("http".equals(protocol)) { + kind = ISVNAuthenticationManager.PASSWORD; + } else if ("https".equals(protocol)) { + kind = ISVNAuthenticationManager.SSL; + } else if ("file".equals(protocol)) { + kind = ISVNAuthenticationManager.USERNAME; + } + SVNAuthentication authentication = null; + try { + authentication = myAuthenticationManager.getFirstAuthentication(kind, actualRealm, url); + while (! passwordSpecified(authentication)) { + authentication = myAuthenticationManager.getNextAuthentication(kind, actualRealm, url); + } + } finally { + myAuthenticationManager.acknowledgeAuthentication(authentication != null, kind, actualRealm, null, authentication); + } + } + + private static class SvnTestInteractiveAuthentication implements ISVNAuthenticationProvider { + private final SvnAuthenticationManager myManager; + private boolean mySaveData; + + public SvnTestInteractiveAuthentication(SvnAuthenticationManager manager) { + myManager = manager; + mySaveData = true; + } + + public void setSaveData(boolean saveData) { + mySaveData = saveData; + } + + @Override + public int acceptServerAuthentication(SVNURL url, String realm, Object certificate, boolean resultMayBeStored) { + return ISVNAuthenticationProvider.REJECTED; + } + + @Override + public SVNAuthentication requestClientAuthentication(String kind, + SVNURL url, + String realm, + SVNErrorMessage errorMessage, + SVNAuthentication previousAuth, + boolean authMayBeStored) { + authMayBeStored = authMayBeStored && mySaveData; + SVNAuthentication result = null; + if (ISVNAuthenticationManager.USERNAME.equals(kind)) { + result = new SVNUserNameAuthentication("username", authMayBeStored); + } else if (ISVNAuthenticationManager.PASSWORD.equals(kind)) { + result = new SVNPasswordAuthentication("username", "abc", authMayBeStored, url, false); + } else if (ISVNAuthenticationManager.SSH.equals(kind)) { + result = new SVNSSHAuthentication("username", "abc", -1, authMayBeStored, url, false); + } else if (ISVNAuthenticationManager.SSL.equals(kind)) { + result = new SVNSSLAuthentication(new File("aaa"), "abc", authMayBeStored, url, false); + } + if (! ISVNAuthenticationManager.USERNAME.equals(kind)) { + myManager.requested(ProviderType.interactive, url, realm, kind, result == null); + } + return result; + } + } + + private static boolean passwordSpecified(final SVNAuthentication authentication) { + final String kind = authentication.getKind(); + if (ISVNAuthenticationManager.SSH.equals(kind)) { + if (((SVNSSHAuthentication) authentication).hasPrivateKey()) { + return ((SVNSSHAuthentication) authentication).getPassphrase() != null && + (((SVNSSHAuthentication) authentication).getPrivateKey() != null || + ((SVNSSHAuthentication) authentication).getPrivateKeyFile() != null); + } else { + return ((SVNSSHAuthentication) authentication).getPassword() != null; + } + } else if (ISVNAuthenticationManager.PASSWORD.equals(kind)) { + return ((SVNPasswordAuthentication) authentication).getPassword() != null; + } else if (ISVNAuthenticationManager.SSL.equals(kind)) { + return ((SVNSSLAuthentication) authentication).getPassword() != null; + } else if (ISVNAuthenticationManager.USERNAME.equals(kind)) { + return true; + } + return true; + } + + private static class TestListener implements SvnAuthenticationListener { + private final List> myExpectedSequence; + private int myCnt; + private final Object mySynchObject; + private boolean mySuccess; + private boolean mySaved; + + private TestListener(final Object synchObject) { + mySynchObject = synchObject; + myExpectedSequence = new ArrayList>(); + myCnt = 0; + mySuccess = true; + } + + public void addStep(Trinity step) { + myExpectedSequence.add(step); + } + + public int getCnt() { + return myCnt; + } + + @Override + public void saveAttemptStarted(ProviderType type, SVNURL url, String realm, String kind) { + mySaved = false; + } + + @Override + public void saveAttemptFinished(ProviderType type, SVNURL url, String realm, String kind) { + if (! mySuccess) return; + if (! mySaved) { + saveRegistration(type, url, false); + } + } + + @Override + public void actualSaveWillBeTried(ProviderType type, SVNURL url, String realm, String kind) { + if (! mySuccess) return; + mySaved = true; + saveRegistration(type, url, true); + } + + private void saveRegistration(ProviderType type, SVNURL url, boolean withCredentials) { + mySuccess = myExpectedSequence.get(myCnt).equals(new Trinity(type, url, withCredentials ? Type.save : Type.without_pasword_save)); + if (mySuccess) { + ++ myCnt; + } + synchronized (mySynchObject) { + mySynchObject.notifyAll(); + } + } + + public void reset() { + myExpectedSequence.clear(); + myCnt = 0; + mySuccess = true; + } + + @Override + public void requested(ProviderType type, SVNURL url, String realm, String kind, boolean canceled) { + if (! mySuccess) return; + + mySuccess = myExpectedSequence.get(myCnt).equals(new Trinity(type, url, Type.request)); + if (mySuccess) { + ++ myCnt; + } + synchronized (mySynchObject) { + mySynchObject.notifyAll(); + } + } + + public void assertForAwt() { + Assert.assertTrue("last cnt = " + myCnt, mySuccess); + } + } + + private static class SavedOnceListener implements SvnAuthenticationListener { + private final Set> myClientRequested; + private final Set> mySaved; + private String myCause; + private boolean mySaveCalled; + + private SavedOnceListener() { + myClientRequested = new HashSet>(); + mySaved = new HashSet>(); + } + + public void reset() { + mySaved.clear(); + myClientRequested.clear(); + } + + @Override + public void saveAttemptStarted(ProviderType type, SVNURL url, String realm, String kind) { + mySaveCalled = false; + } + + @Override + public void saveAttemptFinished(ProviderType type, SVNURL url, String realm, String kind) { + if (! mySaveCalled) { + saveRegistration(url, kind); + } + } + + @Override + public void actualSaveWillBeTried(ProviderType type, SVNURL url, String realm, String kind) { + mySaveCalled = true; + saveRegistration(url, kind); + } + + private void saveRegistration(SVNURL url, String kind) { + final Pair pair = new Pair(url, kind); + if (mySaved.contains(pair)) { + myCause = "saved twice"; + } + mySaved.add(pair); + } + + public boolean isSaved(final SVNURL url, final String kind) { + return mySaved.contains(new Pair(url, kind)); + } + + @Override + public void requested(ProviderType type, SVNURL url, String realm, String kind, boolean canceled) { + if (ProviderType.interactive.equals(type)) { + final Pair pair = new Pair(url, kind); + if (myClientRequested.contains(pair)) { + myCause = "client requested twice"; + } + myClientRequested.add(pair); + } + } + + public void assertForAwt() { + assertNull(myCause, myCause); + } + + public void assertSaved(final SVNURL url, final String kind) { + Assert.assertTrue("not saved", mySaved.contains(new Pair(url, kind))); + } + + public void assertNotSaved(final SVNURL url, final String kind) { + Assert.assertTrue("saved", ! mySaved.contains(new Pair(url, kind))); + } + } + + private static enum Type { + request, + save, + without_pasword_save + } + + private static class TestInteraction implements SvnAuthenticationInteraction { + private int myNumAuthWarn; + private int myNumPasswordsWarn; + private int myNumSSLWarn; + private int myNumPlaintextPrompt; + private int myNumSSLPlaintextPrompt; + + private boolean myPlaintextAnswer; + private boolean mySSLPlaintextAnswer; + + public void setPlaintextAnswer(boolean plaintextAnswer) { + myPlaintextAnswer = plaintextAnswer; + } + + public void setSSLPlaintextAnswer(boolean SSLPlaintextAnswer) { + mySSLPlaintextAnswer = SSLPlaintextAnswer; + } + + public void assertNothing() { + Assert.assertEquals("myNumAuthWarn", myNumAuthWarn, 0); + Assert.assertEquals("myNumPasswordsWarn", myNumPasswordsWarn, 0); + Assert.assertEquals("myNumSSLWarn", myNumSSLWarn, 0); + Assert.assertEquals("myNumPlaintextPrompt", myNumPlaintextPrompt, 0); + Assert.assertEquals("myNumSSLPlaintextPrompt", myNumSSLPlaintextPrompt, 0); + } + + public void reset() { + myNumAuthWarn = 0; + myNumPasswordsWarn = 0; + myNumSSLWarn = 0; + myNumPlaintextPrompt = 0; + myNumSSLPlaintextPrompt = 0; + } + + @Override + public boolean promptForPlaintextPasswordSaving(SVNURL url, String realm) { + ++ myNumPlaintextPrompt; + return myPlaintextAnswer; + } + + @Override + public boolean promptInAwt() { + return false; + } + + @Override + public void warnOnAuthStorageDisabled(SVNURL url) { + ++ myNumAuthWarn; + } + + @Override + public void warnOnPasswordStorageDisabled(SVNURL url) { + ++ myNumPasswordsWarn; + } + + @Override + public void warnOnSSLPassphraseStorageDisabled(SVNURL url) { + ++ myNumSSLWarn; + } + + @Override + public boolean promptForSSLPlaintextPassphraseSaving(SVNURL url, String realm, File certificateFile, String certificateName) { + ++ myNumSSLPlaintextPrompt; + return mySSLPlaintextAnswer; + } + + public int getNumAuthWarn() { + return myNumAuthWarn; + } + + public int getNumPasswordsWarn() { + return myNumPasswordsWarn; + } + + public int getNumPlaintextPrompt() { + return myNumPlaintextPrompt; + } + + public int getNumSSLPlaintextPrompt() { + return myNumSSLPlaintextPrompt; + } + + public int getNumSSLWarn() { + return myNumSSLWarn; + } + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnCachingRevisionsTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnCachingRevisionsTest.java new file mode 100644 index 000000000000..9deba58136bd --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnCachingRevisionsTest.java @@ -0,0 +1,271 @@ +package org.jetbrains.idea.svn17; + +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.changes.committed.ChangesBunch; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.testFramework.fixtures.CodeInsightFixtureTestCase; +import org.jetbrains.idea.svn17.history.*; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNLogEntry; +import org.tmatesoft.svn.core.wc.SVNRevision; + +import java.util.*; + +public class SvnCachingRevisionsTest extends CodeInsightFixtureTestCase { + private SvnRepositoryLocation myLocation; + private LoadedRevisionsCache17 myInternalManager; + private final static String URL = "file:///C:/repo/trunk"; + private final static String ROOT = "file:///C:/repo"; + private final static String AUTHOR = "author"; + private final static int PAGE = 5; + + @Override + protected void setUp() throws Exception { + super.setUp(); + myLocation = new SvnRepositoryLocation(URL); + myInternalManager = LoadedRevisionsCache17.getInstance(myFixture.getProject()); + } + + @Override + protected void tearDown() throws Exception { + FileUtil.delete(SvnApplicationSettings17.getLoadedRevisionsDir(myFixture.getProject())); + super.tearDown(); + } + + private SvnChangeList createList(final long revision) { + return new SvnChangeList(null, myLocation, + new SVNLogEntry(Collections.emptyMap(), revision, AUTHOR, new Date(System.currentTimeMillis()), ""), ROOT); + } + + private class MockSvnLogLoader implements SvnLogLoader { + private final List myRevisions; + + private MockSvnLogLoader(final List revisions) { + myRevisions = revisions; + } + + @Override + public List loadInterval(final SVNRevision fromIncluding, final SVNRevision toIncluding, final int maxCount, + final boolean includingYoungest, final boolean includeOldest) throws SVNException { + long young = fromIncluding.getNumber(); + young = (young == -1) ? myRevisions.get(myRevisions.size() - 1) : young; + final long old = toIncluding.getNumber(); + + final List result = new ArrayList(); + + int cnt = -1; + // from back + for (int i = myRevisions.size() - 1; i >= 0; -- i) { + final Long current = myRevisions.get(i); + if ((cnt == -1) && (current <= young)) { + cnt = 0; + } + if (cnt >= 0) { + ++ cnt; + } + if ((young > current) || (includingYoungest && (young == current))) { + if ((old < current) || (includeOldest && (old == current))) { + result.add(createList(current)); + } + } + if (cnt == maxCount) { + break; + } + } + return result; + } + } + + private LiveProvider createLiveProvider(final List liveRevisions) { + return new LiveProvider(null, myLocation, liveRevisions.get(liveRevisions.size() - 1), new MockSvnLogLoader(liveRevisions)); + } + + private void checkBounds(final Pair bounds, final long startRevision, final int step) { + assert (bounds.first - startRevision) % step == 0; + assert (bounds.second - startRevision) % step == 0; + } + + private class MockCachedProvider extends CachedProvider { + private MockCachedProvider(final Iterator iterator, final Origin origin) { + super(iterator, origin); + } + + @Override + public void doCacheUpdate(final List> fragments) { + assert false; + } + } + + private Iterator createCommittedIterator(final int bunchSize, final List revisions) { + final List list = new ArrayList(); + for (int i = revisions.size() - 1; i >= 0; i -= bunchSize) { + final int j = (bunchSize > i) ? -1 : (i - bunchSize); + final List subList = new ArrayList(); + for (int k = i; k > j; -- k) { + subList.add(createList(revisions.get(k))); + } + list.add(new ChangesBunch(subList, (j != -1))); + if (j == -1) { + break; + } + } + return list.iterator(); + } + + private void performTest(final long startRevision, final int step, final Pair committedBounds, + final List> internalBounds, final long endRevision) throws Exception { + assert ((endRevision - startRevision) % step) == 0; + if (committedBounds != null) { + checkBounds(committedBounds, startRevision, step); + } + for (Pair bound : internalBounds) { + checkBounds(bound, startRevision, step); + } + + final List liveRevisions = new ArrayList(); + final List committedRevisions = new ArrayList(); + for (long i = startRevision; i <= endRevision; i += step) { + liveRevisions.add(i); + if (committedBounds != null) { + if ((committedBounds.first <= i) && (committedBounds.second >= i)) { + committedRevisions.add(i); + } + } + } + + // each pair corresponds to interval + final List internalRevisions = new ArrayList(); + LoadedRevisionsCache17.Bunch bindTo = null; + for (int i = 0; i < internalBounds.size(); i++) { + final Pair bound = internalBounds.get(i); + final boolean consistent = (i != 0) && (internalBounds.get(i - 1).second + step == bound.first); + final List revisions = new ArrayList(); + for (long j = bound.first; j <= bound.second; j += step) { + revisions.add(j); + internalRevisions.add(j); + } + bindTo = putToInternalCache(revisions, consistent, bindTo); + } + + assert Collections.disjoint(committedRevisions, internalRevisions); + + final LiveProvider liveProvider = createLiveProvider(liveRevisions); + final CachedProvider committedProvider = committedRevisions.isEmpty() ? null : + new MockCachedProvider(createCommittedIterator(PAGE, committedRevisions), Origin.VISUAL); + final CachedProvider internalProvider = internalRevisions.isEmpty() ? null : + new MockCachedProvider(myInternalManager.iterator(myLocation.getURL()), Origin.INTERNAL); + + final BunchFactory factory = new BunchFactory(internalProvider, committedProvider, liveProvider); + Ref myYoungestRead = new Ref(Boolean.FALSE); + long i = endRevision; + for (; i >= startRevision; i -= step * PAGE) { + assert (! Boolean.TRUE.equals(myYoungestRead.get())); + final List fragments = factory.goBack(PAGE, myYoungestRead); + debugFragments(i, fragments); + checkFragments(i, internalRevisions, committedRevisions, fragments, step); + } + // otherwise end of live stream is not jet detected (additional request is required) + assert (! (i + step < startRevision) ^ Boolean.TRUE.equals(myYoungestRead.get())); + } + + private void checkFragments(final long earlyRevision, final List internally, final List committed, + final List fragments, final int step) { + final List expectedRevisions = new ArrayList(); + for (long i = earlyRevision; i > (earlyRevision - PAGE * step); i -= step) { + expectedRevisions.add(i); + } + + for (Fragment fragment : fragments) { + final Origin currentOrigin = fragment.getOrigin(); + for (CommittedChangeList list : fragment.getList()) { + assert ! expectedRevisions.isEmpty(); + final long expected = expectedRevisions.remove(0); + assert expected == list.getNumber(); + assert (Origin.INTERNAL.equals(currentOrigin) && (internally.contains(expected))) || + (Origin.VISUAL.equals(currentOrigin) && (committed.contains(expected))) || + (Origin.LIVE.equals(currentOrigin) && (! internally.contains(expected)) && (! committed.contains(expected))); + } + } + } + + private void debugFragments(final long earlyRevision, final List fragments) { + //System.out.println("Loaded for start revision: " + earlyRevision); + //for (Fragment fragment : fragments) { + // System.out.println(fragment.getOrigin().toString() + " from: " + fragment.getList().get(0).getNumber() + + // " to: " + fragment.getList().get(fragment.getList().size() - 1).getNumber()); + //} + //System.out.println(); + } + + private List revisionsToLists(final List revisions) { + final List lists = new ArrayList(); + for (Long revision : revisions) { + lists.add(createList(revision)); + } + return lists; + } + + private LoadedRevisionsCache17.Bunch putToInternalCache(final List revisions, final boolean consistent, final LoadedRevisionsCache17.Bunch bindTo) { + final List lists = revisionsToLists(revisions); + Collections.reverse(lists); + return myInternalManager.put(lists, consistent, bindTo); + } + + public void testJustLiveProvider() throws Exception { + performTest(11, 2, null, Collections.>emptyList(), 121); + } + + public void testLiveAndSimpleInternalProvider() throws Exception { + for (int i = 0; i < 2 * PAGE; i+=2) { + performTest(11, 2, null, Collections.singletonList(new Pair(109L, 117L)), 121 + i); + } + } + + public void testLiveAndSimpleCommittedProvider() throws Exception { + for (int i = 0; i < 2 * PAGE; i+=2) { + performTest(11, 2, new Pair(19L, 117L), Collections.>emptyList(), 121 + i); + } + } + + public void testLiveAndTwoInternalsProvider() throws Exception { + for (int i = 0; i < 2 * PAGE; i+=2) { + performTest(11, 2, null, Arrays.asList(new Pair(101L, 111L), new Pair(113L, 117L)), 121 + i); + } + } + + public void testCommittedAndSeveralInternalsProvider() throws Exception { + for (int i = 0; i < 2 * PAGE; i+=2) { + performTest(11, 2, new Pair(11L, 17L), Arrays.asList(new Pair(19L, 23L), new Pair(25L, 37L + i)), 37 + i); + } + } + + public void testAllThreeProviders() throws Exception { + for (int i = 0; i < 2 * PAGE; i+=2) { + performTest(11, 2, new Pair(11L, 17L), Arrays.asList(new Pair(23L, 37L), new Pair(45L, 57L)), 87 + i); + } + } + + public void testShift() throws Exception { + for (int i = 0; i < 2 * PAGE; i+=2) { + performTest(11, 2, new Pair(11L, 15L), Arrays.asList(new Pair(17L, 25L), new Pair(27L, 35L)), 37 + i); + } + } + + public void testShortLive() throws Exception { + performTest(11, 2, null, Collections.>emptyList(), 13); + } + + public void testShortInternal() throws Exception { + performTest(11, 2, null, Collections.singletonList(new Pair(11L, 15L)), 15); + } + + public void testShortCommitted() throws Exception { + performTest(11, 2, new Pair(11L, 15L), Collections.>emptyList(), 15); + } + + public void testThreeByOne() throws Exception { + performTest(11, 2, new Pair(11L, 11L), Collections.singletonList(new Pair(13L, 13L)), 15); + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnChangesCorrectlyRefreshedTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnChangesCorrectlyRefreshedTest.java new file mode 100644 index 000000000000..11597a794369 --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnChangesCorrectlyRefreshedTest.java @@ -0,0 +1,303 @@ +package org.jetbrains.idea.svn17; + +import com.intellij.openapi.fileEditor.impl.LoadTextUtil; +import com.intellij.openapi.vcs.VcsConfiguration; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.changes.ChangeListManagerImpl; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vcs.changes.pending.DuringChangeListManagerUpdateTestScheme; +import com.intellij.openapi.vcs.changes.ui.RollbackWorker; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import org.junit.Assert; +import org.junit.Test; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +public class SvnChangesCorrectlyRefreshedTest extends SvnTestCase { + private ChangeListManager clManager; + + @Override + public void setUp() throws Exception { + super.setUp(); + + clManager = ChangeListManager.getInstance(myProject); + + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + } + + private class SubTree { + private final VirtualFile myRootDir; + private VirtualFile mySourceDir; + private final VirtualFile myTargetDir; + + private VirtualFile myS1File; + private VirtualFile myS2File; + + private final List myTargetFiles; + private static final String ourS1Contents = "123"; + private static final String ourS2Contents = "abc"; + + private SubTree(final VirtualFile base) throws Throwable { + myRootDir = createDirInCommand(base, "root"); + mySourceDir = createDirInCommand(myRootDir, "source"); + myS1File = createFileInCommand(mySourceDir, "s1.txt", ourS1Contents); + myS2File = createFileInCommand(mySourceDir, "s2.txt", ourS2Contents); + + myTargetDir = createDirInCommand(myRootDir, "target"); + myTargetFiles = new ArrayList(); + for (int i = 0; i < 10; i++) { + myTargetFiles.add(createFileInCommand(myTargetDir, "t" + (i+10) +".txt", ourS1Contents)); + } + } + } + + private static void sleep300() { + try { + Thread.sleep(300); + } + catch (InterruptedException ignore) { } + } + + @Test + public void testModificationAndAfterRevert() throws Throwable { + final SubTree subTree = new SubTree(myWorkingCopyDir); + checkin(); + sleep300(); + + editFileInCommand(myProject, subTree.myS1File, "new"); + + final CharSequence text1 = LoadTextUtil.loadText(subTree.myS1File); + Assert.assertEquals("new", text1.toString()); + + sleep300(); + LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(subTree.myS1File.getPath())); + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + clManager.ensureUpToDate(false); + final VcsException updateException = ((ChangeListManagerImpl)clManager).getUpdateException(); + if (updateException != null) { + updateException.printStackTrace(); + } + Assert.assertNull(updateException == null ? null : updateException.getMessage(), updateException); + + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(new VirtualFile[] {subTree.myS1File}, clManager.getDefaultListName(), clManager); + + final Collection changes = clManager.getDefaultChangeList().getChanges(); + + final RollbackWorker worker = new RollbackWorker(myProject, true); + worker.doRollback(changes, false, null, null); + + final CharSequence text = LoadTextUtil.loadText(subTree.myS1File); + Assert.assertEquals(SubTree.ourS1Contents, text.toString()); + + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + clManager.ensureUpToDate(false); + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(new VirtualFile[] {}, clManager.getDefaultListName(), clManager); + } + + @Test + public void testRenameFileAndAfterRevert() throws Throwable { + final SubTree subTree = new SubTree(myWorkingCopyDir); + checkin(); + + final String newName = "newName"; + renameFileInCommand(subTree.myS1File, newName); + + assertVF(subTree.mySourceDir, newName); + + sleep300(); + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + clManager.ensureUpToDate(false); + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(new VirtualFile[] {subTree.myS1File}, clManager.getDefaultListName(), clManager); + + final Collection changes = clManager.getDefaultChangeList().getChanges(); + + final RollbackWorker worker = new RollbackWorker(myProject, true); + worker.doRollback(changes, false, null, null); + + assertVF(subTree.mySourceDir, "s1.txt"); + + clManager.ensureUpToDate(false); + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(new VirtualFile[] {}, clManager.getDefaultListName(), clManager); + } + + @Test + public void testMoveFileAndAfterRevert() throws Throwable { + final SubTree subTree = new SubTree(myWorkingCopyDir); + checkin(); + + moveFileInCommand(subTree.myS1File, subTree.myTargetDir); + + assertVF(subTree.myTargetDir, "s1.txt"); + + sleep300(); + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + clManager.ensureUpToDate(false); + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(new VirtualFile[] {subTree.myS1File}, clManager.getDefaultListName(), clManager); + + final Collection changes = clManager.getDefaultChangeList().getChanges(); + + final RollbackWorker worker = new RollbackWorker(myProject, true); + worker.doRollback(changes, false, null, null); + + assertVF(subTree.mySourceDir, "s1.txt"); + + clManager.ensureUpToDate(false); + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(new VirtualFile[] {}, clManager.getDefaultListName(), clManager); + } + + @Test + public void testRenameDirAndAfterRevert() throws Throwable { + final SubTree subTree = new SubTree(myWorkingCopyDir); + checkin(); + + final String newName = "newName"; + renameFileInCommand(subTree.mySourceDir, newName); + + assertVF(subTree.myRootDir, newName); + assertVF(subTree.mySourceDir, "s1.txt"); + assertVF(subTree.mySourceDir, "s2.txt"); + + sleep300(); + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + clManager.ensureUpToDate(false); + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(new VirtualFile[] {subTree.mySourceDir, subTree.myS1File, subTree.myS2File}, + clManager.getDefaultListName(), clManager); + + final Collection changes = clManager.getDefaultChangeList().getChanges(); + + final RollbackWorker worker = new RollbackWorker(myProject, true); + worker.doRollback(changes, false, null, null); + + subTree.mySourceDir = assertVF(subTree.myRootDir, "source"); + Assert.assertTrue(subTree.mySourceDir.getPath().endsWith("/root/source")); + assertVF(subTree.mySourceDir, "s1.txt"); + assertVF(subTree.mySourceDir, "s2.txt"); + + clManager.ensureUpToDate(false); + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(new VirtualFile[] {}, clManager.getDefaultListName(), clManager); + } + + @Test + public void testMoveDirEditFileAndAfterRevert() throws Throwable { + final SubTree subTree = new SubTree(myWorkingCopyDir); + checkin(); + + moveFileInCommand(subTree.mySourceDir, subTree.myTargetDir); + Assert.assertTrue(subTree.mySourceDir.getPath().endsWith("/target/source")); + assertVF(subTree.myTargetDir, "source"); + + editFileInCommand(myProject, subTree.myS1File, "new"); + final CharSequence text1 = LoadTextUtil.loadText(subTree.myS1File); + Assert.assertEquals("new", text1.toString()); + + sleep300(); + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + clManager.ensureUpToDate(false); + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(new VirtualFile[] {subTree.mySourceDir, subTree.myS1File, subTree.myS2File}, + clManager.getDefaultListName(), clManager); + + final Collection changes = clManager.getDefaultChangeList().getChanges(); + + final RollbackWorker worker = new RollbackWorker(myProject, true); + worker.doRollback(changes, false, null, null); + + subTree.mySourceDir = assertVF(subTree.myRootDir, "source"); + Assert.assertTrue(subTree.mySourceDir.getPath().endsWith("/root/source")); + + subTree.myS1File = assertVF(subTree.mySourceDir, "s1.txt"); + subTree.myS2File = assertVF(subTree.mySourceDir, "s2.txt"); + final CharSequence text = LoadTextUtil.loadText(subTree.myS1File); + Assert.assertEquals(SubTree.ourS1Contents, text.toString()); + + clManager.ensureUpToDate(false); + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(new VirtualFile[] {}, clManager.getDefaultListName(), clManager); + } + + @Test + public void testAddDirEditFileAndAfterRevert() throws Throwable { + final SubTree subTree = new SubTree(myWorkingCopyDir); + + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + clManager.ensureUpToDate(false); + final List files = getAllFiles(subTree); + + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(VfsUtil.toVirtualFileArray(files), clManager.getDefaultListName(), clManager); + + final Collection changes = clManager.getDefaultChangeList().getChanges(); + + final RollbackWorker worker = new RollbackWorker(myProject, true); + worker.doRollback(changes, false, null, null); + + assertVF(subTree.myRootDir, "source"); + assertVF(subTree.mySourceDir, "s1.txt"); + assertVF(subTree.myRootDir, "target"); + + clManager.ensureUpToDate(false); + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(new VirtualFile[] {}, clManager.getDefaultListName(), clManager); + + for (VirtualFile file : files) { + Assert.assertTrue(file.getPath(), clManager.isUnversioned(file)); + } + } + + private List getAllFiles(final SubTree subTree) { + final List files = new ArrayList(); + files.addAll(Arrays.asList(subTree.myRootDir, subTree.mySourceDir, subTree.myS2File, subTree.myS1File, subTree.myTargetDir)); + files.addAll(subTree.myTargetFiles); + return files; + } + + @Test + public void testDeleteDirEditFileAndAfterRevert() throws Throwable { + final SubTree subTree = new SubTree(myWorkingCopyDir); + checkin(); + + deleteFileInCommand(subTree.myRootDir); + sleep300(); + + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + clManager.ensureUpToDate(false); + final List files = getAllFiles(subTree); + DuringChangeListManagerUpdateTestScheme.checkDeletedFilesAreInList(VfsUtil.toVirtualFileArray(files), clManager.getDefaultListName(), + clManager); + + final Collection changes = clManager.getDefaultChangeList().getChanges(); + + final RollbackWorker worker = new RollbackWorker(myProject, true); + worker.doRollback(changes, false, null, null); + + assertVF(subTree.myRootDir, "source"); + assertVF(subTree.mySourceDir, "s1.txt"); + assertVF(subTree.myRootDir, "target"); + + assertVF(subTree.myTargetDir, "t11.txt"); + assertVF(subTree.myTargetDir, "t13.txt"); + assertVF(subTree.myTargetDir, "t14.txt"); + assertVF(subTree.myTargetDir, "t15.txt"); + + clManager.ensureUpToDate(false); + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(new VirtualFile[] {}, clManager.getDefaultListName(), clManager); + } + + private VirtualFile assertVF(final VirtualFile parent, final String name) { + final VirtualFile[] files = parent.getChildren(); + final StringBuilder sb = new StringBuilder("Files: "); + for (VirtualFile file : files) { + sb.append(file.getName()).append(' '); + if (name.equals(file.getName())) return file; + } + System.out.println("not found as child"); + Assert.assertNotNull(LocalFileSystem.getInstance().findFileByIoFile(new File(parent.getPath(), name))); + //Assert.assertTrue(sb.toString(), false); + return null; + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnCommittedViewTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnCommittedViewTest.java new file mode 100644 index 000000000000..5042d6f4a18a --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnCommittedViewTest.java @@ -0,0 +1,283 @@ +package org.jetbrains.idea.svn17; + +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.vcs.CommittedChangesProvider; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vcs.VcsConfiguration; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ContentRevision; +import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.svn17.history.SvnChangeList; +import org.jetbrains.idea.svn17.history.SvnRepositoryLocation; +import org.junit.Assert; +import org.junit.Test; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class SvnCommittedViewTest extends SvnTestCase { + + @Test + public void testAdd() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + + final VirtualFile d1 = createDirInCommand(myWorkingCopyDir, "d1"); + + final VirtualFile f11 = createFileInCommand(d1, "f11.txt", "123\n456"); + final VirtualFile f12 = createFileInCommand(d1, "f12.txt", "----"); + + // r1, addition without history + checkin(); + + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); + final List changeListList = + committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), + new SvnRepositoryLocation(myRepoUrl), 0); + checkList(changeListList, 1, new Data[] {new Data(absPath(f11), FileStatus.ADDED, null), + new Data(absPath(f12), FileStatus.ADDED, null), new Data(absPath(d1), FileStatus.ADDED, null)}); + } + + @Test + public void testDelete() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + + final VirtualFile d1 = createDirInCommand(myWorkingCopyDir, "d1"); + + final VirtualFile f11 = createFileInCommand(d1, "f11.txt", "123\n456"); + final VirtualFile f12 = createFileInCommand(d1, "f12.txt", "----"); + + // r1, addition without history + checkin(); + + deleteFileInCommand(f11); + + checkin(); + update(); + + deleteFileInCommand(d1); + + checkin(); + + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); + final List changeListList = + committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), + new SvnRepositoryLocation(myRepoUrl), 0); + checkList(changeListList, 2, new Data[] {new Data(absPath(f11), FileStatus.DELETED, null)}); + checkList(changeListList, 3, new Data[] {new Data(absPath(d1), FileStatus.DELETED, null)}); + } + + @Test + public void testReplaced() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + + VirtualFile d1 = createDirInCommand(myWorkingCopyDir, "d1"); + + VirtualFile f11 = createFileInCommand(d1, "f11.txt", "123\n456"); + VirtualFile f12 = createFileInCommand(d1, "f12.txt", "----"); + + // r1, addition without history + checkin(); + + final String d1Path = new File(d1.getPath()).getAbsolutePath(); + verify(runSvn("delete", d1Path)); + verify(runSvn("add", d1Path)); + + checkin(); + + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); + final List changeListList = + committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), + new SvnRepositoryLocation(myRepoUrl), 0); + checkList(changeListList, 2, new Data[] {new Data(absPath(d1), FileStatus.MODIFIED, "- replaced")}); + } + + @Test + public void testMoveDir() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + + VirtualFile d1 = createDirInCommand(myWorkingCopyDir, "d1"); + VirtualFile d2 = createDirInCommand(myWorkingCopyDir, "d2"); + + VirtualFile f11 = createFileInCommand(d1, "f11.txt", "123\n456"); + VirtualFile f12 = createFileInCommand(d1, "f12.txt", "----"); + + // r1, addition without history + checkin(); + + final String oldPath = absPath(d1); + moveFileInCommand(d1, d2); + Thread.sleep(100); + + checkin(); + + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); + final List changeListList = + committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), + new SvnRepositoryLocation(myRepoUrl), 0); + checkList(changeListList, 2, new Data[] {new Data(absPath(d1), FileStatus.MODIFIED, "- moved from .." + File.separatorChar + "d1"), + new Data(oldPath, FileStatus.DELETED, null)}); + } + + @Test + public void testMoveDirChangeFile() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + + VirtualFile d1 = createDirInCommand(myWorkingCopyDir, "d1"); + VirtualFile d2 = createDirInCommand(myWorkingCopyDir, "d2"); + + VirtualFile f11 = createFileInCommand(d1, "f11.txt", "123\n456"); + VirtualFile f12 = createFileInCommand(d1, "f12.txt", "----"); + + // r1, addition without history + checkin(); + + final String oldPath = absPath(d1); + final String oldF11Path = new File(f11.getPath()).getAbsolutePath(); + moveFileInCommand(d1, d2); + editFileInCommand(myProject, f11, "new"); + + Thread.sleep(100); + + checkin(); + + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); + final List changeListList = + committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), + new SvnRepositoryLocation(myRepoUrl), 0); + checkList(changeListList, 2, new Data[] {new Data(absPath(d1), FileStatus.MODIFIED, "- moved from .." + File.separatorChar + "d1"), + new Data(oldPath, FileStatus.DELETED, null), + new Data(absPath(f11), FileStatus.MODIFIED, "- moved from " + oldF11Path)}); + } + + @Test + public void testCopyDir() throws Exception { + final File trunk = new File(myTempDirFixture.getTempDirPath(), "trunk"); + trunk.mkdir(); + Thread.sleep(100); + final File folder = new File(trunk, "folder"); + folder.mkdir(); + Thread.sleep(100); + new File(folder, "f1.txt").createNewFile(); + new File(folder, "f2.txt").createNewFile(); + Thread.sleep(100); + + verify(runSvn("import", "-m", "test", trunk.getAbsolutePath(), myRepoUrl + "/trunk")); + verify(runSvn("copy", "-m", "test", myRepoUrl + "/trunk", myRepoUrl + "/branch")); + + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); + final List changeListList = + committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), + new SvnRepositoryLocation(myRepoUrl + "/branch"), 0); + checkList(changeListList, 2, new Data[] {new Data(new File(myWorkingCopyDir.getPath(), "branch").getAbsolutePath(), FileStatus.ADDED, "- copied from /trunk")}); + } + + @Test + public void testCopyAndModify() throws Exception { + final File trunk = new File(myTempDirFixture.getTempDirPath(), "trunk"); + trunk.mkdir(); + Thread.sleep(100); + final File folder = new File(trunk, "folder"); + folder.mkdir(); + Thread.sleep(100); + new File(folder, "f1.txt").createNewFile(); + new File(folder, "f2.txt").createNewFile(); + Thread.sleep(100); + + verify(runSvn("import", "-m", "test", trunk.getAbsolutePath(), myRepoUrl + "/trunk")); + + update(); + + verify(runSvn("copy", myWorkingCopyDir.getPath() + "/trunk", myWorkingCopyDir.getPath() + "/branch")); + verify(runSvn("propset", "testprop", "testval", myWorkingCopyDir.getPath() + "/branch/folder")); + + checkin(); + + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); + final List changeListList = + committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), + new SvnRepositoryLocation(myRepoUrl + "/branch"), 0); + checkList(changeListList, 2, new Data[] {new Data(new File(myWorkingCopyDir.getPath(), "branch").getAbsolutePath(), FileStatus.ADDED, "- copied from /trunk"), + new Data(new File(myWorkingCopyDir.getPath(), "branch/folder").getAbsolutePath(), FileStatus.MODIFIED, "- copied from /trunk/folder")}); + } + + protected String absPath(final VirtualFile vf) { + return new File(vf.getPath()).getAbsolutePath(); + } + + protected static class Data { + public final String myLocalPath; + public final FileStatus myStatus; + @Nullable + public final String myOriginText; + + protected Data(@NotNull final String localPath, @NotNull final FileStatus status, @Nullable final String originText) { + myLocalPath = localPath; + myStatus = status; + myOriginText = originText; + } + + public boolean shouldBeComparedWithChange(final Change change) { + if (FileStatus.DELETED.equals(myStatus) && (change.getAfterRevision() == null)) { + // before path + return (change.getBeforeRevision() != null) && myLocalPath.equals(change.getBeforeRevision().getFile().getIOFile().getAbsolutePath()); + } else { + return (change.getAfterRevision() != null) && myLocalPath.equals(change.getAfterRevision().getFile().getIOFile().getAbsolutePath()); + } + } + } + + protected void checkList(final List lists, final long revision, final Data[] content) throws Exception { + SvnChangeList list = null; + for (SvnChangeList changeList : lists) { + if (changeList.getNumber() == revision) { + list = changeList; + } + } + Assert.assertNotNull("Change list #" + revision + " not found.", list); + + final Collection changes = new ArrayList(list.getChanges()); + Assert.assertNotNull("Null changes list", changes); + Assert.assertEquals(changes.size(), content.length); + + for (Data data : content) { + boolean found = false; + for (Change change : changes) { + if (data.shouldBeComparedWithChange(change)) { + Assert.assertTrue(Comparing.equal(data.myOriginText, change.getOriginText(myProject))); + Assert.assertEquals(data.myStatus, change.getFileStatus()); + found = true; + break; + } + } + Assert.assertTrue(printChanges(data, changes), found); + } + } + + private static String printChanges(final Data data, final Collection changes) { + final StringBuilder sb = new StringBuilder("Data: ").append(data.myLocalPath).append(" exists: "). + append(new File(data.myLocalPath).exists()).append(" Changes: "); + for (Change change : changes) { + final ContentRevision cr = change.getAfterRevision() == null ? change.getBeforeRevision() : change.getAfterRevision(); + final File ioFile = cr.getFile().getIOFile(); + sb.append("'").append(ioFile.getAbsolutePath()).append("' exists: ").append(ioFile.exists()).append(" | "); + } + return sb.toString(); + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnConcurrentChangeListManagerTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnConcurrentChangeListManagerTest.java new file mode 100644 index 000000000000..42be2b1bc28f --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnConcurrentChangeListManagerTest.java @@ -0,0 +1,326 @@ +package org.jetbrains.idea.svn17; + +import com.intellij.openapi.vcs.VcsBundle; +import com.intellij.openapi.vcs.VcsConfiguration; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.changes.LocalChangeList; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vcs.changes.pending.DuringChangeListManagerUpdateTestScheme; +import com.intellij.openapi.vfs.VirtualFile; +import org.junit.Test; + +import java.util.Collection; + +public class SvnConcurrentChangeListManagerTest extends SvnTestCase { + private DuringChangeListManagerUpdateTestScheme myScheme; + private String myDefaulListName; + + @Override + public void setUp() throws Exception { + super.setUp(); + myDefaulListName = VcsBundle.message("changes.default.changlist.name"); + + myScheme = new DuringChangeListManagerUpdateTestScheme(myProject, myTempDirFixture.getTempDirPath()); + } + + @Test + public void testRenameList() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile file = createFileInCommand("a.txt", "old content"); + final ChangeListManager changeListManager = ChangeListManager.getInstance(myProject); + changeListManager.ensureUpToDate(false); + + final LocalChangeList list = changeListManager.addChangeList("test", null); + changeListManager.moveChangesTo(list, new Change[] {changeListManager.getChange(file)}); + + final String newName = "renamed"; + + myScheme.doTest(new Runnable() { + @Override + public void run() { + changeListManager.editName(list.getName(), newName); + checkFilesAreInList(new VirtualFile[] {file}, newName, changeListManager); + } + }); + + checkFilesAreInList(new VirtualFile[] {file}, newName, changeListManager); + + changeListManager.ensureUpToDate(false); + checkFilesAreInList(new VirtualFile[] {file}, newName, changeListManager); + } + + @Test + public void testEditComment() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile file = createFileInCommand("a.txt", "old content"); + final ChangeListManager changeListManager = ChangeListManager.getInstance(myProject); + changeListManager.ensureUpToDate(false); + + final String listName = "test"; + final LocalChangeList list = changeListManager.addChangeList(listName, null); + changeListManager.moveChangesTo(list, new Change[] {changeListManager.getChange(file)}); + + final String finalText = "final text"; + + myScheme.doTest(new Runnable() { + @Override + public void run() { + final String intermediate = "intermediate text"; + changeListManager.editComment(list.getName(), intermediate); + assert changeListManager.findChangeList(listName) != null; + LocalChangeList list = changeListManager.findChangeList(listName); + assert intermediate.equals(list.getComment()); + + changeListManager.editComment(list.getName(), finalText); + list = changeListManager.findChangeList(listName); + assert finalText.equals(list.getComment()); + } + }); + + LocalChangeList changedList = changeListManager.findChangeList(listName); + assert finalText.equals(changedList.getComment()); + + changeListManager.ensureUpToDate(false); + changedList = changeListManager.findChangeList(listName); + assert finalText.equals(changedList.getComment()); + } + + @Test + public void testMove() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile file = createFileInCommand("a.txt", "old content"); + final ChangeListManager changeListManager = ChangeListManager.getInstance(myProject); + changeListManager.ensureUpToDate(false); + + final LocalChangeList list = changeListManager.addChangeList("test", null); + final LocalChangeList target = changeListManager.addChangeList("target", null); + changeListManager.moveChangesTo(list, new Change[] {changeListManager.getChange(file)}); + + myScheme.doTest(new Runnable() { + @Override + public void run() { + changeListManager.moveChangesTo(target, new Change[] {changeListManager.getChange(file)}); + checkFilesAreInList(new VirtualFile[] {file}, target.getName(), changeListManager); + } + }); + + checkFilesAreInList(new VirtualFile[] {file}, target.getName(), changeListManager); + + changeListManager.ensureUpToDate(false); + checkFilesAreInList(new VirtualFile[] {file}, target.getName(), changeListManager); + } + + @Test + public void testSetActive() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile file = createFileInCommand("a.txt", "old content"); + final ChangeListManager changeListManager = ChangeListManager.getInstance(myProject); + changeListManager.ensureUpToDate(false); + + final LocalChangeList list = changeListManager.addChangeList("test", null); + final LocalChangeList target = changeListManager.addChangeList("target", null); + changeListManager.moveChangesTo(list, new Change[] {changeListManager.getChange(file)}); + + myScheme.doTest(new Runnable() { + @Override + public void run() { + changeListManager.setDefaultChangeList(target); + assert changeListManager.getDefaultChangeList().getName().equals(target.getName()); + } + }); + + assert changeListManager.getDefaultChangeList().getName().equals(target.getName()); + + changeListManager.ensureUpToDate(false); + assert changeListManager.getDefaultChangeList().getName().equals(target.getName()); + } + + @Test + public void testRemove() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile file = createFileInCommand("a.txt", "old content"); + final VirtualFile fileB = createFileInCommand("b.txt", "old content"); + final ChangeListManager changeListManager = ChangeListManager.getInstance(myProject); + changeListManager.ensureUpToDate(false); + + final LocalChangeList list = changeListManager.addChangeList("test", null); + changeListManager.moveChangesTo(list, new Change[] {changeListManager.getChange(file)}); + + myScheme.doTest(new Runnable() { + @Override + public void run() { + changeListManager.removeChangeList(list.getName()); + assert changeListManager.findChangeList(list.getName()) == null; + checkFilesAreInList(new VirtualFile[] {file, fileB}, myDefaulListName, changeListManager); + } + }); + + assert changeListManager.findChangeList(list.getName()) == null; + checkFilesAreInList(new VirtualFile[] {file, fileB}, myDefaulListName, changeListManager); + + changeListManager.ensureUpToDate(false); + assert changeListManager.findChangeList(list.getName()) == null; + checkFilesAreInList(new VirtualFile[] {file, fileB}, myDefaulListName, changeListManager); + } + + @Test + public void testDoubleMove() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile file = createFileInCommand("a.txt", "old content"); + final ChangeListManager changeListManager = ChangeListManager.getInstance(myProject); + changeListManager.ensureUpToDate(false); + + final LocalChangeList list = changeListManager.addChangeList("test", null); + final LocalChangeList target = changeListManager.addChangeList("target", null); + final LocalChangeList target2 = changeListManager.addChangeList("target2", null); + changeListManager.moveChangesTo(list, new Change[] {changeListManager.getChange(file)}); + + myScheme.doTest(new Runnable() { + @Override + public void run() { + changeListManager.moveChangesTo(target, new Change[] {changeListManager.getChange(file)}); + checkFilesAreInList(new VirtualFile[] {file}, target.getName(), changeListManager); + changeListManager.moveChangesTo(target2, new Change[] {changeListManager.getChange(file)}); + checkFilesAreInList(new VirtualFile[] {file}, target2.getName(), changeListManager); + } + }); + + checkFilesAreInList(new VirtualFile[] {file}, target2.getName(), changeListManager); + + changeListManager.ensureUpToDate(false); + checkFilesAreInList(new VirtualFile[] {file}, target2.getName(), changeListManager); + } + + @Test + public void testDoubleMoveBack() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile file = createFileInCommand("a.txt", "old content"); + final ChangeListManager changeListManager = ChangeListManager.getInstance(myProject); + changeListManager.ensureUpToDate(false); + + final LocalChangeList list = changeListManager.addChangeList("test", null); + final LocalChangeList target = changeListManager.addChangeList("target", null); + changeListManager.moveChangesTo(list, new Change[] {changeListManager.getChange(file)}); + + myScheme.doTest(new Runnable() { + @Override + public void run() { + changeListManager.moveChangesTo(target, new Change[] {changeListManager.getChange(file)}); + checkFilesAreInList(new VirtualFile[] {file}, target.getName(), changeListManager); + changeListManager.moveChangesTo(list, new Change[] {changeListManager.getChange(file)}); + checkFilesAreInList(new VirtualFile[] {file}, list.getName(), changeListManager); + } + }); + + checkFilesAreInList(new VirtualFile[] {file}, list.getName(), changeListManager); + + changeListManager.ensureUpToDate(false); + checkFilesAreInList(new VirtualFile[] {file}, list.getName(), changeListManager); + } + + @Test + public void testAddPlusMove() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile file = createFileInCommand("a.txt", "old content"); + final ChangeListManager changeListManager = ChangeListManager.getInstance(myProject); + changeListManager.ensureUpToDate(false); + + final LocalChangeList list = changeListManager.addChangeList("test", null); + changeListManager.moveChangesTo(list, new Change[] {changeListManager.getChange(file)}); + + final String targetName = "target"; + + myScheme.doTest(new Runnable() { + @Override + public void run() { + final LocalChangeList target = changeListManager.addChangeList(targetName, null); + changeListManager.moveChangesTo(target, new Change[] {changeListManager.getChange(file)}); + checkFilesAreInList(new VirtualFile[] {file}, targetName, changeListManager); + } + }); + + checkFilesAreInList(new VirtualFile[] {file}, targetName, changeListManager); + + changeListManager.ensureUpToDate(false); + checkFilesAreInList(new VirtualFile[] {file}, targetName, changeListManager); + } + + @Test + public void testAddListBySvn() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile file = createFileInCommand("a.txt", "old content"); + final ChangeListManager changeListManager = ChangeListManager.getInstance(myProject); + + final String targetName = "target"; + // not parralel, just test of correct detection + runSvn("changelist", targetName, file.getPath()); + + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + changeListManager.ensureUpToDate(false); + checkFilesAreInList(new VirtualFile[] {file}, targetName, changeListManager); + } + + @Test + public void testComplex() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile file = createFileInCommand("a.txt", "old content"); + final VirtualFile fileB = createFileInCommand("b.txt", "old content"); + final VirtualFile fileC = createFileInCommand("c.txt", "old content"); + final VirtualFile fileD = createFileInCommand("d.txt", "old content"); + + final ChangeListManager changeListManager = ChangeListManager.getInstance(myProject); + + final LocalChangeList list = changeListManager.addChangeList("test", null); + final LocalChangeList toBeDeletedList = changeListManager.addChangeList("toBeDeletedList", null); + changeListManager.moveChangesTo(list, new Change[] {changeListManager.getChange(file), changeListManager.getChange(fileB)}); + changeListManager.moveChangesTo(toBeDeletedList, new Change[] {changeListManager.getChange(fileC), changeListManager.getChange(fileD)}); + changeListManager.ensureUpToDate(false); + + final String targetName = "target"; + final String finalName = "final list name"; + + myScheme.doTest(new Runnable() { + @Override + public void run() { + final LocalChangeList target = changeListManager.addChangeList(targetName, null); + changeListManager.moveChangesTo(target, new Change[] {changeListManager.getChange(file), changeListManager.getChange(fileB)}); + checkFilesAreInList(new VirtualFile[] {file, fileB}, targetName, changeListManager); + changeListManager.editName(targetName, finalName); + checkFilesAreInList(new VirtualFile[] {file, fileB}, finalName, changeListManager); + changeListManager.removeChangeList(toBeDeletedList.getName()); + checkFilesAreInList(new VirtualFile[] {fileC, fileD}, myDefaulListName, changeListManager); + changeListManager.moveChangesTo(LocalChangeList.createEmptyChangeList(myProject, finalName), + new Change[] {changeListManager.getChange(fileC)}); + checkFilesAreInList(new VirtualFile[] {file, fileB, fileC}, finalName, changeListManager); + checkFilesAreInList(new VirtualFile[] {fileD}, myDefaulListName, changeListManager); + } + }); + + checkFilesAreInList(new VirtualFile[] {file, fileB, fileC}, finalName, changeListManager); + checkFilesAreInList(new VirtualFile[] {fileD}, myDefaulListName, changeListManager); + + changeListManager.ensureUpToDate(false); + checkFilesAreInList(new VirtualFile[] {file, fileB, fileC}, finalName, changeListManager); + checkFilesAreInList(new VirtualFile[] {fileD}, myDefaulListName, changeListManager); + } + + private void checkFilesAreInList(final VirtualFile[] files, final String listName, final ChangeListManager manager) { + System.out.println("Checking files for list: " + listName); + assert manager.findChangeList(listName) != null; + final Collection changes = manager.findChangeList(listName).getChanges(); + assert changes.size() == files.length; + + for (Change change : changes) { + final VirtualFile vf = change.getAfterRevision().getFile().getVirtualFile(); + boolean found = false; + for (VirtualFile file : files) { + if (file.equals(vf)) { + found = true; + break; + } + } + assert found == true; + } + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnDeleteTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnDeleteTest.java new file mode 100644 index 000000000000..bda3e0a2b340 --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnDeleteTest.java @@ -0,0 +1,53 @@ +package org.jetbrains.idea.svn17; + +import com.intellij.openapi.vcs.FilePathImpl; +import com.intellij.openapi.vcs.VcsConfiguration; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.changes.LocalChangeList; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn17.integrate.AlienDirtyScope; +import org.junit.Assert; +import org.junit.Test; + +import java.io.File; +import java.util.Collection; +import java.util.List; + +/** + * @author yole + */ +public class SvnDeleteTest extends SvnTestCase { + // IDEADEV-16066 + @Test + public void testDeletePackage() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + VirtualFile dir = createDirInCommand(myWorkingCopyDir, "child"); + createFileInCommand(dir, "a.txt", "content"); + + verify(runSvn("status"), "A child", "A child" + File.separatorChar + "a.txt"); + checkin(); + + deleteFileInCommand(dir); + verify(runSvn("status"), "D child", "D child" + File.separatorChar + "a.txt"); + + LocalFileSystem.getInstance().refresh(false); + + final AlienDirtyScope dirtyScope = new AlienDirtyScope(); + dirtyScope.addDir(new FilePathImpl(myWorkingCopyDir)); + final List changesManually = getChangesInScope(dirtyScope); + Assert.assertEquals(2, changesManually.size()); + + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + // since ChangeListManager is runnning, it can take dirty scope itself;... it's easier to just take changes from it + final ChangeListManager clManager = ChangeListManager.getInstance(myProject); + clManager.ensureUpToDate(false); + final List lists = clManager.getChangeListsCopy(); + Assert.assertEquals(1, lists.size()); + final Collection changes = lists.get(0).getChanges(); + Assert.assertEquals(2, changes.size()); + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnGroupSplitterTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnGroupSplitterTest.java new file mode 100644 index 000000000000..c9909d31949a --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnGroupSplitterTest.java @@ -0,0 +1,75 @@ +package org.jetbrains.idea.svn17; + +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeListImpl; +import junit.framework.Assert; +import junit.framework.TestCase; +import org.jetbrains.idea.svn17.integrate.GroupSplitter; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; + +public class SvnGroupSplitterTest extends TestCase { + public void testSimple() throws Exception { + final CommittedChangeList[] arr = {create(1), create(2), create(3), create(4), create(5)}; + final GroupSplitter splitter = new GroupSplitter(Arrays.asList(arr)); + + final int[] expected = {5}; + checkExpected(splitter, expected); + } + + public void testSeparate() throws Exception { + final CommittedChangeList[] arr = {create(1), create(3), create(5), create(7), create(9)}; + final GroupSplitter splitter = new GroupSplitter(Arrays.asList(arr)); + + final int[] expected = {1,1,1,1,1}; + checkExpected(splitter, expected); + } + + public void testNothing() throws Exception { + final CommittedChangeList[] arr = {}; + final GroupSplitter splitter = new GroupSplitter(Arrays.asList(arr)); + Assert.assertFalse(splitter.hasNext()); + } + + public void testLongPackAtTheEnd() throws Exception { + final CommittedChangeList[] arr = {create(1), create(3), create(5), create(6), create(7)}; + final GroupSplitter splitter = new GroupSplitter(Arrays.asList(arr)); + + final int[] expected = {1,1,3}; + checkExpected(splitter, expected); + } + + public void testShortAtTheEnd() throws Exception { + final CommittedChangeList[] arr = {create(1), create(2), create(3), create(4), create(8)}; + final GroupSplitter splitter = new GroupSplitter(Arrays.asList(arr)); + + final int[] expected = {4,1}; + checkExpected(splitter, expected); + } + + public void testMixed() throws Exception { + final CommittedChangeList[] arr = {create(1), create(2), create(4), create(6), create(7), create(8), create(9), create(11), + create(13), create(14), create(122)}; + final GroupSplitter splitter = new GroupSplitter(Arrays.asList(arr)); + + final int[] expected = {2,1,4,1,2,1}; + checkExpected(splitter, expected); + } + + private void checkExpected(GroupSplitter splitter, int[] expected) { + int i = 0; + while (splitter.hasNext()) { + Assert.assertTrue(i < expected.length); + final int step = splitter.step(); + Assert.assertEquals(step, expected[i]); + ++ i; + } + } + + private CommittedChangeList create(final long number) { + return new CommittedChangeListImpl("abc", "bca", "committer", number, new Date(), Collections.emptyList()); + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnIgnoreTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnIgnoreTest.java new file mode 100644 index 000000000000..7a567a39f338 --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnIgnoreTest.java @@ -0,0 +1,137 @@ +/* + * 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.vcs.FileStatus; +import com.intellij.openapi.vcs.VcsConfiguration; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vfs.VirtualFile; +import org.junit.Assert; +import org.junit.Test; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNPropertyValue; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * @author irengrig + * Date: 12/20/10 + * Time: 5:05 PM + */ +public class SvnIgnoreTest extends SvnTestCase { + private ChangeListManager clManager; + private SvnVcs17 myVcs; + + @Override + public void setUp() throws Exception { + super.setUp(); + + clManager = ChangeListManager.getInstance(myProject); + myVcs = SvnVcs17.getInstance(myProject); + + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + } + + @Test + public void testOneFileCreatedDeep() throws Exception { + final VirtualFile versionedParent = createDirInCommand(myWorkingCopyDir, "versionedParent"); + final String name = "ign123"; + myVcs.createWCClient().doSetProperty(new File(versionedParent.getPath()), SvnPropertyKeys.SVN_IGNORE, + SVNPropertyValue.create(name + "\n"), true, SVNDepth.EMPTY, null, null); + checkin(); + update(); + + final List ignored = new ArrayList(); + final VirtualFile ignChild = createDirInCommand(versionedParent, name); + ignored.add(ignChild); + VirtualFile current = ignChild; + for (int i = 0; i < 10; i++) { + current = createDirInCommand(current, "dir" + i); + ignored.add(current); + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + clManager.ensureUpToDate(false); + Assert.assertTrue(clManager.getDefaultChangeList().getChanges().isEmpty()); + } + testOneFile(current, "file.txt"); + + final Random rnd = new Random(17); + for (int i = 0; i < 20; i++) { + final int idx = rnd.nextInt(ignored.size()); + testOneFile(ignored.get(idx), "file" + i + ".txt"); + } + } + + @Test + public void testManyDeep() throws Exception { + final VirtualFile versionedParent = createDirInCommand(myWorkingCopyDir, "versionedParent"); + final String name = "ign123"; + final String name2 = "ign321"; + myVcs.createWCClient().doSetProperty(new File(versionedParent.getPath()), SvnPropertyKeys.SVN_IGNORE, + SVNPropertyValue.create(name + "\n" + name2 + "\n"), true, SVNDepth.EMPTY, null, null); + checkin(); + update(); + + final List ignored = new ArrayList(); + final VirtualFile ignChild = createDirInCommand(versionedParent, name); + final VirtualFile ignChild2 = createDirInCommand(versionedParent, name2); + ignored.add(ignChild); + ignored.add(ignChild2); + VirtualFile current = ignChild; + for (int i = 0; i < 10; i++) { + current = createDirInCommand(current, "dir" + i); + ignored.add(current); + } + current = ignChild2; + for (int i = 0; i < 10; i++) { + current = createDirInCommand(current, "dir" + i); + ignored.add(current); + } + + final Random rnd = new Random(17); + final List vf = new ArrayList(); + for (int i = 0; i < 200; i++) { + final int idx = rnd.nextInt(ignored.size()); + vf.add(createFileInCommand(ignored.get(idx), "file" + i + ".txt", "***")); + } + for (int i = 0; i < 50; i++) { + final VirtualFile virtualFile = vf.get(rnd.nextInt(vf.size())); + testImpl(virtualFile); + } + } + + private void testOneFile(VirtualFile current, final String name) { + final VirtualFile file = createFileInCommand(current, name, "123"); + testImpl(file); + } + + private void testImpl(VirtualFile file) { + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + clManager.ensureUpToDate(false); + Assert.assertTrue(clManager.getDefaultChangeList().getChanges().isEmpty()); + + VcsDirtyScopeManager.getInstance(myProject).fileDirty(file); + clManager.ensureUpToDate(false); + + Assert.assertTrue(clManager.getDefaultChangeList().getChanges().isEmpty()); + final FileStatus status = clManager.getStatus(file); + Assert.assertTrue(status.getText(), FileStatus.IGNORED.equals(status)); + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnLocalChangesAndRootsTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnLocalChangesAndRootsTest.java new file mode 100644 index 000000000000..e76f5c3f219c --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnLocalChangesAndRootsTest.java @@ -0,0 +1,210 @@ +package org.jetbrains.idea.svn17; + +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.VcsDirectoryMapping; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.changes.IgnoredBeanFactory; +import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl; +import com.intellij.openapi.vfs.VirtualFile; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class SvnLocalChangesAndRootsTest extends SvnTestCase { + private File myAlienRoot; + private ProjectLevelVcsManagerImpl myProjectLevelVcsManager; + private ChangeListManager myClManager; + + @Override + public void setUp() throws Exception { + /*super.setUp(); + + myAlienRoot = new File(myTempDirFixture.getTempDirPath(), "alien"); + myAlienRoot.mkdir(); + + myProjectLevelVcsManager = (ProjectLevelVcsManagerImpl) ProjectLevelVcsManager.getInstance(myProject); + myProjectLevelVcsManager.setDirectoryMapping(myAlienRoot.getAbsolutePath(), SvnVcs.VCS_NAME); + myProjectLevelVcsManager.updateActiveVcss(); + + myClManager = ChangeListManager.getInstance(myProject);*/ + } + + // a subtree just marked under VCS root but not content root + private class AlienTree { + private final File myDir; + private final File myFile; + private final File myUnversioned; + private final File myIgnored; + + public AlienTree(final String base) throws IOException { + final String name = "alien"; + myDir = new File(base, name); + myDir.mkdir(); + sleep100(); + + myFile = new File(myDir, "file.txt"); + myFile.createNewFile(); + sleep100(); + + verify(runSvn("import", "-m", "test", myDir.getAbsolutePath(), myRepoUrl + "/" + name)); + FileUtil.delete(myDir); + verify(runSvn("co", myRepoUrl + "/" + name, myDir.getAbsolutePath())); + + myUnversioned = new File(myDir, "unversioned.txt"); + myFile.createNewFile(); + sleep100(); + myIgnored = new File(myDir, "ignored.txt"); + myFile.createNewFile(); + sleep100(); + + // ignore + myClManager.setFilesToIgnore(IgnoredBeanFactory.withMask("ignored*")); + } + + private void sleep100() { + try { + Thread.sleep(100); + } + catch (InterruptedException e) { + // + } + } + } + + @Override + public void tearDown() throws Exception { + //super.tearDown(); + } + + @Test + public void testAlienRoot() throws Throwable { + /*final AlienTree alienTree = new AlienTree(myAlienRoot.getAbsolutePath()); + + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + + final VirtualFile tmpVf = LocalFileSystem.getInstance().findFileByIoFile(new File(myTempDirFixture.getTempDirPath())); + Assert.assertNotNull(tmpVf); + + sleep100(); + + tmpVf.refresh(false, true); + + final SvnVcs vcs = SvnVcs.getInstance(myProject); + ((SvnFileUrlMappingImpl) vcs.getSvnFileUrlMapping()).realRefresh(myRefreshCopiesStub); + + Assert.assertEquals(2, vcs.getSvnFileUrlMapping().getAllWcInfos().size()); + + final VirtualFile vf = LocalFileSystem.getInstance().findFileByIoFile(alienTree.myFile); + editFileInCommand(myProject, vf, "78"); + + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + myClManager.ensureUpToDate(false); + + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(new VirtualFile[] {vf}, + myClManager.getDefaultListName(), myClManager); + + final VirtualFile vfUnv = LocalFileSystem.getInstance().findFileByIoFile(alienTree.myUnversioned); + myClManager.isUnversioned(vfUnv); + + final VirtualFile vfIgn = LocalFileSystem.getInstance().findFileByIoFile(alienTree.myIgnored); + myClManager.isUnversioned(vfIgn);*/ + } + + private class SubTree { + private final VirtualFile myOuterDir; + private final VirtualFile myOuterFile; + private final VirtualFile myRootDir; + private final VirtualFile myInnerFile; + private VirtualFile myNonVersionedUpper; + private final VirtualFile myMappingTarget; + + private SubTree(final VirtualFile base, final VirtualFile projectRoot) throws Throwable { + // todo +- + myMappingTarget = projectRoot.getParent(); + myOuterDir = createDirInCommand(myMappingTarget, "outer" + System.currentTimeMillis()); + myOuterFile = createFileInCommand(myOuterDir, "outer.txt", "123"); + + myRootDir = createDirInCommand(base, "root"); + myInnerFile = createFileInCommand(myRootDir, "inner.txt", "321"); + + myNonVersionedUpper = createFileInCommand(base, "nonVersioned.txt", "135"); + + // correct mappings + final List mappings = myProjectLevelVcsManager.getDirectoryMappings(); + final String basePath = base.getPath(); + final List newMappings = new ArrayList(mappings.size()); + + for (VcsDirectoryMapping mapping : mappings) { + if (! basePath.equals(mapping.getDirectory())) { + newMappings.add(mapping); + } + } + newMappings.add(new VcsDirectoryMapping(myMappingTarget.getPath(), SvnVcs17.VCS_NAME)); + myProjectLevelVcsManager.setDirectoryMappings(newMappings); + myProjectLevelVcsManager.updateActiveVcss(); + sleep100(); + } + + public void createNonVers() { + myNonVersionedUpper = createFileInCommand(myMappingTarget, "nonVersioned.txt", "135"); + } + } + + @Test + public void testSvnVcsRootAbove() throws Throwable { + /*enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + + final SubTree subTree = new SubTree(myWorkingCopyDir, myProject.getBaseDir()); + + checkin(); + + subTree.createNonVers(); + + editFileInCommand(myProject, subTree.myOuterFile, "***"); + editFileInCommand(myProject, subTree.myInnerFile, "*&*"); + + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + myClManager.ensureUpToDate(false); + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(new VirtualFile[] {subTree.myOuterFile, subTree.myInnerFile}, + myClManager.getDefaultListName(), myClManager);*/ + } + + @Test + public void testFakeScopeDontBreakTheView() throws Throwable { + /*enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + + final SubTree subTree = new SubTree(myWorkingCopyDir, myProject.getBaseDir()); + + checkin(); + + subTree.createNonVers(); + + editFileInCommand(myProject, subTree.myOuterFile, "***"); + editFileInCommand(myProject, subTree.myInnerFile, "*&*"); + + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + myClManager.ensureUpToDate(false); + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(new VirtualFile[] {subTree.myOuterFile, subTree.myInnerFile}, + myClManager.getDefaultListName(), myClManager); + + VcsDirtyScopeManagerImpl.getInstance(myProject).fileDirty(subTree.myNonVersionedUpper); + myClManager.ensureUpToDate(false); + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(new VirtualFile[] {subTree.myOuterFile, subTree.myInnerFile}, + myClManager.getDefaultListName(), myClManager);*/ + } + + private void sleep100() { + try { + Thread.sleep(100); + } + catch (InterruptedException e) { + // + } + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnMergeInfoTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnMergeInfoTest.java new file mode 100644 index 000000000000..9c5987e9e081 --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnMergeInfoTest.java @@ -0,0 +1,568 @@ +package org.jetbrains.idea.svn17; + +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.CommittedChangesProvider; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.VcsConfiguration; +import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl; +import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.idea.svn17.dialogs.WCInfo; +import org.jetbrains.idea.svn17.history.SvnChangeList; +import org.jetbrains.idea.svn17.history.SvnRepositoryLocation; +import org.jetbrains.idea.svn17.mergeinfo.BranchInfo; +import org.jetbrains.idea.svn17.mergeinfo.OneShotMergeInfoHelper; +import org.jetbrains.idea.svn17.mergeinfo.SvnMergeInfoCache17; +import org.junit.Assert; +import org.junit.Test; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.wc.SVNInfo; +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; +import java.util.List; + +public class SvnMergeInfoTest extends SvnTestCase { + private File myBranchVcsRoot; + private ProjectLevelVcsManagerImpl myProjectLevelVcsManager; + private WCInfo myWCInfo; + private OneShotMergeInfoHelper myOneShotMergeInfoHelper; + + @Override + public void setUp() throws Exception { + super.setUp(); + + myBranchVcsRoot = new File(myTempDirFixture.getTempDirPath(), "branch"); + myBranchVcsRoot.mkdir(); + + myProjectLevelVcsManager = (ProjectLevelVcsManagerImpl) ProjectLevelVcsManager.getInstance(myProject); + myProjectLevelVcsManager.setDirectoryMapping(myBranchVcsRoot.getAbsolutePath(), SvnVcs17.VCS_NAME); + + myWCInfo = new WCInfo(myBranchVcsRoot.getAbsolutePath(), SVNURL.parseURIEncoded(myRepoUrl + "/branch"), WorkingCopyFormat.ONE_DOT_SIX, + myRepoUrl, true, null, SVNDepth.INFINITY); + myOneShotMergeInfoHelper = new OneShotMergeInfoHelper(myProject, myWCInfo, myRepoUrl + "/trunk"); + + SvnConfiguration17.getInstance(myProject).CHECK_NESTED_FOR_QUICK_MERGE = true; +// AbstractVcs vcsFound = myProjectLevelVcsManager.findVcsByName(SvnVcs.VCS_NAME); +// Assert.assertEquals(1, myProjectLevelVcsManager.getRootsUnderVcs(vcsFound).length); + } + + @Test + public void testSimpleNotMerged() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + + final File trunk = new File(myTempDirFixture.getTempDirPath(), "trunk"); + trunk.mkdir(); + Thread.sleep(100); + final File folder = new File(trunk, "folder"); + folder.mkdir(); + Thread.sleep(100); + final File f1 = new File(folder, "f1.txt"); + f1.createNewFile(); + new File(folder, "f2.txt").createNewFile(); + Thread.sleep(100); + + verify(runSvn("import", "-m", "test", trunk.getAbsolutePath(), myRepoUrl + "/trunk")); + verify(runSvn("copy", "-m", "test", myRepoUrl + "/trunk", myRepoUrl + "/branch")); + + FileUtil.delete(trunk); + verify(runSvn("co", myRepoUrl + "/trunk", trunk.getAbsolutePath())); + verify(runSvn("co", myRepoUrl + "/branch", myBranchVcsRoot.getAbsolutePath())); + + // rev 3 + + final VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(f1); + editFileInCommand(myProject, vf, "123\n456\n123"); + Thread.sleep(100); + verify(runSvn("ci", "-m", "test", trunk.getAbsolutePath())); + + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); + final List changeListList = + committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), + new SvnRepositoryLocation(myRepoUrl + "/trunk"), 0); + + final SvnChangeList changeList = changeListList.get(0); + final BranchInfo mergeChecker = + new BranchInfo(vcs, myRepoUrl, myRepoUrl + "/branch", myRepoUrl + "/trunk", myRepoUrl + "/trunk", vcs.createWCClient()); + final SvnMergeInfoCache17.MergeCheckResult result = mergeChecker.checkList(changeList, myBranchVcsRoot.getAbsolutePath()); + assert SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED.equals(result); + + myOneShotMergeInfoHelper.prepare(); + final SvnMergeInfoCache17.MergeCheckResult oneShotResult = myOneShotMergeInfoHelper.checkList(changeList); + Assert.assertEquals(SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED, oneShotResult); + } + + @Test + public void testSimpleMerged() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + + final File trunk = new File(myTempDirFixture.getTempDirPath(), "trunk"); + trunk.mkdir(); + Thread.sleep(100); + final File folder = new File(trunk, "folder"); + folder.mkdir(); + Thread.sleep(100); + final File f1 = new File(folder, "f1.txt"); + f1.createNewFile(); + new File(folder, "f2.txt").createNewFile(); + Thread.sleep(100); + + verify(runSvn("import", "-m", "test", trunk.getAbsolutePath(), myRepoUrl + "/trunk")); + verify(runSvn("copy", "-m", "test", myRepoUrl + "/trunk", myRepoUrl + "/branch")); + + FileUtil.delete(trunk); + verify(runSvn("co", myRepoUrl + "/trunk", trunk.getAbsolutePath())); + verify(runSvn("co", myRepoUrl + "/branch", myBranchVcsRoot.getAbsolutePath())); + + // rev 3 + final VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(f1); + editFileInCommand(myProject, vf, "123\n456\n123"); + Thread.sleep(100); + verify(runSvn("ci", "-m", "test", trunk.getAbsolutePath())); + + // rev 4: record as merged into branch + verify(runSvn("merge", "-c", "3", myRepoUrl + "/trunk", myBranchVcsRoot.getAbsolutePath(), "--record-only")); + Thread.sleep(100); + verify(runSvn("ci", "-m", "test", myBranchVcsRoot.getAbsolutePath())); + + Thread.sleep(100); + verify(runSvn("up", myBranchVcsRoot.getAbsolutePath())); + + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final SVNWCClient wcClient = vcs.createWCClient(); + final SVNPropertyData data = wcClient.doGetProperty(myBranchVcsRoot, "svn:mergeinfo", SVNRevision.UNDEFINED, SVNRevision.WORKING); + assert data != null && data.getValue() != null && "/trunk:3".equals(data.getValue().getString()); + + final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); + final List changeListList = + committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), + new SvnRepositoryLocation(myRepoUrl + "/trunk"), 0); + + final SvnChangeList changeList = changeListList.get(0); + final String encodedRepoUrl = SVNURL.parseURIDecoded(myRepoUrl).toString(); + final BranchInfo mergeChecker = + new BranchInfo(vcs, encodedRepoUrl, encodedRepoUrl + "/branch", encodedRepoUrl + "/trunk", encodedRepoUrl + "/trunk", vcs.createWCClient()); + final SvnMergeInfoCache17.MergeCheckResult result = mergeChecker.checkList(changeList, myBranchVcsRoot.getAbsolutePath()); + assert SvnMergeInfoCache17.MergeCheckResult.MERGED.equals(result); + + myOneShotMergeInfoHelper.prepare(); + final SvnMergeInfoCache17.MergeCheckResult oneShotResult = myOneShotMergeInfoHelper.checkList(changeList); + Assert.assertEquals(SvnMergeInfoCache17.MergeCheckResult.MERGED, oneShotResult); + } + + @Test + public void testEmptyMergeinfoBlocks() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + + final File trunk = new File(myTempDirFixture.getTempDirPath(), "trunk"); + trunk.mkdir(); + Thread.sleep(100); + final File folder = new File(trunk, "folder"); + folder.mkdir(); + Thread.sleep(100); + final File f1 = new File(folder, "f1.txt"); + f1.createNewFile(); + new File(folder, "f2.txt").createNewFile(); + Thread.sleep(100); + + verify(runSvn("import", "-m", "test", trunk.getAbsolutePath(), myRepoUrl + "/trunk")); + verify(runSvn("copy", "-m", "test", myRepoUrl + "/trunk", myRepoUrl + "/branch")); + + FileUtil.delete(trunk); + verify(runSvn("co", myRepoUrl + "/trunk", trunk.getAbsolutePath())); + verify(runSvn("co", myRepoUrl + "/branch", myBranchVcsRoot.getAbsolutePath())); + + // rev 3 + final VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(f1); + editFileInCommand(myProject, vf, "123\n456\n123"); + Thread.sleep(100); + verify(runSvn("ci", "-m", "test", trunk.getAbsolutePath())); + + // rev 4: record as merged into branch + verify(runSvn("merge", "-c", "3", myRepoUrl + "/trunk", myBranchVcsRoot.getAbsolutePath())); + Thread.sleep(100); + verify(runSvn("ci", "-m", "test", myBranchVcsRoot.getAbsolutePath())); + Thread.sleep(100); + verify(runSvn("up", myBranchVcsRoot.getAbsolutePath())); + Thread.sleep(100); + // rev5: put blocking empty mergeinfo + //verify(runSvn("merge", "-c", "-3", myRepoUrl + "/trunk/folder", new File(myBranchVcsRoot, "folder").getAbsolutePath(), "--record-only")); + verify(runSvn("merge", "-r", "3:2", myRepoUrl + "/trunk/folder", new File(myBranchVcsRoot, "folder").getAbsolutePath())); + Thread.sleep(100); + verify(runSvn("ci", "-m", "test", myBranchVcsRoot.getAbsolutePath())); + + Thread.sleep(100); + verify(runSvn("up", myBranchVcsRoot.getAbsolutePath())); + + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final SVNWCClient wcClient = vcs.createWCClient(); + final SVNPropertyData data = wcClient.doGetProperty(myBranchVcsRoot, "svn:mergeinfo", SVNRevision.UNDEFINED, SVNRevision.WORKING); + assert data != null && data.getValue() != null && "/trunk:3".equals(data.getValue().getString()); + final SVNPropertyData dataFolder = wcClient.doGetProperty(new File(myBranchVcsRoot, "folder"), "svn:mergeinfo", SVNRevision.UNDEFINED, SVNRevision.WORKING); + assert dataFolder != null && dataFolder.getValue() != null && "".equals(dataFolder.getValue().getString()); + + final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); + final List changeListList = + committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), + new SvnRepositoryLocation(myRepoUrl + "/trunk"), 0); + + final SvnChangeList changeList = changeListList.get(0); + final String encodedRepoUrl = SVNURL.parseURIDecoded(myRepoUrl).toString(); + final BranchInfo mergeChecker = + new BranchInfo(vcs, encodedRepoUrl, encodedRepoUrl + "/branch", encodedRepoUrl + "/trunk", encodedRepoUrl + "/trunk", vcs.createWCClient()); + final SvnMergeInfoCache17.MergeCheckResult result = mergeChecker.checkList(changeList, myBranchVcsRoot.getAbsolutePath()); + assert SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED.equals(result); + + myOneShotMergeInfoHelper.prepare(); + final SvnMergeInfoCache17.MergeCheckResult oneShotResult = myOneShotMergeInfoHelper.checkList(changeList); + Assert.assertEquals(SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED, oneShotResult); // todo + } + + @Test + public void testNonInheritableMergeinfo() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + + final File trunk = new File(myTempDirFixture.getTempDirPath(), "trunk"); + trunk.mkdir(); + Thread.sleep(100); + final File folder = new File(trunk, "folder"); + folder.mkdir(); + Thread.sleep(100); + final File f1 = new File(folder, "f1.txt"); + f1.createNewFile(); + new File(folder, "f2.txt").createNewFile(); + Thread.sleep(100); + + verify(runSvn("import", "-m", "test", trunk.getAbsolutePath(), myRepoUrl + "/trunk")); + verify(runSvn("copy", "-m", "test", myRepoUrl + "/trunk", myRepoUrl + "/branch")); + + FileUtil.delete(trunk); + verify(runSvn("co", myRepoUrl + "/trunk", trunk.getAbsolutePath())); + verify(runSvn("co", myRepoUrl + "/branch", myBranchVcsRoot.getAbsolutePath())); + + // rev 3 + final VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(f1); + editFileInCommand(myProject, vf, "123\n456\n123"); + Thread.sleep(100); + verify(runSvn("ci", "-m", "test", trunk.getAbsolutePath())); + + // rev 4: record non inheritable merge + verify(runSvn("propset", "svn:mergeinfo", "/trunk:3*", myBranchVcsRoot.getAbsolutePath())); + Thread.sleep(100); + verify(runSvn("ci", "-m", "test", myBranchVcsRoot.getAbsolutePath())); + + Thread.sleep(100); + verify(runSvn("up", myBranchVcsRoot.getAbsolutePath())); + + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final SVNWCClient wcClient = vcs.createWCClient(); + final SVNPropertyData data = wcClient.doGetProperty(myBranchVcsRoot, "svn:mergeinfo", SVNRevision.UNDEFINED, SVNRevision.WORKING); + assert data != null && data.getValue() != null && "/trunk:3*".equals(data.getValue().getString()); + + final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); + final List changeListList = + committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), + new SvnRepositoryLocation(myRepoUrl + "/trunk"), 0); + + final SvnChangeList changeList = changeListList.get(0); + final String encodedRepoUrl = SVNURL.parseURIDecoded(myRepoUrl).toString(); + final BranchInfo mergeChecker = + new BranchInfo(vcs, encodedRepoUrl, encodedRepoUrl + "/branch", encodedRepoUrl + "/trunk", encodedRepoUrl + "/trunk", vcs.createWCClient()); + final SvnMergeInfoCache17.MergeCheckResult result = mergeChecker.checkList(changeList, myBranchVcsRoot.getAbsolutePath()); + assert SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED.equals(result); + + myOneShotMergeInfoHelper.prepare(); + final SvnMergeInfoCache17.MergeCheckResult oneShotResult = myOneShotMergeInfoHelper.checkList(changeList); + Assert.assertEquals(SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED, oneShotResult); + } + + @Test + public void testOnlyImmediateInheritableMergeinfo() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + + final File trunk = new File(myTempDirFixture.getTempDirPath(), "trunk"); + trunk.mkdir(); + Thread.sleep(100); + final File folder = new File(trunk, "folder"); + folder.mkdir(); + Thread.sleep(100); + final File f1 = new File(folder, "f1.txt"); + f1.createNewFile(); + new File(folder, "f2.txt").createNewFile(); + Thread.sleep(100); + + verify(runSvn("import", "-m", "test", trunk.getAbsolutePath(), myRepoUrl + "/trunk")); + verify(runSvn("copy", "-m", "test", myRepoUrl + "/trunk", myRepoUrl + "/branch")); + + FileUtil.delete(trunk); + verify(runSvn("co", myRepoUrl + "/trunk", trunk.getAbsolutePath())); + verify(runSvn("co", myRepoUrl + "/branch", myBranchVcsRoot.getAbsolutePath())); + + // rev 3 + final VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(f1); + editFileInCommand(myProject, vf, "123\n456\n123"); + Thread.sleep(100); + verify(runSvn("ci", "-m", "test", trunk.getAbsolutePath())); + Thread.sleep(100); + // rev4 + editFileInCommand(myProject, vf, "123\n456\n123\n4"); + Thread.sleep(100); + verify(runSvn("ci", "-m", "test", trunk.getAbsolutePath())); + Thread.sleep(100); + verify(runSvn("up", myBranchVcsRoot.getAbsolutePath())); + Thread.sleep(100); + + // rev 4: record non inheritable merge + verify(runSvn("propset", "svn:mergeinfo", "/trunk:3,4", myBranchVcsRoot.getAbsolutePath())); + verify(runSvn("propset", "svn:mergeinfo", "/trunk:3", new File(myBranchVcsRoot, "folder").getAbsolutePath())); + Thread.sleep(100); + verify(runSvn("ci", "-m", "test", myBranchVcsRoot.getAbsolutePath())); + + Thread.sleep(100); + verify(runSvn("up", myBranchVcsRoot.getAbsolutePath())); + + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final SVNWCClient wcClient = vcs.createWCClient(); + final SVNPropertyData data = wcClient.doGetProperty(myBranchVcsRoot, "svn:mergeinfo", SVNRevision.UNDEFINED, SVNRevision.WORKING); + assert data != null && data.getValue() != null && ("/trunk:3-4".equals(data.getValue().getString()) || + "/trunk:3,4".equals(data.getValue().getString())); + final SVNPropertyData dataFolder = wcClient.doGetProperty(new File(myBranchVcsRoot, "folder"), "svn:mergeinfo", SVNRevision.UNDEFINED, SVNRevision.WORKING); + assert dataFolder != null && dataFolder.getValue() != null && "/trunk:3".equals(dataFolder.getValue().getString()); + + final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); + final List changeListList = + committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), + new SvnRepositoryLocation(myRepoUrl + "/trunk"), 0); + + final SvnChangeList changeList4 = changeListList.get(0); + assert changeList4.getNumber() == 4; + final SvnChangeList changeList3 = changeListList.get(1); + assert changeList3.getNumber() == 3; + + final String encodedRepoUrl = SVNURL.parseURIDecoded(myRepoUrl).toString(); + final BranchInfo mergeChecker = + new BranchInfo(vcs, encodedRepoUrl, encodedRepoUrl + "/branch", encodedRepoUrl + "/trunk", encodedRepoUrl + "/trunk", vcs.createWCClient()); + SvnMergeInfoCache17.MergeCheckResult result = mergeChecker.checkList(changeList3, myBranchVcsRoot.getAbsolutePath()); + assert SvnMergeInfoCache17.MergeCheckResult.MERGED.equals(result); + result = mergeChecker.checkList(changeList4, myBranchVcsRoot.getAbsolutePath()); + assert SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED.equals(result); + + myOneShotMergeInfoHelper.prepare(); + final SvnMergeInfoCache17.MergeCheckResult oneShotResult = myOneShotMergeInfoHelper.checkList(changeList3); + Assert.assertEquals(SvnMergeInfoCache17.MergeCheckResult.MERGED, oneShotResult); + + final SvnMergeInfoCache17.MergeCheckResult oneShotResult1 = myOneShotMergeInfoHelper.checkList(changeList4); + Assert.assertEquals(SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED, oneShotResult1); + } + + @Test + public void testTwoPaths() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + + final File trunk = new File(myTempDirFixture.getTempDirPath(), "trunk"); + trunk.mkdir(); + Thread.sleep(100); + final File folder = new File(trunk, "folder"); + folder.mkdir(); + Thread.sleep(100); + final File f1 = new File(folder, "f1.txt"); + f1.createNewFile(); + final File folder1 = new File(folder, "folder1"); + folder1.mkdir(); + final File f2 = new File(folder1, "f2.txt"); + f2.createNewFile(); + Thread.sleep(100); + + verify(runSvn("import", "-m", "test", trunk.getAbsolutePath(), myRepoUrl + "/trunk")); + verify(runSvn("copy", "-m", "test", myRepoUrl + "/trunk", myRepoUrl + "/branch")); + + FileUtil.delete(trunk); + verify(runSvn("co", myRepoUrl + "/trunk", trunk.getAbsolutePath())); + verify(runSvn("co", myRepoUrl + "/branch", myBranchVcsRoot.getAbsolutePath())); + + // rev 3 + final VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(f1); + editFileInCommand(myProject, vf, "123\n456\n123"); + final VirtualFile vf2 = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(f2); + editFileInCommand(myProject, vf2, "123\n456\n123"); + Thread.sleep(100); + verify(runSvn("ci", "-m", "test", trunk.getAbsolutePath())); + Thread.sleep(100); + verify(runSvn("up", myBranchVcsRoot.getAbsolutePath())); + Thread.sleep(100); + + // rev 4: record non inheritable merge + verify(runSvn("propset", "svn:mergeinfo", "/trunk:3", myBranchVcsRoot.getAbsolutePath())); + // this makes not merged for f2 path + verify(runSvn("propset", "svn:mergeinfo", "/trunk:3*", new File(myBranchVcsRoot, "folder/folder1").getAbsolutePath())); + Thread.sleep(100); + verify(runSvn("ci", "-m", "test", myBranchVcsRoot.getAbsolutePath())); + + Thread.sleep(100); + verify(runSvn("up", myBranchVcsRoot.getAbsolutePath())); + + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final SVNWCClient wcClient = vcs.createWCClient(); + final SVNPropertyData data = wcClient.doGetProperty(myBranchVcsRoot, "svn:mergeinfo", SVNRevision.UNDEFINED, SVNRevision.WORKING); + assert data != null && data.getValue() != null && "/trunk:3".equals(data.getValue().getString()); + final SVNPropertyData dataFolder = wcClient.doGetProperty(new File(myBranchVcsRoot, "folder/folder1"), "svn:mergeinfo", SVNRevision.UNDEFINED, SVNRevision.WORKING); + assert dataFolder != null && dataFolder.getValue() != null && "/trunk:3*".equals(dataFolder.getValue().getString()); + + final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); + final List changeListList = + committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), + new SvnRepositoryLocation(myRepoUrl + "/trunk"), 0); + + final SvnChangeList changeList3 = changeListList.get(0); + assert changeList3.getNumber() == 3; + + final String encodedRepoUrl = SVNURL.parseURIDecoded(myRepoUrl).toString(); + final BranchInfo mergeChecker = + new BranchInfo(vcs, encodedRepoUrl, encodedRepoUrl + "/branch", encodedRepoUrl + "/trunk", encodedRepoUrl + "/trunk", vcs.createWCClient()); + SvnMergeInfoCache17.MergeCheckResult result = mergeChecker.checkList(changeList3, myBranchVcsRoot.getAbsolutePath()); + assert SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED.equals(result); + + myOneShotMergeInfoHelper.prepare(); + final SvnMergeInfoCache17.MergeCheckResult oneShotResult = myOneShotMergeInfoHelper.checkList(changeList3); + Assert.assertEquals(SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED, oneShotResult); + } + + @Test + public void testWhenInfoInRepo() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + + final File fullBranch = new File(myTempDirFixture.getTempDirPath(), "fullBranch"); + fullBranch.mkdir(); + + final File trunk = new File(myTempDirFixture.getTempDirPath(), "trunk"); + trunk.mkdir(); + Thread.sleep(100); + final File folder = new File(trunk, "folder"); + folder.mkdir(); + Thread.sleep(100); + final File f1 = new File(folder, "f1.txt"); + f1.createNewFile(); + // this will be taken as branch wc root + final File folder1 = new File(folder, "folder1"); + folder1.mkdir(); + final File f2 = new File(folder1, "f2.txt"); + f2.createNewFile(); + Thread.sleep(100); + + verify(runSvn("import", "-m", "test", trunk.getAbsolutePath(), myRepoUrl + "/trunk")); + verify(runSvn("copy", "-m", "test", myRepoUrl + "/trunk", myRepoUrl + "/branch")); + + FileUtil.delete(trunk); + verify(runSvn("co", myRepoUrl + "/trunk", trunk.getAbsolutePath())); + verify(runSvn("co", myRepoUrl + "/branch", fullBranch.getAbsolutePath())); + verify(runSvn("co", myRepoUrl + "/branch/folder/folder1", myBranchVcsRoot.getAbsolutePath())); + + // rev 3 : f2 changed + final VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(f2); + editFileInCommand(myProject, vf, "123\n456\n123"); + Thread.sleep(100); + verify(runSvn("ci", "-m", "test", trunk.getAbsolutePath())); + + // rev 4: record as merged into branch using full branch WC + verify(runSvn("merge", "-c", "3", myRepoUrl + "/trunk", fullBranch.getAbsolutePath(), "--record-only")); + Thread.sleep(100); + verify(runSvn("ci", "-m", "test", fullBranch.getAbsolutePath())); + + Thread.sleep(100); + verify(runSvn("up", myBranchVcsRoot.getAbsolutePath())); + + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + + final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); + final List changeListList = + committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), + new SvnRepositoryLocation(myRepoUrl + "/trunk"), 0); + + final SvnChangeList changeList3 = changeListList.get(0); + assert changeList3.getNumber() == 3; + + final String encodedRepoUrl = SVNURL.parseURIDecoded(myRepoUrl).toString(); + final BranchInfo mergeChecker = + new BranchInfo(vcs, encodedRepoUrl, encodedRepoUrl + "/branch", encodedRepoUrl + "/trunk", encodedRepoUrl + "/trunk", vcs.createWCClient()); + SvnMergeInfoCache17.MergeCheckResult result = mergeChecker.checkList(changeList3, myBranchVcsRoot.getAbsolutePath()); + assert SvnMergeInfoCache17.MergeCheckResult.MERGED.equals(result); + } + + @Test + public void testMixedWorkingRevisions() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + + final File trunk = new File(myTempDirFixture.getTempDirPath(), "trunk"); + trunk.mkdir(); + Thread.sleep(100); + final File folder = new File(trunk, "folder"); + folder.mkdir(); + Thread.sleep(100); + final File f1 = new File(folder, "f1.txt"); + f1.createNewFile(); + new File(folder, "f2.txt").createNewFile(); + Thread.sleep(100); + + verify(runSvn("import", "-m", "test", trunk.getAbsolutePath(), myRepoUrl + "/trunk")); + verify(runSvn("copy", "-m", "test", myRepoUrl + "/trunk", myRepoUrl + "/branch")); + + FileUtil.delete(trunk); + verify(runSvn("co", myRepoUrl + "/trunk", trunk.getAbsolutePath())); + verify(runSvn("co", myRepoUrl + "/branch", myBranchVcsRoot.getAbsolutePath())); + + // rev 3 + final VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(f1); + editFileInCommand(myProject, vf, "123\n456\n123"); + Thread.sleep(100); + verify(runSvn("ci", "-m", "test", trunk.getAbsolutePath())); + + // rev 4: record non inheritable merge + verify(runSvn("propset", "svn:mergeinfo", "/trunk:3", myBranchVcsRoot.getAbsolutePath())); + Thread.sleep(100); + verify(runSvn("ci", "-m", "test", myBranchVcsRoot.getAbsolutePath())); + + Thread.sleep(100); + // ! no update! + + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + final SVNWCClient wcClient = vcs.createWCClient(); + final SVNPropertyData data = wcClient.doGetProperty(myBranchVcsRoot, "svn:mergeinfo", SVNRevision.UNDEFINED, SVNRevision.WORKING); + assert data != null && data.getValue() != null && "/trunk:3".equals(data.getValue().getString()); + + final SVNInfo f1info = wcClient.doInfo(new File(myBranchVcsRoot, "folder/f1.txt"), SVNRevision.UNDEFINED); + assert f1info.getRevision().getNumber() == 2; + + final CommittedChangesProvider committedChangesProvider = vcs.getCommittedChangesProvider(); + final List changeListList = + committedChangesProvider.getCommittedChanges(committedChangesProvider.createDefaultSettings(), + new SvnRepositoryLocation(myRepoUrl + "/trunk"), 0); + + final SvnChangeList changeList = changeListList.get(0); + final String encodedRepoUrl = SVNURL.parseURIDecoded(myRepoUrl).toString(); + final BranchInfo mergeChecker = + new BranchInfo(vcs, encodedRepoUrl, encodedRepoUrl + "/branch", encodedRepoUrl + "/trunk", encodedRepoUrl + "/trunk", vcs.createWCClient()); + SvnMergeInfoCache17.MergeCheckResult result = mergeChecker.checkList(changeList, myBranchVcsRoot.getAbsolutePath()); + assert SvnMergeInfoCache17.MergeCheckResult.NOT_MERGED.equals(result); + + // and after update + verify(runSvn("up", myBranchVcsRoot.getAbsolutePath())); + Thread.sleep(100); + + mergeChecker.clear(); + result = mergeChecker.checkList(changeList, myBranchVcsRoot.getAbsolutePath()); + assert SvnMergeInfoCache17.MergeCheckResult.MERGED.equals(result); + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnNativeListsTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnNativeListsTest.java new file mode 100644 index 000000000000..6429e791fa0d --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnNativeListsTest.java @@ -0,0 +1,174 @@ +package org.jetbrains.idea.svn17; + +import com.intellij.openapi.vcs.VcsConfiguration; +import com.intellij.openapi.vcs.changes.*; +import com.intellij.openapi.vfs.VirtualFile; +import org.junit.Test; +import org.tmatesoft.svn.core.SVNException; + +import java.util.Collection; +import java.util.List; + +public class SvnNativeListsTest extends SvnTestCase { + private ChangeListManager myChangeListManager; + private VcsDirtyScopeManager myDirtyScopeManager; + + @Override + public void setUp() throws Exception { + super.setUp(); + myChangeListManager = ChangeListManager.getInstance(myProject); + myDirtyScopeManager = VcsDirtyScopeManager.getInstance(myProject); + } + + @Override + public void tearDown() throws Exception { + final List changeListList = myChangeListManager.getChangeLists(); + for (LocalChangeList list : changeListList) { + if (SvnChangeProvider.ourDefaultListName.equals(list.getName())) continue; + final Collection changes = list.getChanges(); + for (Change change : changes) { + clearListForRevision(change.getBeforeRevision()); + clearListForRevision(change.getAfterRevision()); + } + } + + super.tearDown(); + } + + private void clearListForRevision(final ContentRevision revision) throws SVNException { + if (revision == null) return; + SvnChangelistListener.removeFromList(myProject, revision.getFile().getIOFile()); + } + + @Test + public void testAdd() throws Throwable { + final LocalChangeList newL = myChangeListManager.addChangeList("newOne", null); + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + myChangeListManager.setDefaultChangeList(newL); + + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile file = createFileInCommand("a.txt", "old content"); + + ensureAddedToNativeList(); + + verify(runSvn("status"), "", "--- Changelist 'newOne':", "A a.txt"); + } + + private void ensureAddedToNativeList() { + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); // first time new changes are detected and added to _IDEA_ changeslist + myChangeListManager.ensureUpToDate(false); // and on the same thread a request is put for files addition; + // so stay here for 2nd cycle and wait for native addition completion + } + + @Test + public void testDeleted() throws Throwable { + final LocalChangeList newL = myChangeListManager.addChangeList("newOne", null); + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + myChangeListManager.setDefaultChangeList(newL); + + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + final VirtualFile file = createFileInCommand("a.txt", "old content"); + checkin(); + deleteFileInCommand(file); + + ensureAddedToNativeList(); + + verify(runSvn("status"), "", "--- Changelist 'newOne':", "D a.txt"); + } + + @Test + public void testEdit() throws Throwable { + final LocalChangeList newL = myChangeListManager.addChangeList("newOne", null); + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + myChangeListManager.setDefaultChangeList(newL); + + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile file = createFileInCommand("a.txt", "old content"); + checkin(); + editFileInCommand(myProject, file, "111"); + + ensureAddedToNativeList(); + + verify(runSvn("status"), "", "--- Changelist 'newOne':", "M a.txt"); + } + + @Test + public void testEditAndMove() throws Throwable { + final LocalChangeList newL = myChangeListManager.addChangeList("newOne", null); + + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + myChangeListManager.setDefaultChangeList(newL); + + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + final VirtualFile file = createFileInCommand("a.txt", "old content"); + checkin(); + editFileInCommand(myProject, file, "111"); + + ensureAddedToNativeList(); + + verify(runSvn("status"), "", "--- Changelist 'newOne':", "M a.txt"); + + renameFileInCommand(file, "b.txt"); + verify(runSvn("status"), "", "--- Changelist 'newOne':", "A + b.txt", "D a.txt"); + + ensureAddedToNativeList(); + + verify(runSvn("status"), "", "--- Changelist 'newOne':", "A + b.txt", "D a.txt"); + } + + @Test + public void testMove() throws Throwable { + final LocalChangeList newL = myChangeListManager.addChangeList("newOne", null); + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + myChangeListManager.setDefaultChangeList(newL); + + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + final VirtualFile file = createFileInCommand("a.txt", "old content"); + checkin(); + + renameFileInCommand(file, "b.txt"); + ensureAddedToNativeList(); + + verify(runSvn("status"), "", "--- Changelist 'newOne':", "A + b.txt", "D a.txt"); + } + + @Test + public void testMoveMove() throws Throwable { + final LocalChangeList newL = myChangeListManager.addChangeList("newOne", null); + myDirtyScopeManager.markEverythingDirty(); + myChangeListManager.ensureUpToDate(false); + + myChangeListManager.setDefaultChangeList(newL); + + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + final VirtualFile file = createFileInCommand("a.txt", "old content"); + checkin(); + + renameFileInCommand(file, "b.txt"); + ensureAddedToNativeList(); + + verify(runSvn("status"), "", "--- Changelist 'newOne':", "A + b.txt", "D a.txt"); + + renameFileInCommand(file, "c.txt"); + verify(runSvn("status"), "", "--- Changelist 'newOne':", "A + c.txt", "D a.txt"); + + ensureAddedToNativeList(); + + verify(runSvn("status"), "", "--- Changelist 'newOne':", "A + c.txt", "D a.txt"); + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnRenameTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnRenameTest.java new file mode 100644 index 000000000000..41e4b818363a --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnRenameTest.java @@ -0,0 +1,324 @@ +package org.jetbrains.idea.svn17; + +import com.intellij.execution.process.ProcessOutput; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vcs.VcsConfiguration; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.rollback.RollbackProgressListener; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NonNls; +import org.junit.Assert; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * @author yole + */ +public class SvnRenameTest extends SvnTestCase { + @NonNls private static final String LOG_SEPARATOR = "------------------------------------------------------------------------\n"; + @NonNls private static final String LOG_SEPARATOR_START = "-------------"; + + @Test + public void testSimpleRename() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile a = createFileInCommand("a.txt", "test"); + checkin(); + + renameFileInCommand(a, "b.txt"); + verify(runSvn("status"), "A + b.txt", "D a.txt"); + } + + // IDEADEV-18844 + @Test + public void testRenameReplace() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile a = createFileInCommand("a.txt", "old"); + final VirtualFile aNew = createFileInCommand("aNew.txt", "new"); + checkin(); + + renameFileInCommand(a, "aOld.txt"); + renameFileInCommand(aNew, "a.txt"); + final ProcessOutput result = runSvn("status"); + verifySorted(result, "A + aOld.txt", "D aNew.txt", "R + a.txt"); + } + + // IDEADEV-16251 + @Test + public void testRenameAddedPackage() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile dir = createDirInCommand(myWorkingCopyDir, "child"); + createFileInCommand(dir, "a.txt", "content"); + renameFileInCommand(dir, "newchild"); + verify(runSvn("status"), "A newchild", "A newchild" + File.separatorChar + "a.txt"); + } + + // IDEADEV-8091 + @Test + public void testDoubleRename() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile a = createFileInCommand("a.txt", "test"); + checkin(); + + renameFileInCommand(a, "b.txt"); + renameFileInCommand(a, "c.txt"); + verify(runSvn("status"), "A + c.txt", "D a.txt"); + } + + // IDEADEV-15876 + @Test + public void testRenamePackageWithChildren() throws Exception { + final VirtualFile child = prepareDirectoriesForRename(); + + renameFileInCommand(child, "childnew"); + final ProcessOutput result = runSvn("status"); + verify(result, "D child", "D child" + File.separatorChar + "grandChild", "D child" + File.separatorChar + "grandChild" + File.separatorChar + "b.txt", "D child" + File.separatorChar + "a.txt", "A + childnew"); + + LocalFileSystem.getInstance().refresh(false); // wait for end of refresh operations initiated from SvnFileSystemListener + final ChangeListManager changeListManager = ChangeListManager.getInstance(myProject); + changeListManager.ensureUpToDate(false); + List changes = new ArrayList(changeListManager.getDefaultChangeList().getChanges()); + Assert.assertEquals(4, changes.size()); + sortChanges(changes); + verifyChange(changes.get(0), "child", "childnew"); + verifyChange(changes.get(1), "child" + File.separatorChar + "a.txt", "childnew" + File.separatorChar + "a.txt"); + verifyChange(changes.get(2), "child" + File.separatorChar + "grandChild", "childnew" + File.separatorChar + "grandChild"); + verifyChange(changes.get(3), "child" + File.separatorChar + "grandChild" + File.separatorChar + "b.txt", "childnew" + File.separatorChar + "grandChild" + File.separatorChar + "b.txt"); + + VirtualFile oldChild = myWorkingCopyDir.findChild("child"); + Assert.assertEquals(FileStatus.DELETED, changeListManager.getStatus(oldChild)); + } + + private VirtualFile prepareDirectoriesForRename() throws IOException { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile child = createDirInCommand(myWorkingCopyDir, "child"); + final VirtualFile grandChild = createDirInCommand(child, "grandChild"); + createFileInCommand(child, "a.txt", "a"); + createFileInCommand(grandChild, "b.txt", "b"); + checkin(); + return child; + } + + // IDEADEV-19065 + @Test + public void testCommitAfterRenameDir() throws Exception { + final VirtualFile child = prepareDirectoriesForRename(); + + renameFileInCommand(child, "newchild"); + checkin(); + + final ProcessOutput runResult = runSvn("log", "-q", "newchild/a.txt"); + verify(runResult); + final List lines = StringUtil.split(runResult.getStdout(), "\n"); + for (Iterator iterator = lines.iterator(); iterator.hasNext();) { + final String next = iterator.next(); + if (next.startsWith(LOG_SEPARATOR_START)) { + iterator.remove(); + } + } + Assert.assertEquals(2, lines.size()); + Assert.assertTrue(lines.get(0).startsWith("r2 |")); + Assert.assertTrue(lines.get(1).startsWith("r1 |")); + } + + // IDEADEV-9755 + @Test + public void testRollbackRenameDir() throws Exception { + final VirtualFile child = prepareDirectoriesForRename(); + renameFileInCommand(child, "newchild"); + + final ChangeListManager changeListManager = ChangeListManager.getInstance(myProject); + changeListManager.ensureUpToDate(false); + final Change change = changeListManager.getChange(myWorkingCopyDir.findChild("newchild")); + Assert.assertNotNull(change); + + final List exceptions = new ArrayList(); + SvnVcs17.getInstance(myProject).getRollbackEnvironment().rollbackChanges(Collections.singletonList(change), exceptions, + RollbackProgressListener.EMPTY); + Assert.assertTrue(exceptions.isEmpty()); + Assert.assertFalse(new File(myWorkingCopyDir.getPath(), "newchild").exists()); + Assert.assertTrue(new File(myWorkingCopyDir.getPath(), "child").exists()); + } + + // IDEADEV-7697 + @Test + public void testMovePackageToParent() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile child = createDirInCommand(myWorkingCopyDir, "child"); + final VirtualFile grandChild = createDirInCommand(child, "grandChild"); + createFileInCommand(grandChild, "a.txt", "a"); + checkin(); + final ChangeListManager changeListManager = ChangeListManager.getInstance(myProject); + + moveFileInCommand(grandChild, myWorkingCopyDir); + LocalFileSystem.getInstance().refresh(false); // wait for end of refresh operations initiated from SvnFileSystemListener + changeListManager.ensureUpToDate(false); + final List changes = new ArrayList(changeListManager.getDefaultChangeList().getChanges()); + Assert.assertEquals(listToString(changes), 2, changes.size()); + sortChanges(changes); + verifyChange(changes.get(0), "child" + File.separatorChar + "grandChild", "grandChild"); + verifyChange(changes.get(1), "child" + File.separatorChar + "grandChild" + File.separatorChar + "a.txt", "grandChild" + File.separatorChar + "a.txt"); + } + + private String listToString(final List changes) { + return "{" + StringUtil.join(changes, StringUtil.createToStringFunction(Change.class), ",") + "}"; + } + + // IDEADEV-19223 + @Test + public void testRollbackRenameWithUnversioned() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile child = createDirInCommand(myWorkingCopyDir, "child"); + createFileInCommand(child, "a.txt", "a"); + checkin(); + disableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile unversioned = createFileInCommand(child, "u.txt", "u"); + final VirtualFile unversionedDir = createDirInCommand(child, "uc"); + createFileInCommand(unversionedDir, "c.txt", "c"); + + final ChangeListManager changeListManager = ChangeListManager.getInstance(myProject); + changeListManager.ensureUpToDate(false); + Assert.assertEquals(FileStatus.UNKNOWN, changeListManager.getStatus(unversioned)); + + renameFileInCommand(child, "newchild"); + File childPath = new File(myWorkingCopyDir.getPath(), "child"); + File newChildPath = new File(myWorkingCopyDir.getPath(), "newchild"); + Assert.assertTrue(new File(newChildPath, "a.txt").exists()); + Assert.assertTrue(new File(newChildPath, "u.txt").exists()); + Assert.assertFalse(new File(childPath, "u.txt").exists()); + + LocalFileSystem.getInstance().refresh(false); + changeListManager.ensureUpToDate(false); + final List changes = new ArrayList(); + changes.add(ChangeListManager.getInstance(myProject).getChange(myWorkingCopyDir.findChild("newchild").findChild("a.txt"))); + changes.add(ChangeListManager.getInstance(myProject).getChange(myWorkingCopyDir.findChild("newchild"))); + + final List exceptions = new ArrayList(); + SvnVcs17.getInstance(myProject).getRollbackEnvironment().rollbackChanges(changes, exceptions, RollbackProgressListener.EMPTY); + try { + Thread.sleep(300); + } + catch (InterruptedException e) { + // + } + Assert.assertTrue(exceptions.isEmpty()); + final File fileA = new File(childPath, "a.txt"); + Assert.assertTrue(fileA.getAbsolutePath(), fileA.exists()); + final File fileU = new File(childPath, "u.txt"); + Assert.assertTrue(fileU.getAbsolutePath(), fileU.exists()); + final File unversionedDirFile = new File(childPath, "uc"); + Assert.assertTrue(unversionedDirFile.exists()); + Assert.assertTrue(new File(unversionedDirFile, "c.txt").exists()); + } + + // IDEA-13824 + @Test + public void testRenameFileRenameDir() throws Exception { + final VirtualFile child = prepareDirectoriesForRename(); + final VirtualFile f = child.findChild("a.txt"); + renameFileInCommand(f, "anew.txt"); + renameFileInCommand(child, "newchild"); + + verifySorted(runSvn("status"), "A + newchild", "A + newchild" + File.separatorChar + "anew.txt", + "D child", "D child" + File.separatorChar + "a.txt", "D child" + File.separatorChar + "grandChild", "D child" + File.separatorChar + "grandChild" + File.separatorChar + "b.txt", "D + newchild" + File.separatorChar + "a.txt"); + + final ChangeListManager changeListManager = ChangeListManager.getInstance(myProject); + LocalFileSystem.getInstance().refresh(false); // wait for end of refresh operations initiated from SvnFileSystemListener + changeListManager.ensureUpToDate(false); + final List changes = new ArrayList(changeListManager.getDefaultChangeList().getChanges()); + final List list = SvnVcs17.getInstance(myProject).getCheckinEnvironment().commit(changes, "test"); + Assert.assertEquals(0, list.size()); + } + + // IDEADEV-19364 + @Test + public void testUndoMovePackage() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile parent1 = createDirInCommand(myWorkingCopyDir, "parent1"); + final VirtualFile parent2 = createDirInCommand(myWorkingCopyDir, "parent2"); + final VirtualFile child = createDirInCommand(parent1, "child"); + createFileInCommand(child, "a.txt", "a"); + checkin(); + + moveFileInCommand(child, parent2); + undo(); + final File childPath = new File(parent1.getPath(), "child"); + Assert.assertTrue(childPath.exists()); + Assert.assertTrue(new File(childPath, "a.txt").exists()); + } + + // IDEADEV-19552 + @Test + public void testUndoRename() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile file = createFileInCommand(myWorkingCopyDir, "a.txt", "A"); + checkin(); + + renameFileInCommand(file, "b.txt"); + undo(); + Assert.assertTrue(new File(myWorkingCopyDir.getPath(), "a.txt").exists()); + Assert.assertFalse(new File(myWorkingCopyDir.getPath(), "b.txt").exists()); + } + + // IDEADEV-19336 + /*@Bombed(user = "Ira", month = 6, day = 15) + @Test + public void testUndoMoveCommittedPackage() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + final VirtualFile parent1 = createDirInCommand(myWorkingCopyDir, "parent1"); + final VirtualFile parent2 = createDirInCommand(myWorkingCopyDir, "parent2"); + final VirtualFile child = createDirInCommand(parent1, "child"); + createFileInCommand(child, "a.txt", "a"); + checkin(); + + moveFileInCommand(child, parent2); + checkin(); + + undo(); + verifySorted(runSvn("status"), "A + parent1" + File.separatorChar + "child", "D parent2" + File.separatorChar + "child", "D parent2" + File.separatorChar + "child" + File.separatorChar + "a.txt"); + }*/ + + @Test + public void testMoveToNewPackage() throws Throwable { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile file = createFileInCommand(myWorkingCopyDir, "a.txt", "A"); + moveToNewPackage(file, "child"); + verifySorted(runSvn("status"), "A child", "A child" + File.separatorChar + "a.txt"); + } + + @Test + public void testMoveToNewPackageCommitted() throws Throwable { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile file = createFileInCommand(myWorkingCopyDir, "a.txt", "A"); + checkin(); + moveToNewPackage(file, "child"); + verifySorted(runSvn("status"), "A child", "A + child" + File.separatorChar + "a.txt", "D a.txt"); + } + + private void moveToNewPackage(final VirtualFile file, final String packageName) throws Throwable { + new WriteCommandAction.Simple(myProject) { + @Override + public void run() { + try { + final VirtualFile dir = myWorkingCopyDir.createChildDirectory(this, packageName); + file.move(this, dir); + } + catch (IOException e) { + throw new RuntimeException(e); + } + + } + }.execute().throwException(); + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnRootAboveTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnRootAboveTest.java new file mode 100644 index 000000000000..d913cdd2c11b --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnRootAboveTest.java @@ -0,0 +1,166 @@ +package org.jetbrains.idea.svn17; + +import com.intellij.openapi.vcs.VcsDirectoryMapping; +import com.intellij.openapi.vcs.changes.ChangeListManager; +import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl; +import com.intellij.openapi.vfs.VirtualFile; +import org.junit.Test; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class SvnRootAboveTest extends SvnTestCase { + private ProjectLevelVcsManagerImpl myProjectLevelVcsManager; + private ChangeListManager myClManager; + + private File myLocalWcRoot; + private File myProjectRoot; + private File myModuleRoot; + + @Override + public void setUp() throws Exception { + /*final IdeaTestFixtureFactory fixtureFactory = IdeaTestFixtureFactory.getFixtureFactory(); + myTempDirFixture = fixtureFactory.createTempDirTestFixture(); + myTempDirFixture.setUp(); + + final File svnRoot = new File(myTempDirFixture.getTempDirPath(), "svnroot"); + svnRoot.mkdir(); + + File pluginRoot = new File(PluginPathManager.getPluginHomePath("svn4idea")); + if (!pluginRoot.isDirectory()) { + // try standalone mode + Class aClass = SvnTestCase.class; + String rootPath = PathManager.getResourceRoot(aClass, "/" + aClass.getName().replace('.', '/') + ".class"); + pluginRoot = new File(rootPath).getParentFile().getParentFile().getParentFile(); + } + myClientBinaryPath = new File(pluginRoot, "testData/svn/bin"); + + ZipUtil.extract(new File(pluginRoot, "testData/svn/newrepo.zip"), svnRoot, null); + + // real WC root + myLocalWcRoot = new File(myTempDirFixture.getTempDirPath(), "wcroot"); + myLocalWcRoot.mkdir(); + + myRepoUrl = "file:///" + FileUtil.toSystemIndependentName(svnRoot.getPath()); + verify(runSvn("co", myRepoUrl, myLocalWcRoot.getAbsolutePath())); + + // one level below - project root dir + sleep100(); + myProjectRoot = new File(myLocalWcRoot, "projectRoot"); + myProjectRoot.mkdir(); + // one more level below - content root + sleep100(); + myModuleRoot = new File(myProjectRoot, "moduleRoot"); + myModuleRoot.mkdir(); + sleep100(); + + // set current directory + verify(runArbitrary("cd", new String[] {myProjectRoot.getAbsolutePath()})); + initProject(myModuleRoot); + verify(runArbitrary("cd", new String[] {myTempDirFixture.getTempDirPath()})); + + myProjectLevelVcsManager = (ProjectLevelVcsManagerImpl) ProjectLevelVcsManager.getInstance(myProject); + myProjectLevelVcsManager.setDirectoryMapping(myLocalWcRoot.getAbsolutePath(), SvnVcs.VCS_NAME); + myProjectLevelVcsManager.updateActiveVcss(); + + final SvnVcs vcs = SvnVcs.getInstance(myProject); + ((SvnFileUrlMappingImpl) vcs.getSvnFileUrlMapping()).realRefresh(myRefreshCopiesStub); + + myClManager = ChangeListManager.getInstance(myProject);*/ + } + + private class SubTree { + private final VirtualFile myOuterDir; + private final VirtualFile myOuterFile; + private final VirtualFile myRootDir; + private final VirtualFile myInnerFile; + private VirtualFile myNonVersionedUpper; + private final VirtualFile myMappingTarget; + + private SubTree(final VirtualFile base, final VirtualFile projectRoot) throws Throwable { + // todo +- + myMappingTarget = projectRoot.getParent(); + myOuterDir = createDirInCommand(myMappingTarget, "outer" + System.currentTimeMillis()); + myOuterFile = createFileInCommand(myOuterDir, "outer.txt", "123"); + + myRootDir = createDirInCommand(base, "root"); + myInnerFile = createFileInCommand(myRootDir, "inner.txt", "321"); + + myNonVersionedUpper = createFileInCommand(base, "nonVersioned.txt", "135"); + + // correct mappings + final List mappings = myProjectLevelVcsManager.getDirectoryMappings(); + final String basePath = base.getPath(); + final List newMappings = new ArrayList(mappings.size()); + + for (VcsDirectoryMapping mapping : mappings) { + if (! basePath.equals(mapping.getDirectory())) { + newMappings.add(mapping); + } + } + newMappings.add(new VcsDirectoryMapping(myMappingTarget.getPath(), SvnVcs17.VCS_NAME)); + myProjectLevelVcsManager.setDirectoryMappings(newMappings); + myProjectLevelVcsManager.updateActiveVcss(); + sleep100(); + } + + public void createNonVers() { + myNonVersionedUpper = createFileInCommand(myMappingTarget, "nonVersioned.txt", "135"); + } + } + + @Test + public void testSvnVcsRootAbove() throws Throwable { + /*enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + + final SubTree subTree = new SubTree(myWorkingCopyDir, myProject.getBaseDir()); + + checkin(); + + subTree.createNonVers(); + + editFileInCommand(myProject, subTree.myOuterFile, "***"); + editFileInCommand(myProject, subTree.myInnerFile, "*&*"); + + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + myClManager.ensureUpToDate(false); + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(new VirtualFile[] {subTree.myOuterFile, subTree.myInnerFile}, + myClManager.getDefaultListName(), myClManager);*/ + } + + @Test + public void testFakeScopeDontBreakTheView() throws Throwable { + /*enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + + final SubTree subTree = new SubTree(myWorkingCopyDir, myProject.getBaseDir()); + + checkin(); + + subTree.createNonVers(); + + editFileInCommand(myProject, subTree.myOuterFile, "***"); + editFileInCommand(myProject, subTree.myInnerFile, "*&*"); + + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + myClManager.ensureUpToDate(false); + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(new VirtualFile[] {subTree.myOuterFile, subTree.myInnerFile}, + myClManager.getDefaultListName(), myClManager); + + VcsDirtyScopeManagerImpl.getInstance(myProject).fileDirty(subTree.myNonVersionedUpper); + myClManager.ensureUpToDate(false); + DuringChangeListManagerUpdateTestScheme.checkFilesAreInList(new VirtualFile[] {subTree.myOuterFile, subTree.myInnerFile}, + myClManager.getDefaultListName(), myClManager);*/ + } + + private void sleep100() { + try { + Thread.sleep(100); + } + catch (InterruptedException e) { + // + } + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnTestCase.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnTestCase.java new file mode 100644 index 000000000000..05c0cf3d7edd --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnTestCase.java @@ -0,0 +1,188 @@ +package org.jetbrains.idea.svn17; + +import com.intellij.execution.process.ProcessOutput; +import com.intellij.ide.startup.impl.StartupManagerImpl; +import com.intellij.lifecycle.AtomicSectionsAware; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.application.PluginPathManager; +import com.intellij.openapi.command.undo.UndoManager; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.startup.StartupManager; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.ui.TestDialog; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vcs.VcsConfiguration; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.VcsShowConfirmationOption; +import com.intellij.openapi.vcs.changes.*; +import com.intellij.openapi.vcs.changes.pending.MockChangeListManagerGate; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.testFramework.AbstractVcsTestCase; +import com.intellij.testFramework.PlatformTestCase; +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; +import com.intellij.testFramework.fixtures.TempDirTestFixture; +import com.intellij.testFramework.vcs.MockChangelistBuilder; +import com.intellij.util.io.ZipUtil; +import com.intellij.util.ui.UIUtil; +import org.junit.After; +import org.junit.Before; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +/** + * @author yole + */ +public abstract class SvnTestCase extends AbstractVcsTestCase { + protected TempDirTestFixture myTempDirFixture; + private File myWcRoot; + protected String myRepoUrl; + private ChangeListManagerGate myGate; + protected AtomicSectionsAware myRefreshCopiesStub; + + protected SvnTestCase() { + PlatformTestCase.initPlatformLangPrefix(); + } + + @Before + public void setUp() throws Exception { + System.setProperty("svnkit.wc.17", "false"); + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + @Override + public void run() { + try { + final IdeaTestFixtureFactory fixtureFactory = IdeaTestFixtureFactory.getFixtureFactory(); + myTempDirFixture = fixtureFactory.createTempDirTestFixture(); + myTempDirFixture.setUp(); + + final File svnRoot = new File(myTempDirFixture.getTempDirPath(), "svnroot"); + svnRoot.mkdir(); + + File pluginRoot = new File(PluginPathManager.getPluginHomePath("svn4idea")); + if (!pluginRoot.isDirectory()) { + // try standalone mode + Class aClass = SvnTestCase.class; + String rootPath = PathManager.getResourceRoot(aClass, "/" + aClass.getName().replace('.', '/') + ".class"); + pluginRoot = new File(rootPath).getParentFile().getParentFile().getParentFile(); + } + myClientBinaryPath = new File(pluginRoot, "testData/svn/bin"); + + ZipUtil.extract(new File(pluginRoot, "testData/svn/newrepo.zip"), svnRoot, null); + + myWcRoot = new File(myTempDirFixture.getTempDirPath(), "wcroot"); + myWcRoot.mkdir(); + + myRepoUrl = "file:///" + FileUtil.toSystemIndependentName(svnRoot.getPath()); + + initProject(myWcRoot); + activateVCS(SvnVcs17.VCS_NAME); + + verify(runSvn("co", myRepoUrl, ".")); + + myGate = new MockChangeListManagerGate(ChangeListManager.getInstance(myProject)); + + myRefreshCopiesStub = new AtomicSectionsAware() { + @Override + public void enter() { + } + @Override + public void exit() { + } + @Override + public boolean shouldExitAsap() { + return false; + } + @Override + public void checkShouldExit() throws ProcessCanceledException { + } + }; + + final SvnVcs17 vcs = SvnVcs17.getInstance(myProject); + ((StartupManagerImpl) StartupManager.getInstance(myProject)).runPostStartupActivities(); + //vcs.postStartup(); + ((SvnFileUrlMappingImpl17) vcs.getSvnFileUrlMapping()).realRefresh(); + + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + // there should be kind-a waiting for after change list manager finds all changes and runs inner refresh of copies in the above method + ChangeListManager changeListManager = ChangeListManager.getInstance(myProject); + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + changeListManager.ensureUpToDate(false); + } + + @After + public void tearDown() throws Exception { + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + @Override + public void run() { + try { + tearDownProject(); + if (myTempDirFixture != null) { + myTempDirFixture.tearDown(); + myTempDirFixture = null; + } + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + } + + protected ProcessOutput runSvn(String... commandLine) throws IOException { + return runClient("svn", null, myWcRoot, commandLine); + } + + protected void enableSilentOperation(final VcsConfiguration.StandardConfirmation op) { + setStandardConfirmation(SvnVcs17.VCS_NAME, op, VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY); + } + + protected void disableSilentOperation(final VcsConfiguration.StandardConfirmation op) { + setStandardConfirmation(SvnVcs17.VCS_NAME, op, VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY); + } + + protected void checkin() throws IOException { + verify(runSvn("ci", "-m", "test")); + } + + protected void update() throws IOException { + verify(runSvn("up")); + } + + protected List getAllChanges() throws VcsException { + return getChangesInScope(getAllDirtyScope()); + } + + protected List getChangesForFile(VirtualFile file) throws VcsException { + return getChangesInScope(getDirtyScopeForFile(file)); + } + + protected List getChangesInScope(final VcsDirtyScope dirtyScope) throws VcsException { + ChangeProvider changeProvider = SvnVcs17.getInstance(myProject).getChangeProvider(); + assert changeProvider != null; + MockChangelistBuilder builder = new MockChangelistBuilder(); + changeProvider.getChanges(dirtyScope, builder, new EmptyProgressIndicator(), myGate); + return builder.getChanges(); + } + + protected void undo() { + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + @Override + public void run() { + final TestDialog oldTestDialog = Messages.setTestDialog(TestDialog.OK); + try { + UndoManager.getInstance(myProject).undo(null); + } + finally { + Messages.setTestDialog(oldTestDialog); + } + } + }); + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnTestDirtyScopeStateTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnTestDirtyScopeStateTest.java new file mode 100644 index 000000000000..5d485bb224cf --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/SvnTestDirtyScopeStateTest.java @@ -0,0 +1,113 @@ +package org.jetbrains.idea.svn17; + +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.FilePathImpl; +import com.intellij.openapi.vcs.ObjectsConvertor; +import com.intellij.openapi.vcs.VcsConfiguration; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManagerImpl; +import com.intellij.openapi.vfs.VirtualFile; +import junit.framework.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +public class SvnTestDirtyScopeStateTest extends SvnTestCase { + @Override + public void setUp() throws Exception { + super.setUp(); + + myInitChangeListManager = false; + } + + @Test + public void testWhatIsDirty() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + + final VcsDirtyScopeManagerImpl vcsDirtyScopeManager = (VcsDirtyScopeManagerImpl) VcsDirtyScopeManager.getInstance(myProject); + + final VirtualFile file = createFileInCommand("a.txt", "old content"); + final VirtualFile fileB = createFileInCommand("b.txt", "old content"); + final VirtualFile fileC = createFileInCommand("c.txt", "old content"); + final VirtualFile fileD = createFileInCommand("d.txt", "old content"); + + final List list = ObjectsConvertor.vf2fp(Arrays.asList(file, fileB, fileC, fileD)); + + vcsDirtyScopeManager.retrieveScopes(); + vcsDirtyScopeManager.changesProcessed(); + + vcsDirtyScopeManager.fileDirty(file); + vcsDirtyScopeManager.fileDirty(fileB); + + final Collection dirty1 = vcsDirtyScopeManager.whatFilesDirty(list); + Assert.assertTrue(dirty1.contains(new FilePathImpl(file))); + Assert.assertTrue(dirty1.contains(new FilePathImpl(fileB))); + + Assert.assertTrue(! dirty1.contains(new FilePathImpl(fileC))); + Assert.assertTrue(! dirty1.contains(new FilePathImpl(fileD))); + + vcsDirtyScopeManager.retrieveScopes(); + + final Collection dirty2 = vcsDirtyScopeManager.whatFilesDirty(list); + Assert.assertTrue(dirty2.contains(new FilePathImpl(file))); + Assert.assertTrue(dirty2.contains(new FilePathImpl(fileB))); + + Assert.assertTrue(! dirty2.contains(new FilePathImpl(fileC))); + Assert.assertTrue(! dirty2.contains(new FilePathImpl(fileD))); + + vcsDirtyScopeManager.changesProcessed(); + + final Collection dirty3 = vcsDirtyScopeManager.whatFilesDirty(list); + Assert.assertTrue(! dirty3.contains(new FilePathImpl(file))); + Assert.assertTrue(! dirty3.contains(new FilePathImpl(fileB))); + + Assert.assertTrue(! dirty3.contains(new FilePathImpl(fileC))); + Assert.assertTrue(! dirty3.contains(new FilePathImpl(fileD))); + } + + @Test + public void testOkToAddScopeUnderWriteAction() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + + final VcsDirtyScopeManagerImpl vcsDirtyScopeManager = (VcsDirtyScopeManagerImpl) VcsDirtyScopeManager.getInstance(myProject); + + final VirtualFile file = createFileInCommand("a.txt", "old content"); + final VirtualFile fileB = createFileInCommand("b.txt", "old content"); + final VirtualFile fileC = createFileInCommand("c.txt", "old content"); + final VirtualFile fileD = createFileInCommand("d.txt", "old content"); + + final List list = ObjectsConvertor.vf2fp(Arrays.asList(file, fileB, fileC, fileD)); + + vcsDirtyScopeManager.retrieveScopes(); + vcsDirtyScopeManager.changesProcessed(); + + new WriteCommandAction.Simple(myProject) { + @Override + protected void run() throws Throwable { + vcsDirtyScopeManager.fileDirty(file); + vcsDirtyScopeManager.fileDirty(fileB); + } + }.execute().throwException(); + + + final FilePathImpl fp = new FilePathImpl(file); + final FilePathImpl fpB = new FilePathImpl(fileB); + final long start = System.currentTimeMillis(); + while (System.currentTimeMillis() < (start + 3000)) { + synchronized (this) { + try { + wait(50); + } + catch (InterruptedException e) { + // + } + } + final Collection dirty1 = vcsDirtyScopeManager.whatFilesDirty(list); + if (dirty1.contains(fp) && dirty1.contains(fpB)) return; + } + Assert.assertTrue(false); + } +} diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/VcsWaitForUpdateForTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/VcsWaitForUpdateForTest.java new file mode 100644 index 000000000000..2a16b7fd44bf --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn17/VcsWaitForUpdateForTest.java @@ -0,0 +1,46 @@ +package org.jetbrains.idea.svn17; + +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.VcsConfiguration; +import com.intellij.openapi.vcs.changes.EnsureUpToDateFromNonAWTThread; +import com.intellij.openapi.vfs.VirtualFile; +import org.junit.Test; + + +public class VcsWaitForUpdateForTest extends SvnTestCase { + @Test + public void testRefreshes() throws Exception { + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + final VirtualFile file = createFileInCommand("a.txt", "old content"); + final Object lock = new Object(); + + final Ref done = new Ref(); + final Thread thread = new Thread(new Runnable() { + @Override + public void run() { + new EnsureUpToDateFromNonAWTThread(myProject).execute(); + done.set(Boolean.TRUE); + synchronized (lock) { + lock.notifyAll(); + } + } + }); + + thread.start(); + synchronized (lock) { + final long start = System.currentTimeMillis(); + final int timeout = 3000; + + while ((System.currentTimeMillis() - start < timeout) && (! Boolean.TRUE.equals(done.get()))) { + try { + lock.wait(timeout); + } + catch (InterruptedException e) { + // + } + } + } + + assert Boolean.TRUE.equals(done.get()); + } +} diff --git a/plugins/svn4ideaOld/README.txt b/plugins/svn4ideaOld/README.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/plugins/svn4ideaOld/build/build.xml b/plugins/svn4ideaOld/build/build.xml new file mode 100644 index 000000000000..ce70b9940bda --- /dev/null +++ b/plugins/svn4ideaOld/build/build.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/svn4ideaOld/lib/ANTLR-LICENSE b/plugins/svn4ideaOld/lib/ANTLR-LICENSE new file mode 100644 index 000000000000..67e047c05e9f --- /dev/null +++ b/plugins/svn4ideaOld/lib/ANTLR-LICENSE @@ -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/svn4ideaOld/lib/COPYING b/plugins/svn4ideaOld/lib/COPYING new file mode 100644 index 000000000000..231c99901635 --- /dev/null +++ b/plugins/svn4ideaOld/lib/COPYING @@ -0,0 +1,47 @@ +The TMate License + +This license applies to all portions of TMate SVNKit library, which +are not externally-maintained libraries (e.g. Trilead SSH library). + +All the source code and compiled classes in package org.tigris.subversion.javahl +except SvnClient class are covered by the license in JAVAHL-LICENSE file + +Copyright (c) 2004-2011 TMate Software. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * 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. + + * Redistributions in any form must be accompanied by information on how to + obtain complete source code for the software that uses SVNKit and any + accompanying software that uses the software that uses SVNKit. The source + code must either be included in the distribution or be available for no + more than the cost of distribution plus a nominal fee, and must be freely + redistributable under reasonable conditions. For an executable file, complete + source code means the source code for all modules it contains. It does not + include source code for modules or files that typically accompany the major + components of the operating system on which the executable file runs. + + * Redistribution in any form without redistributing source code for software + that uses SVNKit is possible only when such redistribution is explictly permitted + by TMate Software. Please, contact TMate Software at support@svnkit.com to + get such permission. + +THIS SOFTWARE IS PROVIDED BY TMATE SOFTWARE ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE +DISCLAIMED. + +IN NO EVENT SHALL TMATE SOFTWARE 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/svn4ideaOld/lib/GANYMED-LICENSE b/plugins/svn4ideaOld/lib/GANYMED-LICENSE new file mode 100644 index 000000000000..a6fdd4c8f0ce --- /dev/null +++ b/plugins/svn4ideaOld/lib/GANYMED-LICENSE @@ -0,0 +1,57 @@ +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/svn4ideaOld/lib/JAVAHL-LICENSE b/plugins/svn4ideaOld/lib/JAVAHL-LICENSE new file mode 100644 index 000000000000..9c3e114d766c --- /dev/null +++ b/plugins/svn4ideaOld/lib/JAVAHL-LICENSE @@ -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/svn4ideaOld/lib/JNA-LICENSE b/plugins/svn4ideaOld/lib/JNA-LICENSE new file mode 100644 index 000000000000..b1a63d1686d1 --- /dev/null +++ b/plugins/svn4ideaOld/lib/JNA-LICENSE @@ -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/svn4ideaOld/lib/README.txt b/plugins/svn4ideaOld/lib/README.txt new file mode 100644 index 000000000000..0e072966c7ca --- /dev/null +++ b/plugins/svn4ideaOld/lib/README.txt @@ -0,0 +1,45 @@ +This is a README.txt file for SVNKit - pure Java Subversion client library. + +1. DESCRIPTION + +SVNKit is a pure Java Subversion client library. + +2. LICENSING + +SVNKit is open source product. The complete source code, documentation, and files required +to build the library are available for download from our Web site at http://svnkit.com/ + +The TMate open source license permits you to use SVNKit at no charge under the condition +that if you use the software in an application you redistribute, the complete source code for +your application must be available and freely redistributable under reasonable conditions. + +If you do not want to release the source code for your application, you may purchase a license +from TMate Software. For pricing information, or if you have further questions on licensing, +please contact us at support@svnkit.com. + +You may find the TMate open source license in COPYING file that is located within the same +directory as this file or at http://svnkit.com/license.html + +3. FEATURES + +SVNKit allows to work with Subversion repositories and Subversion working copies. SVNKit features +direct repository access as well as support for all high level Subversion operation that are +available with the command line Subversion client. + +This version of SVNKit supports all Subversion 1.6.15 features and supports older Subversion repositories. + +4. RESOURCES + +SVNKit Web Site: http://svnkit.com/ +SVNKit Documentation: http://svnkit.com/documentation.html +SVNKit Wiki: https://wiki.svnkit.com/ +SVNKit JavaDoc: http://svnkit.com/javadoc/ + +SVNKit Mailing List: http://svnkit.com/support.html +SVNKit Issues Tracker: http://svnkit.com/tracker/ + +SVNKit Continuous Build Server: https://teamcity.svnkit.com/ + +5. CONTACTS + +On all questions related to SVNKit please contact us at support@svnkit.com diff --git a/plugins/svn4ideaOld/lib/SEQUENCE-LICENSE b/plugins/svn4ideaOld/lib/SEQUENCE-LICENSE new file mode 100644 index 000000000000..35b7847f302d --- /dev/null +++ b/plugins/svn4ideaOld/lib/SEQUENCE-LICENSE @@ -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/svn4ideaOld/lib/SQLJET-LICENSE b/plugins/svn4ideaOld/lib/SQLJET-LICENSE new file mode 100644 index 000000000000..a5be225f3d70 --- /dev/null +++ b/plugins/svn4ideaOld/lib/SQLJET-LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2009-2011 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/svn4ideaOld/lib/SQLJET-README.txt b/plugins/svn4ideaOld/lib/SQLJET-README.txt new file mode 100644 index 000000000000..4c223e663def --- /dev/null +++ b/plugins/svn4ideaOld/lib/SQLJET-README.txt @@ -0,0 +1,42 @@ +This is a README.txt file for SQLJet v1.0.4. +SQLJet is a pure Java (tm) embedded database management engine with SQLite format support. + +1. DESCRIPTION + +SQLJet is a pure Java embedded database management engine with SQLite format support. + +2. LICENSING + +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. + +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 + +3. FEATURES + +* Current version of SQLJet provides API to manipulate B-Tree data structures + (tables and indices) stored in the database and database schema. + +* SQLJet database format is compatible with SQLite 3.6, + i.e. databases created with SQLJet could be used by SQLite 3.6 (assuming schema is valid) + and vice versa, databases created by SQLite 3.6 are understood and could be read and modified + by SQLJet. + +* SQLJet is pure Java and could be ran on any platform under JVM 1.5 or newer. + +* SQLJet implements ACID transactions. + +4. RESOURCES + +SQLJet web site: http://sqljet.com/ +SQLJet source code: http://svn.sqljet.com/repos/sqljet/ +SQLJet API usage tutorial: http://sqljet.com/tutorial.html + +SQLite web site: http://sqlite.org/ + +5. CONTACTS + +On all questions related to SQLJet please contact us at support@sqljet.com diff --git a/plugins/svn4ideaOld/lib/TRILEAD-LICENSE b/plugins/svn4ideaOld/lib/TRILEAD-LICENSE new file mode 100644 index 000000000000..579538537a7c --- /dev/null +++ b/plugins/svn4ideaOld/lib/TRILEAD-LICENSE @@ -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/svn4ideaOld/lib/changelog.txt b/plugins/svn4ideaOld/lib/changelog.txt new file mode 100644 index 000000000000..114700d8ee6d --- /dev/null +++ b/plugins/svn4ideaOld/lib/changelog.txt @@ -0,0 +1,534 @@ +== version 1.3.5 == + ++ Changes made in Subversion since version 1.6.12 implemented, + all Subversion 1.6.15 features are supported. + ++ Support for OS X Keychain as credentials storage added. ++ SSL client certificate passphrase is cached in a way + compatible with that of the native Subversion. ++ HTTP cookies are handled properly. ++ SQLJet library upgraded to v1.0.4 + ++ Other minor bugfixes and improvements. + +== version 1.3.4 == + ++ Changes made in Subversion since version 1.6.11 implemented, + all Subversion 1.6.12 features are supported. + ++ Added support for SSH host fingerprints verification. ++ Name conflict status added which is reported on + case-insensitive filesystems for files which name differs only in case. ++ HTTP digest authentication support improved. ++ svnkit:charset property support significantly improved. ++ SQLJet library upgraded to v1.0.3 ++ JNA library upgraded to v3.2.7 + ++ Other minor bugfixes and improvements. + +== version 1.3.3 == + ++ Changes made in Subversion since version 1.6.6 implemented, + all Subversion 1.6.11 features are supported. + ++ HTTP Negotiate authentication improvements. ++ Support of MSCAPI SSL certificates added. ++ SQLJet library upgraded to version 1.0.2 ++ File externals support improved + ++ Other bugfixes + +== version 1.3.2 == + ++ Changes made in Subversion since version 1.6.5 implemented, + all Subversion 1.6.6 features are supported. + + * Negotiate authorization mechanism was not used when multiple authorization + schemes were offered to client by server. + * SQLJet library upgraded to version 1.0.1. + * JNA library upgraded to version 3.2.3. + ++ Bugfixes and performance improvements. + +== version 1.3.1 == + ++ Changes made in Subversion since version 1.6.2 implemented, + all Subversion 1.6.5 features are supported. + + * Representation cache DB support, using SQLJet (http://sqljet.com) library. + * Added implementations for couple of missing JavaHL methods. + * Fixed 'svn: Checksum mismatch while reading representation' exception on + reading vdelta from local repositories. + * Several dump and load operation issues fixed. + * JNA library upgraded to version 3.1.0. + ++ Other bugfixes and performance improvements. + +== version 1.3.0 + ++ Changes made in Subversion since version 1.5.6 implemented, + all Subversion 1.6.2 features are supported: + * Tree conflicts handled more gracefully + * File-externals support for intra-repository files + * File protocol: 1.6.2 Subversion repositories format supported + * Administrative tools: support for packing repository shards + * Command line client: repository root relative URL support on most commands + * Performance improvements on huge files during update\commits + ++ Other bugfixes and performance improvements. + +== version 1.3.0-beta3 + ++ Changes made in Subversion since version 1.5.6 implemented, + all Subversion 1.6.1 features are supported. ++ Bugfixes since version 1.3.0-beta2 + +== version 1.3.0-beta2 + ++ Changes made in Subversion since version 1.5.6 implemented, + all Subversion 1.6.1 features are supported. ++ JavaHL implementation bugfixes. ++ Compatibility with pre-1.6 working copies improved for + 'no working copy upgrade' cases. ++ Update performance on large files improved. ++ Command line client improvements. ++ Other bugfixes and performance improvements. + +== version 1.3.0-beta + ++ Changes made in Subversion since version 1.5.6 implemented, + all Subversion 1.6.1 features are supported. ++ Merge tracking bugfixes ++ Other bugfixes and performance improvements. + +== version 1.2.3 + ++ Changes made in Subversion since version 1.5.5 implemented, + all Subversion 1.5.6 features are supported. ++ Merge tracking bugfixes ++ Better interacting with external diff programs ++ svnkit.no.safe.copy system property introduced for file system copy operations ++ Other bugfixes and performance improvements. + +== version 1.2.2 + ++ Changes made in Subversion since version 1.5.4 implemented, + all Subversion 1.5.5 features are supported. ++ Merge tracking implementation changed to match Subversion 1.5.5. ++ HP-UX support added. ++ Other stability and performance improvements. + +== version 1.2.1 + ++ Changes made in Subversion since version 1.5.0 implemented, + all Subversion 1.5.4 features are supported. ++ JNA library updated to version 3.0.9. ++ JNA-based code fixed to work properly on amd64 systems. ++ HTTP Negotiate authentication scheme support added. ++ "Fast" update feature has been added to SVNUpdateClient API. ++ Command line client improvements. ++ Other stability and performance improvements. + +== version 1.2.0 + ++ Possible NPE on WC to URL copy operation fixed. ++ Log operation didn't work properly with 'limit' options set to value + larger than Integer.MAX_VALUE, fixed. ++ OSGI manifest fixes. + +== version 1.2.0-rc4 + ++ JNA library updated to version 3.0.5. ++ jsvnlook 'cat' and 'lock' commands added. ++ Update operation didn't work properly on sparse directories with 1.4.x Subversion servers, fixed. ++ SASL: minor bugfixes. ++ Other bugfixes. + +== version 1.2.0-rc3 + ++ SVNKit Eclipse plugin divided into two features: core svnkit library and optional JNA library. ++ SVNKit Eclipse plugin made compatible with base OSGI framework. ++ SVN+SSH protocol performance improvements. ++ File uid and guid wasn't properly fetched on OSX and BSD. ++ Other bugfixes and minor improvements. + +== version 1.2.0-rc2 + ++ SASL: incorrect output buffer size might result in failed commits, fixed. ++ Possible NPE on copy operation fixed. + +== version 1.2.0-rc1 + ++ Javadoc updated ++ SASL authentication support bugfixes ++ Bug in keyword expansion code fixed ++ Other bugfixes + +== version 1.2.0-beta5 + ++ SASL authentication support improved ++ Java SSH Trilead library updated to build 213 ++ JNA library updated to version 3.0.4 ++ Fixed: SSH connectivity problems with non-OpenSSH servers ++ Javadoc updated ++ Other bugfixes + +== version 1.2.0-beta4 + ++ Bugfixes + +== version 1.2.0-beta3 + ++ SASL authentication support improved. ++ Other bugfixes. + +== version 1.2.0-beta2 + ++ JavaHL compatiblity bugfixes ++ DAV protocol performance improvements + +== version 1.2.0-beta + ++ Subversion 1.5.0 features support + +== version 1.1.7 + ++ SVNKit didn't work properly with Subversion 1.5.0 servers over svn protocol ++ JNA library updated to version 3.0.2 ++ Trilead-SSH library updated to build 212 ++ Other bugfixes + +== version 1.1.6 + ++ Bugfixes: ++ Copy WC to URL sometimes didn't work properly with switched directories. ++ Several NPEs in HTTP connection code fixed. ++ Authentication cancellation didn't work with SSH connection in some cases. + +== version 1.1.5 + ++ Connections pool has been improved. ++ New version of SSH library (Trilead build 211) is used. ++ When possible, JNA library is used for most of native calls instead of Runtime.exec(...) ++ HTTP Digest authentications works better. ++ Bug in keywords expansion code fixed. ++ Other bugfixes. + += version 1.1.4 + ++ Connections pool closes inactive connections on timeout. ++ Bugfixes. + += version 1.1.3 + ++ Encrypted credentials supported on Windows. ++ OpenVMS support improved. ++ Bugfixes. ++ Daily builds now available at http://teamcity.svnkit.com/ + += version 1.1.2 + ++ SVNKit command line client: interactive credentials prompt added. ++ SVNKit command line client: jsvnversion added. ++ JavaHL: SVNAdmin implemented using SVNKit API. ++ System-wide configuration files support (e.g. /etc/subversion on Linux) ++ bugfixes. + += version 1.1.1 + ++ added support for svnlook features. ++ added support for svnadmin dump/verify/load/rmtxns/lstxns features. ++ svn, svnadmin, svnlook and svnsync scripts are + renamed to jsvn, jsvnadmin, jsvnlook and jsvnsync to distinguish them + from the native programs. ++ command line scripts supports OpenVMS. ++ bugfixes. + += version 1.1.0 + ++ command line application and API to perform svnsync. ++ bugfixes. + += version 1.1.0.beta5 + ++ support for Subversion 1.4 working copy and new fsfs repository format. ++ support for new features introduced by Subversion 1.4. ++ bugfixes. + += version 1.1.0.beta4 + ++ bug introduced in beta3 version fixed: + Eclipse keyring credentials storage support was broken. + += version 1.1.0.beta3 + ++ Minor API improvements. ++ Minor bugfixes. + += version 1.1.0.beta2 + ++ NTLM authentication method support. ++ Javadoc updated to match new SVNKit features. ++ Minor bugfixes. + += version 1.1.0.beta + ++ 'file' protocol support (for 'fsfs' repositories). + += version 1.0.3 + ++ improved cancellation support ++ improved versioned symlinks handling ++ improved HTTP proxies support ++ bugfixes + += version 1.0.2 + ++ full support of the new Subversion 1.3.0 features. ++ ssh 'keyboard-interactive' authentication support. ++ ssl client certificate prompt support. ++ error codes and error messages hierarchy support in the API. ++ utility classes to genereta operatios output in XML format. ++ faster delta generation and delta processing code. ++ utility API classes to process and generate deltas. ++ documentations improved, Commit and Export examples simplified. ++ bugfixes + +! Note: + This version includes minor API changes and is not binary compatible + with the previous versions. API changes are: + * String error messages replaced with SVNErrorMessage objects + * ISVNFileRevisionHanlder callback interface changed + * all callback interfaces methods now declared as throwing SVNException + += version 1.0.1 + ++ bugfixes + += version 1.0.0 + ++ documentation related improvements ++ bugfixes + += version 0.9.3 + ++ licensing conditions changed ++ support for atomic commit from different working copies ++ persistent connections support ++ improvements in performance and memory consumption ++ bugfixes + += version 0.9.2 + ++ RC2: bugfixes related to configuration handling and svn+ssh authentication + += version 0.9.1 + ++ RC1: All features planned for 1.0 version are completed. + += version 0.8.8.1 + ++ critical bug in SVNKit Eclipse plugin fixed. + += version 0.8.8 + ++ http digest auth support (requires apache version > 2.0.48) ++ wc->url and url->wc copy operations ++ use-commit-times option support ++ bugfixes + += version 0.8.7.2 + ++ Subclipse v0.9.30 compatible + += version 0.8.7.1 + ++ Subclipse v0.9.29 compatible + += version 0.8.7 + ++ http proxy support ++ svn "keywords" are expanded properly ++ different eol's in a file are handled correctly ++ other minor bugfixes + += version 0.8.6 + ++ annotate (blame) operation is implemented. ++ http: server socket close detected properly and doesn't result in operation failure. ++ SVNClient: absolute paths are used in notifications. ++ SVNClient: fileContent method traces file renames. ++ SVNClient: list and logMessages methods return paths sorted alphabetically. ++ SVNClient: auth info is stored in Eclipse key ring when used within Eclipse. + += version 0.8.5 + ++ SVNClient: WC copy and move operations work properly on Linux and OS X. ++ SVNClient: "conflicted" status is reported correctly during update + ("merged" was reported instead). + += version 0.8.4 + ++ Subclipse Extension supports Subclipse 0.9.28 ++ tabulations in externals definitions processed correctly. + += version 0.8.3 + ++ children of copied or moved directories were not committed properly when + their wc revision was different from the parent's one. ++ http: all DAV requests (including commit comments) are sent in UTF-8 encoding. ++ SvnClient: add and remove methods didn't work for directories, fixed. ++ SvnClient: commit of single file deletion didn't work, fixed. ++ Eclipse: SVNKit features installed from update site supports automatic update + += version 0.8.2 + ++ svn+ssh: persistent ssh connection is used to improve svn+ssh performance. ++ http: problems with accessing repository location that needs different credentials then + repository root is fixed. ++ http: all paths in requests are canonicalized, some svn server versions failed to + process non-canonicalized paths. ++ wc: changes in externals were not committed correctly over http when using ISVNWorkspace.commit(...), fixed. ++ SvnClient: diff method implemented (so that Eclipse "generate patch" action works). ++ SvnClient: copy and move methods fixed to work properly in Subclipse repository view. ++ SvnClient: setPrompt support, better notifications, "windows" paths are handled correctly. ++ logging: Pluggable loggers support and detailed http logging added (thanks to Marc Strapez). ++ logging: Eclipse SVNKit version uses Eclipse logging facilities. + += version 0.8.1 + ++ bugs with committing files within moved directories fixed ++ bugfixes related to operations over http connection ++ for subclipse users: more detailed console notifications on svn operations + += version 0.8.0 + ++ new builds versioning schema introduced ++ update site with eclipse plugin and subclipse extension added + += build 20050131 + ++ import works properly with single file and when importing to + non-yet-existing directories ++ switch works properly when switching single file ++ more minor bugfixes + += build 20050120 + ++ bugs with commiting locally copied files fixed ++ DAV '/' repository location handled correctly + += build 20050112 + ++ lot of incompatibilities with native svn fixed ++ SVNClient supports remote deletion, copy and move operations + += build 20050106 + ++ number of bugs in remote status implementation fixed ++ ISVNWorkspace.getFileContent method restored to keep binary compatibility ++ Diff generators framework added, now SVNKit provides API for + generating diff in different formats ('unified' and 'normal') for arbitrary + input sources + += build 20050105 + ++ svn+ssh authentication failed to work in some cases - fixed ++ revisions in working copy now updated correctly after update ++ got rid of number of minor inconsistences between SVNKit and native SVN ++ http and https protocols supports compressed data (gzip) + += build 20041223 + ++ ISVNWorkspace.delete now accepts 'force' parameter ++ bug fixed that prevented Subclipse committing single + file in non-windows systems ++ complete svn and svn+ssh protocol logging may be enabled + with -Dsvnkit.log.svn=true command line switch + += build 20041221 + ++ when using svn+ssh SVNKit takes private key path from the java property, + if it is not defined explicitly ++ svn+ssh bugfixes ++ bugfixes + += build 20041219 + ++ infinite loop on commits that includes workspace root fixed ++ JavaHL: SVNClient accepts all revision kinds (DATE, HEAD, etc.) + += build 20041217 + ++ bugfixes + += build 20041216 + ++ bugfixes + += build 20041211 + ++ command line svn client (status, import, checkout are implemented) ++ python tests launcher to reuse original svn python tests suite ++ bugfixes + += build 20041209 + ++ bugfixes + += build 20041204 + ++ bugfixes ++ SVNKit works with Subclipse 0.9.24 + += build 20041130 + ++ bugfixes + += build 20041126 + ++ svn+shh protocol support (with the jsch library) ++ bugfixes + += build 20041124 + ++ javahl copy, move and mkdir methods implementation ++ methods to manage global ignore added to ISVNWorkspace ++ bugfixes + += build 20041123 + ++ recursive property set operation ++ ISVNWorkspace accepts credentials provider ++ SSLContext made pluggable ++ javahl replacement works with Subclipse ++ bugfixes + += build 20041118 + ++ javahl implementation (SvnClient) is included into the library ++ workspace accepts credentials provider + += build 20041116 + ++ bugfixes + += build 20041110 + ++ svn:externals support ++ bugfixes + += build 20041109 + ++ bug that prevents checkout sometimes fixed + += build 20041108 + ++ authentication API simplified ++ performance improvements ++ bugfixes + += build 20041105 + ++ switch and relocate operations ++ bugfixes diff --git a/plugins/svn4ideaOld/lib/jna.jar b/plugins/svn4ideaOld/lib/jna.jar new file mode 100644 index 000000000000..55ca1eb4e308 Binary files /dev/null and b/plugins/svn4ideaOld/lib/jna.jar differ diff --git a/plugins/svn4idea/sequence-library.jar b/plugins/svn4ideaOld/lib/sequence-library.jar similarity index 100% rename from plugins/svn4idea/sequence-library.jar rename to plugins/svn4ideaOld/lib/sequence-library.jar diff --git a/plugins/svn4ideaOld/lib/svnkit.jar b/plugins/svn4ideaOld/lib/svnkit.jar new file mode 100644 index 000000000000..53d6c9230c4c Binary files /dev/null and b/plugins/svn4ideaOld/lib/svnkit.jar differ diff --git a/plugins/svn4ideaOld/lib/svnkitsrc.zip b/plugins/svn4ideaOld/lib/svnkitsrc.zip new file mode 100644 index 000000000000..d4d9454286e4 Binary files /dev/null and b/plugins/svn4ideaOld/lib/svnkitsrc.zip differ diff --git a/plugins/svn4ideaOld/lib/trilead.jar b/plugins/svn4ideaOld/lib/trilead.jar new file mode 100644 index 000000000000..5ba0ce62b6ac Binary files /dev/null and b/plugins/svn4ideaOld/lib/trilead.jar differ diff --git a/plugins/svn4ideaOld/resources/icons/Common.png b/plugins/svn4ideaOld/resources/icons/Common.png new file mode 100644 index 000000000000..b9981a55cf32 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/Common.png differ diff --git a/plugins/svn4ideaOld/resources/icons/ConfigureBranches.png b/plugins/svn4ideaOld/resources/icons/ConfigureBranches.png new file mode 100644 index 000000000000..b180243f5780 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/ConfigureBranches.png differ diff --git a/plugins/svn4ideaOld/resources/icons/FilterIntegrated.png b/plugins/svn4ideaOld/resources/icons/FilterIntegrated.png new file mode 100644 index 000000000000..d2a37572e4ee Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/FilterIntegrated.png differ diff --git a/plugins/svn4ideaOld/resources/icons/FilterNotIntegrated.png b/plugins/svn4ideaOld/resources/icons/FilterNotIntegrated.png new file mode 100644 index 000000000000..2b696a9c7c39 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/FilterNotIntegrated.png differ diff --git a/plugins/svn4ideaOld/resources/icons/FilterOthers.png b/plugins/svn4ideaOld/resources/icons/FilterOthers.png new file mode 100644 index 000000000000..cc8e2795c79a Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/FilterOthers.png differ diff --git a/plugins/svn4ideaOld/resources/icons/IntegrateToBranch.png b/plugins/svn4ideaOld/resources/icons/IntegrateToBranch.png new file mode 100644 index 000000000000..6c71933cc0b2 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/IntegrateToBranch.png differ diff --git a/plugins/svn4ideaOld/resources/icons/Integrated.png b/plugins/svn4ideaOld/resources/icons/Integrated.png new file mode 100644 index 000000000000..69e829c63ea0 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/Integrated.png differ diff --git a/plugins/svn4ideaOld/resources/icons/IntegrationStatusUnknown.png b/plugins/svn4ideaOld/resources/icons/IntegrationStatusUnknown.png new file mode 100644 index 000000000000..a0578927786a Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/IntegrationStatusUnknown.png differ diff --git a/plugins/svn4ideaOld/resources/icons/MarkAsMerged.png b/plugins/svn4ideaOld/resources/icons/MarkAsMerged.png new file mode 100644 index 000000000000..13534b9d0e65 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/MarkAsMerged.png differ diff --git a/plugins/svn4ideaOld/resources/icons/MarkAsNotMerged.png b/plugins/svn4ideaOld/resources/icons/MarkAsNotMerged.png new file mode 100644 index 000000000000..32cc98172d20 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/MarkAsNotMerged.png differ diff --git a/plugins/svn4ideaOld/resources/icons/Notintegrated.png b/plugins/svn4ideaOld/resources/icons/Notintegrated.png new file mode 100644 index 000000000000..b31b4620e871 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/Notintegrated.png differ diff --git a/plugins/svn4ideaOld/resources/icons/OnDefault.png b/plugins/svn4ideaOld/resources/icons/OnDefault.png new file mode 100644 index 000000000000..61e239063ebe Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/OnDefault.png differ diff --git a/plugins/svn4ideaOld/resources/icons/PropertiesDiff.png b/plugins/svn4ideaOld/resources/icons/PropertiesDiff.png new file mode 100644 index 000000000000..da4c3166b2c7 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/PropertiesDiff.png differ diff --git a/plugins/svn4ideaOld/resources/icons/PropertiesDiffWithLocal.png b/plugins/svn4ideaOld/resources/icons/PropertiesDiffWithLocal.png new file mode 100644 index 000000000000..4c67ae4b4358 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/PropertiesDiffWithLocal.png differ diff --git a/plugins/svn4ideaOld/resources/icons/ShowIntegratedFrom.png b/plugins/svn4ideaOld/resources/icons/ShowIntegratedFrom.png new file mode 100644 index 000000000000..8d0ebaab10a6 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/ShowIntegratedFrom.png differ diff --git a/plugins/svn4ideaOld/resources/icons/ShowIntegratedTo.png b/plugins/svn4ideaOld/resources/icons/ShowIntegratedTo.png new file mode 100644 index 000000000000..02a48dff7cfb Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/ShowIntegratedTo.png differ diff --git a/plugins/svn4ideaOld/resources/icons/ShowWorkingCopies.png b/plugins/svn4ideaOld/resources/icons/ShowWorkingCopies.png new file mode 100644 index 000000000000..d6a0c235f605 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/ShowWorkingCopies.png differ diff --git a/plugins/svn4ideaOld/resources/icons/UndoIntegrateToBranch.png b/plugins/svn4ideaOld/resources/icons/UndoIntegrateToBranch.png new file mode 100644 index 000000000000..034d0e6e4501 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/UndoIntegrateToBranch.png differ diff --git a/plugins/svn4ideaOld/resources/icons/allRevisions.png b/plugins/svn4ideaOld/resources/icons/allRevisions.png new file mode 100644 index 000000000000..3d1bf88b6477 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/allRevisions.png differ diff --git a/plugins/svn4ideaOld/resources/icons/conflictc.png b/plugins/svn4ideaOld/resources/icons/conflictc.png new file mode 100644 index 000000000000..9abaa1646b90 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/conflictc.png differ diff --git a/plugins/svn4ideaOld/resources/icons/conflictcp.png b/plugins/svn4ideaOld/resources/icons/conflictcp.png new file mode 100644 index 000000000000..4a212f63ab68 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/conflictcp.png differ diff --git a/plugins/svn4ideaOld/resources/icons/conflictct.png b/plugins/svn4ideaOld/resources/icons/conflictct.png new file mode 100644 index 000000000000..39275458e5f7 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/conflictct.png differ diff --git a/plugins/svn4ideaOld/resources/icons/conflictctp.png b/plugins/svn4ideaOld/resources/icons/conflictctp.png new file mode 100644 index 000000000000..5ea8b136cdd9 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/conflictctp.png differ diff --git a/plugins/svn4ideaOld/resources/icons/conflictp.png b/plugins/svn4ideaOld/resources/icons/conflictp.png new file mode 100644 index 000000000000..5dd25593d14b Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/conflictp.png differ diff --git a/plugins/svn4ideaOld/resources/icons/conflictt.png b/plugins/svn4ideaOld/resources/icons/conflictt.png new file mode 100644 index 000000000000..806197c7d7ff Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/conflictt.png differ diff --git a/plugins/svn4ideaOld/resources/icons/conflicttp.png b/plugins/svn4ideaOld/resources/icons/conflicttp.png new file mode 100644 index 000000000000..21b8cb658187 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/conflicttp.png differ diff --git a/plugins/svn4ideaOld/resources/icons/mergeSourcesDetails.png b/plugins/svn4ideaOld/resources/icons/mergeSourcesDetails.png new file mode 100644 index 000000000000..87a869672899 Binary files /dev/null and b/plugins/svn4ideaOld/resources/icons/mergeSourcesDetails.png differ diff --git a/plugins/svn4ideaOld/sequence-library.jar b/plugins/svn4ideaOld/sequence-library.jar new file mode 100644 index 000000000000..4421d3d48096 Binary files /dev/null and b/plugins/svn4ideaOld/sequence-library.jar differ diff --git a/plugins/svn4ideaOld/src/META-INF/plugin.xml b/plugins/svn4ideaOld/src/META-INF/plugin.xml new file mode 100644 index 000000000000..91272191afc3 --- /dev/null +++ b/plugins/svn4ideaOld/src/META-INF/plugin.xml @@ -0,0 +1,139 @@ + + Subversion Integration + Subversion + 1.0 + VCS Integration + + com.intellij.modules.vcs + + JetBrains + org.jetbrains.idea.svn.SvnBundle + + + + org.jetbrains.idea.svn.SvnFileUrlMappingImpl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/ConflictState.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ConflictState.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/ConflictState.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ConflictState.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/ConflictedSvnChange.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ConflictedSvnChange.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/ConflictedSvnChange.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ConflictedSvnChange.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/CopiesRefresh.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/CopiesRefresh.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/CopiesRefresh.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/CopiesRefresh.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/DefendedCopiesRefreshProxy.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/DefendedCopiesRefreshProxy.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/DefendedCopiesRefreshProxy.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/DefendedCopiesRefreshProxy.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/DepthCombo.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/DepthCombo.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/DepthCombo.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/DepthCombo.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/FilePathUtil.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/FilePathUtil.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/FilePathUtil.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/FilePathUtil.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/ForNestedRootChecker.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ForNestedRootChecker.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/ForNestedRootChecker.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ForNestedRootChecker.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/FragmentsMerger.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/FragmentsMerger.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/FragmentsMerger.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/FragmentsMerger.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/IdeaSVNConfigFile.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/IdeaSVNConfigFile.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/IdeaSVNConfigFile.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/IdeaSVNConfigFile.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/IdeaSubversionConfigurationDirectory.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/IdeaSubversionConfigurationDirectory.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/IdeaSubversionConfigurationDirectory.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/IdeaSubversionConfigurationDirectory.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/IgnoredFileInfo.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/IgnoredFileInfo.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/IgnoredFileInfo.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/IgnoredFileInfo.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/LimitedStringsList.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/LimitedStringsList.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/LimitedStringsList.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/LimitedStringsList.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/MoveRenameReplaceCheck.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/MoveRenameReplaceCheck.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/MoveRenameReplaceCheck.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/MoveRenameReplaceCheck.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/NestedCopiesBuilder.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/NestedCopiesBuilder.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/NestedCopiesBuilder.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/NestedCopiesBuilder.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/NestedCopiesData.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/NestedCopiesData.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/NestedCopiesData.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/NestedCopiesData.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/NestedCopiesSink.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/NestedCopiesSink.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/NestedCopiesSink.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/NestedCopiesSink.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/NestedCopyType.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/NestedCopyType.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/NestedCopyType.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/NestedCopyType.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/Node.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/Node.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/Node.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/Node.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/ProxySvnAuthentication.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ProxySvnAuthentication.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/ProxySvnAuthentication.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ProxySvnAuthentication.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/Real.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/Real.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/Real.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/Real.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/ReentranceDefence.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ReentranceDefence.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/ReentranceDefence.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ReentranceDefence.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/RootMixedInfo.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/RootMixedInfo.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/RootMixedInfo.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/RootMixedInfo.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/RootUrlInfo.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/RootUrlInfo.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/RootUrlInfo.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/RootUrlInfo.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/RootUrlPair.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/RootUrlPair.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/RootUrlPair.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/RootUrlPair.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/RootsToWorkingCopies.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/RootsToWorkingCopies.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/RootsToWorkingCopies.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/RootsToWorkingCopies.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/StatusReceiver.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/StatusReceiver.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/StatusReceiver.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/StatusReceiver.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/StatusWalkerPartner.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/StatusWalkerPartner.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/StatusWalkerPartner.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/StatusWalkerPartner.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/StatusWalkerPartnerImpl.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/StatusWalkerPartnerImpl.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/StatusWalkerPartnerImpl.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/StatusWalkerPartnerImpl.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnApplicationSettings.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnApplicationSettings.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnApplicationSettings.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnApplicationSettings.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthEquals.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnAuthEquals.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthEquals.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnAuthEquals.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationManager.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnAuthenticationManager.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationManager.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnAuthenticationManager.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationNotifier.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnAuthenticationNotifier.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAuthenticationNotifier.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnAuthenticationNotifier.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBinaryContentRevision.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnBinaryContentRevision.java similarity index 99% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBinaryContentRevision.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnBinaryContentRevision.java index 409f3cf734aa..c2e6ced711e2 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBinaryContentRevision.java +++ b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnBinaryContentRevision.java @@ -46,4 +46,4 @@ class SvnBinaryContentRevision extends SvnContentRevision implements BinaryConte public String toString() { return "SvnBinaryContentRevision:" + myFile; } -} \ No newline at end of file +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBranchConfiguration.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnBranchConfiguration.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBranchConfiguration.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnBranchConfiguration.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBranchConfigurationManager.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnBranchConfigurationManager.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBranchConfigurationManager.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnBranchConfigurationManager.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBranchMapperManager.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnBranchMapperManager.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBranchMapperManager.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnBranchMapperManager.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnBundle.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnBundle.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnBundle.properties similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnBundle.properties rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnBundle.properties diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProvider.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnChangeProvider.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProvider.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnChangeProvider.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProviderContext.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnChangeProviderContext.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangeProviderContext.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnChangeProviderContext.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangedFile.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnChangedFile.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangedFile.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnChangedFile.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangelistListener.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnChangelistListener.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnChangelistListener.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnChangelistListener.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnCompatibilityChecker.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnCompatibilityChecker.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnCompatibilityChecker.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnCompatibilityChecker.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfigurable.form b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnConfigurable.form similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfigurable.form rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnConfigurable.form diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfigurable.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnConfigurable.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfigurable.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnConfigurable.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfiguration.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnConfiguration.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnConfiguration.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnConfiguration.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnContentRevision.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnContentRevision.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnContentRevision.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnContentRevision.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnCopiesRefreshManager.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnCopiesRefreshManager.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnCopiesRefreshManager.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnCopiesRefreshManager.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnCopyRootSimple.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnCopyRootSimple.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnCopyRootSimple.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnCopyRootSimple.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnDiffProvider.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnDiffProvider.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnDiffProvider.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnDiffProvider.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnEditFileProvider.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnEditFileProvider.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnEditFileProvider.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnEditFileProvider.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnEntriesFileListener.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnEntriesFileListener.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnEntriesFileListener.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnEntriesFileListener.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnEntriesListener.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnEntriesListener.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnEntriesListener.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnEntriesListener.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileStatus.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnFileStatus.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileStatus.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnFileStatus.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileSystemListener.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnFileSystemListener.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileSystemListener.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnFileSystemListener.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileSystemListenerWrapper.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnFileSystemListenerWrapper.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileSystemListenerWrapper.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnFileSystemListenerWrapper.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileUrlMapping.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnFileUrlMapping.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileUrlMapping.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnFileUrlMapping.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileUrlMappingImpl.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnFileUrlMappingImpl.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFileUrlMappingImpl.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnFileUrlMappingImpl.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFormatSelector.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnFormatSelector.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnFormatSelector.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnFormatSelector.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnHttpAuthMethodsDefaultChecker.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnHttpAuthMethodsDefaultChecker.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnHttpAuthMethodsDefaultChecker.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnHttpAuthMethodsDefaultChecker.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnLoadedBrachesStorage.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnLoadedBrachesStorage.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnLoadedBrachesStorage.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnLoadedBrachesStorage.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnLoadedBranches.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnLoadedBranches.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnLoadedBranches.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnLoadedBranches.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnLocallyDeletedChange.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnLocallyDeletedChange.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnLocallyDeletedChange.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnLocallyDeletedChange.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnMapping.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnMapping.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnMapping.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnMapping.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnMappingSavedPart.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnMappingSavedPart.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnMappingSavedPart.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnMappingSavedPart.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnNestedTypeRechecker.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnNestedTypeRechecker.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnNestedTypeRechecker.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnNestedTypeRechecker.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnProgressCanceller.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnProgressCanceller.java similarity index 99% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnProgressCanceller.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnProgressCanceller.java index ca1b9a76f132..5163dfa0ffea 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnProgressCanceller.java +++ b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnProgressCanceller.java @@ -35,4 +35,4 @@ public class SvnProgressCanceller implements ISVNEventHandler { public void handleEvent(final SVNEvent event, final double progress) throws SVNException { } -} \ No newline at end of file +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnPropertyKeys.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnPropertyKeys.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnPropertyKeys.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnPropertyKeys.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnProxyAuthenticationNotifier.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnProxyAuthenticationNotifier.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnProxyAuthenticationNotifier.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnProxyAuthenticationNotifier.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnRecursiveStatusWalker.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnRecursiveStatusWalker.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnRecursiveStatusWalker.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnRecursiveStatusWalker.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnRevisionNumber.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnRevisionNumber.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnRevisionNumber.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnRevisionNumber.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnScopeZipper.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnScopeZipper.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnScopeZipper.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnScopeZipper.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnServerFileManager.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnServerFileManager.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnServerFileManager.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnServerFileManager.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnServerFileManagerImpl.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnServerFileManagerImpl.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnServerFileManagerImpl.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnServerFileManagerImpl.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnStatusConvertor.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnStatusConvertor.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnStatusConvertor.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnStatusConvertor.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnStatusUtil.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnStatusUtil.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnStatusUtil.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnStatusUtil.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnUtil.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnUtil.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnUtil.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnUtil.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnVcs.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnVcs.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnVcs.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnWCRootCrawler.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnWCRootCrawler.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnWCRootCrawler.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnWCRootCrawler.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnWorkingCopyFormatHolder.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnWorkingCopyFormatHolder.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/SvnWorkingCopyFormatHolder.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/SvnWorkingCopyFormatHolder.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/WorkingCopiesContent.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/WorkingCopiesContent.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/WorkingCopiesContent.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/WorkingCopiesContent.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/WorkingCopy.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/WorkingCopy.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/WorkingCopy.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/WorkingCopy.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/WorkingCopyFormat.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/WorkingCopyFormat.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/WorkingCopyFormat.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/WorkingCopyFormat.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/Wrapper.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/Wrapper.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/Wrapper.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/Wrapper.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/AbstractIntegrateChangesAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/AbstractIntegrateChangesAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/AbstractIntegrateChangesAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/AbstractIntegrateChangesAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/AbstractShowPropertiesDiffAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/AbstractShowPropertiesDiffAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/AbstractShowPropertiesDiffAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/AbstractShowPropertiesDiffAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/AddAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/AddAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/AddAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/AddAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/AddToIgnoreListAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/AddToIgnoreListAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/AddToIgnoreListAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/AddToIgnoreListAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/BasicAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/BasicAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/BasicAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/BasicAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/BrowseRepositoryAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/BrowseRepositoryAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/BrowseRepositoryAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/BrowseRepositoryAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ChangeListsMergerFactory.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ChangeListsMergerFactory.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ChangeListsMergerFactory.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ChangeListsMergerFactory.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ChangeSetMergerFactory.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ChangeSetMergerFactory.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ChangeSetMergerFactory.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ChangeSetMergerFactory.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CleanupAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/CleanupAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CleanupAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/CleanupAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CleanupProjectAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/CleanupProjectAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CleanupProjectAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/CleanupProjectAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CleanupWorker.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/CleanupWorker.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CleanupWorker.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/CleanupWorker.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CompareWithBranchAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/CompareWithBranchAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CompareWithBranchAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/CompareWithBranchAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ConfigureBranchesAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ConfigureBranchesAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ConfigureBranchesAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ConfigureBranchesAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CopyAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/CopyAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CopyAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/CopyAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CopyEventHandler.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/CopyEventHandler.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CopyEventHandler.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/CopyEventHandler.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ExclusiveBackgroundVcsAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ExclusiveBackgroundVcsAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ExclusiveBackgroundVcsAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ExclusiveBackgroundVcsAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/IgnoreActionGroup.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/IgnoreActionGroup.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/IgnoreActionGroup.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/IgnoreActionGroup.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ImportToRepositoryAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ImportToRepositoryAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ImportToRepositoryAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ImportToRepositoryAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/IntegrateChangeSetAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/IntegrateChangeSetAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/IntegrateChangeSetAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/IntegrateChangeSetAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/LockAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/LockAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/LockAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/LockAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/MarkLocallyDeletedTreeConflictResolvedAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/MarkLocallyDeletedTreeConflictResolvedAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/MarkLocallyDeletedTreeConflictResolvedAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/MarkLocallyDeletedTreeConflictResolvedAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/MarkResolvedAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/MarkResolvedAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/MarkResolvedAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/MarkResolvedAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/MarkTreeConflictResolvedAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/MarkTreeConflictResolvedAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/MarkTreeConflictResolvedAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/MarkTreeConflictResolvedAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/RecordOnlyMergerFactory.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/RecordOnlyMergerFactory.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/RecordOnlyMergerFactory.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/RecordOnlyMergerFactory.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/RelocateAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/RelocateAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/RelocateAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/RelocateAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/RemoveFromIgnoreListAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/RemoveFromIgnoreListAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/RemoveFromIgnoreListAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/RemoveFromIgnoreListAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ResolveAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ResolveAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ResolveAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ResolveAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SelectBranchPopup.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/SelectBranchPopup.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SelectBranchPopup.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/SelectBranchPopup.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SetPropertyAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/SetPropertyAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SetPropertyAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/SetPropertyAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShareProjectAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ShareProjectAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShareProjectAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ShareProjectAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShareWholeProject.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ShareWholeProject.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShareWholeProject.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ShareWholeProject.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowAllSubmittedFilesAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ShowAllSubmittedFilesAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowAllSubmittedFilesAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ShowAllSubmittedFilesAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowPropertiesAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ShowPropertiesAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowPropertiesAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ShowPropertiesAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowPropertiesDiffAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ShowPropertiesDiffAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowPropertiesDiffAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ShowPropertiesDiffAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowPropertiesDiffWithLocalAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ShowPropertiesDiffWithLocalAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowPropertiesDiffWithLocalAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ShowPropertiesDiffWithLocalAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowPropertiesDiffWithLocalAlienAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ShowPropertiesDiffWithLocalAlienAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowPropertiesDiffWithLocalAlienAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ShowPropertiesDiffWithLocalAlienAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowSvnMapAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ShowSvnMapAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/ShowSvnMapAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/ShowSvnMapAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SubversionGroup.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/SubversionGroup.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SubversionGroup.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/SubversionGroup.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SvnExcludingIgnoredOperation.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/SvnExcludingIgnoredOperation.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SvnExcludingIgnoredOperation.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/SvnExcludingIgnoredOperation.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SvnMergeProvider.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/SvnMergeProvider.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SvnMergeProvider.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/SvnMergeProvider.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SvnQuickListContentProvider.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/SvnQuickListContentProvider.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/SvnQuickListContentProvider.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/SvnQuickListContentProvider.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/UnlockAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/UnlockAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/actions/UnlockAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/actions/UnlockAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/annotate/SvnAnnotationProvider.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/annotate/SvnAnnotationProvider.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/annotate/SvnAnnotationProvider.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/annotate/SvnAnnotationProvider.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/annotate/SvnFileAnnotation.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/annotate/SvnFileAnnotation.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/annotate/SvnFileAnnotation.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/annotate/SvnFileAnnotation.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/auth/ProviderType.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/auth/ProviderType.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/auth/ProviderType.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/auth/ProviderType.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/auth/SvnAuthenticationInteraction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/auth/SvnAuthenticationInteraction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/auth/SvnAuthenticationInteraction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/auth/SvnAuthenticationInteraction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/auth/SvnAuthenticationListener.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/auth/SvnAuthenticationListener.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/auth/SvnAuthenticationListener.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/auth/SvnAuthenticationListener.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/BranchesLoader.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/branchConfig/BranchesLoader.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/BranchesLoader.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/branchConfig/BranchesLoader.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/DefaultConfigLoader.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/branchConfig/DefaultConfigLoader.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/DefaultConfigLoader.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/branchConfig/DefaultConfigLoader.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/InfoReliability.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/branchConfig/InfoReliability.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/InfoReliability.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/branchConfig/InfoReliability.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/InfoStorage.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/branchConfig/InfoStorage.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/InfoStorage.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/branchConfig/InfoStorage.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/NewRootBunch.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/branchConfig/NewRootBunch.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/NewRootBunch.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/branchConfig/NewRootBunch.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigManager.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigManager.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigManager.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigManager.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationNew.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationNew.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationNew.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/branchConfig/SvnBranchConfigurationNew.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/SvnCheckinEnvironment.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/checkin/SvnCheckinEnvironment.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/checkin/SvnCheckinEnvironment.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/checkin/SvnCheckinEnvironment.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/checkout/CheckoutEventHandler.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/checkout/CheckoutEventHandler.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/checkout/CheckoutEventHandler.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/checkout/CheckoutEventHandler.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/checkout/SvnCheckoutProvider.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/checkout/SvnCheckoutProvider.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/checkout/SvnCheckoutProvider.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/checkout/SvnCheckoutProvider.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/AmbiguousPatternsFinder.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/AmbiguousPatternsFinder.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/config/AmbiguousPatternsFinder.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/AmbiguousPatternsFinder.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/CompositeRunnable.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/CompositeRunnable.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/config/CompositeRunnable.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/CompositeRunnable.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/ConfigureProxiesListener.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/ConfigureProxiesListener.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/config/ConfigureProxiesListener.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/ConfigureProxiesListener.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/ConfigureProxiesOptionsPanel.form b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/ConfigureProxiesOptionsPanel.form similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/config/ConfigureProxiesOptionsPanel.form rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/ConfigureProxiesOptionsPanel.form diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/ConfigureProxiesOptionsPanel.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/ConfigureProxiesOptionsPanel.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/config/ConfigureProxiesOptionsPanel.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/ConfigureProxiesOptionsPanel.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/DefaultProxyGroup.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/DefaultProxyGroup.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/config/DefaultProxyGroup.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/DefaultProxyGroup.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/DefaultRepositoryUrlFilter.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/DefaultRepositoryUrlFilter.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/config/DefaultRepositoryUrlFilter.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/DefaultRepositoryUrlFilter.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/GroupConfigurable.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/GroupConfigurable.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/config/GroupConfigurable.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/GroupConfigurable.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/GroupsValidator.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/GroupsValidator.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/config/GroupsValidator.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/GroupsValidator.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/PatternsListener.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/PatternsListener.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/config/PatternsListener.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/PatternsListener.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/ProxyGroup.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/ProxyGroup.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/config/ProxyGroup.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/ProxyGroup.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/RepositoryUrlFilter.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/RepositoryUrlFilter.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/config/RepositoryUrlFilter.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/RepositoryUrlFilter.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/RepositoryUrlsListener.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/RepositoryUrlsListener.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/config/RepositoryUrlsListener.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/RepositoryUrlsListener.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/SvnConfigureProxiesComponent.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/SvnConfigureProxiesComponent.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/config/SvnConfigureProxiesComponent.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/SvnConfigureProxiesComponent.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/SvnConfigureProxiesDialog.form b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/SvnConfigureProxiesDialog.form similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/config/SvnConfigureProxiesDialog.form rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/SvnConfigureProxiesDialog.form diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/SvnConfigureProxiesDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/SvnConfigureProxiesDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/config/SvnConfigureProxiesDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/SvnConfigureProxiesDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/SvnServerFileKeys.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/SvnServerFileKeys.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/config/SvnServerFileKeys.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/SvnServerFileKeys.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/TestConnectionPerformer.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/TestConnectionPerformer.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/config/TestConnectionPerformer.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/TestConnectionPerformer.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/config/ValidationListener.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/ValidationListener.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/config/ValidationListener.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/config/ValidationListener.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/AddRepositoryLocationDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/AddRepositoryLocationDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/AddRepositoryLocationDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/AddRepositoryLocationDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BasePageEngine.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/BasePageEngine.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BasePageEngine.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/BasePageEngine.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.form b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.form similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.form rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.form diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/BranchConfigurationDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchMerger.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/BranchMerger.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/BranchMerger.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/BranchMerger.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/ByKeySelectedSearcher.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/ByKeySelectedSearcher.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/ByKeySelectedSearcher.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/ByKeySelectedSearcher.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/ChangeFormatDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/ChangeFormatDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/ChangeFormatDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/ChangeFormatDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CheckoutDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/CheckoutDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CheckoutDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/CheckoutDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CopiesPanel.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/CopiesPanel.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CopiesPanel.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/CopiesPanel.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CopyDialog.form b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/CopyDialog.form similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CopyDialog.form rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/CopyDialog.form diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CopyDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/CopyDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/CopyDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/CopyDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/FactsCalculator.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/FactsCalculator.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/FactsCalculator.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/FactsCalculator.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/FileByKeySelectedSearcher.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/FileByKeySelectedSearcher.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/FileByKeySelectedSearcher.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/FileByKeySelectedSearcher.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/FolderByKeySelectedSearcher.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/FolderByKeySelectedSearcher.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/FolderByKeySelectedSearcher.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/FolderByKeySelectedSearcher.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/ImportDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/ImportDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/ImportDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/ImportDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/IntersectingLocalChangesPanel.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/IntersectingLocalChangesPanel.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/IntersectingLocalChangesPanel.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/IntersectingLocalChangesPanel.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/IterationResultHolder.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/IterationResultHolder.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/IterationResultHolder.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/IterationResultHolder.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/LockDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/LockDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/LockDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/LockDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/MergeFromAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/MergeFromAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/MergeFromAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/MergeFromAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/PageEngine.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/PageEngine.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/PageEngine.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/PageEngine.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/PagedListWithActions.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/PagedListWithActions.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/PagedListWithActions.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/PagedListWithActions.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/PropertiesComponent.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/PropertiesComponent.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/PropertiesComponent.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/PropertiesComponent.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/QuickMerge.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/QuickMerge.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/QuickMerge.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/QuickMerge.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RelocateDialog.form b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/RelocateDialog.form similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RelocateDialog.form rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/RelocateDialog.form diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RelocateDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/RelocateDialog.java similarity index 99% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RelocateDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/RelocateDialog.java index 89354abc925e..a7e3a565091b 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RelocateDialog.java +++ b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/RelocateDialog.java @@ -50,4 +50,4 @@ public class RelocateDialog extends DialogWrapper { public String getAfterURL() { return myToURLTextField.getText(); } -} \ No newline at end of file +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserComponent.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserComponent.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserComponent.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserComponent.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/RepositoryBrowserDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeModel.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeModel.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeModel.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeModel.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeNode.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeNode.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeNode.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeNode.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeRootNode.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeRootNode.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeRootNode.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/RepositoryTreeRootNode.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RunOrContinuation.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/RunOrContinuation.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/RunOrContinuation.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/RunOrContinuation.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SSHCredentialsDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SSHCredentialsDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SSHCredentialsDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SSHCredentialsDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SSLCredentialsDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SSLCredentialsDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SSLCredentialsDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SSLCredentialsDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SelectFilesDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SelectFilesDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SelectFilesDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SelectFilesDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SelectLocationDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SelectLocationDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SelectLocationDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SelectLocationDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/ServerSSHDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/ServerSSHDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/ServerSSHDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/ServerSSHDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/ServerSSLDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/ServerSSLDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/ServerSSLDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/ServerSSLDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SetKeywordsDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SetKeywordsDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SetKeywordsDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SetKeywordsDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SetPropertyDialog.form b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SetPropertyDialog.form similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SetPropertyDialog.form rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SetPropertyDialog.form diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SetPropertyDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SetPropertyDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SetPropertyDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SetPropertyDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/ShareDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/ShareDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/ShareDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/ShareDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SimpleCredentialsDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SimpleCredentialsDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SimpleCredentialsDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SimpleCredentialsDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnAuthenticationProvider.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SvnAuthenticationProvider.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnAuthenticationProvider.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SvnAuthenticationProvider.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnBranchPointsCalculator.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SvnBranchPointsCalculator.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnBranchPointsCalculator.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SvnBranchPointsCalculator.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnChangeListsPageEngine.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SvnChangeListsPageEngine.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnChangeListsPageEngine.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SvnChangeListsPageEngine.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnFormatWorker.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SvnFormatWorker.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnFormatWorker.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SvnFormatWorker.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnInteractiveAuthenticationProvider.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SvnInteractiveAuthenticationProvider.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnInteractiveAuthenticationProvider.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SvnInteractiveAuthenticationProvider.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnMapDialog.form b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SvnMapDialog.form similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnMapDialog.form rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SvnMapDialog.form diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnMapDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SvnMapDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnMapDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SvnMapDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnRepositoryTreeCellRenderer.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SvnRepositoryTreeCellRenderer.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/SvnRepositoryTreeCellRenderer.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/SvnRepositoryTreeCellRenderer.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/ToBeMergedDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/ToBeMergedDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/ToBeMergedDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/ToBeMergedDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/UpgradeFormatDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/UpgradeFormatDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/UpgradeFormatDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/UpgradeFormatDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/UserNameCredentialsDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/UserNameCredentialsDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/UserNameCredentialsDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/UserNameCredentialsDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/WCInfo.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/WCInfo.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/WCInfo.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/WCInfo.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/WCInfoWithBranches.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/WCInfoWithBranches.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/WCInfoWithBranches.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/WCInfoWithBranches.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/WCPaths.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/WCPaths.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/WCPaths.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/WCPaths.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/AbstractOpeningExpander.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/AbstractOpeningExpander.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/AbstractOpeningExpander.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/AbstractOpeningExpander.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/CheckoutOptionsDialog.form b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/CheckoutOptionsDialog.form similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/CheckoutOptionsDialog.form rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/CheckoutOptionsDialog.form diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/CheckoutOptionsDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/CheckoutOptionsDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/CheckoutOptionsDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/CheckoutOptionsDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/CopyOptionsDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/CopyOptionsDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/CopyOptionsDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/CopyOptionsDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/DeleteOptionsDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/DeleteOptionsDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/DeleteOptionsDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/DeleteOptionsDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/DiffOptionsDialog.form b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/DiffOptionsDialog.form similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/DiffOptionsDialog.form rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/DiffOptionsDialog.form diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/DiffOptionsDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/DiffOptionsDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/DiffOptionsDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/DiffOptionsDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/ExportOptionsDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/ExportOptionsDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/ExportOptionsDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/ExportOptionsDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/ImportOptionsDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/ImportOptionsDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/ImportOptionsDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/ImportOptionsDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/MessageBoxCellRenderer.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/MessageBoxCellRenderer.java similarity index 99% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/MessageBoxCellRenderer.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/MessageBoxCellRenderer.java index 4eb47200a4cd..983f3db40f01 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/MessageBoxCellRenderer.java +++ b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/MessageBoxCellRenderer.java @@ -39,4 +39,4 @@ class MessageBoxCellRenderer extends DefaultListCellRenderer { } return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); } -} \ No newline at end of file +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/MkdirOptionsDialog.form b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/MkdirOptionsDialog.form similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/MkdirOptionsDialog.form rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/MkdirOptionsDialog.form diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/MkdirOptionsDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/MkdirOptionsDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/MkdirOptionsDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/MkdirOptionsDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/OpeningExpander.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/OpeningExpander.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/OpeningExpander.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/OpeningExpander.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/SvnTrunkCheckoutStrategy.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/SvnTrunkCheckoutStrategy.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/SvnTrunkCheckoutStrategy.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/SvnTrunkCheckoutStrategy.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/UrlOpeningExpander.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/UrlOpeningExpander.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browser/UrlOpeningExpander.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browser/UrlOpeningExpander.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/CacheLoader.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browserCache/CacheLoader.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/CacheLoader.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browserCache/CacheLoader.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/Expander.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browserCache/Expander.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/Expander.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browserCache/Expander.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/KeepingExpandedExpander.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browserCache/KeepingExpandedExpander.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/KeepingExpandedExpander.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browserCache/KeepingExpandedExpander.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/KeepingSelectionExpander.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browserCache/KeepingSelectionExpander.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/KeepingSelectionExpander.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browserCache/KeepingSelectionExpander.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/Loader.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browserCache/Loader.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/Loader.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browserCache/Loader.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/NodeLoadState.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browserCache/NodeLoadState.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/NodeLoadState.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browserCache/NodeLoadState.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/RepositoryLoader.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browserCache/RepositoryLoader.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/RepositoryLoader.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browserCache/RepositoryLoader.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/SvnRepositoryCache.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browserCache/SvnRepositoryCache.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/SvnRepositoryCache.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browserCache/SvnRepositoryCache.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/SyntheticWorker.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browserCache/SyntheticWorker.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/dialogs/browserCache/SyntheticWorker.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/dialogs/browserCache/SyntheticWorker.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/BunchFactory.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/BunchFactory.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/BunchFactory.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/BunchFactory.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/BunchProvider.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/BunchProvider.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/BunchProvider.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/BunchProvider.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/CachedProvider.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/CachedProvider.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/CachedProvider.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/CachedProvider.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/CopyData.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/CopyData.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/CopyData.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/CopyData.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/FirstInBranch.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/FirstInBranch.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/FirstInBranch.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/FirstInBranch.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/FirstInBranchAbstractBase.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/FirstInBranchAbstractBase.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/FirstInBranchAbstractBase.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/FirstInBranchAbstractBase.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/FirstInBranchAccurate.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/FirstInBranchAccurate.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/FirstInBranchAccurate.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/FirstInBranchAccurate.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/Fragment.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/Fragment.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/Fragment.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/Fragment.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/InternallyCachedChangesBunch.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/InternallyCachedChangesBunch.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/InternallyCachedChangesBunch.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/InternallyCachedChangesBunch.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/LatestExistentSearcher.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/LatestExistentSearcher.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/LatestExistentSearcher.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/LatestExistentSearcher.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/LiveProvider.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/LiveProvider.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/LiveProvider.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/LiveProvider.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/LoadedRevisionsCache.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/LoadedRevisionsCache.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/LoadedRevisionsCache.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/LoadedRevisionsCache.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/LocationDetector.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/LocationDetector.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/LocationDetector.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/LocationDetector.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/MergeInfoUpdatesListener.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/MergeInfoUpdatesListener.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/MergeInfoUpdatesListener.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/MergeInfoUpdatesListener.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/MergeSourceDetailsAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/MergeSourceDetailsAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/MergeSourceDetailsAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/MergeSourceDetailsAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/Origin.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/Origin.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/Origin.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/Origin.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/RootsAndBranches.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/RootsAndBranches.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/RootsAndBranches.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/RootsAndBranches.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnChangeList.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnChangeList.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnChangeList.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnChangeList.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnCommittedChangesProvider.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnCommittedChangesProvider.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnCommittedChangesProvider.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnCommittedChangesProvider.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnCommittedChangesTableModel.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnCommittedChangesTableModel.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnCommittedChangesTableModel.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnCommittedChangesTableModel.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnFileRevision.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnFileRevision.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnFileRevision.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnFileRevision.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnHistoryProvider.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnLoadingRepositoryLocation.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnLoadingRepositoryLocation.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnLoadingRepositoryLocation.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnLoadingRepositoryLocation.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnLogLoader.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnLogLoader.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnLogLoader.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnLogLoader.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnLogUtil.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnLogUtil.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnLogUtil.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnLogUtil.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnMergeInfoRootPanelManual.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnMergeInfoRootPanelManual.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnMergeInfoRootPanelManual.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnMergeInfoRootPanelManual.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnMergeSourceDetails.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnMergeSourceDetails.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnMergeSourceDetails.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnMergeSourceDetails.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnMergeSourceTracker.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnMergeSourceTracker.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnMergeSourceTracker.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnMergeSourceTracker.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryBinaryContentRevision.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnRepositoryBinaryContentRevision.java similarity index 99% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryBinaryContentRevision.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnRepositoryBinaryContentRevision.java index 850a42179485..082f1831f532 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryBinaryContentRevision.java +++ b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnRepositoryBinaryContentRevision.java @@ -39,4 +39,4 @@ public class SvnRepositoryBinaryContentRevision extends SvnRepositoryContentRevi } return myBinaryContent; } -} \ No newline at end of file +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryContentRevision.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnRepositoryContentRevision.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryContentRevision.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnRepositoryContentRevision.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryLocation.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnRepositoryLocation.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryLocation.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnRepositoryLocation.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryLocationGroup.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnRepositoryLocationGroup.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRepositoryLocationGroup.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnRepositoryLocationGroup.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRevisionsNavigationMediator.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnRevisionsNavigationMediator.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRevisionsNavigationMediator.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnRevisionsNavigationMediator.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnVersionFilterComponent.form b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnVersionFilterComponent.form similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnVersionFilterComponent.form rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnVersionFilterComponent.form diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnVersionFilterComponent.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnVersionFilterComponent.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnVersionFilterComponent.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SvnVersionFilterComponent.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SwitchRevisionsProviderException.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SwitchRevisionsProviderException.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/SwitchRevisionsProviderException.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/SwitchRevisionsProviderException.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/TreeStructureNode.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/TreeStructureNode.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/TreeStructureNode.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/TreeStructureNode.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/WcInfoLoader.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/WcInfoLoader.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/history/WcInfoLoader.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/history/WcInfoLoader.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/ignore/FileGroupInfo.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ignore/FileGroupInfo.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/ignore/FileGroupInfo.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ignore/FileGroupInfo.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/ignore/FileIterationListener.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ignore/FileIterationListener.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/ignore/FileIterationListener.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ignore/FileIterationListener.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/ignore/IgnoreGroupHelperAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ignore/IgnoreGroupHelperAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/ignore/IgnoreGroupHelperAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ignore/IgnoreGroupHelperAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/ignore/IgnoreInfoGetter.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ignore/IgnoreInfoGetter.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/ignore/IgnoreInfoGetter.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ignore/IgnoreInfoGetter.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/ignore/SvnPropertyService.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ignore/SvnPropertyService.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/ignore/SvnPropertyService.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/ignore/SvnPropertyService.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/AlienDirtyScope.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/AlienDirtyScope.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/AlienDirtyScope.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/AlienDirtyScope.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/ByNumberChangeListComparator.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/ByNumberChangeListComparator.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/ByNumberChangeListComparator.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/ByNumberChangeListComparator.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/GatheringChangelistBuilder.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/GatheringChangelistBuilder.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/GatheringChangelistBuilder.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/GatheringChangelistBuilder.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/GroupMerger.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/GroupMerger.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/GroupMerger.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/GroupMerger.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/GroupSplitter.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/GroupSplitter.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/GroupSplitter.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/GroupSplitter.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/IMerger.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/IMerger.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/IMerger.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/IMerger.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/IntegrateEventHandler.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/IntegrateEventHandler.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/IntegrateEventHandler.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/IntegrateEventHandler.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/IntegratedSelectedOptionsDialog.form b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/IntegratedSelectedOptionsDialog.form similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/IntegratedSelectedOptionsDialog.form rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/IntegratedSelectedOptionsDialog.form diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/IntegratedSelectedOptionsDialog.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/IntegratedSelectedOptionsDialog.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/IntegratedSelectedOptionsDialog.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/IntegratedSelectedOptionsDialog.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/Merger.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/Merger.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/Merger.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/Merger.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/MergerFactory.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/MergerFactory.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/MergerFactory.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/MergerFactory.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/PointMerger.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/PointMerger.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/PointMerger.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/PointMerger.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/ResolveWorker.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/ResolveWorker.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/ResolveWorker.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/ResolveWorker.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SelectedChangeListsChecker.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/SelectedChangeListsChecker.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SelectedChangeListsChecker.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/SelectedChangeListsChecker.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SelectedChangeSetChecker.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/SelectedChangeSetChecker.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SelectedChangeSetChecker.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/SelectedChangeSetChecker.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SelectedCommittedStuffChecker.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/SelectedCommittedStuffChecker.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SelectedCommittedStuffChecker.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/SelectedCommittedStuffChecker.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/Splitter.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/Splitter.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/Splitter.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/Splitter.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SvnBranchItem.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/SvnBranchItem.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SvnBranchItem.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/SvnBranchItem.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SvnIntegrateChangesActionPerformer.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/SvnIntegrateChangesActionPerformer.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SvnIntegrateChangesActionPerformer.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/SvnIntegrateChangesActionPerformer.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SvnIntegrateChangesTask.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/SvnIntegrateChangesTask.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/SvnIntegrateChangesTask.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/SvnIntegrateChangesTask.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/WorkingCopyInfo.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/WorkingCopyInfo.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/integrate/WorkingCopyInfo.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/integrate/WorkingCopyInfo.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/BranchInfo.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/BranchInfo.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/BranchInfo.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/BranchInfo.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/MergeChecker.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/MergeChecker.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/MergeChecker.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/MergeChecker.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/MergeInfoHolder.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/MergeInfoHolder.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/MergeInfoHolder.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/MergeInfoHolder.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/MergeInfoWorker.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/MergeInfoWorker.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/MergeInfoWorker.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/MergeInfoWorker.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/MergeinfoCached.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/MergeinfoCached.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/MergeinfoCached.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/MergeinfoCached.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/OneRecursiveShotMergeInfoWorker.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/OneRecursiveShotMergeInfoWorker.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/OneRecursiveShotMergeInfoWorker.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/OneRecursiveShotMergeInfoWorker.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/OneShotMergeInfoHelper.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/OneShotMergeInfoHelper.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/OneShotMergeInfoHelper.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/OneShotMergeInfoHelper.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/SelectBranchAction.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/SelectBranchAction.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/SelectBranchAction.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/SelectBranchAction.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/SelectRootListener.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/SelectRootListener.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/SelectRootListener.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/SelectRootListener.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/SvnMergeInfoCache.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/SvnMergeInfoCache.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/mergeinfo/SvnMergeInfoCache.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/mergeinfo/SvnMergeInfoCache.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/revision/SvnSelectRevisionPanel.form b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/revision/SvnSelectRevisionPanel.form similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/revision/SvnSelectRevisionPanel.form rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/revision/SvnSelectRevisionPanel.form diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/revision/SvnSelectRevisionPanel.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/revision/SvnSelectRevisionPanel.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/revision/SvnSelectRevisionPanel.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/revision/SvnSelectRevisionPanel.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/rollback/SvnRollbackEnvironment.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/rollback/SvnRollbackEnvironment.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/rollback/SvnRollbackEnvironment.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/rollback/SvnRollbackEnvironment.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/status/DiffContentRevision.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/status/DiffContentRevision.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/status/DiffContentRevision.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/status/DiffContentRevision.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/status/SvnDiffEditor.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/status/SvnDiffEditor.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/status/SvnDiffEditor.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/status/SvnDiffEditor.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/AbstractSvnUpdateIntegrateEnvironment.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/AbstractSvnUpdateIntegrateEnvironment.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/AbstractSvnUpdateIntegrateEnvironment.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/AbstractSvnUpdateIntegrateEnvironment.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/AbstractSvnUpdatePanel.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/AbstractSvnUpdatePanel.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/AbstractSvnUpdatePanel.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/AbstractSvnUpdatePanel.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/AbstractUpdateIntegrateCrawler.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/AbstractUpdateIntegrateCrawler.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/AbstractUpdateIntegrateCrawler.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/AbstractUpdateIntegrateCrawler.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/MergeRootInfo.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/MergeRootInfo.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/MergeRootInfo.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/MergeRootInfo.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/MultipleRootEditorWithSplitter.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/MultipleRootEditorWithSplitter.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/MultipleRootEditorWithSplitter.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/MultipleRootEditorWithSplitter.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnIntegrateEnvironment.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnIntegrateEnvironment.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnIntegrateEnvironment.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnIntegrateEnvironment.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnIntegratePanel.form b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnIntegratePanel.form similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnIntegratePanel.form rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnIntegratePanel.form diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnIntegratePanel.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnIntegratePanel.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnIntegratePanel.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnIntegratePanel.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnIntegrateRootOptionsPanel.form b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnIntegrateRootOptionsPanel.form similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnIntegrateRootOptionsPanel.form rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnIntegrateRootOptionsPanel.form diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnIntegrateRootOptionsPanel.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnIntegrateRootOptionsPanel.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnIntegrateRootOptionsPanel.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnIntegrateRootOptionsPanel.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnPanel.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnPanel.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnPanel.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnPanel.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnRevisionPanel.form b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnRevisionPanel.form similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnRevisionPanel.form rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnRevisionPanel.form diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnRevisionPanel.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnRevisionPanel.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnRevisionPanel.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnRevisionPanel.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnSelectRevisionUtil.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnSelectRevisionUtil.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnSelectRevisionUtil.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnSelectRevisionUtil.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnStatusWorker.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnStatusWorker.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnStatusWorker.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnStatusWorker.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateConfigurable.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnUpdateConfigurable.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateConfigurable.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnUpdateConfigurable.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateContext.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnUpdateContext.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateContext.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnUpdateContext.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateEnvironment.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnUpdateEnvironment.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateEnvironment.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnUpdateEnvironment.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateGroups.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnUpdateGroups.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateGroups.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnUpdateGroups.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdatePanel.form b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnUpdatePanel.form similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdatePanel.form rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnUpdatePanel.form diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdatePanel.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnUpdatePanel.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdatePanel.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnUpdatePanel.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateRootOptionsPanel.form b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnUpdateRootOptionsPanel.form similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateRootOptionsPanel.form rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnUpdateRootOptionsPanel.form diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateRootOptionsPanel.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnUpdateRootOptionsPanel.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/SvnUpdateRootOptionsPanel.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/SvnUpdateRootOptionsPanel.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/UpdateEventHandler.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/UpdateEventHandler.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/UpdateEventHandler.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/UpdateEventHandler.java diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/update/UpdateRootInfo.java b/plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/UpdateRootInfo.java similarity index 100% rename from plugins/svn4idea/src/org/jetbrains/idea/svn/update/UpdateRootInfo.java rename to plugins/svn4ideaOld/src/org/jetbrains/idea/svn/update/UpdateRootInfo.java diff --git a/plugins/svn4idea/svn4idea-standalone.ipr b/plugins/svn4ideaOld/svn4idea-standalone.ipr similarity index 100% rename from plugins/svn4idea/svn4idea-standalone.ipr rename to plugins/svn4ideaOld/svn4idea-standalone.ipr diff --git a/plugins/svn4ideaOld/svn4idea-testsOld.iml b/plugins/svn4ideaOld/svn4idea-testsOld.iml new file mode 100644 index 000000000000..0408c596ec62 --- /dev/null +++ b/plugins/svn4ideaOld/svn4idea-testsOld.iml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/svn4ideaOld/svn4ideaOld.iml b/plugins/svn4ideaOld/svn4ideaOld.iml new file mode 100644 index 000000000000..89ea2748a34e --- /dev/null +++ b/plugins/svn4ideaOld/svn4ideaOld.iml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/svn4idea/svn4idea_plugin.iml b/plugins/svn4ideaOld/svn4idea_plugin.iml similarity index 100% rename from plugins/svn4idea/svn4idea_plugin.iml rename to plugins/svn4ideaOld/svn4idea_plugin.iml diff --git a/plugins/svn4ideaOld/testData/svn/bin/libapr-1.dll b/plugins/svn4ideaOld/testData/svn/bin/libapr-1.dll new file mode 100644 index 000000000000..7dc02e5fa815 Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/libapr-1.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/libapriconv-1.dll b/plugins/svn4ideaOld/testData/svn/bin/libapriconv-1.dll new file mode 100644 index 000000000000..3fb1d4f93c54 Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/libapriconv-1.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/libaprutil-1.dll b/plugins/svn4ideaOld/testData/svn/bin/libaprutil-1.dll new file mode 100644 index 000000000000..937b10ae3b80 Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/libaprutil-1.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/libdb41.dll b/plugins/svn4ideaOld/testData/svn/bin/libdb41.dll new file mode 100644 index 000000000000..0c2bcb36c3aa Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/libdb41.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/libeay32.dll b/plugins/svn4ideaOld/testData/svn/bin/libeay32.dll new file mode 100644 index 000000000000..4202253daab2 Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/libeay32.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/libhttpd.dll b/plugins/svn4ideaOld/testData/svn/bin/libhttpd.dll new file mode 100644 index 000000000000..c296ea02c680 Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/libhttpd.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/libsasl.dll b/plugins/svn4ideaOld/testData/svn/bin/libsasl.dll new file mode 100644 index 000000000000..088c0e02b20b Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/libsasl.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/libsvn_client-1.dll b/plugins/svn4ideaOld/testData/svn/bin/libsvn_client-1.dll new file mode 100644 index 000000000000..d24ad27a9484 Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/libsvn_client-1.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/libsvn_delta-1.dll b/plugins/svn4ideaOld/testData/svn/bin/libsvn_delta-1.dll new file mode 100644 index 000000000000..8d481d5b8601 Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/libsvn_delta-1.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/libsvn_diff-1.dll b/plugins/svn4ideaOld/testData/svn/bin/libsvn_diff-1.dll new file mode 100644 index 000000000000..85cec96c1aa2 Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/libsvn_diff-1.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/libsvn_fs-1.dll b/plugins/svn4ideaOld/testData/svn/bin/libsvn_fs-1.dll new file mode 100644 index 000000000000..e518b0c56a4f Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/libsvn_fs-1.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/libsvn_ra-1.dll b/plugins/svn4ideaOld/testData/svn/bin/libsvn_ra-1.dll new file mode 100644 index 000000000000..b5958a48dfd1 Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/libsvn_ra-1.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/libsvn_repos-1.dll b/plugins/svn4ideaOld/testData/svn/bin/libsvn_repos-1.dll new file mode 100644 index 000000000000..56ded4b21d44 Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/libsvn_repos-1.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/libsvn_subr-1.dll b/plugins/svn4ideaOld/testData/svn/bin/libsvn_subr-1.dll new file mode 100644 index 000000000000..bd2a932c64dd Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/libsvn_subr-1.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/libsvn_wc-1.dll b/plugins/svn4ideaOld/testData/svn/bin/libsvn_wc-1.dll new file mode 100644 index 000000000000..e671d5558462 Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/libsvn_wc-1.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/libsvnjavahl-1.dll b/plugins/svn4ideaOld/testData/svn/bin/libsvnjavahl-1.dll new file mode 100644 index 000000000000..d6b69dcd116b Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/libsvnjavahl-1.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/saslANONYMOUS.dll b/plugins/svn4ideaOld/testData/svn/bin/saslANONYMOUS.dll new file mode 100644 index 000000000000..a4e23df9f53a Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/saslANONYMOUS.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/saslCRAMMD5.dll b/plugins/svn4ideaOld/testData/svn/bin/saslCRAMMD5.dll new file mode 100644 index 000000000000..544f523fd437 Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/saslCRAMMD5.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/saslDIGESTMD5.dll b/plugins/svn4ideaOld/testData/svn/bin/saslDIGESTMD5.dll new file mode 100644 index 000000000000..15efd9448920 Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/saslDIGESTMD5.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/saslNTLM.dll b/plugins/svn4ideaOld/testData/svn/bin/saslNTLM.dll new file mode 100644 index 000000000000..f5b50200a427 Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/saslNTLM.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/saslSASLDB.dll b/plugins/svn4ideaOld/testData/svn/bin/saslSASLDB.dll new file mode 100644 index 000000000000..54ea349e6ff7 Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/saslSASLDB.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/ssleay32.dll b/plugins/svn4ideaOld/testData/svn/bin/ssleay32.dll new file mode 100644 index 000000000000..93ae5876c5fd Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/ssleay32.dll differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/svn b/plugins/svn4ideaOld/testData/svn/bin/svn new file mode 100644 index 000000000000..1c3cce2d7080 Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/svn differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/svn.exe b/plugins/svn4ideaOld/testData/svn/bin/svn.exe new file mode 100644 index 000000000000..47bd5c68d0a7 Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/svn.exe differ diff --git a/plugins/svn4ideaOld/testData/svn/bin/svnadmin.exe b/plugins/svn4ideaOld/testData/svn/bin/svnadmin.exe new file mode 100644 index 000000000000..582cf9eafc48 Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/bin/svnadmin.exe differ diff --git a/plugins/svn4ideaOld/testData/svn/newrepo.zip b/plugins/svn4ideaOld/testData/svn/newrepo.zip new file mode 100644 index 000000000000..f4ef47399dbf Binary files /dev/null and b/plugins/svn4ideaOld/testData/svn/newrepo.zip differ diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/IgnoredFilesTest.java b/plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/IgnoredFilesTest.java similarity index 100% rename from plugins/svn4idea/testSource/org/jetbrains/idea/svn/IgnoredFilesTest.java rename to plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/IgnoredFilesTest.java diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnAddTest.java b/plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnAddTest.java similarity index 100% rename from plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnAddTest.java rename to plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnAddTest.java diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnAuthenticationTest.java b/plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnAuthenticationTest.java similarity index 100% rename from plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnAuthenticationTest.java rename to plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnAuthenticationTest.java diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnCachingRevisionsTest.java b/plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnCachingRevisionsTest.java similarity index 100% rename from plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnCachingRevisionsTest.java rename to plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnCachingRevisionsTest.java diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnChangesCorrectlyRefreshedTest.java b/plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnChangesCorrectlyRefreshedTest.java similarity index 100% rename from plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnChangesCorrectlyRefreshedTest.java rename to plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnChangesCorrectlyRefreshedTest.java diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnCommittedViewTest.java b/plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnCommittedViewTest.java similarity index 100% rename from plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnCommittedViewTest.java rename to plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnCommittedViewTest.java diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnConcurrentChangeListManagerTest.java b/plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnConcurrentChangeListManagerTest.java similarity index 100% rename from plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnConcurrentChangeListManagerTest.java rename to plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnConcurrentChangeListManagerTest.java diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnDeleteTest.java b/plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnDeleteTest.java similarity index 100% rename from plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnDeleteTest.java rename to plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnDeleteTest.java diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnGroupSplitterTest.java b/plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnGroupSplitterTest.java similarity index 100% rename from plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnGroupSplitterTest.java rename to plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnGroupSplitterTest.java diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnIgnoreTest.java b/plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnIgnoreTest.java similarity index 100% rename from plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnIgnoreTest.java rename to plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnIgnoreTest.java diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnLocalChangesAndRootsTest.java b/plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnLocalChangesAndRootsTest.java similarity index 100% rename from plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnLocalChangesAndRootsTest.java rename to plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnLocalChangesAndRootsTest.java diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnMergeInfoTest.java b/plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnMergeInfoTest.java similarity index 100% rename from plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnMergeInfoTest.java rename to plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnMergeInfoTest.java diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnNativeListsTest.java b/plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnNativeListsTest.java similarity index 100% rename from plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnNativeListsTest.java rename to plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnNativeListsTest.java diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnRenameTest.java b/plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnRenameTest.java similarity index 100% rename from plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnRenameTest.java rename to plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnRenameTest.java diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnRootAboveTest.java b/plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnRootAboveTest.java similarity index 100% rename from plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnRootAboveTest.java rename to plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnRootAboveTest.java diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnTestCase.java b/plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnTestCase.java similarity index 100% rename from plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnTestCase.java rename to plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnTestCase.java diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnTestDirtyScopeStateTest.java b/plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnTestDirtyScopeStateTest.java similarity index 100% rename from plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnTestDirtyScopeStateTest.java rename to plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/SvnTestDirtyScopeStateTest.java diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/VcsWaitForUpdateForTest.java b/plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/VcsWaitForUpdateForTest.java similarity index 100% rename from plugins/svn4idea/testSource/org/jetbrains/idea/svn/VcsWaitForUpdateForTest.java rename to plugins/svn4ideaOld/testSource/org/jetbrains/idea/svn/VcsWaitForUpdateForTest.java