Skip to content

Commit f6063dc

Browse files
committed
Refresh out-of-sync resources in DeleteResourcesProcessor
When the workspace "Refresh on access" preference (Preferences > General > Workspace > "Refresh on access", backed by ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH) is enabled, the Delete Resources refactoring wizard reports "is not in sync with" warnings even though the user explicitly opted into refresh on access. Note that this preference is enabled by default. DeleteResourcesProcessor now follows the same pattern as RenameResourceProcessor: if the sync check fails and the preference is enabled, the resource is refreshed and the check is repeated. Resources that are already in sync are not refreshed. If the refresh reveals that a resource was deleted externally, the resource is dropped from the set to delete instead of failing later in DeleteResourceChange with "resource does not exist". The refresh runs under a child progress monitor so it stays cancelable, and a failed refresh degrades to the existing out-of-sync warning rather than aborting the condition check. Fixes #3982
1 parent d390a10 commit f6063dc

2 files changed

Lines changed: 117 additions & 2 deletions

File tree

bundles/org.eclipse.ltk.core.refactoring/src/org/eclipse/ltk/internal/core/refactoring/resource/DeleteResourcesProcessor.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,22 @@
1616
import java.net.URI;
1717
import java.util.ArrayList;
1818
import java.util.Arrays;
19+
import java.util.List;
1920

2021
import org.eclipse.core.runtime.CoreException;
2122
import org.eclipse.core.runtime.IPath;
2223
import org.eclipse.core.runtime.IProgressMonitor;
2324
import org.eclipse.core.runtime.OperationCanceledException;
25+
import org.eclipse.core.runtime.Platform;
26+
import org.eclipse.core.runtime.SubMonitor;
2427

2528
import org.eclipse.core.resources.IContainer;
2629
import org.eclipse.core.resources.IFile;
2730
import org.eclipse.core.resources.IFolder;
2831
import org.eclipse.core.resources.IProject;
2932
import org.eclipse.core.resources.IResource;
3033
import org.eclipse.core.resources.IResourceVisitor;
34+
import org.eclipse.core.resources.ResourcesPlugin;
3135
import org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory;
3236

3337
import org.eclipse.core.filebuffers.FileBuffers;
@@ -120,12 +124,30 @@ public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws Core
120124

121125
@Override
122126
public RefactoringStatus checkFinalConditions(IProgressMonitor pm, CheckConditionsContext context) throws CoreException, OperationCanceledException {
123-
pm.beginTask("", 1); //$NON-NLS-1$
127+
SubMonitor subMonitor= SubMonitor.convert(pm, fResources.length);
124128
try {
125129
RefactoringStatus result= new RefactoringStatus();
126130

131+
boolean lightweightAutoRefresh= Platform.getPreferencesService().getBoolean(ResourcesPlugin.PI_RESOURCES,
132+
ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH, false, null);
133+
134+
List<IResource> remainingResources= new ArrayList<>(fResources.length);
127135
for (IResource resource : fResources) {
128-
if (!isSynchronizedExcludingLinkedResources(resource)) {
136+
boolean inSync= isSynchronizedExcludingLinkedResources(resource);
137+
if (!inSync && lightweightAutoRefresh && resource.isAccessible()) {
138+
try {
139+
resource.refreshLocal(IResource.DEPTH_INFINITE, subMonitor.split(1));
140+
} catch (CoreException e) {
141+
// refresh failed; the out-of-sync warning below covers it
142+
}
143+
if (!resource.exists()) {
144+
// deleted externally; the refresh already removed it from the workspace
145+
continue;
146+
}
147+
inSync= isSynchronizedExcludingLinkedResources(resource);
148+
}
149+
remainingResources.add(resource);
150+
if (!inSync) {
129151
String pathLabel= BasicElementLabels.getPathLabel(resource.getFullPath(), false);
130152

131153
String locationLabel= null;
@@ -156,6 +178,9 @@ public RefactoringStatus checkFinalConditions(IProgressMonitor pm, CheckConditio
156178
result.addWarning(warning);
157179
}
158180
}
181+
if (remainingResources.size() != fResources.length) {
182+
fResources= remainingResources.toArray(IResource[]::new);
183+
}
159184

160185
checkDirtyResources(result);
161186

tests/org.eclipse.ltk.core.refactoring.tests/src/org/eclipse/ltk/core/refactoring/tests/resource/ResourceRefactoringTests.java

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import static org.junit.jupiter.api.Assertions.assertNotNull;
2020
import static org.junit.jupiter.api.Assertions.assertTrue;
2121

22+
import java.io.File;
2223
import java.io.IOException;
24+
import java.nio.file.Files;
2325

2426
import org.junit.jupiter.api.AfterEach;
2527
import org.junit.jupiter.api.BeforeEach;
@@ -29,6 +31,8 @@
2931

3032
import org.eclipse.core.runtime.CoreException;
3133
import org.eclipse.core.runtime.IPath;
34+
import org.eclipse.core.runtime.NullProgressMonitor;
35+
import org.eclipse.core.runtime.preferences.InstanceScope;
3236

3337
import org.eclipse.core.resources.IContainer;
3438
import org.eclipse.core.resources.IFile;
@@ -66,6 +70,7 @@ public void setUp() throws Exception {
6670

6771
@AfterEach
6872
public void tearDown() throws Exception {
73+
InstanceScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES).remove(ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH);
6974
fProject.delete();
7075
}
7176

@@ -392,6 +397,65 @@ public void testDeleteRefactoring3_bug343584() throws Exception {
392397
}
393398
}
394399

400+
@Test
401+
public void testDeleteRefactoringOutOfSync_noAutoRefresh() throws Exception {
402+
setLightweightAutoRefresh(false);
403+
IFolder testFolder= fProject.createFolder("test");
404+
IFile file= fProject.createFile(testFolder, "myFile.txt", "hello");
405+
modifyExternally(file);
406+
407+
RefactoringContext context= createDeleteRefactoringContext(file);
408+
try {
409+
RefactoringStatus status= context.getRefactoring().checkAllConditions(new NullProgressMonitor());
410+
assertTrue(status.hasWarning(), "expected an out-of-sync warning");
411+
} finally {
412+
context.dispose();
413+
}
414+
}
415+
416+
@Test
417+
public void testDeleteRefactoringOutOfSync_autoRefresh() throws Exception {
418+
setLightweightAutoRefresh(true);
419+
IFolder testFolder= fProject.createFolder("test");
420+
IFile file= fProject.createFile(testFolder, "myFile.txt", "hello");
421+
File localFile= modifyExternally(file);
422+
423+
RefactoringContext context= createDeleteRefactoringContext(file);
424+
try {
425+
Refactoring refactoring= context.getRefactoring();
426+
RefactoringStatus status= refactoring.checkAllConditions(new NullProgressMonitor());
427+
assertTrue(status.isOK(), () -> "expected no warning but was: " + status);
428+
429+
perform(refactoring.createChange(new NullProgressMonitor()));
430+
431+
assertFalse(file.exists());
432+
assertFalse(localFile.exists());
433+
} finally {
434+
context.dispose();
435+
}
436+
}
437+
438+
@Test
439+
public void testDeleteRefactoringDeletedExternally_autoRefresh() throws Exception {
440+
setLightweightAutoRefresh(true);
441+
IFolder testFolder= fProject.createFolder("test");
442+
IFile file= fProject.createFile(testFolder, "myFile.txt", "hello");
443+
Files.delete(file.getLocation().toFile().toPath());
444+
445+
RefactoringContext context= createDeleteRefactoringContext(file);
446+
try {
447+
Refactoring refactoring= context.getRefactoring();
448+
RefactoringStatus status= refactoring.checkAllConditions(new NullProgressMonitor());
449+
assertTrue(status.isOK(), () -> "expected no warning but was: " + status);
450+
451+
perform(refactoring.createChange(new NullProgressMonitor()));
452+
453+
assertFalse(file.exists());
454+
} finally {
455+
context.dispose();
456+
}
457+
}
458+
395459
@Test
396460
public void testCopyProjectRefactoring() throws Exception {
397461
String content1= "hello";
@@ -419,6 +483,32 @@ public void testCopyProjectRefactoring() throws Exception {
419483
assertFalse(targetProject.exists());
420484
}
421485

486+
private void setLightweightAutoRefresh(boolean enabled) {
487+
InstanceScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES).putBoolean(ResourcesPlugin.PREF_LIGHTWEIGHT_AUTO_REFRESH, enabled);
488+
}
489+
490+
/**
491+
* Modifies the file in the local file system without notifying the workspace, so the file is
492+
* out of sync afterwards.
493+
*/
494+
private File modifyExternally(IFile file) throws IOException {
495+
File localFile= file.getLocation().toFile();
496+
Files.writeString(localFile.toPath(), "external change");
497+
assertTrue(localFile.setLastModified(localFile.lastModified() + 5000));
498+
assertFalse(file.isSynchronized(IResource.DEPTH_ZERO));
499+
return localFile;
500+
}
501+
502+
private RefactoringContext createDeleteRefactoringContext(IResource... resources) throws CoreException {
503+
RefactoringContribution contribution= RefactoringCore.getRefactoringContribution(DeleteResourcesDescriptor.ID);
504+
DeleteResourcesDescriptor descriptor= (DeleteResourcesDescriptor) contribution.createDescriptor();
505+
descriptor.setResources(resources);
506+
RefactoringStatus status= new RefactoringStatus();
507+
RefactoringContext context= descriptor.createRefactoringContext(status);
508+
assertTrue(status.isOK());
509+
return context;
510+
}
511+
422512
private Change perform(Change change) throws CoreException {
423513
PerformChangeOperation op= new PerformChangeOperation(change);
424514
op.run(null);

0 commit comments

Comments
 (0)