svn: external tests:

- external working copy is detected
- external change is seen
This commit is contained in:
irengrig
2012-11-12 16:20:18 +04:00
parent efbc24eb8c
commit eaa2b4ebbd
5 changed files with 219 additions and 47 deletions
@@ -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<ScheduledExecutorService> ourUpdateAlarm = new AtomicReference<ScheduledExecutorService>();
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<Pair<String, AbstractVcs>> {
private RemoteRevisionsCache myRevisionsCache;
private final ProjectLevelVcsManager myVcsManager;
private final ExecutorService myService;
private final AtomicReference<ScheduledExecutorService> myService;
public MyChangesDeltaForwarder(final Project project, final ExecutorService service) {
public MyChangesDeltaForwarder(final Project project, final AtomicReference<ScheduledExecutorService> service) {
myService = service;
myRevisionsCache = RemoteRevisionsCache.getInstance(project);
myVcsManager = ProjectLevelVcsManager.getInstance(project);
}
public void plus(final Pair<String, AbstractVcs> stringAbstractVcsPair) {
myService.submit(new Runnable() {
myService.get().submit(new Runnable() {
public void run() {
final Pair<String, AbstractVcs> correctedPair = getCorrectedPair(stringAbstractVcsPair);
if (correctedPair == null) return;
@@ -1439,7 +1446,7 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec
}
public void minus(final Pair<String, AbstractVcs> stringAbstractVcsPair) {
myService.submit(new Runnable() {
myService.get().submit(new Runnable() {
public void run() {
final Pair<String, AbstractVcs> correctedPair = getCorrectedPair(stringAbstractVcsPair);
if (correctedPair == null) return;
@@ -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<ChangeListListener> 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<ScheduledExecutorService> myService;
private final MyProxyDispatcher myProxyDispatcher;
public DelayedNotificator(EventDispatcher<ChangeListListener> dispatcher, final ScheduledExecutorService service) {
public DelayedNotificator(EventDispatcher<ChangeListListener> dispatcher, final AtomicReference<ScheduledExecutorService> 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<Change> 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<Change> 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<Change> 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();
}
@@ -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<ScheduledExecutorService> 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<Boolean> myIsStoppedGetter;
public UpdateRequestsQueue(final Project project, final ScheduledExecutorService executor, final Runnable delegate) {
public UpdateRequestsQueue(final Project project, final AtomicReference<ScheduledExecutorService> 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;
@@ -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;
}
@@ -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<RootUrlInfo> infos = workingCopies.getAllWcInfos();
Assert.assertEquals(2, infos.size());
final Set<String> expectedUrls = new HashSet<String>();
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());
}
}