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

devonfw / IDEasy / 28849265105

07 Jul 2026 07:28AM UTC coverage: 71.962% (-0.1%) from 72.066%
28849265105

Pull #1878

github

web-flow
Merge 36018b14f into 2cf3f5070
Pull Request #1878: #1695: Clone settings to temporary directory, analyse, and then move

4845 of 7426 branches covered (65.24%)

Branch coverage included in aggregate %.

12418 of 16563 relevant lines covered (74.97%)

3.18 hits per line

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

81.01
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.io.FileCopyMode;
17
import com.devonfw.tools.ide.log.IdeLogLevel;
18
import com.devonfw.tools.ide.property.FlagProperty;
19
import com.devonfw.tools.ide.property.StringProperty;
20
import com.devonfw.tools.ide.version.IdeVersion;
21

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

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

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

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

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

44
  @Override
45
  public String getName() {
46

47
    return "create";
2✔
48
  }
49

50
  @Override
51
  public boolean isIdeHomeRequired() {
52

53
    return false;
2✔
54
  }
55

56
  @Override
57
  protected void doRun() {
58

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

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

68
    initializeProject(tempProjectPath);
3✔
69
    this.context.setIdeHome(tempProjectPath);
4✔
70
    super.doRun();
2✔
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.
93
   * The repository can either be a settings repository (with ide.properties or devon.properties on the top level)
94
   * 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.
95
   */
96
  private void analyzeProject() {
97
    // Settings repository: ide.properties on top levels (or devon.properties for legacy users)
98
    // Code repository: settings folder on top level with ide.properties inside (or devon.properties for legacy users)
99
    String projectName = this.context.getProjectName();
4✔
100
    Path actualProjectPath = this.context.getIdeRoot().resolve(projectName);
6✔
101
    FileAccess fileAccess = this.context.getFileAccess();
4✔
102
    Path settingsPath = this.context.getSettingsPath();
4✔
103

104
    // Check whether the repository is a valid settings repository, code repository, or neither
105
    if (isSettingsRepository(settingsPath)) {
4✔
106
      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✔
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
      
112
      String gitProjectName = GitUrl.of(this.settingsRepo.getValue(0)).getProjectName();
×
113
      Path codeFolderPath = actualProjectPath.resolve(IdeContext.FOLDER_WORKSPACES).resolve(IdeContext.WORKSPACE_MAIN).resolve(gitProjectName);
×
114
      // Move temp project to actual project location $IDE_ROOT/<project_name>
115
      moveProject(this.context.getIdeHome(), actualProjectPath);
×
116

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

120
      // Set IDE_HOME to new (and actual) project location
121
      this.context.setIdeHome(actualProjectPath);
×
122

123
      // Link settings folder in IDE_HOME to settings folder in code repository
124
      fileAccess.symlink(codeFolderPath.resolve(IdeContext.FOLDER_SETTINGS), actualProjectPath.resolve(IdeContext.FOLDER_SETTINGS));
×
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(actualProjectPath);
4✔
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.mkdirs(newPath);
3✔
145
      fileAccess.move(oldPath, newPath, StandardCopyOption.REPLACE_EXISTING);
10✔
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