Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This file documents all notable changes to https://github.com/devonfw/IDEasy[IDE

Release with new features and bugfixes:

* https://github.com/devonfw/IDEasy/issues/2142[#2142]: Move IDE-specific metadata (.idea, .vscode) out of workspace
Comment thread
quando632 marked this conversation as resolved.
* https://github.com/devonfw/IDEasy/issues/989[#989]: Allow expressions in template variable definitions

The full list of changes for this release can be found in https://github.com/devonfw/IDEasy/milestone/50?closed=1[milestone 2026.09.002].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.devonfw.tools.ide.log.IdeLogLevel;
import com.devonfw.tools.ide.migration.v2025.Mig202502001;
import com.devonfw.tools.ide.migration.v2025.Mig202510001;
import com.devonfw.tools.ide.migration.v2026.Mig202609002;
import com.devonfw.tools.ide.step.Step;
import com.devonfw.tools.ide.version.IdeVersion;
import com.devonfw.tools.ide.version.VersionIdentifier;
Expand All @@ -31,7 +32,7 @@ public class IdeMigrator implements IdeMigration {
public IdeMigrator() {

// migrations must be strictly in ascending order (from oldest to newest version)
this(List.of(new Mig202502001(), new Mig202510001()));
this(List.of(new Mig202502001(), new Mig202510001(), new Mig202609002()));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.devonfw.tools.ide.migration.v2026;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.io.FileAccess;
import com.devonfw.tools.ide.migration.IdeVersionMigration;

/**
* Migration to 2026.09.002. Moves the VSCode user-data folder ({@code .vscode/.userdata}) out of each workspace into the dedicated
* {@code $IDE_HOME/.ide/vscode/«workspace»/config} folder so workspaces stay clean and independent of the IDE being used. See
* <a href="https://github.com/devonfw/IDEasy/issues/2142">#2142</a>.
*/
public class Mig202609002 extends IdeVersionMigration {

private static final Logger LOG = LoggerFactory.getLogger(Mig202609002.class);

/**
* The constructor.
*/
public Mig202609002() {

super("2026.09.002");
}

@Override
public void run(IdeContext context) {

Path workspacesPath = context.getWorkspacesBasePath();
if (workspacesPath == null) {
return;
}
FileAccess fileAccess = context.getFileAccess();
Path vscodeMetaPath = context.getIdeHome().resolve(IdeContext.FOLDER_DOT_IDE).resolve("vscode");
List<Path> workspaces = fileAccess.listChildren(workspacesPath, Files::isDirectory);
for (Path workspace : workspaces) {
Path oldUserData = workspace.resolve(".vscode").resolve(".userdata");
if (fileAccess.isExpectedFolder(oldUserData)) {
Path target = vscodeMetaPath.resolve(workspace.getFileName().toString()).resolve("config");
if (Files.exists(target)) {
LOG.warn("Skipping migration of {} since target already exists: {}", oldUserData, target);
continue;
}
fileAccess.mkdirs(target.getParent());
fileAccess.move(oldUserData, target);
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ protected void configureToolArgs(ProcessContext pc, ProcessMode processMode, Lis
if (this.context.getSystemInfo().isWsl()) {
pc.withEnvVar("DONT_PROMPT_WSL_INSTALL", "1");
}
Path vsCodeConf = this.context.getWorkspacePath().resolve(".vscode/.userdata");
Path vsCodeConf = getIdeMetadataPath().resolve("config");
pc.addArg("--new-window");
pc.addArg("--user-data-dir=" + vsCodeConf);
Path vsCodeExtensionFolder = this.context.getIdeHome().resolve("plugins/vscode");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.devonfw.tools.ide.migration.v2026;

import java.nio.file.Path;

import org.junit.jupiter.api.Test;

import com.devonfw.tools.ide.context.AbstractIdeContextTest;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.context.IdeTestContext;
import com.devonfw.tools.ide.io.FileAccess;

/**
* Test of {@link Mig202609002}.
*/
class Mig202609002Test extends AbstractIdeContextTest {

/**
* Tests that an existing {@code .vscode/.userdata} folder is moved out of the workspace into {@code $IDE_HOME/.ide/vscode/«workspace»/config}.
*/
@Test
void testMovesVscodeUserDataOutOfWorkspace() {

// arrange
IdeTestContext context = newContext("vscode");
FileAccess fileAccess = context.getFileAccess();
Path workspace = context.getWorkspacePath();
Path oldUserData = workspace.resolve(".vscode").resolve(".userdata");
fileAccess.mkdirs(oldUserData);
fileAccess.writeFileContent("dummy", oldUserData.resolve("state.json"));
// act
new Mig202609002().run(context);
// assert
Path newConfig = context.getIdeHome().resolve(IdeContext.FOLDER_DOT_IDE).resolve("vscode").resolve(context.getWorkspaceName()).resolve("config");
assertThat(newConfig.resolve("state.json")).exists().hasContent("dummy");
assertThat(oldUserData).doesNotExist();
}

/**
* Tests that the migration is a no-op (and does not fail) when no {@code .vscode/.userdata} folder exists.
*/
@Test
void testDoesNothingWhenNoUserData() {

// arrange
IdeTestContext context = newContext("vscode");
Path vscodeMeta = context.getIdeHome().resolve(IdeContext.FOLDER_DOT_IDE).resolve("vscode");
// act
new Mig202609002().run(context);
// assert
assertThat(vscodeMeta).doesNotExist();
}

/**
* Tests that the migration skips a workspace whose target folder already exists, without failing and without overwriting the existing data.
*/
@Test
void testSkipsWhenTargetAlreadyExists() {

// arrange
IdeTestContext context = newContext("vscode");
FileAccess fileAccess = context.getFileAccess();
Path oldUserData = context.getWorkspacePath().resolve(".vscode").resolve(".userdata");
fileAccess.mkdirs(oldUserData);
fileAccess.writeFileContent("old", oldUserData.resolve("state.json"));
Path target = context.getIdeHome().resolve(IdeContext.FOLDER_DOT_IDE).resolve("vscode").resolve(context.getWorkspaceName()).resolve("config");
fileAccess.mkdirs(target);
fileAccess.writeFileContent("new", target.resolve("state.json"));
// act
new Mig202609002().run(context);
// assert
assertThat(oldUserData).exists();
assertThat(target.resolve("state.json")).exists().hasContent("new");
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.devonfw.tools.ide.tool.vscode;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
Expand All @@ -10,6 +11,7 @@
import org.junit.jupiter.api.Test;

import com.devonfw.tools.ide.context.AbstractIdeContextTest;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.context.IdeTestContext;
import com.devonfw.tools.ide.context.ProcessContextTestImpl;
import com.devonfw.tools.ide.environment.EnvironmentVariablesType;
Expand Down Expand Up @@ -152,6 +154,26 @@ void testConfigureToolArgsDoesNotSetWslEnvVarOnNonWsl() {
assertThat(pc.getEnvVar("DONT_PROMPT_WSL_INSTALL")).isNull();
}

/**
* Tests that {@link Vscode#configureToolArgs(ProcessContext, ProcessMode, List)} points {@code --user-data-dir} to the IDE metadata folder
* ({@code $IDE_HOME/.ide/vscode/«workspace»/config}) instead of a {@code .vscode} folder inside the workspace.
*/
@Test
void testConfigureToolArgsUsesIdeMetadataPathForUserData() {

// arrange
IdeTestContext context = newContext(PROJECT_VSCODE);
context.setSystemInfo(SystemInfoMock.LINUX_X64);
Vscode commandlet = new Vscode(context);
ArgCapturingProcessContext pc = new ArgCapturingProcessContext(context);
// act
commandlet.configureToolArgs(pc, ProcessMode.DEFAULT, List.of());
// assert
Path expectedUserData = context.getIdeHome().resolve(IdeContext.FOLDER_DOT_IDE).resolve("vscode").resolve(context.getWorkspaceName()).resolve("config");
assertThat(pc.capturedArgs).contains("--user-data-dir=" + expectedUserData);
assertThat(pc.capturedArgs).noneMatch(arg -> arg.contains(".vscode"));
}

/**
* Tests that {@code VSCODE_OPTIONS} is honoured by appending its tokens as additional command-line arguments when starting the IDE (analogue to the
* global {@code IDE_OPTIONS} used for IDEasy itself, see issue #788).
Expand Down Expand Up @@ -300,6 +322,26 @@ String getEnvVar(String key) {
}
}

/**
* {@link ProcessContextTestImpl} subclass that captures the CLI arguments added via {@link #addArg(String)} for test assertions.
*/
private static class ArgCapturingProcessContext extends ProcessContextTestImpl {

private final List<String> capturedArgs = new ArrayList<>();

private ArgCapturingProcessContext(IdeTestContext context) {

super(context);
}

@Override
public ProcessContext addArg(String arg) {

this.capturedArgs.add(arg);
return super.addArg(arg);
}
}

private void checkVscodiumInstallation(IdeTestContext context) {

assertThat(context.getSoftwarePath().resolve("vscode/bin/codium.cmd")).exists().hasContent("@echo test for windows");
Expand Down