diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportJob.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportJob.java index c7bf3059348..628a017d5e2 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportJob.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportJob.java @@ -168,13 +168,11 @@ public void setListener(RecursiveImportListener listener) { @Override public IStatus run(IProgressMonitor monitor) { + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + boolean isAutoBuilding = workspace.isAutoBuilding(); try { - IWorkspace workspace = ResourcesPlugin.getWorkspace(); - IWorkspaceDescription description = workspace.getDescription(); - boolean isAutoBuilding = workspace.isAutoBuilding(); if (isAutoBuilding) { - description.setAutoBuilding(false); - workspace.setDescription(description); + setAutoBuilding(workspace, false); } if (directoriesToImport != null) { @@ -194,24 +192,29 @@ public IStatus run(IProgressMonitor monitor) { SortedMap leafToRootProjects = new TreeMap<>(Collections.reverseOrder(rootToLeafComparator)); final Set alreadyConfiguredProjects = new HashSet<>(); loopMonitor.worked(1); - for (final File directoryToImport : directories) { - final boolean alreadyAnEclipseProject = new File(directoryToImport, IProjectDescription.DESCRIPTION_FILE_NAME).isFile(); - try { - IProject newProject = toExistingOrNewProject(directoryToImport, loopMonitor.split(1), - IResource.BACKGROUND_REFRESH); - if (alreadyAnEclipseProject) { - alreadyConfiguredProjects.add(newProject); - } - leafToRootProjects.put(directoryToImport, newProject); - loopMonitor.worked(1); - } catch (CouldNotImportProjectException ex) { - IPath path = IPath.fromOSString(directoryToImport.getAbsolutePath()); - if (listener != null) { - listener.errorHappened(path, ex); + // Create all projects in one workspace operation, so listeners see a single + // resource delta instead of one per project. No configurator runs here. + workspace.run(creationMonitor -> { + for (final File directoryToImport : directories) { + final boolean alreadyAnEclipseProject = new File(directoryToImport, + IProjectDescription.DESCRIPTION_FILE_NAME).isFile(); + try { + IProject newProject = toExistingOrNewProject(directoryToImport, loopMonitor.split(1), + IResource.BACKGROUND_REFRESH); + if (alreadyAnEclipseProject) { + alreadyConfiguredProjects.add(newProject); + } + leafToRootProjects.put(directoryToImport, newProject); + loopMonitor.worked(1); + } catch (CouldNotImportProjectException ex) { + IPath path = IPath.fromOSString(directoryToImport.getAbsolutePath()); + if (listener != null) { + listener.errorHappened(path, ex); + } + this.errors.put(path, ex); } - this.errors.put(path, ex); } - } + }, this.workspaceRoot, IWorkspace.AVOID_UPDATE, null); if (configureProjects) { JobGroup multiDirectoriesJobGroup = new JobGroup( DataTransferMessages.SmartImportJob_configuringSelectedDirectories, 20, 1); @@ -270,21 +273,32 @@ protected IStatus run(IProgressMonitor aMonitor) { try { project.close(monitor); } catch (CoreException e) { - listener.errorHappened(project.getLocation(), e); + if (listener != null) { + listener.errorHappened(project.getLocation(), e); + } } }); } - - if (isAutoBuilding) { - description.setAutoBuilding(true); - workspace.setDescription(description); - } } catch (Exception ex) { return new Status(IStatus.ERROR, IDEWorkbenchPlugin.IDE_WORKBENCH, ex.getMessage(), ex); + } finally { + if (isAutoBuilding) { + try { + setAutoBuilding(workspace, true); + } catch (CoreException ex) { + IDEWorkbenchPlugin.log("Could not restore auto-building after import", ex); //$NON-NLS-1$ + } + } } return Status.OK_STATUS; } + private static void setAutoBuilding(IWorkspace workspace, boolean autoBuilding) throws CoreException { + IWorkspaceDescription description = workspace.getDescription(); + description.setAutoBuilding(autoBuilding); + workspace.setDescription(description); + } + protected boolean rootProjectWorthBeingRemoved() { if (this.report.size() == 1) { return false; @@ -451,7 +465,9 @@ private Set importProjectAndChildrenRecursively(final IContainer conta } } - if (!mainProjectConfigurators.isEmpty()) { + // The container was already refreshed above, so refresh again only if the project + // is a different resource (nested project created for a child folder). + if (!mainProjectConfigurators.isEmpty() && !project.equals(container)) { project.refreshLocal(IResource.DEPTH_INFINITE, subMonitor.split(1)); } for (ProjectConfigurator configurator : mainProjectConfigurators) { diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/datatransfer/SmartImportTests.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/datatransfer/SmartImportTests.java index 9c26d87df35..aa24c0a6ce3 100644 --- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/datatransfer/SmartImportTests.java +++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/datatransfer/SmartImportTests.java @@ -43,6 +43,9 @@ import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.FileLocator; @@ -189,6 +192,42 @@ public void testImport6Projects() throws IOException, OperationCanceledException assertEquals(6, ResourcesPlugin.getWorkspace().getRoot().getProjects().length); } + @Test + public void testProjectsAreCreatedInASingleWorkspaceOperation() throws Exception { + AtomicInteger eventsAddingProjects = new AtomicInteger(); + IResourceChangeListener listener = event -> { + IResourceDelta delta = event.getDelta(); + if (delta != null && delta.getAffectedChildren(IResourceDelta.ADDED).length > 0) { + eventsAddingProjects.incrementAndGet(); + } + }; + java.nio.file.Path tempDir = Files.createTempDirectory("smartImportBatch"); + try { + Set directories = new HashSet<>(); + for (int i = 0; i < 3; i++) { + String name = "batchProject" + i; + File projectDirectory = new File(tempDir.toFile(), name); + projectDirectory.mkdirs(); + Files.writeString(new File(projectDirectory, ".project").toPath(), + "" + name + + ""); + directories.add(projectDirectory); + } + SmartImportJob job = new SmartImportJob(tempDir.toFile(), Collections.emptySet(), false, false); + job.setDirectoriesToImport(directories); + ResourcesPlugin.getWorkspace().addResourceChangeListener(listener, IResourceChangeEvent.POST_CHANGE); + IStatus status = job.run(new NullProgressMonitor()); + + assertTrue("Import failed: " + status, status.isOK()); + assertEquals(directories.size(), ResourcesPlugin.getWorkspace().getRoot().getProjects().length); + assertEquals("All projects should be created in a single workspace operation", 1, + eventsAddingProjects.get()); + } finally { + ResourcesPlugin.getWorkspace().removeResourceChangeListener(listener); + org.eclipse.core.tests.harness.FileSystemHelper.clear(tempDir.toFile()); + } + } + @Test public void testImportModularProjectsWithSameName() throws IOException, OperationCanceledException, InterruptedException { @@ -572,4 +611,20 @@ public void testSmartImportJobSkipsDotFoldersInProposals() throws Exception { org.eclipse.core.tests.harness.FileSystemHelper.clear(tempDir.toFile()); } } + + @Test + public void testAutoBuildingRestoredAfterFailedImport() throws Exception { + assertTrue("Test expects auto-building to be enabled", ResourcesPlugin.getWorkspace().isAutoBuilding()); + // a regular file as import root makes the project creation fail + java.nio.file.Path notADirectory = Files.createTempFile("smartImportFailure", ".txt"); + try { + SmartImportJob job = new SmartImportJob(notADirectory.toFile(), Collections.emptySet(), true, true); + IStatus status = job.run(new NullProgressMonitor()); + assertEquals("Import was expected to fail", IStatus.ERROR, status.getSeverity()); + assertTrue("Auto-building must be restored after a failed import", + ResourcesPlugin.getWorkspace().isAutoBuilding()); + } finally { + Files.deleteIfExists(notADirectory); + } + } }