• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

devonfw / IDEasy / 26281905478

22 May 2026 10:13AM UTC coverage: 70.945% (-0.1%) from 71.064%
26281905478

Pull #1878

github

web-flow
Merge 900c0f291 into 2a8d4e347
Pull Request #1878: #1695: Clone settings to temporary directory, analyse, and then move

4453 of 6946 branches covered (64.11%)

Branch coverage included in aggregate %.

11521 of 15570 relevant lines covered (73.99%)

3.13 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.65
cli/src/main/java/com/devonfw/tools/ide/commandlet/CreateCommandlet.java
1
package com.devonfw.tools.ide.commandlet;
2

3
import java.nio.file.Files;
4
import java.nio.file.Path;
5
import java.util.function.Predicate;
6

7
import org.slf4j.Logger;
8
import org.slf4j.LoggerFactory;
9

10
import com.devonfw.tools.ide.cli.CliException;
11
import com.devonfw.tools.ide.context.IdeContext;
12
import com.devonfw.tools.ide.environment.EnvironmentVariables;
13
import com.devonfw.tools.ide.io.FileAccess;
14
import com.devonfw.tools.ide.io.FileCopyMode;
15
import com.devonfw.tools.ide.log.IdeLogLevel;
16
import com.devonfw.tools.ide.property.FlagProperty;
17
import com.devonfw.tools.ide.property.StringProperty;
18
import com.devonfw.tools.ide.version.IdeVersion;
19

20
/**
21
 * {@link Commandlet} to create a new IDEasy instance
22
 */
23
public class CreateCommandlet extends AbstractUpdateCommandlet {
24

25
  private static final Logger LOG = LoggerFactory.getLogger(CreateCommandlet.class);
4✔
26

27
  /** {@link StringProperty} for the name of the new project */
28
  public final StringProperty newProject;
29

30
  /**
31
   * The constructor.
32
   *
33
   * @param context the {@link IdeContext}.
34
   */
35
  public CreateCommandlet(IdeContext context) {
36

37
    super(context);
3✔
38
    this.newProject = add(new StringProperty("", true, "project"));
11✔
39
    add(this.settingsRepo);
5✔
40
  }
1✔
41

42
  @Override
43
  public String getName() {
44

45
    return "create";
2✔
46
  }
47

48
  @Override
49
  public boolean isIdeHomeRequired() {
50

51
    return false;
2✔
52
  }
53

54
  @Override
55
  protected void doRun() {
56

57
    String newProjectName = this.newProject.getValue();
5✔
58
    Path newProjectPath = this.context.getIdeRoot().resolve(newProjectName);
6✔
59
    Path tempProjectPath = this.context.getIdeRoot().resolve("_ide/tmp/projects").resolve(newProjectName);
8✔
60

61
    LOG.info("Creating new IDEasy project in {}", newProjectPath);
4✔
62
    if (!this.context.getFileAccess().isEmptyDir(newProjectPath)) {
6!
63
      this.context.askToContinue("Directory {} already exists. Do you want to continue?", newProjectPath);
×
64
    }
65

66
    initializeProject(tempProjectPath);
3✔
67
    this.context.setIdeHome(tempProjectPath);
4✔
68
    this.context.verifyIdeMinVersion(true);
4✔
69
    super.doRun();
2✔
70
    this.context.verifyIdeMinVersion(true);
4✔
71
    this.context.getFileAccess().writeFileContent(IdeVersion.getVersionString(), newProjectPath.resolve(IdeContext.FILE_SOFTWARE_VERSION));
8✔
72
    IdeLogLevel.SUCCESS.log(LOG, "Successfully created new project '{}'.", newProjectName);
10✔
73

74
    logWelcomeMessage();
2✔
75
  }
1✔
76

77
  private void initializeProject(Path newInstancePath) {
78

79
    FileAccess fileAccess = this.context.getFileAccess();
4✔
80
    fileAccess.mkdirs(newInstancePath.resolve(IdeContext.FOLDER_SOFTWARE));
5✔
81
    fileAccess.mkdirs(newInstancePath.resolve(IdeContext.FOLDER_PLUGINS));
5✔
82
    fileAccess.mkdirs(newInstancePath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN));
7✔
83
  }
1✔
84

85
  @Override
86
  protected void updateSettings() {
87
    super.updateSettings();
2✔
88
    analyzeProject();
2✔
89
  }
1✔
90

91
    /**
92
   * 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)
93
   * 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.
94
   */
95
  private void analyzeProject() {
96
    // Settings repository: ide.properties on top levels (or devon.properties for legacy users)
97
    // Code repository: settings folder on top level with ide.properties inside (or devon.properties for legacy users)
98
    String projectName = this.context.getProjectName();
4✔
99
    Path actualProjectPath;
100
    FileAccess fileAccess = this.context.getFileAccess();
4✔
101
    Path settingsPath = this.context.getSettingsPath();
4✔
102

103
    // Check whether the repository is a valid settings repository, code repository, or neither
104
    if (isSettingsRepository(settingsPath)) {
4✔
105
      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.");
3✔
106
      actualProjectPath = this.context.getIdeRoot();
4✔
107
      moveProject(this.context.getIdeHome(), actualProjectPath);
7✔
108

109
    } else if (isCodeRepository(settingsPath)) {
4!
110
      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.");
×
111
      // Move settings folder contents containing code into workspace/main/<project_name>
112
      actualProjectPath = this.context.getIdeRoot().resolve(projectName).resolve("workspaces/main/").resolve(projectName);
×
113
      for (Path child : fileAccess.listChildren(settingsPath, f -> true)) {
×
114
        moveProject(child, actualProjectPath);
×
115
      }
×
116
      // Move remaining folders into IDE_ROOT/<project_name>
117
      actualProjectPath = this.context.getIdeRoot();
×
118
      moveProject(this.context.getIdeHome(), actualProjectPath);
×
119
      // Delete empty settings folder in IDE_ROOT/<project_name> so we can create a symlink in the next step
120
      fileAccess.delete(actualProjectPath.resolve(projectName).resolve("settings"));
×
121
      // Link settings folder in IDE_HOME to settings folder in code repository
122
      fileAccess.symlink(actualProjectPath.resolve(projectName).resolve("workspaces/main").resolve(projectName).resolve("settings"), actualProjectPath.resolve(projectName).resolve("settings"));
×
123
      // Final cleanup in temp location
124
      fileAccess.delete(this.context.getIdeHome());
×
125

126
    } else {
127
      // Repository seems to be invalid. Clean up temporary location and return error
128
      fileAccess.delete(this.context.getIdeHome());
5✔
129
      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. "
5✔
130
      + "The repository does not seem to be a valid IDEasy repository. Please verify the repository and try again.");
131
    }
132
    // Set IDE_HOME to new (and actual) project location
133
    this.context.setIdeHome(this.context.getIdeRoot().resolve(projectName));
8✔
134
  }
1✔
135

136
  /**
137
   * Moves files of a new projectfrom the temporary location to the final project location.
138
   * @param oldPath - The path of the file or directory to be moved.
139
   * @param newPath - The path of the destination.
140
   */
141
  private void moveProject(Path oldPath, Path newPath) {
142
    FileAccess fileAccess = this.context.getFileAccess();
4✔
143
    try {
144
      fileAccess.copy(oldPath, newPath, FileCopyMode.COPY_TREE_OVERRIDE_FILES);
5✔
145
      fileAccess.delete(oldPath);
3✔
146
    } catch (Exception e) {
×
147
      LOG.error("Failed to move project from {} to {}. Please move it manually.", oldPath, newPath, e);
×
148
    }
1✔
149
  }
1✔
150

151
  /**
152
   * Checks whether te given repository is a settings repository by checking for the presence of ide.properties or devon.properties on the top level.
153
   * @param repositoryPath - The path of the repository to be checked.
154
   */
155
  private boolean isSettingsRepository(Path repositoryPath) {
156
    return Files.exists(repositoryPath.resolve(EnvironmentVariables.DEFAULT_PROPERTIES)) || Files.exists(repositoryPath.resolve(EnvironmentVariables.LEGACY_PROPERTIES));
18!
157
  }
158

159
  /**
160
   * 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.
161
   * @param repositoryPath - The path of the repository to be checked.
162
   */
163
  private boolean isCodeRepository(Path repositoryPath) {
164
    return isSettingsRepository(repositoryPath.resolve(IdeContext.FOLDER_SETTINGS));
6✔
165
  }
166

167
  @Override
168
  protected String getStepMessage() {
169

170
    return "Create (Clone) repository";
2✔
171
  }
172

173
  private void logWelcomeMessage() {
174
    Path settingsFolder = this.context.getSettingsPath();
4✔
175
    if (Files.exists(settingsFolder)) {
5!
176
      Predicate<Path> welcomePredicate = path -> String.valueOf(path.getFileName()).startsWith("welcome.");
8✔
177
      Path welcomeFilePath = this.context.getFileAccess().findFirst(settingsFolder, welcomePredicate, false);
8✔
178
      if (welcomeFilePath != null) {
2✔
179
        LOG.info(this.context.getFileAccess().readFileContent(welcomeFilePath));
7✔
180
      }
181
    }
182
  }
1✔
183
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc