diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java index f21512676306..7c576df81bee 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java @@ -60,7 +60,6 @@ import org.jetbrains.annotations.TestOnly; import javax.swing.*; import java.io.File; import java.util.*; -import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.atomic.AtomicReference; @@ -76,7 +75,10 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec private final FileStatusManager myFileStatusManager; private final UpdateRequestsQueue myUpdater; - private static ScheduledExecutorService ourUpdateAlarm = createChangeListExecutor(); + private static final AtomicReference ourUpdateAlarm = new AtomicReference(); + static { + ourUpdateAlarm.set(createChangeListExecutor()); + } private static ScheduledThreadPoolExecutor createChangeListExecutor() { return VcsUtil.createExecutor("Change List Updater"); @@ -1376,7 +1378,7 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec private static void waitUpdateAlarm() { final Semaphore semaphore = new Semaphore(); semaphore.down(); - ourUpdateAlarm.execute(new Runnable() { + ourUpdateAlarm.get().execute(new Runnable() { @Override public void run() { semaphore.up(); @@ -1387,8 +1389,13 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec public void stopEveryThingIfInTestMode() { assert ApplicationManager.getApplication().isUnitTestMode(); - ourUpdateAlarm.shutdownNow(); - ourUpdateAlarm = createChangeListExecutor(); + ourUpdateAlarm.get().shutdownNow(); + ourUpdateAlarm.set(createChangeListExecutor()); + } + + public void forceGoInTestMode() { + assert ApplicationManager.getApplication().isUnitTestMode(); + myUpdater.forceGo(); } /** @@ -1419,17 +1426,17 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec private static class MyChangesDeltaForwarder implements PlusMinus> { private RemoteRevisionsCache myRevisionsCache; private final ProjectLevelVcsManager myVcsManager; - private final ExecutorService myService; + private final AtomicReference myService; - public MyChangesDeltaForwarder(final Project project, final ExecutorService service) { + public MyChangesDeltaForwarder(final Project project, final AtomicReference service) { myService = service; myRevisionsCache = RemoteRevisionsCache.getInstance(project); myVcsManager = ProjectLevelVcsManager.getInstance(project); } public void plus(final Pair stringAbstractVcsPair) { - myService.submit(new Runnable() { + myService.get().submit(new Runnable() { public void run() { final Pair correctedPair = getCorrectedPair(stringAbstractVcsPair); if (correctedPair == null) return; @@ -1439,7 +1446,7 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec } public void minus(final Pair stringAbstractVcsPair) { - myService.submit(new Runnable() { + myService.get().submit(new Runnable() { public void run() { final Pair correctedPair = getCorrectedPair(stringAbstractVcsPair); if (correctedPair == null) return; diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/DelayedNotificator.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/DelayedNotificator.java index ac361eb0690f..6f715733ef22 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/DelayedNotificator.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/DelayedNotificator.java @@ -21,22 +21,23 @@ import com.intellij.util.EventDispatcher; import java.util.Collection; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicReference; public class DelayedNotificator { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.DelayedNotificator"); private final EventDispatcher myDispatcher; // this is THE SAME service as is used for change list manager update (i.e. one thread for both processes) - private final ScheduledExecutorService myService; + private final AtomicReference myService; private final MyProxyDispatcher myProxyDispatcher; - public DelayedNotificator(EventDispatcher dispatcher, final ScheduledExecutorService service) { + public DelayedNotificator(EventDispatcher dispatcher, final AtomicReference service) { myDispatcher = dispatcher; myService = service; myProxyDispatcher = new MyProxyDispatcher(); } public void callNotify(final ChangeListCommand command) { - myService.execute(new Runnable() { + myService.get().execute(new Runnable() { public void run() { try { command.doNotify(myDispatcher); @@ -54,7 +55,7 @@ public class DelayedNotificator { private class MyProxyDispatcher implements ChangeListListener { public void changeListAdded(final ChangeList list) { - myService.execute(new Runnable() { + myService.get().execute(new Runnable() { public void run() { myDispatcher.getMulticaster().changeListAdded(list); } @@ -62,7 +63,7 @@ public class DelayedNotificator { } public void changesRemoved(final Collection changes, final ChangeList fromList) { - myService.execute(new Runnable() { + myService.get().execute(new Runnable() { public void run() { myDispatcher.getMulticaster().changesRemoved(changes, fromList); } @@ -70,7 +71,7 @@ public class DelayedNotificator { } public void changesAdded(final Collection changes, final ChangeList toList) { - myService.execute(new Runnable() { + myService.get().execute(new Runnable() { public void run() { myDispatcher.getMulticaster().changesAdded(changes, toList); } @@ -78,7 +79,7 @@ public class DelayedNotificator { } public void changeListRemoved(final ChangeList list) { - myService.execute(new Runnable() { + myService.get().execute(new Runnable() { public void run() { myDispatcher.getMulticaster().changeListRemoved(list); } @@ -86,7 +87,7 @@ public class DelayedNotificator { } public void changeListChanged(final ChangeList list) { - myService.execute(new Runnable() { + myService.get().execute(new Runnable() { public void run() { myDispatcher.getMulticaster().changeListChanged(list); } @@ -94,7 +95,7 @@ public class DelayedNotificator { } public void changeListRenamed(final ChangeList list, final String oldName) { - myService.execute(new Runnable() { + myService.get().execute(new Runnable() { public void run() { myDispatcher.getMulticaster().changeListRenamed(list, oldName); } @@ -102,7 +103,7 @@ public class DelayedNotificator { } public void changeListCommentChanged(final ChangeList list, final String oldComment) { - myService.execute(new Runnable() { + myService.get().execute(new Runnable() { public void run() { myDispatcher.getMulticaster().changeListCommentChanged(list, oldComment); } @@ -110,7 +111,7 @@ public class DelayedNotificator { } public void changesMoved(final Collection changes, final ChangeList fromList, final ChangeList toList) { - myService.execute(new Runnable() { + myService.get().execute(new Runnable() { public void run() { myDispatcher.getMulticaster().changesMoved(changes, fromList, toList); } @@ -118,7 +119,7 @@ public class DelayedNotificator { } public void defaultListChanged(final ChangeList oldDefaultList, final ChangeList newDefaultList) { - myService.execute(new Runnable() { + myService.get().execute(new Runnable() { public void run() { myDispatcher.getMulticaster().defaultListChanged(oldDefaultList, newDefaultList); } @@ -126,7 +127,7 @@ public class DelayedNotificator { } public void unchangedFileStatusChanged() { - myService.execute(new Runnable() { + myService.get().execute(new Runnable() { public void run() { myDispatcher.getMulticaster().unchangedFileStatusChanged(); } @@ -134,7 +135,7 @@ public class DelayedNotificator { } public void changeListUpdateDone() { - myService.execute(new Runnable() { + myService.get().execute(new Runnable() { public void run() { myDispatcher.getMulticaster().changeListUpdateDone(); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/UpdateRequestsQueue.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/UpdateRequestsQueue.java index 3ec302258f26..970662c50653 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/UpdateRequestsQueue.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/UpdateRequestsQueue.java @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; /** * ChangeListManager updates scheduler. @@ -45,7 +46,7 @@ public class UpdateRequestsQueue { private final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.UpdateRequestsQueue"); private static final String ourHeavyLatchOptimization = "vcs.local.changes.track.heavy.latch"; private final Project myProject; - private final ScheduledExecutorService myExecutor; + private final AtomicReference myExecutor; private final Runnable myDelegate; private final Object myLock; private volatile boolean myStarted; @@ -62,7 +63,7 @@ public class UpdateRequestsQueue { private final boolean myTrackHeavyLatch; private final Getter myIsStoppedGetter; - public UpdateRequestsQueue(final Project project, final ScheduledExecutorService executor, final Runnable delegate) { + public UpdateRequestsQueue(final Project project, final AtomicReference executor, final Runnable delegate) { myProject = project; myExecutor = executor; myTrackHeavyLatch = Boolean.parseBoolean(System.getProperty(ourHeavyLatchOptimization)); @@ -107,7 +108,7 @@ public class UpdateRequestsQueue { } final MyRunnable runnable = new MyRunnable(); myRequestSubmitted = true; - myExecutor.schedule(runnable, 300, TimeUnit.MILLISECONDS); + myExecutor.get().schedule(runnable, 300, TimeUnit.MILLISECONDS); LOG.debug("Scheduled for project: " + myProject.getName() + ", runnable: " + runnable.hashCode()); } } @@ -120,6 +121,15 @@ public class UpdateRequestsQueue { } } + public void forceGo() { + synchronized (myLock) { + myStopped = false; + myRequestSubmitted = false; + myRequestRunning = false; + } + schedule(); + } + public void go() { synchronized (myLock) { myStopped = false; diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CreateExternalAction.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CreateExternalAction.java index 3c90b3858b0e..1af0550a9d0e 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CreateExternalAction.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/actions/CreateExternalAction.java @@ -37,7 +37,10 @@ import org.jetbrains.idea.svn.SvnBundle; import org.jetbrains.idea.svn.SvnPropertyKeys; import org.jetbrains.idea.svn.SvnVcs; import org.jetbrains.idea.svn.dialogs.SelectCreateExternalTargetDialog; -import org.tmatesoft.svn.core.*; +import org.tmatesoft.svn.core.SVNCancelException; +import org.tmatesoft.svn.core.SVNDepth; +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNPropertyValue; import org.tmatesoft.svn.core.internal.wc.SVNExternal; import org.tmatesoft.svn.core.wc.*; @@ -84,26 +87,8 @@ public class CreateExternalAction extends DumbAwareAction { private void doInBackground(Project project, VirtualFile vf, String url, boolean checkout, String target) { final SvnVcs vcs = SvnVcs.getInstance(project); try { - final SVNURL svnurl = SVNURL.parseURIEncoded(url); final File ioFile = new File(vf.getPath()); - final SVNWCClient wcClient = vcs.createWCClient(); - final SVNPropertyData propertyData = - wcClient.doGetProperty(ioFile, SvnPropertyKeys.SVN_EXTERNALS, SVNRevision.UNDEFINED, SVNRevision.UNDEFINED); - String newValue; - if (propertyData != null && propertyData.getValue() != null && ! StringUtil.isEmptyOrSpaces(propertyData.getValue().getString())) { - final SVNExternal[] externals = SVNExternal.parseExternals("Create External", propertyData.getValue().getString()); - for (SVNExternal external : externals) { - if (Comparing.equal(external.getPath(), target)) { - AbstractVcsHelper.getInstance(project).showError(new VcsException("Selected destination conflicts with existing: " + external.toString()), "Create External"); - return; - } - } - final String string = createExternalDefinitionString(url, target); - newValue = propertyData.getValue().getString() + "\n" + string; - } else { - newValue = createExternalDefinitionString(url, target); - } - wcClient.doSetProperty(ioFile, SvnPropertyKeys.SVN_EXTERNALS, SVNPropertyValue.create(newValue), false, SVNDepth.EMPTY, null, null); + if (addToExternalProperty(vcs, ioFile, target, url)) return; final VcsDirtyScopeManager dirtyScopeManager = VcsDirtyScopeManager.getInstance(project); final FilePathImpl filePath = new FilePathImpl(ioFile, true); dirtyScopeManager.fileDirty(filePath); @@ -135,7 +120,30 @@ public class CreateExternalAction extends DumbAwareAction { } } - private String createExternalDefinitionString(String url, String target) { + public static boolean addToExternalProperty(SvnVcs vcs, File ioFile, String target, String url) throws SVNException { + final SVNWCClient wcClient = vcs.createWCClient(); + final SVNPropertyData propertyData = + wcClient.doGetProperty(ioFile, SvnPropertyKeys.SVN_EXTERNALS, SVNRevision.UNDEFINED, SVNRevision.UNDEFINED); + String newValue; + if (propertyData != null && propertyData.getValue() != null && ! StringUtil.isEmptyOrSpaces(propertyData.getValue().getString())) { + final SVNExternal[] externals = SVNExternal.parseExternals("Create External", propertyData.getValue().getString()); + for (SVNExternal external : externals) { + if (Comparing.equal(external.getPath(), target)) { + AbstractVcsHelper + .getInstance(vcs.getProject()).showError(new VcsException("Selected destination conflicts with existing: " + external.toString()), "Create External"); + return true; + } + } + final String string = createExternalDefinitionString(url, target); + newValue = propertyData.getValue().getString() + "\n" + string; + } else { + newValue = createExternalDefinitionString(url, target); + } + wcClient.doSetProperty(ioFile, SvnPropertyKeys.SVN_EXTERNALS, SVNPropertyValue.create(newValue), false, SVNDepth.EMPTY, null, null); + return false; + } + + public static String createExternalDefinitionString(String url, String target) { return url + " " + target; } diff --git a/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnExternalTests.java b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnExternalTests.java new file mode 100644 index 000000000000..b3e374e481e7 --- /dev/null +++ b/plugins/svn4idea/testSource/org/jetbrains/idea/svn/SvnExternalTests.java @@ -0,0 +1,146 @@ +/* + * 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.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +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.ChangeListManagerImpl; +import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import junit.framework.Assert; +import org.jetbrains.idea.svn.actions.CreateExternalAction; +import org.junit.Test; + +import java.io.File; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Created with IntelliJ IDEA. + * User: Irina.Chernushina + * Date: 11/12/12 + * Time: 10:24 AM + */ +public class SvnExternalTests extends Svn17TestCase { + private ChangeListManagerImpl clManager; + private SvnVcs myVcs; + private String myMainUrl; + private String myExternalURL; + + @Override + public void setUp() throws Exception { + super.setUp(); + + clManager = (ChangeListManagerImpl) ChangeListManager.getInstance(myProject); + + enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); + enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + myVcs = SvnVcs.getInstance(myProject); + myMainUrl = myRepoUrl + "/root/source"; + myExternalURL = myRepoUrl + "/root/target"; + } + + private static void sleep(final int millis) { + try { + Thread.sleep(millis); + } + catch (InterruptedException ignore) { } + } + + @Test + public void testExternalCopyIsDetected() throws Exception { + prepareExternal(); + + final SvnFileUrlMapping workingCopies = myVcs.getSvnFileUrlMapping(); + final List infos = workingCopies.getAllWcInfos(); + Assert.assertEquals(2, infos.size()); + final Set expectedUrls = new HashSet(); + expectedUrls.add(StringUtil.toLowerCase(myExternalURL)); + expectedUrls.add(StringUtil.toLowerCase(myMainUrl)); + + for (RootUrlInfo info : infos) { + expectedUrls.remove(StringUtil.toLowerCase(info.getAbsoluteUrl())); + } + Assert.assertTrue(expectedUrls.isEmpty()); + } + + private void prepareExternal() throws Exception { + final SubTree subTree = new SubTree(myWorkingCopyDir); + checkin(); + clManager.stopEveryThingIfInTestMode(); + sleep(100); + final File rootFile = new File(subTree.myRootDir.getPath()); + FileUtil.delete(rootFile); + FileUtil.delete(new File(myWorkingCopyDir.getPath() + File.separator + ".svn")); + Assert.assertTrue(!rootFile.exists()); + sleep(200); + myWorkingCopyDir.refresh(false, true); + + verify(runSvn("co", myMainUrl)); + final File sourceDir = new File(myWorkingCopyDir.getPath(), "source"); + CreateExternalAction.addToExternalProperty(myVcs, sourceDir, "external", myExternalURL); + sleep(100); + verify(runSvn("up", sourceDir.getPath())); + sleep(100); + myWorkingCopyDir.refresh(false, true); + Assert.assertTrue(new File(sourceDir, "external").exists()); + // above is preparation + + // start change list manager again + clManager.forceGoInTestMode(); + SvnConfiguration.getInstance(myProject).DETECT_NESTED_COPIES = true; + myVcs.invokeRefreshSvnRoots(false); + clManager.ensureUpToDate(false); + clManager.ensureUpToDate(false); + } + + @Test + public void testSimpleExternalsStatus() throws Exception { + prepareExternal(); + final File sourceFile = new File(myWorkingCopyDir.getPath(), "source" + File.separator + "s1.txt"); + final File externalFile = new File(myWorkingCopyDir.getPath(), "source" + File.separator + "external" + File.separator + "t12.txt"); + + final LocalFileSystem lfs = LocalFileSystem.getInstance(); + final VirtualFile vf1 = lfs.refreshAndFindFileByIoFile(sourceFile); + final VirtualFile vf2 = lfs.refreshAndFindFileByIoFile(externalFile); + + Assert.assertNotNull(vf1); + Assert.assertNotNull(vf2); + + editFileInCommand(myProject, vf1, "test externals 123" + System.currentTimeMillis()); + editFileInCommand(myProject, vf2, "test externals 123" + System.currentTimeMillis()); + + VcsDirtyScopeManager.getInstance(myProject).markEverythingDirty(); + clManager.ensureUpToDate(false); + + final Change change1 = clManager.getChange(vf1); + final Change change2 = clManager.getChange(vf2); + + Assert.assertNotNull(change1); + Assert.assertNotNull(change2); + + Assert.assertNotNull(change1.getBeforeRevision()); + Assert.assertNotNull(change2.getBeforeRevision()); + + Assert.assertNotNull(change1.getAfterRevision()); + Assert.assertNotNull(change2.getAfterRevision()); + } +}