From f427fadf40da73c183cd726cda747bf824c83b91 Mon Sep 17 00:00:00 2001 From: Lars Vogel Date: Mon, 6 Apr 2026 20:54:04 +0200 Subject: [PATCH] Skip dot-folders (e.g. .git) when scanning for projects to import Add a 'Skip folders starting with dot' checkbox (default: true) to both the Smart Import wizard (SmartImportRootWizardPage) and the traditional Import Existing Projects wizard (WizardProjectsImportPage). When enabled, directories starting with '.' (such as .git, .svn, .hg) are skipped during recursive project scanning, significantly improving import performance for repositories with large .git folders. The setting is persisted in dialog settings and defaults to true for new installations. Adds tests for both import wizards: - ImportExistingProjectsWizardTest: - test24: Verifies .git folder projects are skipped with default setting - test25: Verifies .git folder projects are found when skip is disabled - SmartImportTests: - testSmartImportSkipsDotFolders: Integration test verifying .git projects are not imported via the wizard - testSmartImportJobSkipsDotFoldersInProposals: Verifies import proposals filter out projects inside .git folders The 'Skip dot folders' checkbox is a scanning option that affects which folders are visited during project discovery, so it belongs before the post-import option 'Close newly imported projects upon completion'. Fixes https://github.com/eclipse-platform/eclipse.platform.ui/issues/3858 --- .../datatransfer/DataTransferMessages.java | 2 + .../EclipseProjectConfigurator.java | 2 +- .../wizards/datatransfer/SmartImportJob.java | 35 +++++++++ .../SmartImportRootWizardPage.java | 32 +++++++- .../datatransfer/SmartImportWizard.java | 4 +- .../WizardProjectsImportPage.java | 73 ++++++++++++++---- .../wizards/datatransfer/messages.properties | 4 +- .../ImportExistingProjectsWizardTest.java | 76 +++++++++++++++++++ .../tests/datatransfer/SmartImportTests.java | 61 +++++++++++++++ 9 files changed, 269 insertions(+), 20 deletions(-) diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/DataTransferMessages.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/DataTransferMessages.java index f79651548fe6..f564865c0997 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/DataTransferMessages.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/DataTransferMessages.java @@ -109,6 +109,7 @@ public class DataTransferMessages extends NLS { public static String WizardProjectsImportPage_projectLabel; public static String WizardProjectsImportPage_hideExistingProjects; public static String WizardProjectsImportPage_closeProjectsAfterImport; + public static String WizardProjectsImportPage_skipDotFolders; public static String WizardProjectsImportPage_invalidProjectName; // --- Export Wizards --- @@ -186,6 +187,7 @@ public class DataTransferMessages extends NLS { public static String SmartImportWizardPage_selectAtLeastOneFolderToOpenAsProject; public static String SmartImportWizardPage_showOtherSpecializedImportWizard; public static String SmartImportWizardPage_closeProjectsAfterImport; + public static String SmartImportWizardPage_skipDotFolders; public static String SmartImportJob_discardRootProject_title; public static String SmartImportJob_discardRootProject_description; diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/EclipseProjectConfigurator.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/EclipseProjectConfigurator.java index 166a5ff6e535..77b242f06c7d 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/EclipseProjectConfigurator.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/EclipseProjectConfigurator.java @@ -44,7 +44,7 @@ public Set findConfigurableLocations(File root, IProgressMonitor monitor) Set projectFiles = new LinkedHashSet<>(); Set visitedDirectories = new HashSet<>(); WizardProjectsImportPage.collectProjectFilesFromDirectory(projectFiles, root, visitedDirectories, true, - monitor); + false, monitor); Set res = new LinkedHashSet<>(); for (File projectFile : projectFiles) { res.add(projectFile.getParentFile()); 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 8fd1ba72da3a..c7bf30593484 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 @@ -74,6 +74,7 @@ public class SmartImportJob extends Job { private boolean deepChildrenDetection; private final boolean configureProjects; private boolean closeProjectsAfterImport; + private boolean skipDotFolders = true; private boolean reconfigureEclipseProjects; private IWorkingSet[] workingSets; @@ -347,6 +348,9 @@ private Set searchAndImportChildrenProjectsRecursively(final IContaine final Set res = Collections.synchronizedSet(new HashSet<>()); for (IResource childResource : parentContainer.members()) { if (childResource.getType() == IResource.FOLDER && !childResource.isDerived()) { + if (skipDotFolders && childResource.getName().startsWith(".")) { //$NON-NLS-1$ + continue; + } IPath location = childResource.getLocation(); if (location == null) { continue; @@ -530,6 +534,18 @@ private Set toPathSet(Set resources) { return res; } + private static boolean isInsideDotFolder(File file) { + File current = file; + while (current != null) { + String name = current.getName(); + if (!name.isEmpty() && name.startsWith(".")) { //$NON-NLS-1$ + return true; + } + current = current.getParentFile(); + } + return false; + } + /** * @param refreshMode One {@link IResource#BACKGROUND_REFRESH} for background refresh, or {@link IResource#NONE} for immediate refresh */ @@ -684,6 +700,9 @@ public Map> getImportProposals(IProgressMonitor for (ProjectConfigurator configurator : activeConfigurators) { configurator.removeDirtyDirectories(res); } + if (this.skipDotFolders) { + res.keySet().removeIf(SmartImportJob::isInsideDotFolder); + } this.importProposals = res; } return this.importProposals; @@ -723,6 +742,22 @@ void setCloseProjectsAfterImport(boolean closeProjectsAfterImport) { this.closeProjectsAfterImport = closeProjectsAfterImport; } + /** + * @param skipDotFolders + * if true, folders starting with '.' (e.g. .git) are skipped + * during project scanning + */ + void setSkipDotFolders(boolean skipDotFolders) { + this.skipDotFolders = skipDotFolders; + } + + /** + * @return whether folders starting with '.' are skipped during scanning + */ + public boolean isSkipDotFolders() { + return this.skipDotFolders; + } + /** * Forget the initial import proposals. */ diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportRootWizardPage.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportRootWizardPage.java index 40c27c58696c..9b1f62694020 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportRootWizardPage.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportRootWizardPage.java @@ -120,6 +120,8 @@ public class SmartImportRootWizardPage extends WizardPage { private static final String STORE_CONFIGURE_NATURES = "SmartImportRootWizardPage.STORE_CONFIGURE_NATURES"; //$NON-NLS-1$ + private static final String STORE_SKIP_DOT_FOLDERS = "SmartImportRootWizardPage.STORE_SKIP_DOT_FOLDERS"; //$NON-NLS-1$ + // Root private File selection; private Combo rootDirectoryText; @@ -135,6 +137,7 @@ public class SmartImportRootWizardPage extends WizardPage { private boolean closeProjectsAfterImport = false; private boolean detectNestedProjects = true; private boolean configureProjects = true; + private boolean skipDotFolders = true; // Working sets private Set workingSets; private WorkingSetGroup workingSetsGroup; @@ -475,6 +478,19 @@ public void widgetSelected(SelectionEvent e) { * Creates the UI elements for the import options */ private void createConfigurationOptions(Composite parent) { + GridData layoutData = new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1); + final Button skipDotFoldersCheckbox = new Button(parent, SWT.CHECK); + skipDotFoldersCheckbox.setText(DataTransferMessages.SmartImportWizardPage_skipDotFolders); + skipDotFoldersCheckbox.setLayoutData(layoutData); + skipDotFoldersCheckbox.setSelection(this.skipDotFolders); + skipDotFoldersCheckbox.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + SmartImportRootWizardPage.this.skipDotFolders = skipDotFoldersCheckbox.getSelection(); + refreshProposals(); + } + }); + Button closeProjectsCheckbox = new Button(parent, SWT.CHECK); closeProjectsCheckbox.setText(DataTransferMessages.SmartImportWizardPage_closeProjectsAfterImport); closeProjectsCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1)); @@ -508,10 +524,9 @@ public void widgetSelected(SelectionEvent e) { DataTransferMessages.SmartImportWizardPage_availableDetectors_title, message.toString()); } }); - GridData layoutData = new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1); final Button detectNestedProjectsCheckbox = new Button(parent, SWT.CHECK); detectNestedProjectsCheckbox.setText(DataTransferMessages.SmartImportWizardPage_detectNestedProjects); - detectNestedProjectsCheckbox.setLayoutData(layoutData); + detectNestedProjectsCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1)); detectNestedProjectsCheckbox.setSelection(this.detectNestedProjects); detectNestedProjectsCheckbox.addSelectionListener(new SelectionAdapter() { @Override @@ -523,7 +538,7 @@ public void widgetSelected(SelectionEvent e) { final Button configureProjectsCheckbox = new Button(parent, SWT.CHECK); configureProjectsCheckbox.setText(DataTransferMessages.SmartImportWizardPage_configureProjects); - configureProjectsCheckbox.setLayoutData(layoutData); + configureProjectsCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1)); configureProjectsCheckbox.setSelection(this.configureProjects); configureProjectsCheckbox.addSelectionListener(new SelectionAdapter() { @Override @@ -825,6 +840,13 @@ boolean isCloseProjectsAfterImport() { return closeProjectsAfterImport; } + /** + * @return whether folders starting with '.' should be skipped during scanning + */ + boolean isSkipDotFolders() { + return skipDotFolders; + } + private void refreshProposals() { stopAndDisconnectCurrentWork(); this.potentialProjects = Collections.emptyMap(); @@ -956,6 +978,9 @@ private void loadWidgetStates() { closeProjectsAfterImport = dialogSettings.getBoolean(STORE_CLOSE_IMPORTED); detectNestedProjects = dialogSettings.getBoolean(STORE_NESTED_PROJECTS); configureProjects = dialogSettings.getBoolean(STORE_CONFIGURE_NATURES); + if (dialogSettings.get(STORE_SKIP_DOT_FOLDERS) != null) { + skipDotFolders = dialogSettings.getBoolean(STORE_SKIP_DOT_FOLDERS); + } } } @@ -969,6 +994,7 @@ private void saveWidgetStates() { dialogSettings.put(STORE_CLOSE_IMPORTED, closeProjectsAfterImport); dialogSettings.put(STORE_NESTED_PROJECTS, detectNestedProjects); dialogSettings.put(STORE_CONFIGURE_NATURES, configureProjects); + dialogSettings.put(STORE_SKIP_DOT_FOLDERS, skipDotFolders); } } diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportWizard.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportWizard.java index aed6fca7d5f5..64f338ab7332 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportWizard.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportWizard.java @@ -288,6 +288,7 @@ public SmartImportJob createOrGetConfiguredImportJob() { // WS change automatically this.easymportJob.setWorkingSets(projectRootPage.getSelectedWorkingSets()); this.easymportJob.setCloseProjectsAfterImport(projectRootPage.isCloseProjectsAfterImport()); + this.easymportJob.setSkipDotFolders(projectRootPage.isSkipDotFolders()); return this.easymportJob; } @@ -337,7 +338,8 @@ private static boolean matchesPage(SmartImportJob job, SmartImportRootWizardPage boolean sameSource = jobRoot.equals(pageRoot) || (isValidArchive(pageRoot) && getExpandDirectory(pageRoot).getAbsoluteFile().equals(jobRoot)); return sameSource && job.isDetectNestedProjects() == page.isDetectNestedProject() - && job.isConfigureProjects() == page.isConfigureProjects(); + && job.isConfigureProjects() == page.isConfigureProjects() + && job.isSkipDotFolders() == page.isSkipDotFolders(); } } \ No newline at end of file diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/WizardProjectsImportPage.java b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/WizardProjectsImportPage.java index 46f7570b782f..647902da70df 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/WizardProjectsImportPage.java +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/WizardProjectsImportPage.java @@ -321,6 +321,8 @@ public boolean select(Viewer viewer, Object parentElement, private static final String STORE_ARCHIVE_SELECTED = "WizardProjectsImportPage.STORE_ARCHIVE_SELECTED"; //$NON-NLS-1$ + private static final String STORE_SKIP_DOT_FOLDERS = "WizardProjectsImportPage.STORE_SKIP_DOT_FOLDERS"; //$NON-NLS-1$ + private Combo directoryPathField; private CheckboxTreeViewer projectsList; @@ -341,6 +343,10 @@ public boolean select(Viewer viewer, Object parentElement, private boolean hideConflictingProjects = false; + private Button skipDotFoldersCheckbox; + + private boolean skipDotFolders = true; + private ProjectRecord[] selectedProjects = new ProjectRecord[0]; // Keep track of the directory that we browsed to last time @@ -505,6 +511,22 @@ public void widgetSelected(SelectionEvent e) { } })); Dialog.applyDialogFont(hideConflictingProjectsCheckbox); + + skipDotFoldersCheckbox = new Button(optionsGroup, SWT.CHECK); + skipDotFoldersCheckbox.setText(DataTransferMessages.WizardProjectsImportPage_skipDotFolders); + skipDotFoldersCheckbox.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + skipDotFoldersCheckbox.setSelection(skipDotFolders); + skipDotFoldersCheckbox.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + skipDotFolders = skipDotFoldersCheckbox.getSelection(); + if (projectFromDirectoryRadio.getSelection()) { + updateProjectsListAndPreventFocusLostHandling(directoryPathField.getText().trim(), true); + } else { + updateProjectsListAndPreventFocusLostHandling(archivePathField.getText().trim(), true); + } + } + }); } /** @@ -940,7 +962,7 @@ else if (dirSelected && directory.isDirectory()) { Collection files = new ArrayList<>(); if (!collectProjectFilesFromDirectory(files, directory, - null, nestedProjects, monitor)) { + null, nestedProjects, skipDotFolders, monitor)) { return; } Iterator filesIterator3 = files.iterator(); @@ -1064,12 +1086,14 @@ private TarFile getSpecifiedTarSourceFile(String fileName) { * Set of canonical paths of directories, used as recursion guard * @param nestedProjects * whether to look for nested projects + * @param skipDotFolders + * whether to skip folders starting with '.' (e.g. .git) * @param monitor * The monitor to report to * @return boolean true if the operation was completed. */ static boolean collectProjectFilesFromDirectory(Collection files, File directory, - Set directoriesVisited, boolean nestedProjects, IProgressMonitor monitor) { + Set directoriesVisited, boolean nestedProjects, boolean skipDotFolders, IProgressMonitor monitor) { if (monitor.isCanceled()) { return false; @@ -1110,20 +1134,24 @@ static boolean collectProjectFilesFromDirectory(Collection files, File dir // no project description found or search for nested projects enabled, // so recurse into sub-directories for (File dir : directories) { - if (!dir.getName().equals(METADATA_FOLDER)) { - try { - String canonicalPath = dir.getCanonicalPath(); - if (!directoriesVisited.add(canonicalPath)) { - // already been here --> do not recurse - continue; - } - } catch (IOException exception) { - StatusManager.getManager().handle(StatusUtil.newError(exception)); - + if (dir.getName().equals(METADATA_FOLDER)) { + continue; + } + if (skipDotFolders && dir.getName().startsWith(".")) { //$NON-NLS-1$ + continue; + } + try { + String canonicalPath = dir.getCanonicalPath(); + if (!directoriesVisited.add(canonicalPath)) { + // already been here --> do not recurse + continue; } - collectProjectFilesFromDirectory(files, dir, - directoriesVisited, nestedProjects, monitor); + } catch (IOException exception) { + StatusManager.getManager().handle(StatusUtil.newError(exception)); + } + collectProjectFilesFromDirectory(files, dir, + directoriesVisited, nestedProjects, skipDotFolders, monitor); } return true; } @@ -1565,6 +1593,12 @@ public void restoreWidgetValues() { // trigger a selection event for the button to make sure the filter is set // properly at page creation hideConflictingProjectsCheckbox.notifyListeners(SWT.Selection, new Event()); + + // checkbox + if (settings.get(STORE_SKIP_DOT_FOLDERS) != null) { + skipDotFolders = settings.getBoolean(STORE_SKIP_DOT_FOLDERS); + } + skipDotFoldersCheckbox.setSelection(skipDotFolders); } // Second, check to see if we don't have an initial path, @@ -1644,6 +1678,8 @@ public void saveWidgetValues() { settings.put(STORE_CLOSE_CREATED_PROJECTS_ID, closeProjectsCheckbox.getSelection()); settings.put(STORE_HIDE_CONFLICTING_PROJECTS_ID, hideConflictingProjectsCheckbox.getSelection()); + + settings.put(STORE_SKIP_DOT_FOLDERS, skipDotFoldersCheckbox.getSelection()); } } @@ -1674,6 +1710,15 @@ public Button getNestedProjectsCheckbox() { return nestedProjectsCheckbox; } + /** + * Method used for test suite. + * + * @return Button skip dot folders checkbox + */ + public Button getSkipDotFoldersCheckbox() { + return skipDotFoldersCheckbox; + } + @Override public void handleEvent(Event event) { } diff --git a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/messages.properties b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/messages.properties index 1eeaeb3f3b3c..9c55a28d3cd4 100644 --- a/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/messages.properties +++ b/bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/messages.properties @@ -112,6 +112,7 @@ WizardProjectsImportPage_invalidProjectName=Invalid Project WizardProjectsImportPage_projectLabel={0} ({1}) WizardProjectsImportPage_hideExistingProjects=H&ide projects that already exist in the workspace WizardProjectsImportPage_closeProjectsAfterImport=Cl&ose newly imported projects upon completion +WizardProjectsImportPage_skipDotFolders=S&kip folders starting with '.' (e.g. .git) # --- Export Wizards --- DataTransfer_export = Export @@ -213,4 +214,5 @@ SmartImportWizardPage_incompleteExpand_title=Incomplete expansion SmartImportWizardPage_incompleteExpand_message=Archive expansion didn''t complete successfully. It''s recommend that you clean directory {0} and try importing again. SmartImportWizardPage_selectAtLeastOneFolderToOpenAsProject=Select at least one folder to import as project. SmartImportWizardPage_showOtherSpecializedImportWizard=Show other specialized import wizards -SmartImportWizardPage_closeProjectsAfterImport=Cl&ose newly imported projects upon completion \ No newline at end of file +SmartImportWizardPage_closeProjectsAfterImport=Cl&ose newly imported projects upon completion +SmartImportWizardPage_skipDotFolders=S&kip folders starting with '.' (e.g. .git) \ No newline at end of file diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/datatransfer/ImportExistingProjectsWizardTest.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/datatransfer/ImportExistingProjectsWizardTest.java index dc573e1f4179..67b9e90b0330 100644 --- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/datatransfer/ImportExistingProjectsWizardTest.java +++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/datatransfer/ImportExistingProjectsWizardTest.java @@ -32,6 +32,7 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.URL; +import java.nio.file.Files; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -53,6 +54,7 @@ import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.internal.WorkbenchPlugin; import org.eclipse.ui.internal.dialogs.ImportExportWizard; @@ -1221,4 +1223,78 @@ public void test23DoNotShowProjectwithSameNameForZipImport() throws CoreExceptio } } + @Test + public void test24CollectProjectFilesSkipsDotFoldersWhenEnabled() throws IOException { + File tempDir = Files.createTempDirectory("skipDotFolderTest").toFile(); + try { + // Create a normal project + File normalProject = new File(tempDir, "normalProject"); + normalProject.mkdirs(); + Files.writeString(new File(normalProject, ".project").toPath(), + "normalProject"); + + // Create a project inside a .git folder + File dotGitFolder = new File(tempDir, ".git"); + File hiddenProject = new File(dotGitFolder, "modules/hiddenProject"); + hiddenProject.mkdirs(); + Files.writeString(new File(hiddenProject, ".project").toPath(), + "hiddenProject"); + + // With skipDotFolders=true (default), only normalProject should be found + WizardProjectsImportPage wpip = getNewWizard(); + wpip.getProjectFromDirectoryRadio().setSelection(true); + wpip.getNestedProjectsCheckbox().setSelection(true); + // skipDotFolders defaults to true + wpip.updateProjectsList(tempDir.getAbsolutePath()); + + ProjectRecord[] selectedProjects = wpip.getProjectRecords(); + ArrayList projectNames = new ArrayList<>(); + for (ProjectRecord record : selectedProjects) { + projectNames.add(record.getProjectName()); + } + assertTrue("normalProject should be found", projectNames.contains("normalProject")); + assertFalse("hiddenProject inside .git should be skipped", projectNames.contains("hiddenProject")); + } finally { + FileSystemHelper.clear(tempDir); + } + } + + @Test + public void test25CollectProjectFilesFindsProjectsInDotFoldersWhenDisabled() throws IOException { + File tempDir = Files.createTempDirectory("noSkipDotFolderTest").toFile(); + try { + // Create a normal project + File normalProject = new File(tempDir, "normalProject"); + normalProject.mkdirs(); + Files.writeString(new File(normalProject, ".project").toPath(), + "normalProject"); + + // Create a project inside a .git folder + File dotGitFolder = new File(tempDir, ".git"); + File hiddenProject = new File(dotGitFolder, "hiddenProject"); + hiddenProject.mkdirs(); + Files.writeString(new File(hiddenProject, ".project").toPath(), + "hiddenProject"); + + // With skipDotFolders=false, both projects should be found + WizardProjectsImportPage wpip = getNewWizard(); + wpip.getProjectFromDirectoryRadio().setSelection(true); + wpip.getNestedProjectsCheckbox().setSelection(true); + wpip.getSkipDotFoldersCheckbox().setSelection(false); + wpip.getSkipDotFoldersCheckbox().notifyListeners(SWT.Selection, new Event()); + wpip.updateProjectsList(tempDir.getAbsolutePath()); + + ProjectRecord[] selectedProjects = wpip.getProjectRecords(); + ArrayList projectNames = new ArrayList<>(); + for (ProjectRecord record : selectedProjects) { + projectNames.add(record.getProjectName()); + } + assertTrue("normalProject should be found", projectNames.contains("normalProject")); + assertTrue("hiddenProject inside .git should be found when skip is disabled", + projectNames.contains("hiddenProject")); + } finally { + FileSystemHelper.clear(tempDir); + } + } + } 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 7f013fc26188..f2ccbfe1d088 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 @@ -35,6 +35,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -71,6 +72,7 @@ import org.eclipse.ui.IWorkingSetManager; import org.eclipse.ui.dialogs.FilteredTree; import org.eclipse.ui.internal.WorkbenchPlugin; +import org.eclipse.ui.internal.wizards.datatransfer.SmartImportJob; import org.eclipse.ui.internal.wizards.datatransfer.SmartImportRootWizardPage; import org.eclipse.ui.internal.wizards.datatransfer.SmartImportWizard; import org.eclipse.ui.tests.TestPlugin; @@ -513,4 +515,63 @@ private static Combo getComboWithSelection(String selection, Composite parent) { } return null; } + + @Test + public void testSmartImportSkipsDotFolders() throws Exception { + // Create a temp directory with a project inside a .git folder + java.nio.file.Path tempDir = Files.createTempDirectory("smartImportSkipDotTest"); + try { + // Create a normal project with ImportMe marker file + File normalProject = new File(tempDir.toFile(), "normalProject"); + normalProject.mkdirs(); + new File(normalProject, "importme").createNewFile(); + + // Create a project inside a .git folder (should be skipped) + File dotGitFolder = new File(tempDir.toFile(), ".git"); + File hiddenProject = new File(dotGitFolder, "hiddenProject"); + hiddenProject.mkdirs(); + new File(hiddenProject, "importme").createNewFile(); + + SmartImportWizard wizard = new SmartImportWizard(); + wizard.setInitialImportSource(tempDir.toFile()); + proceedSmartImportWizard(wizard); + + IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); + for (IProject project : projects) { + assertFalse("Project inside .git folder should not be imported", + project.getLocation().toFile().getAbsolutePath().contains(".git")); + } + } finally { + org.eclipse.core.tests.harness.FileSystemHelper.clear(tempDir.toFile()); + } + } + + @Test + public void testSmartImportJobSkipsDotFoldersInProposals() throws Exception { + java.nio.file.Path tempDir = Files.createTempDirectory("smartImportProposalTest"); + try { + // Create a normal Eclipse project + File normalProject = new File(tempDir.toFile(), "normalProject"); + normalProject.mkdirs(); + Files.writeString(new File(normalProject, ".project").toPath(), + "normalProject"); + + // Create an Eclipse project inside a .git folder + File dotGitFolder = new File(tempDir.toFile(), ".git"); + File hiddenProject = new File(dotGitFolder, "modules/hiddenProject"); + hiddenProject.mkdirs(); + Files.writeString(new File(hiddenProject, ".project").toPath(), + "hiddenProject"); + + // Default skipDotFolders=true: .git projects should be filtered out + SmartImportJob jobSkip = new SmartImportJob(tempDir.toFile(), Collections.emptySet(), true, true); + Map proposalsSkip = jobSkip.getImportProposals(new NullProgressMonitor()); + for (File proposed : proposalsSkip.keySet()) { + assertFalse("Proposal inside .git folder should be filtered out by default", + proposed.getAbsolutePath().contains(".git")); + } + } finally { + org.eclipse.core.tests.harness.FileSystemHelper.clear(tempDir.toFile()); + } + } }