diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java index f1b404cf443..75fe6699486 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java @@ -73,6 +73,7 @@ import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; import org.eclipse.core.runtime.preferences.InstanceScope; @@ -1007,11 +1008,27 @@ public ProjectDescription read(IProject target, boolean creation) throws CoreExc return description; } if (!descriptionStore.fetchInfo().exists()) { - String msg = NLS.bind(Messages.resources_missingProjectMeta, target.getName()); - throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, target.getFullPath(), msg, null); + // .project file is missing, try to restore from private metadata + ProjectDescription restoredDescription = new ProjectDescription(); + restoredDescription.setName(target.getName()); + getWorkspace().getMetaArea().readPrivateDescription(target, restoredDescription); + + // Check if we have useful data to restore (natures or build spec) + if (restoredDescription.getNatureIds().length > 0 || restoredDescription.getBuildSpec(false).length > 0) { + // We can restore useful data - use this description and log a warning + description = restoredDescription; + String msg = NLS.bind(Messages.resources_missingProjectMetaRepaired, target.getName()); + Policy.log(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, msg)); + // The description will be returned and should be written by the caller + } else { + // No useful data to restore - throw the original error + String msg = NLS.bind(Messages.resources_missingProjectMeta, target.getName()); + throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, target.getFullPath(), msg, null); + } + } else { + String msg = NLS.bind(Messages.resources_readProjectMeta, target.getName()); + error = new ResourceException(IResourceStatus.FAILED_READ_METADATA, target.getFullPath(), msg, e); } - String msg = NLS.bind(Messages.resources_readProjectMeta, target.getName()); - error = new ResourceException(IResourceStatus.FAILED_READ_METADATA, target.getFullPath(), msg, e); } catch (IOException ex) { Policy.log(ex); } diff --git a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IProjectTest.java b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IProjectTest.java index d1692c05b5a..6eec3c3e010 100644 --- a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IProjectTest.java +++ b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IProjectTest.java @@ -2572,4 +2572,68 @@ public void testProjectDeletion_Bug347220() throws CoreException { monitor.assertUsedUp(); } + /** + * Tests that a project with a missing .project file can be opened + * if it has natures and buildspec saved in the private metadata area. + * See https://github.com/eclipse-platform/eclipse.platform/issues/2272 + */ + @Test + public void testOpenProjectWithMissingDotProjectButPrivateMetadata() throws CoreException { + IProject project = getWorkspace().getRoot().getProject(createUniqueString()); + monitor.prepare(); + IProjectDescription description = getWorkspace().newProjectDescription(project.getName()); + description.setNatureIds(new String[] { NATURE_SIMPLE }); + project.create(description, monitor); + monitor.assertUsedUp(); + + monitor.prepare(); + project.open(monitor); + monitor.assertUsedUp(); + assertTrue("1.0", project.isOpen()); + assertTrue("1.1", project.hasNature(NATURE_SIMPLE)); + + // Close the project + monitor.prepare(); + project.close(monitor); + monitor.assertUsedUp(); + assertFalse("2.0", project.isOpen()); + + // Delete the .project file from disk + IFile dotProjectFile = project.getFile(IProjectDescription.DESCRIPTION_FILE_NAME); + java.io.File dotProjectOnDisk = dotProjectFile.getLocation().toFile(); + assertTrue("3.0", dotProjectOnDisk.exists()); + assertTrue("3.1", dotProjectOnDisk.delete()); + assertFalse("3.2", dotProjectOnDisk.exists()); + + // Refresh the project to detect the missing .project file + monitor.prepare(); + project.refreshLocal(IResource.DEPTH_ONE, monitor); + monitor.assertUsedUp(); + + // Now try to open the project - it should succeed because private metadata exists + monitor.prepare(); + project.open(monitor); + monitor.assertUsedUp(); + assertTrue("4.0", project.isOpen()); + + // Verify that the nature was restored from private metadata + assertTrue("5.0", project.hasNature(NATURE_SIMPLE)); + + // Trigger a workspace save to write the restored .project file + monitor.prepare(); + getWorkspace().save(true, monitor); + monitor.assertUsedUp(); + + // Verify that the .project file was recreated + monitor.prepare(); + project.refreshLocal(IResource.DEPTH_ONE, monitor); + monitor.assertUsedUp(); + assertTrue("6.0", dotProjectFile.exists()); + + // Clean up + monitor.prepare(); + project.delete(true, monitor); + monitor.assertUsedUp(); + } + }