From a122556747bc9fbb1e043119df66aa8554267667 Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Mon, 27 Apr 2026 13:16:31 +0200 Subject: [PATCH 01/26] WIP Commit --- .../commandlet/AbstractUpdateCommandlet.java | 31 +++++++++++++++++++ .../ide/commandlet/CreateCommandlet.java | 4 ++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java index 246bb8d55f..a980911222 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java @@ -99,6 +99,8 @@ protected void doRun() { if (!this.context.isSettingsRepositorySymlinkOrJunction() || this.context.isForceMode() || forcePull.isTrue()) { updateSettings(); + analyze_project(); // This will likely break when running "ide update" + } updateConf(); reloadContext(); @@ -108,6 +110,35 @@ protected void doRun() { createStartScripts(); } + private void analyze_project() { + String projectName = this.context.getProjectName(); + Path actualProjectPath; + // Check if the repository is a code repository containing a top-level settings folder + if (isCodeRepository() && Files.exists(this.context.getSettingsPath()) && Files.isDirectory(this.context.getSettingsPath())) { + actualProjectPath = this.context.getIdeRoot().resolve(projectName).resolve("workspaces/main/").resolve(projectName); + moveProject(this.context.getIdeHome(), actualProjectPath); + createSettingsLink(); + } else { + actualProjectPath = this.context.getIdeRoot(); + moveProject(this.context.getIdeHome(), actualProjectPath); + } + this.context.setIdeHome(actualProjectPath.resolve(projectName)); + + } + + private void moveProject(Path oldPath, Path newPath) { + try { + this.context.getFileAccess().copy(oldPath, newPath); + this.context.getFileAccess().delete(oldPath); + } catch (Exception e) { + LOG.error("Failed to move project from {} to {}. Please move it manually.", oldPath, newPath, e); + } + } + + private void createSettingsLink() { + + } + private void reloadContext() { ((AbstractIdeContext) this.context).reload(); diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java index deb031459d..dd38e4c9c6 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java @@ -56,7 +56,8 @@ public boolean isIdeHomeRequired() { protected void doRun() { String newProjectName = this.newProject.getValue(); - Path newProjectPath = this.context.getIdeRoot().resolve(newProjectName); + //Path newProjectPath = this.context.getIdeRoot().resolve(newProjectName); + Path newProjectPath = this.context.getIdeRoot().resolve("_ide/tmp/projects").resolve(newProjectName); LOG.info("Creating new IDEasy project in {}", newProjectPath); if (!this.context.getFileAccess().isEmptyDir(newProjectPath)) { @@ -69,6 +70,7 @@ protected void doRun() { this.context.setIdeHome(newProjectPath); this.context.verifyIdeMinVersion(true); super.doRun(); + newProjectPath = this.context.getIdeHome(); this.context.verifyIdeMinVersion(true); this.context.getFileAccess().writeFileContent(IdeVersion.getVersionString(), newProjectPath.resolve(IdeContext.FILE_SOFTWARE_VERSION)); IdeLogLevel.SUCCESS.log(LOG, "Successfully created new project '{}'.", newProjectName); From c497948298afad5c658f4b18c4023667b8967f64 Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Tue, 28 Apr 2026 10:44:25 +0200 Subject: [PATCH 02/26] Update logic --- .../commandlet/AbstractUpdateCommandlet.java | 45 ++++++++++++++++--- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java index a980911222..7e2264d8e7 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java @@ -20,6 +20,7 @@ import com.devonfw.tools.ide.git.GitUrl; import com.devonfw.tools.ide.git.repository.RepositoryCommandlet; import com.devonfw.tools.ide.io.FileAccess; +import com.devonfw.tools.ide.io.FileCopyMode; import com.devonfw.tools.ide.property.FlagProperty; import com.devonfw.tools.ide.property.StringProperty; import com.devonfw.tools.ide.step.Step; @@ -99,8 +100,12 @@ protected void doRun() { if (!this.context.isSettingsRepositorySymlinkOrJunction() || this.context.isForceMode() || forcePull.isTrue()) { updateSettings(); - analyze_project(); // This will likely break when running "ide update" + // Check if instance of create commandlet. Only then will we analyze the project + if (this instanceof CreateCommandlet) { + analyze_project(); + } + } updateConf(); reloadContext(); @@ -111,16 +116,42 @@ protected void doRun() { } private void analyze_project() { + + // Settings repository: ide.properties on top levels (or devon.properties (<- verify file name) for legacy users) + // Code repository: settings folder on top level with ide.properties inside (or devon.properties for legacy users) + System.out.println("Settings Path: " + this.context.getSettingsPath()); + String projectName = this.context.getProjectName(); - Path actualProjectPath; - // Check if the repository is a code repository containing a top-level settings folder - if (isCodeRepository() && Files.exists(this.context.getSettingsPath()) && Files.isDirectory(this.context.getSettingsPath())) { + Path actualProjectPath = null; + + //Check if a file called ide.properties or devon.properties in settingsPath + Path SettingsPath = this.context.getSettingsPath(); + if (Files.exists(SettingsPath.resolve("ide.properties")) || Files.exists(SettingsPath.resolve("devon.properties"))) { + // Repository is a settings repository + LOG.info("The repository seems to be a settings repository based on the presence of ide.properties or devon.properties on the top level."); + actualProjectPath = this.context.getIdeRoot(); + moveProject(this.context.getIdeHome(), actualProjectPath); + } else if (Files.exists(SettingsPath.resolve("settings/ide.properties")) || Files.exists(SettingsPath.resolve("settings/devon.properties"))) { + // Repository is a code repository + LOG.info("ide.properties or devon.properties (legacy) found in settings subfolder. This indicates a code repository with settings folder on the top level."); + // Move "settings" folder containing code in workspace/main actualProjectPath = this.context.getIdeRoot().resolve(projectName).resolve("workspaces/main/").resolve(projectName); + for (Path child : this.context.getFileAccess().listChildren(SettingsPath, f -> true)) { + System.out.println("Child: " + child); + moveProject(child, actualProjectPath.resolve(child.getFileName())); + } + moveProject(SettingsPath, actualProjectPath); + // Move remaining folders into IDE_HOME + actualProjectPath = this.context.getIdeRoot(); moveProject(this.context.getIdeHome(), actualProjectPath); + + // Link settings folder in IDE_HOME to settings folder in code repository createSettingsLink(); } else { - actualProjectPath = this.context.getIdeRoot(); - moveProject(this.context.getIdeHome(), actualProjectPath); + // Repository seems to be invalid + LOG.warn("No ide.properties or devon.properties found in settings repository. Cannot determine if it is a code repository or settings repository. Keeping the current project setup."); + // To-Do: print error message and perform cleanup + return; } this.context.setIdeHome(actualProjectPath.resolve(projectName)); @@ -128,7 +159,7 @@ private void analyze_project() { private void moveProject(Path oldPath, Path newPath) { try { - this.context.getFileAccess().copy(oldPath, newPath); + this.context.getFileAccess().copy(oldPath, newPath, FileCopyMode.COPY_TREE_OVERRIDE_FILES); this.context.getFileAccess().delete(oldPath); } catch (Exception e) { LOG.error("Failed to move project from {} to {}. Please move it manually.", oldPath, newPath, e); From 54a1fed8860f272842487f7cf8ac99f1202b1528 Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Thu, 30 Apr 2026 08:33:46 +0200 Subject: [PATCH 03/26] Removed --code option & Added further functionality --- .../commandlet/AbstractUpdateCommandlet.java | 92 +++++-------------- .../ide/commandlet/CreateCommandlet.java | 22 +---- 2 files changed, 29 insertions(+), 85 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java index 7e2264d8e7..dfb630a29b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java @@ -100,12 +100,12 @@ protected void doRun() { if (!this.context.isSettingsRepositorySymlinkOrJunction() || this.context.isForceMode() || forcePull.isTrue()) { updateSettings(); - // Check if instance of create commandlet. Only then will we analyze the project if (this instanceof CreateCommandlet) { analyze_project(); } + } updateConf(); reloadContext(); @@ -129,31 +129,38 @@ private void analyze_project() { if (Files.exists(SettingsPath.resolve("ide.properties")) || Files.exists(SettingsPath.resolve("devon.properties"))) { // Repository is a settings repository LOG.info("The repository seems to be a settings repository based on the presence of ide.properties or devon.properties on the top level."); + actualProjectPath = this.context.getIdeRoot(); moveProject(this.context.getIdeHome(), actualProjectPath); } else if (Files.exists(SettingsPath.resolve("settings/ide.properties")) || Files.exists(SettingsPath.resolve("settings/devon.properties"))) { // Repository is a code repository LOG.info("ide.properties or devon.properties (legacy) found in settings subfolder. This indicates a code repository with settings folder on the top level."); + // Move "settings" folder containing code in workspace/main actualProjectPath = this.context.getIdeRoot().resolve(projectName).resolve("workspaces/main/").resolve(projectName); for (Path child : this.context.getFileAccess().listChildren(SettingsPath, f -> true)) { System.out.println("Child: " + child); - moveProject(child, actualProjectPath.resolve(child.getFileName())); + moveProject(child, actualProjectPath); } - moveProject(SettingsPath, actualProjectPath); // Move remaining folders into IDE_HOME actualProjectPath = this.context.getIdeRoot(); moveProject(this.context.getIdeHome(), actualProjectPath); + // Delete empty settings folder in IDE_ROOT/ so we can create a symlink in the next step + this.context.getFileAccess().delete(actualProjectPath.resolve(projectName).resolve("settings")); // Link settings folder in IDE_HOME to settings folder in code repository - createSettingsLink(); + this.context.getFileAccess().symlink(actualProjectPath.resolve(projectName).resolve("workspaces/main").resolve(projectName).resolve("settings"), actualProjectPath.resolve(projectName).resolve("settings")); + + // Final cleanup in temp location + this.context.getFileAccess().delete(this.context.getIdeHome()); } else { - // Repository seems to be invalid - LOG.warn("No ide.properties or devon.properties found in settings repository. Cannot determine if it is a code repository or settings repository. Keeping the current project setup."); - // To-Do: print error message and perform cleanup - return; + // Repository seems to be invalid. Clean up temporary location and return error + this.context.getFileAccess().delete(this.context.getIdeHome()); + throw new CliException("This repository does not include an ide.properties file at the top level or a settings folder with such a file. " + + "The respository does not seem to be a valid IDEasy repository. Please verify the repository and try again."); } - this.context.setIdeHome(actualProjectPath.resolve(projectName)); + // Set IDE_HOME to new (and actual) project location + this.context.setIdeHome(this.context.getIdeRoot().resolve(projectName)); } @@ -166,9 +173,6 @@ private void moveProject(Path oldPath, Path newPath) { } } - private void createSettingsLink() { - - } private void reloadContext() { @@ -259,7 +263,6 @@ private void updateSettingsInStep() { } GitUrl gitUrl = getOrAskSettingsUrl(); - checkProjectNameConvention(gitUrl.getProjectName()); initializeRepository(gitUrl); } } @@ -268,17 +271,10 @@ private GitUrl getOrAskSettingsUrl() { String repository = this.settingsRepo.getValue(); repository = handleDefaultRepository(repository); - String userPromt; - String defaultUrl; - if (isCodeRepository()) { - userPromt = "Code repository URL:"; - defaultUrl = null; - LOG.info(MESSAGE_CODE_REPO_URL); - } else { - userPromt = "Settings URL [" + IdeContext.DEFAULT_SETTINGS_REPO_URL + "]:"; - defaultUrl = IdeContext.DEFAULT_SETTINGS_REPO_URL; - LOG.info(MESSAGE_SETTINGS_REPO_URL, this.context.getSettingsPath()); - } + String userPromt = "Repository URL [" + IdeContext.DEFAULT_SETTINGS_REPO_URL + "]:"; + String defaultUrl = IdeContext.DEFAULT_SETTINGS_REPO_URL; + LOG.info(MESSAGE_SETTINGS_REPO_URL, this.context.getSettingsPath()); + GitUrl gitUrl = null; if (repository != null) { gitUrl = GitUrl.of(repository); @@ -296,49 +292,19 @@ private GitUrl getOrAskSettingsUrl() { private String handleDefaultRepository(String repository) { if ("-".equals(repository)) { - if (isCodeRepository()) { - LOG.warn("'-' is found after '--code'. This is invalid."); - repository = null; - } else { - LOG.info("'-' was found for settings repository, the default settings repository '{}' will be used.", IdeContext.DEFAULT_SETTINGS_REPO_URL); - repository = IdeContext.DEFAULT_SETTINGS_REPO_URL; - } + LOG.info("'-' was found for the repository, the default settings repository '{}' will be used.", IdeContext.DEFAULT_SETTINGS_REPO_URL); + repository = IdeContext.DEFAULT_SETTINGS_REPO_URL; } return repository; } - private void checkProjectNameConvention(String projectName) { - boolean isSettingsRepo = projectName.contains(IdeContext.SETTINGS_REPOSITORY_KEYWORD); - boolean codeRepository = isCodeRepository(); - if (isSettingsRepo == codeRepository) { - String warningTemplate; - if (codeRepository) { - warningTemplate = """ - Your git URL is pointing to the project name {} that contains the keyword '{}'. - Therefore we assume that you did a mistake by adding the '--code' option to the ide project creation. - Do you really want to create the project?"""; - } else { - warningTemplate = """ - Your git URL is pointing to the project name {} that does not contain the keyword ''{}''. - Therefore we assume that you forgot to add the '--code' option to the ide project creation. - Do you really want to create the project?"""; - } - this.context.askToContinue(warningTemplate, projectName, IdeContext.SETTINGS_REPOSITORY_KEYWORD); - } - } - private void initializeRepository(GitUrl gitUrl) { GitContext gitContext = this.context.getGitContext(); Path settingsPath = this.context.getSettingsPath(); Path repoPath = settingsPath; - boolean codeRepository = isCodeRepository(); - if (codeRepository) { - // clone the given code repository into IDE_HOME/workspaces/main - repoPath = context.getWorkspacePath().resolve(gitUrl.getProjectName()); - } gitContext.pullOrClone(gitUrl, repoPath); - if (codeRepository) { + /*if (codeRepository) { // check for settings folder and create symlink to IDE_HOME/settings Path settingsFolder = repoPath.resolve(IdeContext.FOLDER_SETTINGS); if (Files.exists(settingsFolder)) { @@ -346,7 +312,7 @@ private void initializeRepository(GitUrl gitUrl) { } else { throw new CliException("Invalid code repository " + gitUrl + ": missing a settings folder at " + settingsFolder); } - } + }*/ this.context.getGitContext().saveCurrentCommitId(settingsPath, this.context.getSettingsCommitIdPath()); } @@ -505,14 +471,4 @@ private void createStartScript(String ide, String workspace) { fileAccess.writeFileContent(scriptContent, scriptPath); fileAccess.makeExecutable(scriptPath); } - - /** - * Judge if the repository is a code repository. - * - * @return true when the repository is a code repository, otherwise false. - */ - protected boolean isCodeRepository() { - return false; - } - } diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java index dd38e4c9c6..db48e7b328 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java @@ -24,9 +24,6 @@ public class CreateCommandlet extends AbstractUpdateCommandlet { /** {@link StringProperty} for the name of the new project */ public final StringProperty newProject; - /** {@link FlagProperty} for creating a project with settings inside a code repository */ - public final FlagProperty codeRepositoryFlag; - /** * The constructor. * @@ -36,7 +33,6 @@ public CreateCommandlet(IdeContext context) { super(context); this.newProject = add(new StringProperty("", true, "project")); - this.codeRepositoryFlag = add(new FlagProperty("--code")); add(this.settingsRepo); } @@ -56,21 +52,18 @@ public boolean isIdeHomeRequired() { protected void doRun() { String newProjectName = this.newProject.getValue(); - //Path newProjectPath = this.context.getIdeRoot().resolve(newProjectName); - Path newProjectPath = this.context.getIdeRoot().resolve("_ide/tmp/projects").resolve(newProjectName); + Path newProjectPath = this.context.getIdeRoot().resolve(newProjectName); + Path tempProjectPath = this.context.getIdeRoot().resolve("_ide/tmp/projects").resolve(newProjectName); LOG.info("Creating new IDEasy project in {}", newProjectPath); if (!this.context.getFileAccess().isEmptyDir(newProjectPath)) { this.context.askToContinue("Directory {} already exists. Do you want to continue?", newProjectPath); - } else { - this.context.getFileAccess().mkdirs(newProjectPath); } - initializeProject(newProjectPath); - this.context.setIdeHome(newProjectPath); + initializeProject(tempProjectPath); + this.context.setIdeHome(tempProjectPath); this.context.verifyIdeMinVersion(true); super.doRun(); - newProjectPath = this.context.getIdeHome(); this.context.verifyIdeMinVersion(true); this.context.getFileAccess().writeFileContent(IdeVersion.getVersionString(), newProjectPath.resolve(IdeContext.FILE_SOFTWARE_VERSION)); IdeLogLevel.SUCCESS.log(LOG, "Successfully created new project '{}'.", newProjectName); @@ -86,15 +79,10 @@ private void initializeProject(Path newInstancePath) { fileAccess.mkdirs(newInstancePath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN)); } - @Override - protected boolean isCodeRepository() { - return this.codeRepositoryFlag.isTrue(); - } - @Override protected String getStepMessage() { - return "Create (clone) " + (isCodeRepository() ? "code" : "settings") + " repository"; + return "Creating (Cloning) repository"; } private void logWelcomeMessage() { From 90e43e797771190c2ac23301b179b6685634600b Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Thu, 30 Apr 2026 10:38:53 +0200 Subject: [PATCH 04/26] Fix tests --- .../commandlet/AbstractUpdateCommandlet.java | 2 - .../ide/commandlet/CreateCommandletTest.java | 52 ++----------------- .../tools/ide/context/IdeTestContext.java | 2 +- .../devonfw/tools/ide/git/GitContextMock.java | 16 +++++- .../test/resources/settings/ide.properties | 0 5 files changed, 20 insertions(+), 52 deletions(-) create mode 100644 cli/src/test/resources/settings/ide.properties diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java index dfb630a29b..3a2442030e 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java @@ -119,8 +119,6 @@ private void analyze_project() { // Settings repository: ide.properties on top levels (or devon.properties (<- verify file name) for legacy users) // Code repository: settings folder on top level with ide.properties inside (or devon.properties for legacy users) - System.out.println("Settings Path: " + this.context.getSettingsPath()); - String projectName = this.context.getProjectName(); Path actualProjectPath = null; diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java index be6009feeb..6757a4cac7 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java @@ -68,54 +68,6 @@ void testCreateCommandletRun() { assertThat(newProjectPath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN)).exists(); } - @ParameterizedTest - @ValueSource(strings = { "https://some-code-repository", "ssh://some-settings-repository" }) - void testWarningWhenRepoDoesNotMeetNamingConvention(String invalidRepo, @TempDir Path tempDir) { - // arrange - ProcessContextGitMock gitMock = new ProcessContextGitMock(context, tempDir); - context.setProcessContext(gitMock); - CreateCommandlet cc = context.getCommandletManager().getCommandlet(CreateCommandlet.class); - cc.newProject.setValueAsString(NEW_PROJECT_NAME, context); - cc.codeRepositoryFlag.setValue(!invalidRepo.contains("code")); // raise conflict - cc.settingsRepo.setValue(invalidRepo); - cc.skipTools.setValue(true); - context.setAnswers("yes"); - // act - cc.run(); - // assert - assertThat(context).logAtInteraction().hasMessageContaining("Do you really want to create the project?"); - Path newProjectPath = context.getIdeRoot().resolve(NEW_PROJECT_NAME); - assertThat(newProjectPath).exists(); - assertThat(context.getIdeHome()).isEqualTo(newProjectPath); - assertThat(newProjectPath.resolve(IdeContext.FOLDER_PLUGINS)).exists(); - assertThat(newProjectPath.resolve(IdeContext.FOLDER_SOFTWARE)).exists(); - assertThat(newProjectPath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN)).exists(); - } - - @Test - void testWarningWhenCodeRepoUsingDefaultMark(@TempDir Path tempDir) { - String invalidCodeRepo = "-"; - // arrange - ProcessContextGitMock gitMock = new ProcessContextGitMock(context, tempDir); - context.setProcessContext(gitMock); - CreateCommandlet cc = context.getCommandletManager().getCommandlet(CreateCommandlet.class); - cc.newProject.setValueAsString(NEW_PROJECT_NAME, context); - cc.settingsRepo.setValue(invalidCodeRepo); - cc.codeRepositoryFlag.setValue(true); - cc.skipTools.setValue(true); - context.setAnswers("https://some-code-repository"); - // act - cc.run(); - // assert - assertThat(context).logAtWarning().hasMessageContaining("'-' is found after '--code'. This is invalid."); - Path newProjectPath = context.getIdeRoot().resolve(NEW_PROJECT_NAME); - assertThat(newProjectPath).exists(); - assertThat(context.getIdeHome()).isEqualTo(newProjectPath); - assertThat(newProjectPath.resolve(IdeContext.FOLDER_PLUGINS)).exists(); - assertThat(newProjectPath.resolve(IdeContext.FOLDER_SOFTWARE)).exists(); - assertThat(newProjectPath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN)).exists(); - } - @Test void testIdeVersionTooOldOnProjectCreation() { // arrange @@ -214,6 +166,10 @@ void testWelcomeMessageDisplayed() { // assert Path newProjectPath = context.getIdeRoot().resolve(NEW_PROJECT_NAME); assertThat(newProjectPath).exists(); + assertThat(context.getIdeHome()).isEqualTo(newProjectPath); + assertThat(newProjectPath.resolve(IdeContext.FOLDER_PLUGINS)).exists(); + assertThat(newProjectPath.resolve(IdeContext.FOLDER_SOFTWARE)).exists(); + assertThat(newProjectPath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN)).exists(); assertThat(context).logAtInfo().hasMessageContaining("Welcome to your new IDEasy project!"); } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/IdeTestContext.java b/cli/src/test/java/com/devonfw/tools/ide/context/IdeTestContext.java index 88b1934e1a..d4531b9d51 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/context/IdeTestContext.java +++ b/cli/src/test/java/com/devonfw/tools/ide/context/IdeTestContext.java @@ -56,7 +56,7 @@ public IdeTestContext(Path workingDirectory, IdeLogLevel logLevel, WireMockRunti private IdeTestContext(IdeTestStartContext startContext, Path workingDirectory, WireMockRuntimeInfo wireMockRuntimeInfo) { super(startContext, workingDirectory, wireMockRuntimeInfo); - this.gitContext = new GitContextMock(); + this.gitContext = new GitContextMock(this); } @Override diff --git a/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java b/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java index 0aded183ef..ce012d94eb 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java +++ b/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java @@ -3,12 +3,25 @@ import java.nio.file.Files; import java.nio.file.Path; +import com.devonfw.tools.ide.context.IdeTestContext; + + /** * Mock implementation of {@link GitContext}. */ public class GitContextMock implements GitContext { private static final String MOCKED_URL_VALUE = "mocked url value"; + /** @see #getContext() */ + protected final IdeTestContext context; + + /** + * @param context the {@link IdeTestContext context}. + */ + public GitContextMock(IdeTestContext context) { + + this.context = context; + } @Override public void pullOrCloneIfNeeded(GitUrl gitUrl, Path repository) { @@ -32,7 +45,8 @@ public boolean hasUntrackedFiles(Path repository) { @Override public void pullOrClone(GitUrl gitUrl, Path repository) { - + this.context.getFileAccess().mkdirs(repository); + this.context.getFileAccess().touch(repository.resolve("ide.properties")); } @Override diff --git a/cli/src/test/resources/settings/ide.properties b/cli/src/test/resources/settings/ide.properties new file mode 100644 index 0000000000..e69de29bb2 From aa751ff92efa3c5bd8e51f33944bb8aad85c094f Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:10:25 +0200 Subject: [PATCH 05/26] Update comments --- .../commandlet/AbstractUpdateCommandlet.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java index 3a2442030e..7a847c0e10 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java @@ -115,9 +115,12 @@ protected void doRun() { createStartScripts(); } + /** + * This method is invoked when a new porject is created. It analyzes the cloned repository to check if it is a valid IDEasy repository. The repository can either be a settings repository (with ide.properties or devon.properties on the top level) + * or a code repository (with a settings folder on the top level containing such a file). Otherwise, the project creatio fails and an error message is logged. + */ private void analyze_project() { - - // Settings repository: ide.properties on top levels (or devon.properties (<- verify file name) for legacy users) + // Settings repository: ide.properties on top levels (or devon.properties for legacy users) // Code repository: settings folder on top level with ide.properties inside (or devon.properties for legacy users) String projectName = this.context.getProjectName(); Path actualProjectPath = null; @@ -125,16 +128,16 @@ private void analyze_project() { //Check if a file called ide.properties or devon.properties in settingsPath Path SettingsPath = this.context.getSettingsPath(); if (Files.exists(SettingsPath.resolve("ide.properties")) || Files.exists(SettingsPath.resolve("devon.properties"))) { - // Repository is a settings repository + // Repository is a settings repository: ide.properties on top levels (or devon.properties for legacy users) LOG.info("The repository seems to be a settings repository based on the presence of ide.properties or devon.properties on the top level."); actualProjectPath = this.context.getIdeRoot(); moveProject(this.context.getIdeHome(), actualProjectPath); } else if (Files.exists(SettingsPath.resolve("settings/ide.properties")) || Files.exists(SettingsPath.resolve("settings/devon.properties"))) { - // Repository is a code repository + // Repository is a code repository: settings folder on top level with ide.properties inside (or devon.properties for legacy users) LOG.info("ide.properties or devon.properties (legacy) found in settings subfolder. This indicates a code repository with settings folder on the top level."); - // Move "settings" folder containing code in workspace/main + // Move settings folder contents containing code in workspace/main actualProjectPath = this.context.getIdeRoot().resolve(projectName).resolve("workspaces/main/").resolve(projectName); for (Path child : this.context.getFileAccess().listChildren(SettingsPath, f -> true)) { System.out.println("Child: " + child); @@ -159,9 +162,13 @@ private void analyze_project() { } // Set IDE_HOME to new (and actual) project location this.context.setIdeHome(this.context.getIdeRoot().resolve(projectName)); - } + /** + * Moves files of a new projectfrom the temporary location to the final project location. + * @param oldPath - The path of the file or directory to be moved. + * @param newPath - The path of the destination. + */ private void moveProject(Path oldPath, Path newPath) { try { this.context.getFileAccess().copy(oldPath, newPath, FileCopyMode.COPY_TREE_OVERRIDE_FILES); From 3e936dc9fc43335e4c7b30b49f3a3ea3194897de Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:15:13 +0200 Subject: [PATCH 06/26] formatting cleanup --- .../ide/commandlet/AbstractUpdateCommandlet.java | 13 ------------- .../com/devonfw/tools/ide/git/GitContextMock.java | 1 - 2 files changed, 14 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java index 7a847c0e10..ad3db7ac3f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java @@ -104,8 +104,6 @@ protected void doRun() { if (this instanceof CreateCommandlet) { analyze_project(); } - - } updateConf(); reloadContext(); @@ -178,7 +176,6 @@ private void moveProject(Path oldPath, Path newPath) { } } - private void reloadContext() { ((AbstractIdeContext) this.context).reload(); @@ -309,19 +306,9 @@ private void initializeRepository(GitUrl gitUrl) { Path settingsPath = this.context.getSettingsPath(); Path repoPath = settingsPath; gitContext.pullOrClone(gitUrl, repoPath); - /*if (codeRepository) { - // check for settings folder and create symlink to IDE_HOME/settings - Path settingsFolder = repoPath.resolve(IdeContext.FOLDER_SETTINGS); - if (Files.exists(settingsFolder)) { - context.getFileAccess().symlink(settingsFolder, settingsPath); - } else { - throw new CliException("Invalid code repository " + gitUrl + ": missing a settings folder at " + settingsFolder); - } - }*/ this.context.getGitContext().saveCurrentCommitId(settingsPath, this.context.getSettingsCommitIdPath()); } - private void updateSoftware() { if (this.skipTools.isTrue()) { diff --git a/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java b/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java index ce012d94eb..7ee2bdcae0 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java +++ b/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java @@ -5,7 +5,6 @@ import com.devonfw.tools.ide.context.IdeTestContext; - /** * Mock implementation of {@link GitContext}. */ From 9bee5f1925f10155be8334414add226c5e7b6016 Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:23:13 +0200 Subject: [PATCH 07/26] Minor code refactor --- .../commandlet/AbstractUpdateCommandlet.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java index ad3db7ac3f..f1d30a9726 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java @@ -122,39 +122,38 @@ private void analyze_project() { // Code repository: settings folder on top level with ide.properties inside (or devon.properties for legacy users) String projectName = this.context.getProjectName(); Path actualProjectPath = null; + FileAccess fileAccess = this.context.getFileAccess(); //Check if a file called ide.properties or devon.properties in settingsPath Path SettingsPath = this.context.getSettingsPath(); if (Files.exists(SettingsPath.resolve("ide.properties")) || Files.exists(SettingsPath.resolve("devon.properties"))) { // Repository is a settings repository: ide.properties on top levels (or devon.properties for legacy users) LOG.info("The repository seems to be a settings repository based on the presence of ide.properties or devon.properties on the top level."); - actualProjectPath = this.context.getIdeRoot(); moveProject(this.context.getIdeHome(), actualProjectPath); + } else if (Files.exists(SettingsPath.resolve("settings/ide.properties")) || Files.exists(SettingsPath.resolve("settings/devon.properties"))) { // Repository is a code repository: settings folder on top level with ide.properties inside (or devon.properties for legacy users) LOG.info("ide.properties or devon.properties (legacy) found in settings subfolder. This indicates a code repository with settings folder on the top level."); - - // Move settings folder contents containing code in workspace/main + // Move settings folder contents containing code into workspace/main/ actualProjectPath = this.context.getIdeRoot().resolve(projectName).resolve("workspaces/main/").resolve(projectName); - for (Path child : this.context.getFileAccess().listChildren(SettingsPath, f -> true)) { + for (Path child : fileAccess.listChildren(SettingsPath, f -> true)) { System.out.println("Child: " + child); moveProject(child, actualProjectPath); } - // Move remaining folders into IDE_HOME + // Move remaining folders into IDE_ROOT/ actualProjectPath = this.context.getIdeRoot(); moveProject(this.context.getIdeHome(), actualProjectPath); // Delete empty settings folder in IDE_ROOT/ so we can create a symlink in the next step - this.context.getFileAccess().delete(actualProjectPath.resolve(projectName).resolve("settings")); - + fileAccess.delete(actualProjectPath.resolve(projectName).resolve("settings")); // Link settings folder in IDE_HOME to settings folder in code repository - this.context.getFileAccess().symlink(actualProjectPath.resolve(projectName).resolve("workspaces/main").resolve(projectName).resolve("settings"), actualProjectPath.resolve(projectName).resolve("settings")); - + fileAccess.symlink(actualProjectPath.resolve(projectName).resolve("workspaces/main").resolve(projectName).resolve("settings"), actualProjectPath.resolve(projectName).resolve("settings")); // Final cleanup in temp location - this.context.getFileAccess().delete(this.context.getIdeHome()); + fileAccess.delete(this.context.getIdeHome()); + } else { // Repository seems to be invalid. Clean up temporary location and return error - this.context.getFileAccess().delete(this.context.getIdeHome()); + fileAccess.delete(this.context.getIdeHome()); throw new CliException("This repository does not include an ide.properties file at the top level or a settings folder with such a file. " + "The respository does not seem to be a valid IDEasy repository. Please verify the repository and try again."); } @@ -168,9 +167,10 @@ private void analyze_project() { * @param newPath - The path of the destination. */ private void moveProject(Path oldPath, Path newPath) { + FileAccess fileAccess = this.context.getFileAccess(); try { - this.context.getFileAccess().copy(oldPath, newPath, FileCopyMode.COPY_TREE_OVERRIDE_FILES); - this.context.getFileAccess().delete(oldPath); + fileAccess.copy(oldPath, newPath, FileCopyMode.COPY_TREE_OVERRIDE_FILES); + fileAccess.delete(oldPath); } catch (Exception e) { LOG.error("Failed to move project from {} to {}. Please move it manually.", oldPath, newPath, e); } From 29f6d1f240d36dac026cb2b5e05993ffdd991d7e Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:24:28 +0200 Subject: [PATCH 08/26] Update comments --- cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java b/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java index 7ee2bdcae0..3e04fcd225 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java +++ b/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java @@ -44,6 +44,7 @@ public boolean hasUntrackedFiles(Path repository) { @Override public void pullOrClone(GitUrl gitUrl, Path repository) { + // Creates required ide.properties file to make repository valid this.context.getFileAccess().mkdirs(repository); this.context.getFileAccess().touch(repository.resolve("ide.properties")); } From 8b6933902273f5f9e4cb4974cbd3af438e577f9a Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:27:57 +0200 Subject: [PATCH 09/26] Update changelog --- CHANGELOG.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 8094e25c47..d4c75b42ea 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -16,6 +16,7 @@ Release with new features and bugfixes: * https://github.com/devonfw/IDEasy/issues/1724[#1724]: Add gui commandlet * https://github.com/devonfw/IDEasy/issues/1853[#1853]: Add ARM releases for VSCode on Mac * https://github.com/devonfw/IDEasy/issues/1723[#1723]: Add commandlet for GitHub Copilot CLI +* https://github.com/devonfw/IDEasy/issues/1695[#1695]: Clone settings to temporary directory, analyse, and then move The full list of changes for this release can be found in https://github.com/devonfw/IDEasy/milestone/44?closed=1[milestone 2026.05.001]. From d1bfbf573fe9044a8263640a7571477f2dfa97b0 Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:49:57 +0200 Subject: [PATCH 10/26] Add test case for invalid repository --- .../commandlet/AbstractUpdateCommandlet.java | 2 +- .../ide/commandlet/CreateCommandletTest.java | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java index f1d30a9726..78d281eb0a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java @@ -155,7 +155,7 @@ private void analyze_project() { // Repository seems to be invalid. Clean up temporary location and return error fileAccess.delete(this.context.getIdeHome()); throw new CliException("This repository does not include an ide.properties file at the top level or a settings folder with such a file. " - + "The respository does not seem to be a valid IDEasy repository. Please verify the repository and try again."); + + "The repository does not seem to be a valid IDEasy repository. Please verify the repository and try again."); } // Set IDE_HOME to new (and actual) project location this.context.setIdeHome(this.context.getIdeRoot().resolve(projectName)); diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java index 6757a4cac7..82ce13a5f2 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java @@ -11,6 +11,7 @@ import org.junit.jupiter.params.provider.ValueSource; import com.devonfw.tools.ide.cli.CliArguments; +import com.devonfw.tools.ide.cli.CliException; import com.devonfw.tools.ide.context.AbstractIdeContextTest; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.context.IdeTestContext; @@ -66,6 +67,7 @@ void testCreateCommandletRun() { assertThat(newProjectPath.resolve(IdeContext.FOLDER_PLUGINS)).exists(); assertThat(newProjectPath.resolve(IdeContext.FOLDER_SOFTWARE)).exists(); assertThat(newProjectPath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN)).exists(); + assertThat(context.getIdeRoot().resolve("_ide/tmp/projects").resolve(NEW_PROJECT_NAME)).doesNotExist(); } @Test @@ -170,6 +172,31 @@ void testWelcomeMessageDisplayed() { assertThat(newProjectPath.resolve(IdeContext.FOLDER_PLUGINS)).exists(); assertThat(newProjectPath.resolve(IdeContext.FOLDER_SOFTWARE)).exists(); assertThat(newProjectPath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN)).exists(); + assertThat(context.getIdeRoot().resolve("_ide/tmp/projects").resolve(NEW_PROJECT_NAME)).doesNotExist(); assertThat(context).logAtInfo().hasMessageContaining("Welcome to your new IDEasy project!"); } + + @Test + void testProjectWithInvalidRepositoryNotCreated() { + + // arrange - create a new project that is invalid (does not contain ide.properties file) + GitContextImplMock gitContextImplMock = new GitContextImplMock(context, TEST_RESOURCES.resolve("pypi")); + + context.setGitContext(gitContextImplMock); + CreateCommandlet cc = context.getCommandletManager().getCommandlet(CreateCommandlet.class); + cc.newProject.setValueAsString(NEW_PROJECT_NAME, context); + cc.settingsRepo.setValue(IdeContext.DEFAULT_SETTINGS_REPO_URL); + cc.skipTools.setValue(true); + + // act - run the create command + assertThatThrownBy(() -> cc.run()) + .isInstanceOf(CliException.class) + .hasMessageContaining("This repository does not include an ide.properties file at the top level or a settings folder with such a file.") + .hasMessageContaining("The repository does not seem to be a valid IDEasy repository. Please verify the repository and try again."); + + // assert + Path newProjectPath = context.getIdeRoot().resolve(NEW_PROJECT_NAME); + assertThat(newProjectPath).doesNotExist(); + assertThat(context.getIdeRoot().resolve("_ide/tmp/projects").resolve(NEW_PROJECT_NAME)).doesNotExist(); + } } From 64f1220e00b3e2c98cde03a6a938b4aeadad6ae9 Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Thu, 30 Apr 2026 12:18:07 +0200 Subject: [PATCH 11/26] Replace hard coded variables --- .../ide/commandlet/AbstractUpdateCommandlet.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java index 78d281eb0a..23c321f275 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java @@ -36,6 +36,7 @@ import com.devonfw.tools.ide.tool.extra.ExtraToolsMapper; import com.devonfw.tools.ide.variable.IdeVariables; import com.devonfw.tools.ide.version.VersionIdentifier; +import com.devonfw.tools.ide.environment.EnvironmentVariables; /** * Abstract {@link Commandlet} base-class for both {@link UpdateCommandlet} and {@link CreateCommandlet}. @@ -126,15 +127,15 @@ private void analyze_project() { //Check if a file called ide.properties or devon.properties in settingsPath Path SettingsPath = this.context.getSettingsPath(); - if (Files.exists(SettingsPath.resolve("ide.properties")) || Files.exists(SettingsPath.resolve("devon.properties"))) { + if (Files.exists(SettingsPath.resolve(EnvironmentVariables.DEFAULT_PROPERTIES)) || Files.exists(SettingsPath.resolve(EnvironmentVariables.LEGACY_PROPERTIES))) { // Repository is a settings repository: ide.properties on top levels (or devon.properties for legacy users) - LOG.info("The repository seems to be a settings repository based on the presence of ide.properties or devon.properties on the top level."); + LOG.info("The repository seems to be a settings repository based on the presence of " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " on the top level."); actualProjectPath = this.context.getIdeRoot(); moveProject(this.context.getIdeHome(), actualProjectPath); - } else if (Files.exists(SettingsPath.resolve("settings/ide.properties")) || Files.exists(SettingsPath.resolve("settings/devon.properties"))) { + } else if (Files.exists(SettingsPath.resolve("settings/" + EnvironmentVariables.DEFAULT_PROPERTIES)) || Files.exists(SettingsPath.resolve("settings/" + EnvironmentVariables.LEGACY_PROPERTIES))) { // Repository is a code repository: settings folder on top level with ide.properties inside (or devon.properties for legacy users) - LOG.info("ide.properties or devon.properties (legacy) found in settings subfolder. This indicates a code repository with settings folder on the top level."); + LOG.info(EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " found in settings subfolder. This indicates a code repository with a settings folder on the top level."); // Move settings folder contents containing code into workspace/main/ actualProjectPath = this.context.getIdeRoot().resolve(projectName).resolve("workspaces/main/").resolve(projectName); for (Path child : fileAccess.listChildren(SettingsPath, f -> true)) { @@ -154,7 +155,7 @@ private void analyze_project() { } else { // Repository seems to be invalid. Clean up temporary location and return error fileAccess.delete(this.context.getIdeHome()); - throw new CliException("This repository does not include an ide.properties file at the top level or a settings folder with such a file. " + throw new CliException("This repository does not include an " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " file at the top level or a settings folder with such a file. " + "The repository does not seem to be a valid IDEasy repository. Please verify the repository and try again."); } // Set IDE_HOME to new (and actual) project location From 462d6d5b3fb7719ec1e6ee4cb47fbc25dd9bf222 Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Thu, 30 Apr 2026 12:25:20 +0200 Subject: [PATCH 12/26] Fix tests --- .../com/devonfw/tools/ide/commandlet/CreateCommandletTest.java | 2 +- .../test/java/com/devonfw/tools/ide/git/GitContextMock.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java index 82ce13a5f2..b82ad69caf 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java @@ -191,7 +191,7 @@ void testProjectWithInvalidRepositoryNotCreated() { // act - run the create command assertThatThrownBy(() -> cc.run()) .isInstanceOf(CliException.class) - .hasMessageContaining("This repository does not include an ide.properties file at the top level or a settings folder with such a file.") + .hasMessageContaining("This repository does not include an " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " file at the top level or a settings folder with such a file.") .hasMessageContaining("The repository does not seem to be a valid IDEasy repository. Please verify the repository and try again."); // assert diff --git a/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java b/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java index 3e04fcd225..ce39ac7ee8 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java +++ b/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java @@ -2,6 +2,7 @@ import java.nio.file.Files; import java.nio.file.Path; +import com.devonfw.tools.ide.environment.EnvironmentVariables; import com.devonfw.tools.ide.context.IdeTestContext; @@ -46,7 +47,7 @@ public boolean hasUntrackedFiles(Path repository) { public void pullOrClone(GitUrl gitUrl, Path repository) { // Creates required ide.properties file to make repository valid this.context.getFileAccess().mkdirs(repository); - this.context.getFileAccess().touch(repository.resolve("ide.properties")); + this.context.getFileAccess().touch(repository.resolve(EnvironmentVariables.DEFAULT_PROPERTIES)); } @Override From 373fc19a34fc6fdd90a6da21bc37f9a4229ee7bf Mon Sep 17 00:00:00 2001 From: Alexander Reinicke <167530118+areinicke@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:07:57 +0200 Subject: [PATCH 13/26] Apply suggestion from @satorus Co-authored-by: Robin Wenzel --- .../devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java index 23c321f275..53ef8ad687 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java @@ -122,7 +122,7 @@ private void analyze_project() { // Settings repository: ide.properties on top levels (or devon.properties for legacy users) // Code repository: settings folder on top level with ide.properties inside (or devon.properties for legacy users) String projectName = this.context.getProjectName(); - Path actualProjectPath = null; + Path actualProjectPath; FileAccess fileAccess = this.context.getFileAccess(); //Check if a file called ide.properties or devon.properties in settingsPath From 2552b4d22936900601f0b9e29248632afa2b75a0 Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:15:28 +0200 Subject: [PATCH 14/26] Move long if checks to own functions --- .../commandlet/AbstractUpdateCommandlet.java | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java index 53ef8ad687..76e07f4e6c 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java @@ -124,17 +124,15 @@ private void analyze_project() { String projectName = this.context.getProjectName(); Path actualProjectPath; FileAccess fileAccess = this.context.getFileAccess(); - - //Check if a file called ide.properties or devon.properties in settingsPath Path SettingsPath = this.context.getSettingsPath(); - if (Files.exists(SettingsPath.resolve(EnvironmentVariables.DEFAULT_PROPERTIES)) || Files.exists(SettingsPath.resolve(EnvironmentVariables.LEGACY_PROPERTIES))) { - // Repository is a settings repository: ide.properties on top levels (or devon.properties for legacy users) + + // Check whether the repository is a valid settings repository, code repository, or neither + if (isSettingsRepository(SettingsPath)) { LOG.info("The repository seems to be a settings repository based on the presence of " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " on the top level."); actualProjectPath = this.context.getIdeRoot(); moveProject(this.context.getIdeHome(), actualProjectPath); - } else if (Files.exists(SettingsPath.resolve("settings/" + EnvironmentVariables.DEFAULT_PROPERTIES)) || Files.exists(SettingsPath.resolve("settings/" + EnvironmentVariables.LEGACY_PROPERTIES))) { - // Repository is a code repository: settings folder on top level with ide.properties inside (or devon.properties for legacy users) + } else if (isCodeRepository(SettingsPath)) { LOG.info(EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " found in settings subfolder. This indicates a code repository with a settings folder on the top level."); // Move settings folder contents containing code into workspace/main/ actualProjectPath = this.context.getIdeRoot().resolve(projectName).resolve("workspaces/main/").resolve(projectName); @@ -177,6 +175,24 @@ private void moveProject(Path oldPath, Path newPath) { } } + /** + * Checks whether te given repository is a settings repository by checking for the presence of ide.properties or devon.properties on the top level. + * @param repositoryPath - The path of the repository to be checked. + */ + private boolean isSettingsRepository(Path repositoryPath) { + return Files.exists(repositoryPath.resolve(EnvironmentVariables.DEFAULT_PROPERTIES)) || Files.exists(repositoryPath.resolve(EnvironmentVariables.LEGACY_PROPERTIES)); + } + + /** + * Checks whether te given repository is a code repository by checking for the presence of ide.properties or devon.properties within a settings folder on the top level. + * @param repositoryPath - The path of the repository to be checked. + */ + private boolean isCodeRepository(Path repositoryPath) { + return Files.exists(repositoryPath.resolve("settings").resolve(EnvironmentVariables.DEFAULT_PROPERTIES)) || Files.exists(repositoryPath.resolve("settings").resolve(EnvironmentVariables.LEGACY_PROPERTIES)); + } + + + private void reloadContext() { ((AbstractIdeContext) this.context).reload(); From d32fc4b9616ca60f6f3de230d470985c7461a3ca Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:27:42 +0200 Subject: [PATCH 15/26] changed variable name to adhere to coding conventions --- .../tools/ide/commandlet/AbstractUpdateCommandlet.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java index 76e07f4e6c..d0aded4731 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java @@ -124,19 +124,19 @@ private void analyze_project() { String projectName = this.context.getProjectName(); Path actualProjectPath; FileAccess fileAccess = this.context.getFileAccess(); - Path SettingsPath = this.context.getSettingsPath(); + Path settingsPath = this.context.getSettingsPath(); // Check whether the repository is a valid settings repository, code repository, or neither - if (isSettingsRepository(SettingsPath)) { + if (isSettingsRepository(settingsPath)) { LOG.info("The repository seems to be a settings repository based on the presence of " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " on the top level."); actualProjectPath = this.context.getIdeRoot(); moveProject(this.context.getIdeHome(), actualProjectPath); - } else if (isCodeRepository(SettingsPath)) { + } else if (isCodeRepository(settingsPath)) { LOG.info(EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " found in settings subfolder. This indicates a code repository with a settings folder on the top level."); // Move settings folder contents containing code into workspace/main/ actualProjectPath = this.context.getIdeRoot().resolve(projectName).resolve("workspaces/main/").resolve(projectName); - for (Path child : fileAccess.listChildren(SettingsPath, f -> true)) { + for (Path child : fileAccess.listChildren(settingsPath, f -> true)) { System.out.println("Child: " + child); moveProject(child, actualProjectPath); } From 154ff6c85754e82fe9f9b5810147b17472f01c13 Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:19:23 +0200 Subject: [PATCH 16/26] renamed method to follow coding conventions --- .../tools/ide/commandlet/AbstractUpdateCommandlet.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java index d0aded4731..21ad337811 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java @@ -103,7 +103,7 @@ protected void doRun() { updateSettings(); // Check if instance of create commandlet. Only then will we analyze the project if (this instanceof CreateCommandlet) { - analyze_project(); + analyzeProject(); } } updateConf(); @@ -118,7 +118,7 @@ protected void doRun() { * This method is invoked when a new porject is created. It analyzes the cloned repository to check if it is a valid IDEasy repository. The repository can either be a settings repository (with ide.properties or devon.properties on the top level) * or a code repository (with a settings folder on the top level containing such a file). Otherwise, the project creatio fails and an error message is logged. */ - private void analyze_project() { + private void analyzeProject() { // Settings repository: ide.properties on top levels (or devon.properties for legacy users) // Code repository: settings folder on top level with ide.properties inside (or devon.properties for legacy users) String projectName = this.context.getProjectName(); From fcc2a0b747055bea55fa5b6d427d7dcd4a79144d Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Tue, 19 May 2026 09:04:51 +0200 Subject: [PATCH 17/26] Step 1 Refactor --- .../commandlet/AbstractUpdateCommandlet.java | 81 ----------------- .../ide/commandlet/CreateCommandlet.java | 87 ++++++++++++++++++- 2 files changed, 86 insertions(+), 82 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java index 21ad337811..6fa7bca912 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java @@ -101,10 +101,6 @@ protected void doRun() { if (!this.context.isSettingsRepositorySymlinkOrJunction() || this.context.isForceMode() || forcePull.isTrue()) { updateSettings(); - // Check if instance of create commandlet. Only then will we analyze the project - if (this instanceof CreateCommandlet) { - analyzeProject(); - } } updateConf(); reloadContext(); @@ -114,83 +110,6 @@ protected void doRun() { createStartScripts(); } - /** - * This method is invoked when a new porject is created. It analyzes the cloned repository to check if it is a valid IDEasy repository. The repository can either be a settings repository (with ide.properties or devon.properties on the top level) - * or a code repository (with a settings folder on the top level containing such a file). Otherwise, the project creatio fails and an error message is logged. - */ - private void analyzeProject() { - // Settings repository: ide.properties on top levels (or devon.properties for legacy users) - // Code repository: settings folder on top level with ide.properties inside (or devon.properties for legacy users) - String projectName = this.context.getProjectName(); - Path actualProjectPath; - FileAccess fileAccess = this.context.getFileAccess(); - Path settingsPath = this.context.getSettingsPath(); - - // Check whether the repository is a valid settings repository, code repository, or neither - if (isSettingsRepository(settingsPath)) { - LOG.info("The repository seems to be a settings repository based on the presence of " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " on the top level."); - actualProjectPath = this.context.getIdeRoot(); - moveProject(this.context.getIdeHome(), actualProjectPath); - - } else if (isCodeRepository(settingsPath)) { - LOG.info(EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " found in settings subfolder. This indicates a code repository with a settings folder on the top level."); - // Move settings folder contents containing code into workspace/main/ - actualProjectPath = this.context.getIdeRoot().resolve(projectName).resolve("workspaces/main/").resolve(projectName); - for (Path child : fileAccess.listChildren(settingsPath, f -> true)) { - System.out.println("Child: " + child); - moveProject(child, actualProjectPath); - } - // Move remaining folders into IDE_ROOT/ - actualProjectPath = this.context.getIdeRoot(); - moveProject(this.context.getIdeHome(), actualProjectPath); - // Delete empty settings folder in IDE_ROOT/ so we can create a symlink in the next step - fileAccess.delete(actualProjectPath.resolve(projectName).resolve("settings")); - // Link settings folder in IDE_HOME to settings folder in code repository - fileAccess.symlink(actualProjectPath.resolve(projectName).resolve("workspaces/main").resolve(projectName).resolve("settings"), actualProjectPath.resolve(projectName).resolve("settings")); - // Final cleanup in temp location - fileAccess.delete(this.context.getIdeHome()); - - } else { - // Repository seems to be invalid. Clean up temporary location and return error - fileAccess.delete(this.context.getIdeHome()); - throw new CliException("This repository does not include an " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " file at the top level or a settings folder with such a file. " - + "The repository does not seem to be a valid IDEasy repository. Please verify the repository and try again."); - } - // Set IDE_HOME to new (and actual) project location - this.context.setIdeHome(this.context.getIdeRoot().resolve(projectName)); - } - - /** - * Moves files of a new projectfrom the temporary location to the final project location. - * @param oldPath - The path of the file or directory to be moved. - * @param newPath - The path of the destination. - */ - private void moveProject(Path oldPath, Path newPath) { - FileAccess fileAccess = this.context.getFileAccess(); - try { - fileAccess.copy(oldPath, newPath, FileCopyMode.COPY_TREE_OVERRIDE_FILES); - fileAccess.delete(oldPath); - } catch (Exception e) { - LOG.error("Failed to move project from {} to {}. Please move it manually.", oldPath, newPath, e); - } - } - - /** - * Checks whether te given repository is a settings repository by checking for the presence of ide.properties or devon.properties on the top level. - * @param repositoryPath - The path of the repository to be checked. - */ - private boolean isSettingsRepository(Path repositoryPath) { - return Files.exists(repositoryPath.resolve(EnvironmentVariables.DEFAULT_PROPERTIES)) || Files.exists(repositoryPath.resolve(EnvironmentVariables.LEGACY_PROPERTIES)); - } - - /** - * Checks whether te given repository is a code repository by checking for the presence of ide.properties or devon.properties within a settings folder on the top level. - * @param repositoryPath - The path of the repository to be checked. - */ - private boolean isCodeRepository(Path repositoryPath) { - return Files.exists(repositoryPath.resolve("settings").resolve(EnvironmentVariables.DEFAULT_PROPERTIES)) || Files.exists(repositoryPath.resolve("settings").resolve(EnvironmentVariables.LEGACY_PROPERTIES)); - } - private void reloadContext() { diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java index db48e7b328..2a91c5f28d 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java @@ -7,8 +7,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.devonfw.tools.ide.cli.CliException; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.environment.EnvironmentVariables; import com.devonfw.tools.ide.io.FileAccess; +import com.devonfw.tools.ide.io.FileCopyMode; import com.devonfw.tools.ide.log.IdeLogLevel; import com.devonfw.tools.ide.property.FlagProperty; import com.devonfw.tools.ide.property.StringProperty; @@ -79,10 +82,92 @@ private void initializeProject(Path newInstancePath) { fileAccess.mkdirs(newInstancePath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN)); } + @Override + protected void updateSettings() { + super.updateSettings(); + analyzeProject(); + } + + /** + * This method is invoked when a new porject is created. It analyzes the cloned repository to check if it is a valid IDEasy repository. The repository can either be a settings repository (with ide.properties or devon.properties on the top level) + * or a code repository (with a settings folder on the top level containing such a file). Otherwise, the project creation fails and an error message is logged. + */ + private void analyzeProject() { + // Settings repository: ide.properties on top levels (or devon.properties for legacy users) + // Code repository: settings folder on top level with ide.properties inside (or devon.properties for legacy users) + String projectName = this.context.getProjectName(); + Path actualProjectPath; + FileAccess fileAccess = this.context.getFileAccess(); + Path settingsPath = this.context.getSettingsPath(); + + // Check whether the repository is a valid settings repository, code repository, or neither + if (isSettingsRepository(settingsPath)) { + LOG.info("The repository seems to be a settings repository based on the presence of " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " on the top level."); + actualProjectPath = this.context.getIdeRoot(); + moveProject(this.context.getIdeHome(), actualProjectPath); + + } else if (isCodeRepository(settingsPath)) { + LOG.info(EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " found in settings subfolder. This indicates a code repository with a settings folder on the top level."); + // Move settings folder contents containing code into workspace/main/ + actualProjectPath = this.context.getIdeRoot().resolve(projectName).resolve("workspaces/main/").resolve(projectName); + for (Path child : fileAccess.listChildren(settingsPath, f -> true)) { + moveProject(child, actualProjectPath); + } + // Move remaining folders into IDE_ROOT/ + actualProjectPath = this.context.getIdeRoot(); + moveProject(this.context.getIdeHome(), actualProjectPath); + // Delete empty settings folder in IDE_ROOT/ so we can create a symlink in the next step + fileAccess.delete(actualProjectPath.resolve(projectName).resolve("settings")); + // Link settings folder in IDE_HOME to settings folder in code repository + fileAccess.symlink(actualProjectPath.resolve(projectName).resolve("workspaces/main").resolve(projectName).resolve("settings"), actualProjectPath.resolve(projectName).resolve("settings")); + // Final cleanup in temp location + fileAccess.delete(this.context.getIdeHome()); + + } else { + // Repository seems to be invalid. Clean up temporary location and return error + fileAccess.delete(this.context.getIdeHome()); + throw new CliException("This repository does not include an " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " file at the top level or a settings folder with such a file. " + + "The repository does not seem to be a valid IDEasy repository. Please verify the repository and try again."); + } + // Set IDE_HOME to new (and actual) project location + this.context.setIdeHome(this.context.getIdeRoot().resolve(projectName)); + } + + /** + * Moves files of a new projectfrom the temporary location to the final project location. + * @param oldPath - The path of the file or directory to be moved. + * @param newPath - The path of the destination. + */ + private void moveProject(Path oldPath, Path newPath) { + FileAccess fileAccess = this.context.getFileAccess(); + try { + fileAccess.copy(oldPath, newPath, FileCopyMode.COPY_TREE_OVERRIDE_FILES); + fileAccess.delete(oldPath); + } catch (Exception e) { + LOG.error("Failed to move project from {} to {}. Please move it manually.", oldPath, newPath, e); + } + } + + /** + * Checks whether te given repository is a settings repository by checking for the presence of ide.properties or devon.properties on the top level. + * @param repositoryPath - The path of the repository to be checked. + */ + private boolean isSettingsRepository(Path repositoryPath) { + return Files.exists(repositoryPath.resolve(EnvironmentVariables.DEFAULT_PROPERTIES)) || Files.exists(repositoryPath.resolve(EnvironmentVariables.LEGACY_PROPERTIES)); + } + + /** + * Checks whether te given repository is a code repository by checking for the presence of ide.properties or devon.properties within a settings folder on the top level. + * @param repositoryPath - The path of the repository to be checked. + */ + private boolean isCodeRepository(Path repositoryPath) { + return isSettingsRepository(repositoryPath.resolve(IdeContext.FOLDER_SETTINGS)); + } + @Override protected String getStepMessage() { - return "Creating (Cloning) repository"; + return "Create (Clone) repository"; } private void logWelcomeMessage() { From 45d35bea79ccfad832c9c53c6259f9e1c8063311 Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Tue, 26 May 2026 08:51:00 +0200 Subject: [PATCH 18/26] Finish Step 1 Refactor --- .../ide/commandlet/CreateCommandlet.java | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java index 2a91c5f28d..06a80246d9 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java @@ -2,6 +2,7 @@ import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.function.Predicate; import org.slf4j.Logger; @@ -10,6 +11,7 @@ import com.devonfw.tools.ide.cli.CliException; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.environment.EnvironmentVariables; +import com.devonfw.tools.ide.git.GitUrl; import com.devonfw.tools.ide.io.FileAccess; import com.devonfw.tools.ide.io.FileCopyMode; import com.devonfw.tools.ide.log.IdeLogLevel; @@ -89,39 +91,36 @@ protected void updateSettings() { } /** - * This method is invoked when a new porject is created. It analyzes the cloned repository to check if it is a valid IDEasy repository. The repository can either be a settings repository (with ide.properties or devon.properties on the top level) + * This method is invoked when a new porject is created. It analyzes the cloned repository to check if it is a valid IDEasy repository. + * The repository can either be a settings repository (with ide.properties or devon.properties on the top level) * or a code repository (with a settings folder on the top level containing such a file). Otherwise, the project creation fails and an error message is logged. */ private void analyzeProject() { // Settings repository: ide.properties on top levels (or devon.properties for legacy users) // Code repository: settings folder on top level with ide.properties inside (or devon.properties for legacy users) String projectName = this.context.getProjectName(); - Path actualProjectPath; + Path actualProjectPath = this.context.getIdeRoot().resolve(projectName); FileAccess fileAccess = this.context.getFileAccess(); Path settingsPath = this.context.getSettingsPath(); // Check whether the repository is a valid settings repository, code repository, or neither if (isSettingsRepository(settingsPath)) { LOG.info("The repository seems to be a settings repository based on the presence of " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " on the top level."); - actualProjectPath = this.context.getIdeRoot(); moveProject(this.context.getIdeHome(), actualProjectPath); } else if (isCodeRepository(settingsPath)) { LOG.info(EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " found in settings subfolder. This indicates a code repository with a settings folder on the top level."); - // Move settings folder contents containing code into workspace/main/ - actualProjectPath = this.context.getIdeRoot().resolve(projectName).resolve("workspaces/main/").resolve(projectName); - for (Path child : fileAccess.listChildren(settingsPath, f -> true)) { - moveProject(child, actualProjectPath); - } - // Move remaining folders into IDE_ROOT/ - actualProjectPath = this.context.getIdeRoot(); + + String gitProjectName = GitUrl.of(this.settingsRepo.getValue(0)).getProjectName(); + Path codeFolderPath = actualProjectPath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN).resolve(gitProjectName); + // Move temp project to actual project location $IDE_ROOT/ moveProject(this.context.getIdeHome(), actualProjectPath); - // Delete empty settings folder in IDE_ROOT/ so we can create a symlink in the next step - fileAccess.delete(actualProjectPath.resolve(projectName).resolve("settings")); + + // Move settings fodler containing code to $IDE_ROOT//workspaces/main/ + moveProject(actualProjectPath.resolve(IdeContext.FOLDER_SETTINGS), codeFolderPath); + // Link settings folder in IDE_HOME to settings folder in code repository - fileAccess.symlink(actualProjectPath.resolve(projectName).resolve("workspaces/main").resolve(projectName).resolve("settings"), actualProjectPath.resolve(projectName).resolve("settings")); - // Final cleanup in temp location - fileAccess.delete(this.context.getIdeHome()); + fileAccess.symlink(codeFolderPath.resolve(IdeContext.FOLDER_SETTINGS), actualProjectPath.resolve(IdeContext.FOLDER_SETTINGS)); } else { // Repository seems to be invalid. Clean up temporary location and return error @@ -130,7 +129,7 @@ private void analyzeProject() { + "The repository does not seem to be a valid IDEasy repository. Please verify the repository and try again."); } // Set IDE_HOME to new (and actual) project location - this.context.setIdeHome(this.context.getIdeRoot().resolve(projectName)); + this.context.setIdeHome(actualProjectPath); } /** @@ -141,8 +140,8 @@ private void analyzeProject() { private void moveProject(Path oldPath, Path newPath) { FileAccess fileAccess = this.context.getFileAccess(); try { - fileAccess.copy(oldPath, newPath, FileCopyMode.COPY_TREE_OVERRIDE_FILES); - fileAccess.delete(oldPath); + fileAccess.mkdirs(newPath); + fileAccess.move(oldPath, newPath, StandardCopyOption.REPLACE_EXISTING); } catch (Exception e) { LOG.error("Failed to move project from {} to {}. Please move it manually.", oldPath, newPath, e); } From 0e82a267e0b56f609f5b2952a4e48db8f0755425 Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Tue, 26 May 2026 09:12:46 +0200 Subject: [PATCH 19/26] Fixed temp location not being fully empty after projectr creation --- .../com/devonfw/tools/ide/commandlet/CreateCommandlet.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java index 06a80246d9..9cbdc42fb2 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java @@ -119,6 +119,9 @@ private void analyzeProject() { // Move settings fodler containing code to $IDE_ROOT//workspaces/main/ moveProject(actualProjectPath.resolve(IdeContext.FOLDER_SETTINGS), codeFolderPath); + // Set IDE_HOME to new (and actual) project location + this.context.setIdeHome(actualProjectPath); + // Link settings folder in IDE_HOME to settings folder in code repository fileAccess.symlink(codeFolderPath.resolve(IdeContext.FOLDER_SETTINGS), actualProjectPath.resolve(IdeContext.FOLDER_SETTINGS)); From 777b9970e9eccb15f7e50eef6ec1592f7014d4b1 Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Thu, 28 May 2026 09:26:30 +0200 Subject: [PATCH 20/26] Removed references to --code option in project creation --- cli/src/main/resources/nls/Help.properties | 1 - cli/src/main/resources/nls/Help_de.properties | 1 - documentation/settings.adoc | 7 ++++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cli/src/main/resources/nls/Help.properties b/cli/src/main/resources/nls/Help.properties index d653c0e264..d2f84014e0 100644 --- a/cli/src/main/resources/nls/Help.properties +++ b/cli/src/main/resources/nls/Help.properties @@ -155,7 +155,6 @@ cmd.yarn.detail=Yarn is a package manager and build tool for JavaScript. Detaile commandlets=Available commandlets: icd-hint=Hint: Use 'icd' command to easily navigate between your IDE home, projects, and workspaces. Type 'icd --help' for more details. opt.--batch=enable batch mode (non-interactive). -opt.--code=clone given code repository containing a settings folder into workspaces so that settings can be committed alongside code changes. opt.--debug=enable debug logging. opt.--force=enable force mode. opt.--force-plugin-reinstall=resets installed plugins to the project configuration diff --git a/cli/src/main/resources/nls/Help_de.properties b/cli/src/main/resources/nls/Help_de.properties index 5c58dd3934..52d44e405c 100644 --- a/cli/src/main/resources/nls/Help_de.properties +++ b/cli/src/main/resources/nls/Help_de.properties @@ -155,7 +155,6 @@ cmd.yarn.detail=Yarn ist ein Package Manager und Build-Werkzeug für JavaScript. commandlets=Verfügbare Kommandos: icd-hint=Hinweis: Verwenden Sie den Befehl 'icd' um einfach zwischen Ihrem IDE-Hauptverzeichnis, Projekten und Workspaces zu navigieren. Geben Sie 'icd --help' für weitere Details ein. opt.--batch=Aktiviert den Batch-Modus (nicht-interaktive Stapelverarbeitung). -opt.--code=Git-Repository sowohl als Code- als auch als Settings-Repository verwenden. opt.--debug=Aktiviert Debug-Ausgaben (Fehleranalyse). opt.--force=Aktiviert den Force-Modus (Erzwingen). opt.--force-plugin-reinstall=Setzt installierte Plugins zurück auf die Projektkonfiguration. diff --git a/documentation/settings.adoc b/documentation/settings.adoc index e9de2335ca..632c2c4426 100644 --- a/documentation/settings.adoc +++ b/documentation/settings.adoc @@ -18,17 +18,18 @@ This gives you the freedom to control and manage the tools with their versions a To setup and customize these settings simply follow the link:usage.adoc#admin[admin usage guide]. Then tell your team to create the project using your project sepcific settings git URL: ``` -ide create «project-name» --code «settings-url» +ide create «project-name» «settings-url» ``` == Code-repository It is even possible to include your settings into your code repository by having the `settings` folder directly on top-level of your code git repository. This allows you to keep settings changes in sync with code changes and manage them in the same pull/merge requests. -To use this approach simply copy the content of https://github.com/devonfw/ide-settings[ide-settings] to a top-level `settings` folder in your code repository root and tell your developers to create the project usining the `--code` option: +To use this approach simply copy the content of https://github.com/devonfw/ide-settings[ide-settings] to a top-level `settings` folder in your code repository. +You can then create the project as normal as IDEasy will automatically detect that you are using a code repository: ``` -ide create «project-name» --code «code-repo-url» +ide create «project-name» «code-repo-url» ``` IDEasy will clone your repository and create a symlink to the settings folder. From 36018b14f8e0d93e54008866074333009a84b06f Mon Sep 17 00:00:00 2001 From: areinicke <167530118+areinicke@users.noreply.github.com> Date: Tue, 7 Jul 2026 09:28:20 +0200 Subject: [PATCH 21/26] Remove ProjectNameConvention reference --- .../tools/ide/commandlet/AbstractUpdateCommandlet.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java index 21c3b84269..eb7cde5233 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java @@ -12,7 +12,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.devonfw.tools.ide.cli.CliException; import com.devonfw.tools.ide.context.AbstractIdeContext; import com.devonfw.tools.ide.context.IdeContext; import com.devonfw.tools.ide.context.IdeStartContextImpl; @@ -20,7 +19,6 @@ import com.devonfw.tools.ide.git.GitUrl; import com.devonfw.tools.ide.git.repository.RepositoryCommandlet; import com.devonfw.tools.ide.io.FileAccess; -import com.devonfw.tools.ide.io.FileCopyMode; import com.devonfw.tools.ide.property.FlagProperty; import com.devonfw.tools.ide.property.StringProperty; import com.devonfw.tools.ide.step.Step; @@ -36,7 +34,6 @@ import com.devonfw.tools.ide.tool.extra.ExtraToolsMapper; import com.devonfw.tools.ide.variable.IdeVariables; import com.devonfw.tools.ide.version.VersionIdentifier; -import com.devonfw.tools.ide.environment.EnvironmentVariables; /** * Abstract {@link Commandlet} base-class for both {@link UpdateCommandlet} and {@link CreateCommandlet}. @@ -196,7 +193,6 @@ private void updateSettingsInStep(boolean codeRepository) { this.context.getFileAccess().backup(settingsPath); } GitUrl gitUrl = getOrAskSettingsUrl(); - checkProjectNameConvention(gitUrl.getProjectName()); initializeRepository(gitUrl); return; } From 63200676c7a6033d2ba9c8d9e8d54c8ff9bf35ba Mon Sep 17 00:00:00 2001 From: laim2003 Date: Mon, 3 Aug 2026 11:22:49 +0200 Subject: [PATCH 22/26] #1695: applied suggestions by @hohwille from original PR #1878 --- .../commandlet/AbstractUpdateCommandlet.java | 2 - .../ide/commandlet/CreateCommandlet.java | 40 ++++++++++++------- .../ide/commandlet/CreateCommandletTest.java | 8 ++-- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java index eb7cde5233..c8e1b0cc9a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java @@ -106,8 +106,6 @@ protected void doRun() { createStartScripts(); } - - private void reloadContext() { ((AbstractIdeContext) this.context).reload(); diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java index d7b7ec6efa..8d557df584 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java @@ -13,9 +13,7 @@ import com.devonfw.tools.ide.environment.EnvironmentVariables; import com.devonfw.tools.ide.git.GitUrl; import com.devonfw.tools.ide.io.FileAccess; -import com.devonfw.tools.ide.io.FileCopyMode; import com.devonfw.tools.ide.log.IdeLogLevel; -import com.devonfw.tools.ide.property.FlagProperty; import com.devonfw.tools.ide.property.StringProperty; import com.devonfw.tools.ide.version.IdeVersion; @@ -58,7 +56,13 @@ protected void doRun() { String newProjectName = this.newProject.getValue(); Path newProjectPath = this.context.getIdeRoot().resolve(newProjectName); - Path tempProjectPath = this.context.getIdeRoot().resolve("_ide/tmp/projects").resolve(newProjectName); + Path tempProjectPath = this.context.getTempPath().resolve(IdeContext.FOLDER_PROJECTS).resolve(newProjectName); + + if (Files.exists(newProjectPath)) { + throw new CliException( + String.format("Project directory already exists: %s. If the project already exists, try calling 'ide update'.", + newProjectPath)); + } LOG.info("Creating new IDEasy project in {}", newProjectPath); if (!this.context.getFileAccess().isEmptyDir(newProjectPath)) { @@ -88,10 +92,10 @@ protected void updateSettings() { analyzeProject(); } - /** - * This method is invoked when a new porject is created. It analyzes the cloned repository to check if it is a valid IDEasy repository. - * The repository can either be a settings repository (with ide.properties or devon.properties on the top level) - * or a code repository (with a settings folder on the top level containing such a file). Otherwise, the project creation fails and an error message is logged. + /** + * This method is invoked when a new porject is created. It analyzes the cloned repository to check if it is a valid IDEasy repository. The repository can + * either be a settings repository (with ide.properties or devon.properties on the top level) or a code repository (with a settings folder on the top level + * containing such a file). Otherwise, the project creation fails and an error message is logged. */ private void analyzeProject() { // Settings repository: ide.properties on top levels (or devon.properties for legacy users) @@ -103,12 +107,14 @@ private void analyzeProject() { // Check whether the repository is a valid settings repository, code repository, or neither if (isSettingsRepository(settingsPath)) { - LOG.info("The repository seems to be a settings repository based on the presence of " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " on the top level."); + LOG.info("The repository seems to be a settings repository based on the presence of " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + + EnvironmentVariables.LEGACY_PROPERTIES + " on the top level."); moveProject(this.context.getIdeHome(), actualProjectPath); } else if (isCodeRepository(settingsPath)) { - LOG.info(EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " found in settings subfolder. This indicates a code repository with a settings folder on the top level."); - + LOG.info(EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + + " found in settings subfolder. This indicates a code repository with a settings folder on the top level."); + String gitProjectName = GitUrl.of(this.settingsRepo.getValue(0)).getProjectName(); Path codeFolderPath = actualProjectPath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN).resolve(gitProjectName); // Move temp project to actual project location $IDE_ROOT/ @@ -126,8 +132,9 @@ private void analyzeProject() { } else { // Repository seems to be invalid. Clean up temporary location and return error fileAccess.delete(this.context.getIdeHome()); - throw new CliException("This repository does not include an " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " file at the top level or a settings folder with such a file. " - + "The repository does not seem to be a valid IDEasy repository. Please verify the repository and try again."); + throw new CliException("This repository does not include an " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + + " file at the top level or a settings folder with such a file. " + + "The repository does not seem to be a valid IDEasy repository. Please verify the repository and try again."); } // Set IDE_HOME to new (and actual) project location this.context.setIdeHome(actualProjectPath); @@ -135,6 +142,7 @@ private void analyzeProject() { /** * Moves files of a new projectfrom the temporary location to the final project location. + * * @param oldPath - The path of the file or directory to be moved. * @param newPath - The path of the destination. */ @@ -150,14 +158,18 @@ private void moveProject(Path oldPath, Path newPath) { /** * Checks whether te given repository is a settings repository by checking for the presence of ide.properties or devon.properties on the top level. + * * @param repositoryPath - The path of the repository to be checked. */ private boolean isSettingsRepository(Path repositoryPath) { - return Files.exists(repositoryPath.resolve(EnvironmentVariables.DEFAULT_PROPERTIES)) || Files.exists(repositoryPath.resolve(EnvironmentVariables.LEGACY_PROPERTIES)); + return Files.exists(repositoryPath.resolve(EnvironmentVariables.DEFAULT_PROPERTIES)) || Files.exists( + repositoryPath.resolve(EnvironmentVariables.LEGACY_PROPERTIES)); } /** - * Checks whether te given repository is a code repository by checking for the presence of ide.properties or devon.properties within a settings folder on the top level. + * Checks whether te given repository is a code repository by checking for the presence of ide.properties or devon.properties within a settings folder on the + * top level. + * * @param repositoryPath - The path of the repository to be checked. */ private boolean isCodeRepository(Path repositoryPath) { diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java index f117c77c21..5c41631125 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java @@ -7,15 +7,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; import com.devonfw.tools.ide.cli.CliArguments; import com.devonfw.tools.ide.cli.CliException; 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.ProcessContextGitMock; import com.devonfw.tools.ide.environment.EnvironmentVariables; import com.devonfw.tools.ide.environment.EnvironmentVariablesType; import com.devonfw.tools.ide.git.GitContextImplMock; @@ -193,13 +190,14 @@ void testProjectWithInvalidRepositoryNotCreated() { // act - run the create command assertThatThrownBy(() -> cc.run()) .isInstanceOf(CliException.class) - .hasMessageContaining("This repository does not include an " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + " file at the top level or a settings folder with such a file.") + .hasMessageContaining("This repository does not include an " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES + + " file at the top level or a settings folder with such a file.") .hasMessageContaining("The repository does not seem to be a valid IDEasy repository. Please verify the repository and try again."); // assert Path newProjectPath = context.getIdeRoot().resolve(NEW_PROJECT_NAME); assertThat(newProjectPath).doesNotExist(); - assertThat(context.getIdeRoot().resolve("_ide/tmp/projects").resolve(NEW_PROJECT_NAME)).doesNotExist(); + assertThat(context.getTempPath().resolve(IdeContext.FOLDER_PROJECTS).resolve(NEW_PROJECT_NAME)).doesNotExist(); } @Test From 1c92338b78fd29a22e60437ab95bfc7727807268 Mon Sep 17 00:00:00 2001 From: laim2003 Date: Mon, 3 Aug 2026 14:32:44 +0200 Subject: [PATCH 23/26] #1695: added check for potential folder colision in CreateCommandlet Signed-off-by: laim2003 --- .../com/devonfw/tools/ide/commandlet/CreateCommandlet.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java index 8d557df584..119fbfffd7 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java @@ -58,9 +58,12 @@ protected void doRun() { Path newProjectPath = this.context.getIdeRoot().resolve(newProjectName); Path tempProjectPath = this.context.getTempPath().resolve(IdeContext.FOLDER_PROJECTS).resolve(newProjectName); - if (Files.exists(newProjectPath)) { + if (Files.exists(tempProjectPath)) { throw new CliException( - String.format("Project directory already exists: %s. If the project already exists, try calling 'ide update'.", + String.format("Temporary project directory already exists in: %s. Please delete it and try again.", tempProjectPath)); + } else if (Files.exists(newProjectPath)) { + throw new CliException( + String.format("Project directory already exists in: %s. As the project already exists, try calling 'ide update'.", newProjectPath)); } From 43ac0f2e46bdccc41b866a81ed66e638d5a3c1e1 Mon Sep 17 00:00:00 2001 From: laim2003 Date: Mon, 3 Aug 2026 14:33:39 +0200 Subject: [PATCH 24/26] #1695: adapted tests to new project health checks Signed-off-by: laim2003 --- .../tools/ide/commandlet/CreateCommandletTest.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java index 5c41631125..fb73597fef 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java @@ -36,6 +36,10 @@ void setup() { if (Files.exists(newProjectPath)) { context.getFileAccess().delete(newProjectPath); } + Path tempProjectPath = context.getTempPath().resolve(IdeContext.FOLDER_PROJECTS).resolve(NEW_PROJECT_NAME); + if (Files.exists(tempProjectPath)) { + context.getFileAccess().delete(tempProjectPath); + } this.context = context; } @@ -51,6 +55,9 @@ void tearDown() { void testCreateCommandletRun() { // arrange + GitContextImplMock gitContextImplMock = new GitContextImplMock(context, TEST_RESOURCES.resolve("settings")); + context.setGitContext(gitContextImplMock); + CreateCommandlet cc = context.getCommandletManager().getCommandlet(CreateCommandlet.class); cc.newProject.setValueAsString(NEW_PROJECT_NAME, context); cc.settingsRepo.setValue(IdeContext.DEFAULT_SETTINGS_REPO_URL); @@ -123,6 +130,9 @@ void testIdeVersionTooOldForExistingProject() { @Test void testIdeVersionOk() { // arrange + GitContextImplMock gitContextImplMock = new GitContextImplMock(context, TEST_RESOURCES.resolve("settings")); + context.setGitContext(gitContextImplMock); + CreateCommandlet cc = context.getCommandletManager().getCommandlet(CreateCommandlet.class); cc.newProject.setValueAsString(NEW_PROJECT_NAME, context); cc.settingsRepo.setValue(IdeContext.DEFAULT_SETTINGS_REPO_URL); @@ -214,7 +224,7 @@ void testCreateWithDashPlaceholderAsCliArgument() { assertThat(result).isEqualTo(0); assertThat(context).logAtError().hasNoMessageContaining("not found for commandlet"); assertThat(context).logAtInfo() - .hasMessageContaining("'-' was found for settings repository, the default settings repository"); + .hasMessageContaining("'-' was found for the repository, the default settings repository"); Path newProjectPath = context.getIdeRoot().resolve(NEW_PROJECT_NAME); assertThat(newProjectPath).exists(); } From 351e43af44397c8b178c3b9c981ca02b0aeb87c8 Mon Sep 17 00:00:00 2001 From: laim2003 Date: Mon, 3 Aug 2026 14:49:29 +0200 Subject: [PATCH 25/26] #1695: merged orginal changes by @areinicke into GitContextMock Signed-off-by: laim2003 --- .../devonfw/tools/ide/commandlet/CreateCommandletTest.java | 7 ++----- .../java/com/devonfw/tools/ide/git/GitContextMock.java | 3 +++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java index fb73597fef..88eaa0d5d9 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java @@ -55,15 +55,14 @@ void tearDown() { void testCreateCommandletRun() { // arrange - GitContextImplMock gitContextImplMock = new GitContextImplMock(context, TEST_RESOURCES.resolve("settings")); - context.setGitContext(gitContextImplMock); - CreateCommandlet cc = context.getCommandletManager().getCommandlet(CreateCommandlet.class); cc.newProject.setValueAsString(NEW_PROJECT_NAME, context); cc.settingsRepo.setValue(IdeContext.DEFAULT_SETTINGS_REPO_URL); cc.skipTools.setValue(true); + // act cc.run(); + // assert Path newProjectPath = context.getIdeRoot().resolve(NEW_PROJECT_NAME); assertThat(newProjectPath).exists(); @@ -130,8 +129,6 @@ void testIdeVersionTooOldForExistingProject() { @Test void testIdeVersionOk() { // arrange - GitContextImplMock gitContextImplMock = new GitContextImplMock(context, TEST_RESOURCES.resolve("settings")); - context.setGitContext(gitContextImplMock); CreateCommandlet cc = context.getCommandletManager().getCommandlet(CreateCommandlet.class); cc.newProject.setValueAsString(NEW_PROJECT_NAME, context); diff --git a/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java b/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java index 2f5c689834..e608dace12 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java +++ b/cli/src/test/java/com/devonfw/tools/ide/git/GitContextMock.java @@ -53,6 +53,9 @@ public void clone(GitUrl gitUrl, Path repository) { FileAccess fileAccess = this.context.getFileAccess(); fileAccess.mkdirs(repository); + // Create ide.properties to simulate a valid repository + fileAccess.touch(repository.resolve("ide.properties")); + Path gitFolder = repository.resolve(GIT_FOLDER); fileAccess.mkdirs(gitFolder); String branch = gitUrl.branch(); From f413ed37616cd4237ae197e173bd3983caab062d Mon Sep 17 00:00:00 2001 From: laim2003 Date: Mon, 3 Aug 2026 14:52:45 +0200 Subject: [PATCH 26/26] #1695: maven spotless plugin applied Signed-off-by: laim2003 --- .../com/devonfw/tools/ide/commandlet/CreateCommandletTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java index 88eaa0d5d9..cc393b7ff5 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/CreateCommandletTest.java @@ -62,7 +62,7 @@ void testCreateCommandletRun() { // act cc.run(); - + // assert Path newProjectPath = context.getIdeRoot().resolve(NEW_PROJECT_NAME); assertThat(newProjectPath).exists();