diff --git a/platform/vcs-api/src/com/intellij/vcsUtil/LearningProxy.java b/platform/vcs-api/src/com/intellij/vcsUtil/LearningProxy.java index 6795aa114156..381c43e52b0e 100644 --- a/platform/vcs-api/src/com/intellij/vcsUtil/LearningProxy.java +++ b/platform/vcs-api/src/com/intellij/vcsUtil/LearningProxy.java @@ -28,7 +28,7 @@ import java.util.*; * Date: 10/19/12 * Time: 12:42 PM */ -public abstract class LearningProxy implements InvocationHandler { +public abstract class LearningProxy { private final static Map ourDefaultValues = new HashMap(); static { ourDefaultValues.put("byte", new Byte((byte) 0)); @@ -42,13 +42,25 @@ public abstract class LearningProxy implements InvocationHandler { ourDefaultValues.put("void", null); } private final Set myTrackedMethods; + private final InvocationHandler myLearn; public LearningProxy() { myTrackedMethods = new HashSet(); + myLearn = new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + myTrackedMethods.add(new MethodDescriptor(method)); + final Class returnType = method.getReturnType(); + if (returnType.isPrimitive()) { + return ourDefaultValues.get(returnType.getName()); + } + return null; + } + }; } - protected abstract void onBefore(); - protected abstract void onAfter(); + protected abstract void onBefore() throws E; + protected abstract void onAfter() throws E; public T create(Class clazz, final T t) { return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() { @@ -58,6 +70,7 @@ public abstract class LearningProxy implements InvocationHandler { if (myTrackedMethods.contains(current)) { try { onBefore(); + method.setAccessible(true); return method.invoke(t, args); } finally { onAfter(); @@ -69,17 +82,7 @@ public abstract class LearningProxy implements InvocationHandler { } public T learn(Class clazz) { - return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, this); - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - myTrackedMethods.add(new MethodDescriptor(method)); - final Class returnType = method.getReturnType(); - if (returnType.isPrimitive()) { - return ourDefaultValues.get(returnType.getName()); - } - return null; + return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, myLearn); } private static class MethodDescriptor { diff --git a/platform/vcs-api/src/com/intellij/vcsUtil/PseudoImplementProxy.java b/platform/vcs-api/src/com/intellij/vcsUtil/PseudoImplementProxy.java new file mode 100644 index 000000000000..4d9872589264 --- /dev/null +++ b/platform/vcs-api/src/com/intellij/vcsUtil/PseudoImplementProxy.java @@ -0,0 +1,56 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.vcsUtil; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 10/22/12 + * Time: 10:58 AM + */ +public class PseudoImplementProxy { + public static T create(final Class theInterface, final Impl implementation) { + checkMethodsExist(theInterface, implementation); + final Class implementationClass = implementation.getClass(); + return (T) Proxy.newProxyInstance(theInterface.getClassLoader(), new Class[]{theInterface}, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + final Method implementationClassMethod = + implementationClass.getMethod(method.getName(), method.getParameterTypes()); + implementationClassMethod.setAccessible(true); + return implementationClassMethod.invoke(implementation, args); + } + }); + } + + private static void checkMethodsExist(Class theInterface, Impl implementation) { + final Class implementationClass = implementation.getClass(); + final Method[] methods = theInterface.getDeclaredMethods(); + for (Method method : methods) { + try { + implementationClass.getMethod(method.getName(), method.getParameterTypes()); + } + catch (NoSuchMethodException e) { + throw new UnsupportedOperationException(e); + } + } + } +} diff --git a/plugins/svn4idea/lib/sqljetsrc.zip b/plugins/svn4idea/lib/sqljetsrc.zip new file mode 100644 index 000000000000..3d7a71ef7078 Binary files /dev/null and b/plugins/svn4idea/lib/sqljetsrc.zip differ diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/RootsToWorkingCopies.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/RootsToWorkingCopies.java index 9eaf64b48e2b..1717ea261c9c 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/RootsToWorkingCopies.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/RootsToWorkingCopies.java @@ -90,7 +90,7 @@ public class RootsToWorkingCopies implements VcsListener { @Nullable @CalledInBackground public WorkingCopy getMatchingCopy(final SVNURL url) { - assert ! ApplicationManager.getApplication().isDispatchThread(); + assert (! ApplicationManager.getApplication().isDispatchThread()) || ApplicationManager.getApplication().isUnitTestMode(); if (url == null) return null; final VirtualFile[] roots = ProjectLevelVcsManager.getInstance(myProject).getRootsUnderVcs(SvnVcs.getInstance(myProject)); @@ -111,7 +111,7 @@ public class RootsToWorkingCopies implements VcsListener { @CalledInBackground @Nullable public WorkingCopy getWcRoot(final VirtualFile root) { - assert ! ApplicationManager.getApplication().isDispatchThread(); + assert (! ApplicationManager.getApplication().isDispatchThread()) || ApplicationManager.getApplication().isUnitTestMode(); synchronized (myLock) { if (myUnversioned.contains(root)) return null; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAbstractWriteOperationLocks.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAbstractWriteOperationLocks.java new file mode 100644 index 000000000000..fea3d259982c --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnAbstractWriteOperationLocks.java @@ -0,0 +1,142 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.vcs.changes.FilePathsHelper; +import org.jetbrains.annotations.Nullable; +import org.tmatesoft.sqljet.core.SqlJetException; +import org.tmatesoft.sqljet.core.table.ISqlJetBusyHandler; +import org.tmatesoft.sqljet.core.table.ISqlJetTransaction; +import org.tmatesoft.sqljet.core.table.SqlJetDb; +import org.tmatesoft.svn.core.SVNException; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 10/19/12 + * Time: 12:09 PM + */ +public abstract class SvnAbstractWriteOperationLocks { + private final long myTimeout; + private final static Map myLockMap = new HashMap(); + private final static Object myLock = new Object(); + private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnAbstractWriteOperationLocks"); + private ISqlJetBusyHandler ourBusyHandler; + + protected SvnAbstractWriteOperationLocks(long timeout) { + myTimeout = timeout; + ourBusyHandler = new ISqlJetBusyHandler() { + @Override + public boolean call(int i) { + try { + Thread.sleep(myTimeout); + } + catch (InterruptedException e) { + // + } + return true; + } + }; + } + + // null if not 1.7+ copy + @Nullable + private Lock getLockObject(File file) throws SVNException { + final boolean directory = file.isDirectory(); + final WorkingCopy wcRoot = getCopy(file, directory); + if (! wcRoot.is17Copy()) return null; + Lock lock; + final String path = FilePathsHelper.convertPath(wcRoot.getFile().getPath()); + synchronized (myLock) { + lock = myLockMap.get(path); + if (lock == null) { + lock = new ReentrantLock(); + myLockMap.put(path, lock); + } + } + return lock; + } + + protected abstract WorkingCopy getCopy(File file, boolean directory) throws SVNException; + + public void lockWrite(final File file) throws SVNException { + final Lock lock = getLockObject(file); + if (lock != null) { + lock.lock(); + } + } + + public void unlockWrite(final File file) throws SVNException { + final Lock lock = getLockObject(file); + if (lock != null) { + lock.unlock(); + } + } + + // would wait until read is available + public void wrapRead(final File file, final Runnable runnable) throws SVNException { + final WorkingCopy copy = getCopy(file, file.isDirectory()); + if (! copy.is17Copy()) { + runnable.run(); + return; + } + + final File root = copy.getFile(); + SqlJetDb open = null; + final boolean run[] = new boolean[1]; + run[0] = false; + try { + open = SqlJetDb.open(SvnUtil.getWcDb(root), false); + open.setBusyHandler(ourBusyHandler); + try { + final SqlJetDb finalOpen = open; + open.runReadTransaction(new ISqlJetTransaction() { + @Override + public Object run(SqlJetDb db) throws SqlJetException { + run[0] = true; + runnable.run(); + return null; + } + }); + } finally { + open.rollback(); + } + } + catch (SqlJetException e) { + LOG.info(e); + if (! run[0]) { + runnable.run(); + } + } + finally { + if (open != null) { + try { + open.close(); + } + catch (SqlJetException e) { + LOG.info(e); + } + } + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnProxies.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnProxies.java new file mode 100644 index 000000000000..97b87efaa7f9 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnProxies.java @@ -0,0 +1,112 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn; + +import com.intellij.vcsUtil.LearningProxy; +import org.jetbrains.idea.svn.portable.SVNChangelistClientI; +import org.jetbrains.idea.svn.portable.SvnUpdateClientI; +import org.tmatesoft.svn.core.SVNException; + +import java.io.File; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 10/19/12 + * Time: 4:22 PM + */ +public class SvnProxies { + private final SvnAbstractWriteOperationLocks myLocks; + private final AtomicReference> myChangelist; + private final AtomicReference> myUpdate; + + public SvnProxies(SvnAbstractWriteOperationLocks locks) { + myLocks = locks; + myChangelist = new AtomicReference>(); + myUpdate = new AtomicReference>(); + } + + public LearningProxy getChangelistsProfessor(final File file) { + if (myChangelist.get() == null) { + final MyLearningProxy proxy = new MyLearningProxy(myLocks, file); + final SVNChangelistClientI learn = proxy.learn(SVNChangelistClientI.class); + try { + learn.addToChangelist(null, null, null, null); + learn.doAddToChangelist(null, null, null, null); + learn.doRemoveFromChangelist(null, null, null); + learn.removeFromChangelist(null, null, null); + } catch (SVNException e) { + //can not occur since methods are not really called + throw new RuntimeException(e); + } + + myChangelist.set(proxy); + } + return myChangelist.get(); + } + + public LearningProxy getUpdateProfessor(final File file) { + if (myUpdate.get() == null) { + final MyLearningProxy proxy = new MyLearningProxy(myLocks, file); + final SvnUpdateClientI learn = proxy.learn(SvnUpdateClientI.class); + try { + learn.doUpdate(null, null, false); + learn.doUpdate(null, null, false, false); + learn.doUpdate((File[]) null, null, null, false, false); + learn.doUpdate((File[]) null, null, null, false, false, false); + learn.doUpdate((File) null, null, null, false, false); + + learn.doSwitch(null, null, null, false); + learn.doSwitch(null, null, null, null, false); + learn.doSwitch(null, null, null, null, false, false); + learn.doSwitch(null, null, null, null, null, false, false); + learn.doSwitch(null, null, null, null, null, false, false, false); + + learn.doCheckout(null, null, null, null, false); + // todo continue + //learn.doCheckout(); + //learn.doCheckout(); + } + catch (SVNException e) { + //can not occur since methods are not really called + throw new RuntimeException(e); + } + myUpdate.set(proxy); + } + return myUpdate.get(); + } + + private static class MyLearningProxy extends LearningProxy { + private final SvnAbstractWriteOperationLocks myLocks; + private final File myFile; + + private MyLearningProxy(SvnAbstractWriteOperationLocks locks, final File file) { + myLocks = locks; + myFile = file; + } + + @Override + protected void onBefore() throws SVNException { + myLocks.lockWrite(myFile); + } + + @Override + protected void onAfter() throws SVNException { + myLocks.unlockWrite(myFile); + } + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnTestWriteOperationLocks.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnTestWriteOperationLocks.java new file mode 100644 index 000000000000..853e995ca219 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnTestWriteOperationLocks.java @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn; + +import org.tmatesoft.svn.core.SVNException; + +import java.io.File; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 10/23/12 + * Time: 2:31 PM + */ +public class SvnTestWriteOperationLocks extends SvnAbstractWriteOperationLocks { + private final WorkingCopy myWorkingCopy; + + public SvnTestWriteOperationLocks(WorkingCopy copy) { + super(10); + myWorkingCopy = copy; + } + + @Override + protected WorkingCopy getCopy(File file, boolean directory) throws SVNException { + return myWorkingCopy; + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnUtil.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnUtil.java index 840f34ae23aa..cae785cbf77a 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnUtil.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnUtil.java @@ -583,6 +583,10 @@ public class SvnUtil { } } + public static File getWcDb(final File file) { + return new File(file, ".svn/wc.db"); + } + @Nullable public static File getWcCopyRootIf17(final File file, @Nullable final File upperBound) { File current = file; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnWriteOperationLocks.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnWriteOperationLocks.java index 01f784cd4875..36e0b08c5a86 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnWriteOperationLocks.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/SvnWriteOperationLocks.java @@ -15,80 +15,36 @@ */ package org.jetbrains.idea.svn; -import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.FilePathImpl; -import com.intellij.openapi.vcs.ProjectLevelVcsManager; -import com.intellij.openapi.vcs.changes.FilePathsHelper; +import com.intellij.openapi.vcs.changes.ChangesUtil; import com.intellij.openapi.vfs.VirtualFile; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import java.io.File; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; /** * Created with IntelliJ IDEA. * User: Irina.Chernushina - * Date: 10/19/12 - * Time: 12:09 PM + * Date: 10/23/12 + * Time: 2:29 PM */ -public class SvnWriteOperationLocks { - private final ProjectLevelVcsManager myVcsManager; - private final Map myLockMap; - private final Object myLock; +public class SvnWriteOperationLocks extends SvnAbstractWriteOperationLocks { + private final RootsToWorkingCopies myRootsToWorkingCopies; - public SvnWriteOperationLocks(final Project project) { - myVcsManager = ProjectLevelVcsManager.getInstance(project); - myLockMap = new HashMap(); - myLock = new Object(); + public SvnWriteOperationLocks(RootsToWorkingCopies rootsToWorkingCopies) { + super(1000); + myRootsToWorkingCopies = rootsToWorkingCopies; } - public void lock(final File file) throws SVNException { - final Lock lock = getLockObject(file); - lock.lock(); - } - - private Lock getLockObject(File file) throws SVNException { - return getLockObject(new FilePathImpl(file, file.isDirectory()), FilePathsHelper.convertPath(file.getPath())); - } - - private Lock getLockObject(VirtualFile file) throws SVNException { - return getLockObject(new FilePathImpl(file), FilePathsHelper.convertPath(file.getPath())); - } - - private Lock getLockObject(FilePathImpl file, final String path) throws SVNException { - final boolean directory = file.isDirectory(); - final VirtualFile root = myVcsManager.getVcsRootFor(file); - if (root == null) { + protected WorkingCopy getCopy(File file, boolean directory) throws SVNException { + final VirtualFile parentOrSelf = ChangesUtil.findValidParentAccurately(new FilePathImpl(file, directory)); + final WorkingCopy wcRoot = myRootsToWorkingCopies.getWcRoot(parentOrSelf); + if (wcRoot == null) { throw new SVNException(SVNErrorMessage.create(directory ? SVNErrorCode.WC_NOT_WORKING_COPY : SVNErrorCode.WC_NOT_FILE)); } - Lock lock; - synchronized (myLock) { - lock = myLockMap.get(path); - if (lock == null) { - lock = new ReentrantLock(); - myLockMap.put(path, lock); - } - } - return lock; - } - - public void unlock(final File file) throws SVNException { - final Lock lock = getLockObject(file); - lock.unlock(); - } - - public void lock(final VirtualFile vf) throws SVNException { - final Lock lock = getLockObject(vf); - lock.lock(); - } - - public void unlock(final VirtualFile vf) throws SVNException { - final Lock lock = getLockObject(vf); - lock.unlock(); + // todo check about externals! + return wcRoot; } } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnEditCommitMessageAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnEditCommitMessageAction.java new file mode 100644 index 000000000000..ae04a1aa292e --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnEditCommitMessageAction.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.history; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 10/23/12 + * Time: 7:23 PM + */ +public class SvnEditCommitMessageAction extends AnAction { + + // todo we need: + // todo repo url + // todo revision number, and current text + + @Override + public void actionPerformed(AnActionEvent e) { + //To change body of implemented methods use File | Settings | File Templates. + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRevisionComment.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRevisionComment.java new file mode 100644 index 000000000000..a2f48aafc478 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/SvnRevisionComment.java @@ -0,0 +1,25 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.history; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 10/23/12 + * Time: 7:24 PM + */ +public class SvnRevisionComment { +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SVNChangelistClientI.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SVNChangelistClientI.java new file mode 100644 index 000000000000..58ee56902b79 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SVNChangelistClientI.java @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.portable; + +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.wc.ISVNChangelistHandler; + +import java.io.File; +import java.util.Collection; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 10/19/12 + * Time: 3:50 PM + */ +public interface SVNChangelistClientI extends SvnMarkerInterface { + void getChangeLists(File path, Collection changeLists, SVNDepth depth, ISVNChangelistHandler handler) throws SVNException; + + void getChangeListPaths(Collection changeLists, Collection targets, SVNDepth depth, ISVNChangelistHandler handler) throws SVNException; + + void addToChangelist(File[] paths, SVNDepth depth, String changelist, String[] changelists) throws SVNException; + + void removeFromChangelist(File[] paths, SVNDepth depth, String[] changelists) throws SVNException; + + void doAddToChangelist(File[] paths, SVNDepth depth, String changelist, String[] changelists) throws SVNException; + + void doRemoveFromChangelist(File[] paths, SVNDepth depth, String[] changelists) throws SVNException; + + void doGetChangeListPaths(Collection changeLists, Collection targets, SVNDepth depth, ISVNChangelistHandler handler) throws SVNException; + + void doGetChangeLists(File path, Collection changeLists, SVNDepth depth, ISVNChangelistHandler handler) throws SVNException; +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SvnMarkerInterface.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SvnMarkerInterface.java new file mode 100644 index 000000000000..c043188a990f --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SvnMarkerInterface.java @@ -0,0 +1,10 @@ +package org.jetbrains.idea.svn.portable; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 10/19/12 + * Time: 3:48 PM + */ +public interface SvnMarkerInterface { +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SvnStatusClientI.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SvnStatusClientI.java index 978e44c9272b..3b5828239abd 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SvnStatusClientI.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SvnStatusClientI.java @@ -30,7 +30,7 @@ import java.util.Collection; * Date: 1/24/12 * Time: 9:46 AM */ -public interface SvnStatusClientI { +public interface SvnStatusClientI extends SvnMarkerInterface { long doStatus(File path, boolean recursive, boolean remote, boolean reportAll, boolean includeIgnored, ISVNStatusHandler handler) throws SVNException; long doStatus(File path, boolean recursive, boolean remote, boolean reportAll, boolean includeIgnored, boolean collectParentExternals, ISVNStatusHandler handler) throws SVNException; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SvnSvnkitChangelistClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SvnSvnkitChangelistClient.java new file mode 100644 index 000000000000..b57f73139667 --- /dev/null +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SvnSvnkitChangelistClient.java @@ -0,0 +1,85 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn.portable; + +import org.tmatesoft.svn.core.SVNDepth; +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.Collection; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 10/19/12 + * Time: 3:51 PM + */ +public class SvnSvnkitChangelistClient implements SVNChangelistClientI { + private final SVNChangelistClient myClient; + + public SvnSvnkitChangelistClient(SVNChangelistClient client) { + myClient = client; + } + + @Override + public void getChangeLists(File path, Collection changeLists, SVNDepth depth, ISVNChangelistHandler handler) throws SVNException { + myClient.getChangeLists(path, changeLists, depth, handler); + } + + @Override + public void getChangeListPaths(Collection changeLists, + Collection targets, + SVNDepth depth, + ISVNChangelistHandler handler) throws SVNException { + myClient.getChangeListPaths(changeLists, targets, depth, handler); + } + + @Override + public void addToChangelist(File[] paths, SVNDepth depth, String changelist, String[] changelists) throws SVNException { + myClient.addToChangelist(paths, depth, changelist, changelists); + } + + @Override + public void removeFromChangelist(File[] paths, SVNDepth depth, String[] changelists) throws SVNException { + myClient.removeFromChangelist(paths, depth, changelists); + } + + @Override + public void doAddToChangelist(File[] paths, SVNDepth depth, String changelist, String[] changelists) throws SVNException { + myClient.doAddToChangelist(paths, depth, changelist, changelists); + } + + @Override + public void doRemoveFromChangelist(File[] paths, SVNDepth depth, String[] changelists) throws SVNException { + myClient.doRemoveFromChangelist(paths, depth, changelists); + } + + @Override + public void doGetChangeListPaths(Collection changeLists, + Collection targets, + SVNDepth depth, + ISVNChangelistHandler handler) throws SVNException { + myClient.doGetChangeListPaths(changeLists, targets, depth, handler); + } + + @Override + public void doGetChangeLists(File path, Collection changeLists, SVNDepth depth, ISVNChangelistHandler handler) + throws SVNException { + myClient.doGetChangeLists(path, changeLists, depth, handler); + } +} diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SvnUpdateClientI.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SvnUpdateClientI.java index f587581f2c31..8eaafa4c8034 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SvnUpdateClientI.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SvnUpdateClientI.java @@ -29,7 +29,7 @@ import java.io.File; * Date: 2/1/12 * Time: 11:59 AM */ -public interface SvnUpdateClientI { +public interface SvnUpdateClientI extends SvnMarkerInterface { long doUpdate(File file, SVNRevision revision, boolean recursive) throws SVNException; long doUpdate(File file, SVNRevision revision, boolean recursive, boolean force) throws SVNException; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SvnWcClientI.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SvnWcClientI.java index 5aeb4374e145..973058fe3f1b 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SvnWcClientI.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/portable/SvnWcClientI.java @@ -28,7 +28,7 @@ import java.util.Collection; * Date: 1/20/12 * Time: 6:54 PM */ -public interface SvnWcClientI { +public interface SvnWcClientI extends SvnMarkerInterface { void setAddParameters(ISVNAddParameters addParameters); ISVNCommitHandler getCommitHandler(); void setCommitHandler(ISVNCommitHandler handler); diff --git a/plugins/svn4idea/svn4idea-tests.iml b/plugins/svn4idea/svn4idea-tests.iml index 39e6c6bb024d..e95ff454160d 100644 --- a/plugins/svn4idea/svn4idea-tests.iml +++ b/plugins/svn4idea/svn4idea-tests.iml @@ -27,6 +27,15 @@ + + + + + + + + + diff --git a/plugins/svn4idea/svn4idea.iml b/plugins/svn4idea/svn4idea.iml index 58d83821070b..bd4e9cc30efc 100644 --- a/plugins/svn4idea/svn4idea.iml +++ b/plugins/svn4idea/svn4idea.iml @@ -51,7 +51,13 @@ - + + + + + + + diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnBusyOnAddTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnBusyOnAddTest.java index 1ad195c1ac24..a93251736d57 100644 --- a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnBusyOnAddTest.java +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnBusyOnAddTest.java @@ -108,8 +108,8 @@ public class SvnBusyOnAddTest extends TestCase { final File ioFile = new File(myWorkingCopyRoot, filename); ioFile.getParentFile().mkdirs(); - SVNWCClient client11 = new SVNWCClient((ISVNRepositoryPool)null, new DefaultSVNOptions()); - client11.doAdd(ioFile.getParentFile(), true, false, true, true); + /*SVNWCClient client11 = new SVNWCClient((ISVNRepositoryPool)null, new DefaultSVNOptions()); + client11.doAdd(ioFile.getParentFile(), true, false, true, true);*/ ioFile.createNewFile(); try { @@ -153,7 +153,7 @@ public class SvnBusyOnAddTest extends TestCase { // } SVNWCClient client = new SVNWCClient((ISVNRepositoryPool)null, new DefaultSVNOptions()); - client.doAdd(ioFile, true, false, true, true); + client.doAdd(ioFile.getParentFile(), true, false, true, true); semaphore.up(); semaphoreWokeUp.waitFor(); diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnLockingTest.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnLockingTest.java new file mode 100644 index 000000000000..b4949543b66b --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnLockingTest.java @@ -0,0 +1,412 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.svn; + +import com.intellij.idea.Bombed; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.application.PluginPathManager; +import com.intellij.util.concurrency.Semaphore; +import junit.framework.Assert; +import junit.framework.TestCase; +import org.junit.Before; +import org.tmatesoft.sqljet.core.SqlJetException; +import org.tmatesoft.sqljet.core.table.ISqlJetBusyHandler; +import org.tmatesoft.sqljet.core.table.ISqlJetTransaction; +import org.tmatesoft.sqljet.core.table.SqlJetDb; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions; +import org.tmatesoft.svn.core.wc.ISVNRepositoryPool; +import org.tmatesoft.svn.core.wc.SVNRevision; +import org.tmatesoft.svn.core.wc.SVNWCClient; + +import java.io.File; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 10/23/12 + * Time: 2:27 PM + */ +public class SvnLockingTest extends TestCase { + private File myWorkingCopyRoot; + private SvnTestWriteOperationLocks myLocks; + + @Before + public void setUp() throws Exception { + //PlatformTestCase.initPlatformLangPrefix(); + File pluginRoot = new File(PluginPathManager.getPluginHomePath("svn4idea")); + if (!pluginRoot.isDirectory()) { + // try standalone mode + Class aClass = Svn17TestCase.class; + String rootPath = PathManager.getResourceRoot(aClass, "/" + aClass.getName().replace('.', '/') + ".class"); + pluginRoot = new File(rootPath).getParentFile().getParentFile().getParentFile(); + } + myWorkingCopyRoot = new File(pluginRoot, "testData/move2unv"); + myLocks = new SvnTestWriteOperationLocks(new WorkingCopy(myWorkingCopyRoot, SVNURL.parseURIEncoded("http://a.b.c"), true)); + } + + @Override + public void tearDown() throws Exception { + } + + public void testPrepare() throws Exception { + final HangInWrite operation1 = new HangInWrite("one", false); + operation1.hang(); + operation1.go(); + } + + public void testPrepareRead() throws Exception { + final HangInRead read = new HangInRead("READ", false); + read.run(); + read.go(); + } + + public void testWritesSequential() throws Exception { + final HangInWrite operation1 = new HangInWrite("one_"); + final HangInWrite operation2 = new HangInWrite("two_"); + + final Thread thread1 = new Thread(operation1); + final Thread thread2 = new Thread(operation2); + try { + thread1.start(); + waitForRunning(operation1); + Assert.assertTrue(operation1.isRunning()); + + thread2.start(); + waitForRunning(operation2); + Assert.assertFalse(operation2.isRunning()); + + operation1.go(); + waitForRunning(operation2); + Assert.assertTrue(operation2.isRunning()); + operation2.go(); + Thread.sleep(10); + } finally { + operation1.stop(); + operation2.stop(); + + thread1.interrupt(); + thread2.interrupt(); + } + } + + @Bombed(year=2020, month = 1,day = 1,description = "waiting for http://issues.tmatesoft.com/issue/SVNKIT-317") + public void testOnlyWrites() throws Exception { + final OnlyWrite operation1 = new OnlyWrite("one"); + final OnlyWrite operation2 = new OnlyWrite("two"); + + final Thread thread1 = new Thread(operation1); + final Thread thread2 = new Thread(operation2); + + try { + thread1.start(); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + // + } + Assert.assertTrue(operation1.isInsideWrite()); + thread2.start(); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + // + } + Assert.assertFalse(operation2.isInsideWrite()); + operation1.go(); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + // + } + Assert.assertTrue(operation2.isInsideWrite()); + operation2.go(); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + // + } + } finally { + operation1.stop(); + operation2.stop(); + + thread1.interrupt(); + thread2.interrupt(); + } + } + + @Bombed(year=2020, month = 1,day = 1,description = "waiting for http://issues.tmatesoft.com/issue/SVNKIT-317") + public void testReadInBetweenWrites() throws Exception { + final HangInWrite operation1 = new HangInWrite("one1"); + final HangInWrite operation2 = new HangInWrite("two1"); + final HangInRead read = new HangInRead("READ"); + + final Thread thread1 = new Thread(operation1); + final Thread threadRead = new Thread(read); + final Thread thread2 = new Thread(operation2); + + try { + thread1.start(); + waitForRunning(operation1); + Assert.assertTrue(operation1.isRunning()); + + threadRead.start(); + waitForRunning(read); + Assert.assertFalse(read.isRunning()); // not clear why read is allowed to run when write is active, but I've not thought it over so much + + operation1.go(); + waitForRunning(read); + Assert.assertTrue(read.isRunning()); + + thread2.start(); + waitForRunning(operation2); + Assert.assertFalse(operation2.isRunning()); // again, not clear why write is allowed to run when read is active, but I've not thought it over so much + + read.go(); + waitForRunning(operation2); + Assert.assertTrue(operation2.isRunning()); + } finally { + operation1.stop(); + operation2.stop(); + + thread1.interrupt(); + thread2.interrupt(); + threadRead.interrupt(); + } + } + + private void waitForRunning(HangRun operation1) { + int cnt = 10; + while (cnt > 0) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + // + } + if (operation1.isRunning()) break; + -- cnt; + } + } + + public interface HangRun { + void go(); + boolean isRunning(); + void stop(); + } + + private class HangInRead implements Runnable, HangRun { + private final AtomicBoolean myIsRunning; + private final String myName; + private final boolean myWaitFor; + private final Semaphore mySemaphore; + + private HangInRead(String name) { + this(name, true); + } + + private HangInRead(String name, final boolean waitFor) { + myName = name; + myWaitFor = waitFor; + myIsRunning = new AtomicBoolean(false); + mySemaphore = new Semaphore(); + } + + @Override + public void run() { + mySemaphore.down(); + try { + System.out.println("starting read " + myName); + myLocks.wrapRead(myWorkingCopyRoot, new Runnable() { + @Override + public void run() { + myIsRunning.set(true); + System.out.println("inside read " + myName); + final SVNWCClient client = new SVNWCClient((ISVNRepositoryPool)null, new DefaultSVNOptions()); + try { + client.doInfo(myWorkingCopyRoot, SVNRevision.BASE); + } + catch (SVNException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + System.out.println("got status " + myName); + if (myWaitFor) { + mySemaphore.waitFor(); + } + System.out.println("have read " + myName); + } + }); + } + catch (SVNException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + @Override + public void go() { + System.out.println("read going " + myName); + mySemaphore.up(); + } + + @Override + public boolean isRunning() { + System.out.println("read running " + myName + " " + myIsRunning.get()); + return myIsRunning.get(); + } + + @Override + public void stop() { + } + } + + private class OnlyWrite extends HangInWrite { + private OnlyWrite(String name) { + super(name); + } + + @Override + public void run() { + mySemaphore.down(); + operation(); + } + } + + private class HangInWrite implements Runnable, HangRun { + protected final Semaphore mySemaphore; + private final AtomicBoolean myIsRunning; + private final AtomicBoolean myInsideWrite; + private final String myName; + private boolean shouldWait; + private volatile boolean myStopped; + + private HangInWrite(final String name) { + this(name, true); + } + + private HangInWrite(final String name, final boolean shouldWait) { + myName = name; + mySemaphore = new Semaphore(); + this.shouldWait = shouldWait; + myIsRunning = new AtomicBoolean(false); + myInsideWrite = new AtomicBoolean(false); + } + + @Override + public void run() { + try { + hang(); + } + catch (SVNException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + protected void operation() { + System.out.println("TRY OPEN FOR WRITE==="); + SqlJetDb open = null; + try { + open = SqlJetDb.open(SvnUtil.getWcDb(myWorkingCopyRoot), true); + open.setBusyHandler(new ISqlJetBusyHandler() { + @Override + public boolean call(int i) { + if (myStopped) return false; + System.out.println("busy " + myName); + try { + Thread.sleep(10); + } + catch (InterruptedException e) { + // + } + return true; + } + }); + try { + System.out.println("TRY OPEN FOR WRITE " + myName); + open.runWriteTransaction(new ISqlJetTransaction() { + @Override + public Object run(SqlJetDb db) throws SqlJetException { + System.out.println("OPENed FOR WRITE " + myName); + myInsideWrite.set(true); + if (shouldWait) { + mySemaphore.waitFor(); + } + return null; + } + }); + } finally { + myInsideWrite.set(false); + open.rollback(); + } + } + catch (SqlJetException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + finally { + if (open != null) { + try { + open.close(); + } + catch (SqlJetException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + System.out.println("CLOSed FOR WRITE " + myName); + } + } + } + + public boolean isInsideWrite() { + return myInsideWrite.get(); + } + + public void hang() throws SVNException { + System.out.println("starting " + myName); + mySemaphore.down(); + myLocks.lockWrite(myWorkingCopyRoot); + myIsRunning.set(true); + System.out.println("started " + myName); + try { + operation(); + } finally { + myLocks.unlockWrite(myWorkingCopyRoot); + myIsRunning.set(false); + } + } + + public boolean isRunning() { + System.out.println("running " + myName + " " + myIsRunning.get()); + return myIsRunning.get(); + } + + @Override + public void stop() { + myStopped = true; + } + + public void go() { + System.out.println("going " + myName); + mySemaphore.up(); + } + } +}