Skip to content

Commit 6be1015

Browse files
committed
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 #3858
1 parent ed75400 commit 6be1015

File tree

9 files changed

+269
-20
lines changed

9 files changed

+269
-20
lines changed

bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/DataTransferMessages.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ public class DataTransferMessages extends NLS {
109109
public static String WizardProjectsImportPage_projectLabel;
110110
public static String WizardProjectsImportPage_hideExistingProjects;
111111
public static String WizardProjectsImportPage_closeProjectsAfterImport;
112+
public static String WizardProjectsImportPage_skipDotFolders;
112113
public static String WizardProjectsImportPage_invalidProjectName;
113114

114115
// --- Export Wizards ---
@@ -186,6 +187,7 @@ public class DataTransferMessages extends NLS {
186187
public static String SmartImportWizardPage_selectAtLeastOneFolderToOpenAsProject;
187188
public static String SmartImportWizardPage_showOtherSpecializedImportWizard;
188189
public static String SmartImportWizardPage_closeProjectsAfterImport;
190+
public static String SmartImportWizardPage_skipDotFolders;
189191

190192
public static String SmartImportJob_discardRootProject_title;
191193
public static String SmartImportJob_discardRootProject_description;

bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/EclipseProjectConfigurator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public Set<File> findConfigurableLocations(File root, IProgressMonitor monitor)
4444
Set<File> projectFiles = new LinkedHashSet<>();
4545
Set<String> visitedDirectories = new HashSet<>();
4646
WizardProjectsImportPage.collectProjectFilesFromDirectory(projectFiles, root, visitedDirectories, true,
47-
monitor);
47+
false, monitor);
4848
Set<File> res = new LinkedHashSet<>();
4949
for (File projectFile : projectFiles) {
5050
res.add(projectFile.getParentFile());

bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportJob.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public class SmartImportJob extends Job {
7474
private boolean deepChildrenDetection;
7575
private final boolean configureProjects;
7676
private boolean closeProjectsAfterImport;
77+
private boolean skipDotFolders = true;
7778
private boolean reconfigureEclipseProjects;
7879
private IWorkingSet[] workingSets;
7980

@@ -347,6 +348,9 @@ private Set<IProject> searchAndImportChildrenProjectsRecursively(final IContaine
347348
final Set<IProject> res = Collections.synchronizedSet(new HashSet<>());
348349
for (IResource childResource : parentContainer.members()) {
349350
if (childResource.getType() == IResource.FOLDER && !childResource.isDerived()) {
351+
if (skipDotFolders && childResource.getName().startsWith(".")) { //$NON-NLS-1$
352+
continue;
353+
}
350354
IPath location = childResource.getLocation();
351355
if (location == null) {
352356
continue;
@@ -530,6 +534,18 @@ private Set<IPath> toPathSet(Set<? extends IContainer> resources) {
530534
return res;
531535
}
532536

537+
private static boolean isInsideDotFolder(File file) {
538+
File current = file;
539+
while (current != null) {
540+
String name = current.getName();
541+
if (!name.isEmpty() && name.startsWith(".")) { //$NON-NLS-1$
542+
return true;
543+
}
544+
current = current.getParentFile();
545+
}
546+
return false;
547+
}
548+
533549
/**
534550
* @param refreshMode One {@link IResource#BACKGROUND_REFRESH} for background refresh, or {@link IResource#NONE} for immediate refresh
535551
*/
@@ -684,6 +700,9 @@ public Map<File, List<ProjectConfigurator>> getImportProposals(IProgressMonitor
684700
for (ProjectConfigurator configurator : activeConfigurators) {
685701
configurator.removeDirtyDirectories(res);
686702
}
703+
if (this.skipDotFolders) {
704+
res.keySet().removeIf(SmartImportJob::isInsideDotFolder);
705+
}
687706
this.importProposals = res;
688707
}
689708
return this.importProposals;
@@ -723,6 +742,22 @@ void setCloseProjectsAfterImport(boolean closeProjectsAfterImport) {
723742
this.closeProjectsAfterImport = closeProjectsAfterImport;
724743
}
725744

745+
/**
746+
* @param skipDotFolders
747+
* if true, folders starting with '.' (e.g. .git) are skipped
748+
* during project scanning
749+
*/
750+
void setSkipDotFolders(boolean skipDotFolders) {
751+
this.skipDotFolders = skipDotFolders;
752+
}
753+
754+
/**
755+
* @return whether folders starting with '.' are skipped during scanning
756+
*/
757+
public boolean isSkipDotFolders() {
758+
return this.skipDotFolders;
759+
}
760+
726761
/**
727762
* Forget the initial import proposals.
728763
*/

bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportRootWizardPage.java

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ public class SmartImportRootWizardPage extends WizardPage {
120120

121121
private static final String STORE_CONFIGURE_NATURES = "SmartImportRootWizardPage.STORE_CONFIGURE_NATURES"; //$NON-NLS-1$
122122

123+
private static final String STORE_SKIP_DOT_FOLDERS = "SmartImportRootWizardPage.STORE_SKIP_DOT_FOLDERS"; //$NON-NLS-1$
124+
123125
// Root
124126
private File selection;
125127
private Combo rootDirectoryText;
@@ -135,6 +137,7 @@ public class SmartImportRootWizardPage extends WizardPage {
135137
private boolean closeProjectsAfterImport = false;
136138
private boolean detectNestedProjects = true;
137139
private boolean configureProjects = true;
140+
private boolean skipDotFolders = true;
138141
// Working sets
139142
private Set<IWorkingSet> workingSets;
140143
private WorkingSetGroup workingSetsGroup;
@@ -475,6 +478,19 @@ public void widgetSelected(SelectionEvent e) {
475478
* Creates the UI elements for the import options
476479
*/
477480
private void createConfigurationOptions(Composite parent) {
481+
GridData layoutData = new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1);
482+
final Button skipDotFoldersCheckbox = new Button(parent, SWT.CHECK);
483+
skipDotFoldersCheckbox.setText(DataTransferMessages.SmartImportWizardPage_skipDotFolders);
484+
skipDotFoldersCheckbox.setLayoutData(layoutData);
485+
skipDotFoldersCheckbox.setSelection(this.skipDotFolders);
486+
skipDotFoldersCheckbox.addSelectionListener(new SelectionAdapter() {
487+
@Override
488+
public void widgetSelected(SelectionEvent e) {
489+
SmartImportRootWizardPage.this.skipDotFolders = skipDotFoldersCheckbox.getSelection();
490+
refreshProposals();
491+
}
492+
});
493+
478494
Button closeProjectsCheckbox = new Button(parent, SWT.CHECK);
479495
closeProjectsCheckbox.setText(DataTransferMessages.SmartImportWizardPage_closeProjectsAfterImport);
480496
closeProjectsCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1));
@@ -508,10 +524,9 @@ public void widgetSelected(SelectionEvent e) {
508524
DataTransferMessages.SmartImportWizardPage_availableDetectors_title, message.toString());
509525
}
510526
});
511-
GridData layoutData = new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1);
512527
final Button detectNestedProjectsCheckbox = new Button(parent, SWT.CHECK);
513528
detectNestedProjectsCheckbox.setText(DataTransferMessages.SmartImportWizardPage_detectNestedProjects);
514-
detectNestedProjectsCheckbox.setLayoutData(layoutData);
529+
detectNestedProjectsCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1));
515530
detectNestedProjectsCheckbox.setSelection(this.detectNestedProjects);
516531
detectNestedProjectsCheckbox.addSelectionListener(new SelectionAdapter() {
517532
@Override
@@ -523,7 +538,7 @@ public void widgetSelected(SelectionEvent e) {
523538

524539
final Button configureProjectsCheckbox = new Button(parent, SWT.CHECK);
525540
configureProjectsCheckbox.setText(DataTransferMessages.SmartImportWizardPage_configureProjects);
526-
configureProjectsCheckbox.setLayoutData(layoutData);
541+
configureProjectsCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1));
527542
configureProjectsCheckbox.setSelection(this.configureProjects);
528543
configureProjectsCheckbox.addSelectionListener(new SelectionAdapter() {
529544
@Override
@@ -825,6 +840,13 @@ boolean isCloseProjectsAfterImport() {
825840
return closeProjectsAfterImport;
826841
}
827842

843+
/**
844+
* @return whether folders starting with '.' should be skipped during scanning
845+
*/
846+
boolean isSkipDotFolders() {
847+
return skipDotFolders;
848+
}
849+
828850
private void refreshProposals() {
829851
stopAndDisconnectCurrentWork();
830852
this.potentialProjects = Collections.emptyMap();
@@ -956,6 +978,9 @@ private void loadWidgetStates() {
956978
closeProjectsAfterImport = dialogSettings.getBoolean(STORE_CLOSE_IMPORTED);
957979
detectNestedProjects = dialogSettings.getBoolean(STORE_NESTED_PROJECTS);
958980
configureProjects = dialogSettings.getBoolean(STORE_CONFIGURE_NATURES);
981+
if (dialogSettings.get(STORE_SKIP_DOT_FOLDERS) != null) {
982+
skipDotFolders = dialogSettings.getBoolean(STORE_SKIP_DOT_FOLDERS);
983+
}
959984
}
960985
}
961986

@@ -969,6 +994,7 @@ private void saveWidgetStates() {
969994
dialogSettings.put(STORE_CLOSE_IMPORTED, closeProjectsAfterImport);
970995
dialogSettings.put(STORE_NESTED_PROJECTS, detectNestedProjects);
971996
dialogSettings.put(STORE_CONFIGURE_NATURES, configureProjects);
997+
dialogSettings.put(STORE_SKIP_DOT_FOLDERS, skipDotFolders);
972998
}
973999
}
9741000

bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/SmartImportWizard.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ public SmartImportJob createOrGetConfiguredImportJob() {
288288
// WS change automatically
289289
this.easymportJob.setWorkingSets(projectRootPage.getSelectedWorkingSets());
290290
this.easymportJob.setCloseProjectsAfterImport(projectRootPage.isCloseProjectsAfterImport());
291+
this.easymportJob.setSkipDotFolders(projectRootPage.isSkipDotFolders());
291292

292293
return this.easymportJob;
293294
}
@@ -337,7 +338,8 @@ private static boolean matchesPage(SmartImportJob job, SmartImportRootWizardPage
337338
boolean sameSource = jobRoot.equals(pageRoot)
338339
|| (isValidArchive(pageRoot) && getExpandDirectory(pageRoot).getAbsoluteFile().equals(jobRoot));
339340
return sameSource && job.isDetectNestedProjects() == page.isDetectNestedProject()
340-
&& job.isConfigureProjects() == page.isConfigureProjects();
341+
&& job.isConfigureProjects() == page.isConfigureProjects()
342+
&& job.isSkipDotFolders() == page.isSkipDotFolders();
341343
}
342344

343345
}

bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/WizardProjectsImportPage.java

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,8 @@ public boolean select(Viewer viewer, Object parentElement,
321321

322322
private static final String STORE_ARCHIVE_SELECTED = "WizardProjectsImportPage.STORE_ARCHIVE_SELECTED"; //$NON-NLS-1$
323323

324+
private static final String STORE_SKIP_DOT_FOLDERS = "WizardProjectsImportPage.STORE_SKIP_DOT_FOLDERS"; //$NON-NLS-1$
325+
324326
private Combo directoryPathField;
325327

326328
private CheckboxTreeViewer projectsList;
@@ -341,6 +343,10 @@ public boolean select(Viewer viewer, Object parentElement,
341343

342344
private boolean hideConflictingProjects = false;
343345

346+
private Button skipDotFoldersCheckbox;
347+
348+
private boolean skipDotFolders = true;
349+
344350
private ProjectRecord[] selectedProjects = new ProjectRecord[0];
345351

346352
// Keep track of the directory that we browsed to last time
@@ -505,6 +511,22 @@ public void widgetSelected(SelectionEvent e) {
505511
}
506512
}));
507513
Dialog.applyDialogFont(hideConflictingProjectsCheckbox);
514+
515+
skipDotFoldersCheckbox = new Button(optionsGroup, SWT.CHECK);
516+
skipDotFoldersCheckbox.setText(DataTransferMessages.WizardProjectsImportPage_skipDotFolders);
517+
skipDotFoldersCheckbox.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
518+
skipDotFoldersCheckbox.setSelection(skipDotFolders);
519+
skipDotFoldersCheckbox.addSelectionListener(new SelectionAdapter() {
520+
@Override
521+
public void widgetSelected(SelectionEvent e) {
522+
skipDotFolders = skipDotFoldersCheckbox.getSelection();
523+
if (projectFromDirectoryRadio.getSelection()) {
524+
updateProjectsListAndPreventFocusLostHandling(directoryPathField.getText().trim(), true);
525+
} else {
526+
updateProjectsListAndPreventFocusLostHandling(archivePathField.getText().trim(), true);
527+
}
528+
}
529+
});
508530
}
509531

510532
/**
@@ -940,7 +962,7 @@ else if (dirSelected && directory.isDirectory()) {
940962

941963
Collection<File> files = new ArrayList<>();
942964
if (!collectProjectFilesFromDirectory(files, directory,
943-
null, nestedProjects, monitor)) {
965+
null, nestedProjects, skipDotFolders, monitor)) {
944966
return;
945967
}
946968
Iterator<File> filesIterator3 = files.iterator();
@@ -1064,12 +1086,14 @@ private TarFile getSpecifiedTarSourceFile(String fileName) {
10641086
* Set of canonical paths of directories, used as recursion guard
10651087
* @param nestedProjects
10661088
* whether to look for nested projects
1089+
* @param skipDotFolders
1090+
* whether to skip folders starting with '.' (e.g. .git)
10671091
* @param monitor
10681092
* The monitor to report to
10691093
* @return boolean <code>true</code> if the operation was completed.
10701094
*/
10711095
static boolean collectProjectFilesFromDirectory(Collection<File> files, File directory,
1072-
Set<String> directoriesVisited, boolean nestedProjects, IProgressMonitor monitor) {
1096+
Set<String> directoriesVisited, boolean nestedProjects, boolean skipDotFolders, IProgressMonitor monitor) {
10731097

10741098
if (monitor.isCanceled()) {
10751099
return false;
@@ -1110,20 +1134,24 @@ static boolean collectProjectFilesFromDirectory(Collection<File> files, File dir
11101134
// no project description found or search for nested projects enabled,
11111135
// so recurse into sub-directories
11121136
for (File dir : directories) {
1113-
if (!dir.getName().equals(METADATA_FOLDER)) {
1114-
try {
1115-
String canonicalPath = dir.getCanonicalPath();
1116-
if (!directoriesVisited.add(canonicalPath)) {
1117-
// already been here --> do not recurse
1118-
continue;
1119-
}
1120-
} catch (IOException exception) {
1121-
StatusManager.getManager().handle(StatusUtil.newError(exception));
1122-
1137+
if (dir.getName().equals(METADATA_FOLDER)) {
1138+
continue;
1139+
}
1140+
if (skipDotFolders && dir.getName().startsWith(".")) { //$NON-NLS-1$
1141+
continue;
1142+
}
1143+
try {
1144+
String canonicalPath = dir.getCanonicalPath();
1145+
if (!directoriesVisited.add(canonicalPath)) {
1146+
// already been here --> do not recurse
1147+
continue;
11231148
}
1124-
collectProjectFilesFromDirectory(files, dir,
1125-
directoriesVisited, nestedProjects, monitor);
1149+
} catch (IOException exception) {
1150+
StatusManager.getManager().handle(StatusUtil.newError(exception));
1151+
11261152
}
1153+
collectProjectFilesFromDirectory(files, dir,
1154+
directoriesVisited, nestedProjects, skipDotFolders, monitor);
11271155
}
11281156
return true;
11291157
}
@@ -1565,6 +1593,12 @@ public void restoreWidgetValues() {
15651593
// trigger a selection event for the button to make sure the filter is set
15661594
// properly at page creation
15671595
hideConflictingProjectsCheckbox.notifyListeners(SWT.Selection, new Event());
1596+
1597+
// checkbox
1598+
if (settings.get(STORE_SKIP_DOT_FOLDERS) != null) {
1599+
skipDotFolders = settings.getBoolean(STORE_SKIP_DOT_FOLDERS);
1600+
}
1601+
skipDotFoldersCheckbox.setSelection(skipDotFolders);
15681602
}
15691603

15701604
// Second, check to see if we don't have an initial path,
@@ -1644,6 +1678,8 @@ public void saveWidgetValues() {
16441678
settings.put(STORE_CLOSE_CREATED_PROJECTS_ID, closeProjectsCheckbox.getSelection());
16451679

16461680
settings.put(STORE_HIDE_CONFLICTING_PROJECTS_ID, hideConflictingProjectsCheckbox.getSelection());
1681+
1682+
settings.put(STORE_SKIP_DOT_FOLDERS, skipDotFoldersCheckbox.getSelection());
16471683
}
16481684
}
16491685

@@ -1674,6 +1710,15 @@ public Button getNestedProjectsCheckbox() {
16741710
return nestedProjectsCheckbox;
16751711
}
16761712

1713+
/**
1714+
* Method used for test suite.
1715+
*
1716+
* @return Button skip dot folders checkbox
1717+
*/
1718+
public Button getSkipDotFoldersCheckbox() {
1719+
return skipDotFoldersCheckbox;
1720+
}
1721+
16771722
@Override
16781723
public void handleEvent(Event event) {
16791724
}

bundles/org.eclipse.ui.ide/src/org/eclipse/ui/internal/wizards/datatransfer/messages.properties

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ WizardProjectsImportPage_invalidProjectName=Invalid Project
112112
WizardProjectsImportPage_projectLabel={0} ({1})
113113
WizardProjectsImportPage_hideExistingProjects=H&ide projects that already exist in the workspace
114114
WizardProjectsImportPage_closeProjectsAfterImport=Cl&ose newly imported projects upon completion
115+
WizardProjectsImportPage_skipDotFolders=S&kip folders starting with '.' (e.g. .git)
115116

116117
# --- Export Wizards ---
117118
DataTransfer_export = Export
@@ -213,4 +214,5 @@ SmartImportWizardPage_incompleteExpand_title=Incomplete expansion
213214
SmartImportWizardPage_incompleteExpand_message=Archive expansion didn''t complete successfully. It''s recommend that you clean directory {0} and try importing again.
214215
SmartImportWizardPage_selectAtLeastOneFolderToOpenAsProject=Select at least one folder to import as project.
215216
SmartImportWizardPage_showOtherSpecializedImportWizard=Show other specialized import wizards
216-
SmartImportWizardPage_closeProjectsAfterImport=Cl&ose newly imported projects upon completion
217+
SmartImportWizardPage_closeProjectsAfterImport=Cl&ose newly imported projects upon completion
218+
SmartImportWizardPage_skipDotFolders=S&kip folders starting with '.' (e.g. .git)

0 commit comments

Comments
 (0)