From 13b3dddea6832d74db280c368f9fe6c3ed8c0911 Mon Sep 17 00:00:00 2001 From: Simeon Andreev Date: Thu, 1 Jun 2023 11:29:19 +0300 Subject: [PATCH] Fix stale resource link flags after project re-open Whenever a project is closed, its resource tree is saved. This includes linked resources in the project. When the project is re-opened, link changes in the .project file are not reflected on the projects resource tree. The old resource tree is read, new information is stored in ProjectDescription.linkDescriptions, but the old linked resources are not touched. This change adjusts Project.open() and Project.close() to set resp. clear the M_LINK flag of linked resources in the project. Fixes: #470 Signed-off-by: Simeon Andreev --- .../core/internal/resources/Project.java | 39 +++++++ .../tests/resources/AllResourcesTests.java | 1 + .../tests/resources/ProjectLinksTest.java | 102 ++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/ProjectLinksTest.java 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 bc3291d227c..8620f020db8 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) { @@ -1446,6 +1455,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()); + } +}