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

devonfw / IDEasy / 30815427594

03 Aug 2026 12:52PM UTC coverage: 72.518% (-0.09%) from 72.61%
30815427594

Pull #2249

github

web-flow
Merge f413ed376 into cbb2c56d8
Pull Request #2249: #1695: Clone settings to temporary directory, analyse, and then move (taken over)

4989 of 7623 branches covered (65.45%)

Branch coverage included in aggregate %.

13047 of 17248 relevant lines covered (75.64%)

3.21 hits per line

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

76.67
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.nio.file.StandardCopyOption;
6
import java.util.function.Predicate;
7

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

11
import com.devonfw.tools.ide.cli.CliException;
12
import com.devonfw.tools.ide.context.IdeContext;
13
import com.devonfw.tools.ide.environment.EnvironmentVariables;
14
import com.devonfw.tools.ide.git.GitUrl;
15
import com.devonfw.tools.ide.io.FileAccess;
16
import com.devonfw.tools.ide.log.IdeLogLevel;
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.getTempPath().resolve(IdeContext.FOLDER_PROJECTS).resolve(newProjectName);
8✔
60

61
    if (Files.exists(tempProjectPath)) {
5!
62
      throw new CliException(
×
63
          String.format("Temporary project directory already exists in: %s. Please delete it and try again.", tempProjectPath));
×
64
    } else if (Files.exists(newProjectPath)) {
5!
65
      throw new CliException(
×
66
          String.format("Project directory already exists in: %s. As the project already exists, try calling 'ide update'.",
×
67
              newProjectPath));
68
    }
69

70
    LOG.info("Creating new IDEasy project in {}", newProjectPath);
4✔
71
    if (!this.context.getFileAccess().isEmptyDir(newProjectPath)) {
6!
72
      this.context.askToContinue("Directory {} already exists. Do you want to continue?", newProjectPath);
×
73
    }
74

75
    initializeProject(tempProjectPath);
3✔
76
    this.context.setIdeHome(tempProjectPath);
4✔
77
    super.doRun();
2✔
78
    this.context.getFileAccess().writeFileContent(IdeVersion.getVersionString(), newProjectPath.resolve(IdeContext.FILE_SOFTWARE_VERSION));
8✔
79
    IdeLogLevel.SUCCESS.log(LOG, "Successfully created new project '{}'.", newProjectName);
10✔
80

81
    logWelcomeMessage();
2✔
82
  }
1✔
83

84
  private void initializeProject(Path newInstancePath) {
85

86
    FileAccess fileAccess = this.context.getFileAccess();
4✔
87
    fileAccess.mkdirs(newInstancePath.resolve(IdeContext.FOLDER_SOFTWARE));
5✔
88
    fileAccess.mkdirs(newInstancePath.resolve(IdeContext.FOLDER_PLUGINS));
5✔
89
    fileAccess.mkdirs(newInstancePath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN));
7✔
90
  }
1✔
91

92
  @Override
93
  protected void updateSettings() {
94
    super.updateSettings();
2✔
95
    analyzeProject();
2✔
96
  }
1✔
97

98
  /**
99
   * 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
100
   * 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
101
   * containing such a file). Otherwise, the project creation fails and an error message is logged.
102
   */
103
  private void analyzeProject() {
104
    // Settings repository: ide.properties on top levels (or devon.properties for legacy users)
105
    // Code repository: settings folder on top level with ide.properties inside (or devon.properties for legacy users)
106
    String projectName = this.context.getProjectName();
4✔
107
    Path actualProjectPath = this.context.getIdeRoot().resolve(projectName);
6✔
108
    FileAccess fileAccess = this.context.getFileAccess();
4✔
109
    Path settingsPath = this.context.getSettingsPath();
4✔
110

111
    // Check whether the repository is a valid settings repository, code repository, or neither
112
    if (isSettingsRepository(settingsPath)) {
4✔
113
      LOG.info("The repository seems to be a settings repository based on the presence of " + EnvironmentVariables.DEFAULT_PROPERTIES + " or "
3✔
114
          + EnvironmentVariables.LEGACY_PROPERTIES + " on the top level.");
115
      moveProject(this.context.getIdeHome(), actualProjectPath);
7✔
116

117
    } else if (isCodeRepository(settingsPath)) {
4!
118
      LOG.info(EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES
×
119
          + " found in settings subfolder. This indicates a code repository with a settings folder on the top level.");
120

121
      String gitProjectName = GitUrl.of(this.settingsRepo.getValue(0)).getProjectName();
×
122
      Path codeFolderPath = actualProjectPath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN).resolve(gitProjectName);
×
123
      // Move temp project to actual project location $IDE_ROOT/<project_name>
124
      moveProject(this.context.getIdeHome(), actualProjectPath);
×
125

126
      // Move settings fodler containing code to $IDE_ROOT/<project_name>/workspaces/main/<git_project_name>
127
      moveProject(actualProjectPath.resolve(IdeContext.FOLDER_SETTINGS), codeFolderPath);
×
128

129
      // Set IDE_HOME to new (and actual) project location
130
      this.context.setIdeHome(actualProjectPath);
×
131

132
      // Link settings folder in IDE_HOME to settings folder in code repository
133
      fileAccess.symlink(codeFolderPath.resolve(IdeContext.FOLDER_SETTINGS), actualProjectPath.resolve(IdeContext.FOLDER_SETTINGS));
×
134

135
    } else {
×
136
      // Repository seems to be invalid. Clean up temporary location and return error
137
      fileAccess.delete(this.context.getIdeHome());
5✔
138
      throw new CliException("This repository does not include an " + EnvironmentVariables.DEFAULT_PROPERTIES + " or " + EnvironmentVariables.LEGACY_PROPERTIES
5✔
139
          + " file at the top level or a settings folder with such a file. "
140
          + "The repository does not seem to be a valid IDEasy repository. Please verify the repository and try again.");
141
    }
142
    // Set IDE_HOME to new (and actual) project location
143
    this.context.setIdeHome(actualProjectPath);
4✔
144
  }
1✔
145

146
  /**
147
   * Moves files of a new projectfrom the temporary location to the final project location.
148
   *
149
   * @param oldPath - The path of the file or directory to be moved.
150
   * @param newPath - The path of the destination.
151
   */
152
  private void moveProject(Path oldPath, Path newPath) {
153
    FileAccess fileAccess = this.context.getFileAccess();
4✔
154
    try {
155
      fileAccess.mkdirs(newPath);
3✔
156
      fileAccess.move(oldPath, newPath, StandardCopyOption.REPLACE_EXISTING);
10✔
157
    } catch (Exception e) {
×
158
      LOG.error("Failed to move project from {} to {}. Please move it manually.", oldPath, newPath, e);
×
159
    }
1✔
160
  }
1✔
161

162
  /**
163
   * Checks whether te given repository is a settings repository by checking for the presence of ide.properties or devon.properties on the top level.
164
   *
165
   * @param repositoryPath - The path of the repository to be checked.
166
   */
167
  private boolean isSettingsRepository(Path repositoryPath) {
168
    return Files.exists(repositoryPath.resolve(EnvironmentVariables.DEFAULT_PROPERTIES)) || Files.exists(
15!
169
        repositoryPath.resolve(EnvironmentVariables.LEGACY_PROPERTIES));
3✔
170
  }
171

172
  /**
173
   * 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
174
   * top level.
175
   *
176
   * @param repositoryPath - The path of the repository to be checked.
177
   */
178
  private boolean isCodeRepository(Path repositoryPath) {
179
    return isSettingsRepository(repositoryPath.resolve(IdeContext.FOLDER_SETTINGS));
6✔
180
  }
181

182
  @Override
183
  protected String getStepMessage() {
184

185
    return "Create (Clone) repository";
2✔
186
  }
187

188
  private void logWelcomeMessage() {
189
    Path settingsFolder = this.context.getSettingsPath();
4✔
190
    if (Files.exists(settingsFolder)) {
5!
191
      Predicate<Path> welcomePredicate = path -> String.valueOf(path.getFileName()).startsWith("welcome.");
8✔
192
      Path welcomeFilePath = this.context.getFileAccess().findFirst(settingsFolder, welcomePredicate, false);
8✔
193
      if (welcomeFilePath != null) {
2✔
194
        LOG.info(this.context.getFileAccess().readFileContent(welcomeFilePath));
7✔
195
      }
196
    }
197
  }
1✔
198
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc