diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java index 7d18057e692..88c0bbe99da 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java @@ -22,6 +22,7 @@ import java.net.URI; import java.util.*; +import java.util.function.Consumer; import org.eclipse.core.filesystem.*; import org.eclipse.core.internal.events.LifecycleEvent; import org.eclipse.core.internal.preferences.EclipsePreferences; @@ -199,6 +200,11 @@ public void close(IProgressMonitor monitor) throws CoreException { workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_CLOSE, this)); // flush the build order early in case there is a problem workspace.flushBuildOrder(); + // Clear the linked flag of linked resources, + // since changes to the links in .project while the project is closed + // are otherwise not reflected after re-opening the project. + // See: https://github.com/eclipse-platform/eclipse.platform/issues/470 + clearLinkFlags(); IProgressMonitor sub = subMonitor.newChild(49, SubMonitor.SUPPRESS_SUBTASK); IStatus saveStatus = workspace.getSaveManager().save(ISaveContext.PROJECT_SAVE, this, sub); internalClose(subMonitor.newChild(49)); @@ -1107,6 +1113,9 @@ public void open(int updateFlags, IProgressMonitor monitor) throws CoreException writeEncodingAfterOpen(monitor); encodingWritten = true; } + if (used) { + setLinkFlags(); + } //creation of this project may affect overlapping resources workspace.getAliasManager().updateAliases(this, getStore(), IResource.DEPTH_INFINITE, monitor); } catch (OperationCanceledException e) { @@ -1443,6 +1452,36 @@ public String getDefaultLineSeparator() { return System.lineSeparator(); } + /** + * Clears the {@link ICoreConstants#M_LINK} flag of linked resources. + */ + private void clearLinkFlags() { + modifyLinksResourceInfo(info -> info.clear(M_LINK)); + } + + /** + * Sets the {@link ICoreConstants#M_LINK} flag of linked resources. + */ + private void setLinkFlags() { + modifyLinksResourceInfo(info -> info.set(M_LINK)); + } + + private void modifyLinksResourceInfo(Consumer operation) { + ProjectDescription description = internalGetDescription(); + HashMap linkDescriptions = description.linkDescriptions; + if (linkDescriptions != null) { + for (LinkDescription linkDescription : linkDescriptions.values()) { + IFile linkFile = getFile(linkDescription.getProjectRelativePath()); + if (linkFile != null) { + ResourceInfo linkInfo = workspace.getResourceInfo(linkFile.getFullPath(), false, true); + if (linkInfo != null) { + operation.accept(linkInfo); + } + } + } + } + } + private static String getLineSeparatorFromPreferences(Preferences node) { try { // be careful looking up for our node so not to create any nodes as side effect diff --git a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/AllResourcesTests.java b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/AllResourcesTests.java index 26a9fa9f7e1..a0efa4229f5 100644 --- a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/AllResourcesTests.java +++ b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/AllResourcesTests.java @@ -27,6 +27,7 @@ LinkedResourceWithPathVariableTest.class, LinkedResourceSyncMoveAndCopyTest.class, MarkerSetTest.class, MarkerTest.class, NatureTest.class, NonLocalLinkedResourceTest.class, ProjectEncodingTest.class, ProjectOrderTest.class, + ProjectLinksTest.class, ProjectScopeTest.class, ProjectSnapshotTest.class, ResourceAttributeTest.class, ResourceURLTest.class, TeamPrivateMemberTest.class, WorkspaceTest.class }) public class AllResourcesTests { diff --git a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/ProjectLinksTest.java b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/ProjectLinksTest.java new file mode 100644 index 00000000000..15a16f9c3e0 --- /dev/null +++ b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/ProjectLinksTest.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2023 Simeon Andreev and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Simeon Andreev - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.tests.resources; + +import java.nio.file.*; +import java.util.List; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.IPath; + +/** + * + */ +public class ProjectLinksTest extends ResourceTest { + + private IProject project; + private Path tmpFolder; + private IPath tmpPath; + + @Override + protected void setUp() throws Exception { + super.setUp(); + tmpPath = getRandomLocation(); + tmpFolder = Paths.get(tmpPath.toOSString()); + Files.createDirectory(tmpFolder); + + IWorkspaceRoot root = getWorkspace().getRoot(); + project = root.getProject(getUniqueString()); + + project.create(getMonitor()); + project.open(getMonitor()); + project.refreshLocal(IResource.DEPTH_INFINITE, getMonitor()); + } + + @Override + protected void tearDown() throws Exception { + try { + Files.deleteIfExists(tmpFolder); + project.delete(true, getMonitor()); + } finally { + super.tearDown(); + } + } + + /** + * Tests that link information is updated after closing a project, deleting a + * link in the {@code .project} file and then opening the project. + */ + public void testCloseProjectDeleteLinksAndOpen_GH470() throws Exception { + IFile dotProject = project.getFile(".project"); + Path dotProjectPath = Paths.get(dotProject.getLocationURI()); + List dotProjectContentsWithoutLink = Files.readAllLines(dotProjectPath); + + String linkedFolderName = "test"; + IFolder folder = project.getFolder(linkedFolderName); + folder.createLink(tmpPath, IResource.NONE, getMonitor()); + project.refreshLocal(IResource.DEPTH_INFINITE, getMonitor()); + + assertTrue("Failed to create linked folder in test project", folder.isLinked()); + + project.close(getMonitor()); + + Files.write(dotProjectPath, dotProjectContentsWithoutLink); + + project.open(getMonitor()); + project.refreshLocal(IResource.DEPTH_INFINITE, getMonitor()); + + folder = project.getFolder(linkedFolderName); + assertFalse("Expected folder to not be linked after re-opening project", folder.isLinked()); + } + + /** + * Tests that link information is correct after closing a project and then + * opening the project. + */ + public void testCloseAndOpenProject() throws Exception { + String linkedFolderName = "test"; + IFolder folder = project.getFolder(linkedFolderName); + folder.createLink(tmpPath, IResource.NONE, getMonitor()); + project.refreshLocal(IResource.DEPTH_INFINITE, getMonitor()); + + assertTrue("Failed to create linked folder in test project", folder.isLinked()); + + project.close(getMonitor()); + + project.open(getMonitor()); + project.refreshLocal(IResource.DEPTH_INFINITE, getMonitor()); + + folder = project.getFolder(linkedFolderName); + assertTrue("Expected folder to be linked after re-opening project", folder.isLinked()); + } +}