From ce1e8b56c5b67cd19806ed4f03c16e99bfe1f3bd Mon Sep 17 00:00:00 2001 From: Lars Vogel Date: Thu, 20 Aug 2026 10:14:26 +0200 Subject: [PATCH 1/2] Restore auto-building when smart import fails SmartImportJob turns auto-building off for the duration of the import and turns it back on as the last statement of its try block. A configurator that throws, or a cancellation, skips that statement, so the workspace is left with auto-building permanently disabled until the user notices and toggles it manually. Restore the previous setting in a finally block instead. Also null check the optional import listener when closing projects after the import. --- .../wizards/datatransfer/SmartImportJob.java | 31 ++++++++++++------- .../tests/datatransfer/SmartImportTests.java | 16 ++++++++++ 2 files changed, 36 insertions(+), 11 deletions(-) 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..6b0596e9ef1 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) { @@ -270,21 +268,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; 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..b15869f9f12 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 @@ -572,4 +572,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); + } + } } From d815981317a381c667c5d0d21195185819d25a18 Mon Sep 17 00:00:00 2001 From: Lars Vogel Date: Thu, 20 Aug 2026 10:35:57 +0200 Subject: [PATCH 2/2] Create imported projects in a single workspace operation SmartImportJob created every selected project in its own workspace operation, so each project produced its own resource delta and every listener (JDT, PDE, Project Explorer, team providers) was notified once per project. The creation loop now runs as one workspace operation, which collapses those deltas into a single one. No configurator runs in that loop, so the parallel configuration phase and its scheduling rules stay as they are. The import also walked each project twice: importProjectAndChildrenRecursively refreshes the container with DEPTH_INFINITE and afterwards refreshes the project it created for that container, which is the same resource for a project selected in the wizard. The second refresh is now done only when the project differs from the container, which is the case for nested projects detected in a child folder. --- .../wizards/datatransfer/SmartImportJob.java | 41 +++++++++++-------- .../tests/datatransfer/SmartImportTests.java | 39 ++++++++++++++++++ 2 files changed, 63 insertions(+), 17 deletions(-) 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 6b0596e9ef1..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 @@ -192,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); @@ -460,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 b15869f9f12..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 {