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

devonfw / IDEasy / 16592980535

29 Jul 2025 10:02AM UTC coverage: 68.686% (+0.1%) from 68.59%
16592980535

push

github

web-flow
#742: code cleanup and simplification (#1432)

3302 of 5214 branches covered (63.33%)

Branch coverage included in aggregate %.

8422 of 11855 relevant lines covered (71.04%)

3.14 hits per line

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

83.33
cli/src/main/java/com/devonfw/tools/ide/commandlet/AbstractUpdateCommandlet.java
1
package com.devonfw.tools.ide.commandlet;
2

3
import java.io.IOException;
4
import java.nio.file.Files;
5
import java.nio.file.Path;
6
import java.util.HashSet;
7
import java.util.Iterator;
8
import java.util.List;
9
import java.util.Set;
10
import java.util.stream.Stream;
11

12
import com.devonfw.tools.ide.cli.CliException;
13
import com.devonfw.tools.ide.context.AbstractIdeContext;
14
import com.devonfw.tools.ide.context.IdeContext;
15
import com.devonfw.tools.ide.context.IdeStartContextImpl;
16
import com.devonfw.tools.ide.git.GitContext;
17
import com.devonfw.tools.ide.git.GitUrl;
18
import com.devonfw.tools.ide.git.repository.RepositoryCommandlet;
19
import com.devonfw.tools.ide.io.FileAccess;
20
import com.devonfw.tools.ide.property.FlagProperty;
21
import com.devonfw.tools.ide.property.StringProperty;
22
import com.devonfw.tools.ide.step.Step;
23
import com.devonfw.tools.ide.tool.CustomToolCommandlet;
24
import com.devonfw.tools.ide.tool.ToolCommandlet;
25
import com.devonfw.tools.ide.tool.repository.CustomToolMetadata;
26
import com.devonfw.tools.ide.variable.IdeVariables;
27

28
/**
29
 * Abstract {@link Commandlet} base-class for both {@link UpdateCommandlet} and {@link CreateCommandlet}.
30
 */
31
public abstract class AbstractUpdateCommandlet extends Commandlet {
32

33
  private static final String MESSAGE_CODE_REPO_URL = """
34
      No code repository was given after '--code'.
35
      Further details can be found here: https://github.com/devonfw/IDEasy/blob/main/documentation/settings.adoc
36
      Please enter the code repository below that includes your settings folder.""";
37

38
  private static final String MESSAGE_SETTINGS_REPO_URL = """
39
      No settings found at {} and no SETTINGS_URL is defined.
40
      Further details can be found here: https://github.com/devonfw/IDEasy/blob/main/documentation/settings.adoc
41
      Please contact the technical lead of your project to get the SETTINGS_URL for your project to enter.
42
      In case you just want to test IDEasy you may simply hit return to install the default settings.""";
43

44
  /** {@link StringProperty} for the settings repository URL. */
45
  public final StringProperty settingsRepo;
46

47
  /** {@link FlagProperty} for skipping installation/updating of tools. */
48
  public final FlagProperty skipTools;
49

50
  /** {@link FlagProperty} for skipping the setup of git repositories. */
51
  public final FlagProperty skipRepositories;
52

53
  /** {@link FlagProperty} to force the update of the settings git repository. */
54
  public final FlagProperty forcePull;
55

56
  /** {@link FlagProperty} to force the installation/update of plugins. */
57
  public final FlagProperty forcePlugins;
58

59
  /** {@link FlagProperty} to force the setup of git repositories. */
60
  public final FlagProperty forceRepositories;
61

62
  /**
63
   * The constructor.
64
   *
65
   * @param context the {@link IdeContext}.
66
   */
67
  public AbstractUpdateCommandlet(IdeContext context) {
68

69
    super(context);
3✔
70
    addKeyword(getName());
4✔
71
    this.skipTools = add(new FlagProperty("--skip-tools"));
9✔
72
    this.skipRepositories = add(new FlagProperty("--skip-repositories"));
9✔
73
    this.forcePull = add(new FlagProperty("--force-pull"));
9✔
74
    this.forcePlugins = add(new FlagProperty("--force-plugins"));
9✔
75
    this.forceRepositories = add(new FlagProperty("--force-repositories"));
9✔
76
    this.settingsRepo = new StringProperty("", false, "settingsRepository");
8✔
77
  }
1✔
78

79
  @Override
80
  public void run() {
81

82
    IdeStartContextImpl startContext = ((AbstractIdeContext) this.context).getStartContext();
5✔
83
    startContext.setForcePull(forcePull.isTrue());
5✔
84
    startContext.setForcePlugins(forcePlugins.isTrue());
5✔
85
    startContext.setForceRepositories(forceRepositories.isTrue());
5✔
86

87
    if (!this.context.isSettingsRepositorySymlinkOrJunction() || this.context.isForceMode() || forcePull.isTrue()) {
4!
88
      updateSettings();
2✔
89
    }
90
    updateConf();
2✔
91
    reloadContext();
2✔
92

93
    updateSoftware();
2✔
94
    updateRepositories();
2✔
95
    createStartScripts();
2✔
96
  }
1✔
97

98
  private void reloadContext() {
99

100
    ((AbstractIdeContext) this.context).reload();
4✔
101
  }
1✔
102

103
  private void updateConf() {
104

105
    Path templatesFolder = this.context.getSettingsPath().resolve(IdeContext.FOLDER_TEMPLATES);
6✔
106
    if (!Files.exists(templatesFolder)) {
5✔
107
      Path legacyTemplatesFolder = this.context.getSettingsPath().resolve(IdeContext.FOLDER_LEGACY_TEMPLATES);
6✔
108
      if (Files.exists(legacyTemplatesFolder)) {
5!
109
        templatesFolder = legacyTemplatesFolder;
×
110
      } else {
111
        this.context.warning("Templates folder is missing in settings repository.");
4✔
112
        return;
1✔
113
      }
114
    }
115

116
    Step step = this.context.newStep("Copy configuration templates", templatesFolder);
11✔
117
    final Path finalTemplatesFolder = templatesFolder;
2✔
118
    step.run(() -> setupConf(finalTemplatesFolder, this.context.getIdeHome()));
13✔
119
  }
1✔
120

121
  private void setupConf(Path template, Path conf) {
122

123
    List<Path> children = this.context.getFileAccess().listChildren(template, f -> true);
9✔
124
    for (Path child : children) {
10✔
125

126
      String basename = child.getFileName().toString();
4✔
127
      Path confPath = conf.resolve(basename);
4✔
128

129
      if (Files.isDirectory(child)) {
5✔
130
        if (!Files.isDirectory(confPath)) {
5!
131
          this.context.getFileAccess().mkdirs(confPath);
5✔
132
        }
133
        setupConf(child, confPath);
5✔
134
      } else if (Files.isRegularFile(child)) {
5!
135
        if (Files.isRegularFile(confPath)) {
5!
136
          this.context.debug("Configuration {} already exists - skipping to copy from {}", confPath, child);
×
137
        } else {
138
          if (!basename.equals("settings.xml")) {
4!
139
            this.context.info("Copying template {} to {}.", child, conf);
14✔
140
            this.context.getFileAccess().copy(child, conf);
6✔
141
          }
142
        }
143
      }
144
    }
1✔
145
  }
1✔
146

147
  /**
148
   * Updates the settings repository in IDE_HOME/settings by either cloning if no such repository exists or pulling if the repository exists then saves the
149
   * latest current commit ID in the file ".commit.id".
150
   */
151
  protected void updateSettings() {
152

153
    this.context.newStep(getStepMessage()).run(this::updateSettingsInStep);
9✔
154
  }
1✔
155

156
  protected String getStepMessage() {
157

158
    return "update (pull) settings repository";
2✔
159
  }
160

161
  private void updateSettingsInStep() {
162
    Path settingsPath = this.context.getSettingsPath();
4✔
163
    GitContext gitContext = this.context.getGitContext();
4✔
164
    // here we do not use pullOrClone to prevent asking a pointless question for repository URL...
165
    if (Files.isDirectory(settingsPath) && !this.context.getFileAccess().isEmptyDir(settingsPath)) {
11!
166
      if (this.context.isForcePull() || this.context.isForceMode() || Files.isDirectory(settingsPath.resolve(GitContext.GIT_FOLDER))) {
15!
167
        gitContext.pull(settingsPath);
×
168
        this.context.getGitContext().saveCurrentCommitId(settingsPath, this.context.getSettingsCommitIdPath());
×
169
      } else {
170
        this.context.info("Skipping git pull in settings due to code repository. Use --force-pull to enforce pulling.");
5✔
171
      }
172
    } else {
173
      String repositoryUrl = getOrAskSettingsUrl();
3✔
174
      GitUrl gitUrl = GitUrl.of(repositoryUrl);
3✔
175
      checkProjectNameConvention(gitUrl.getProjectName());
4✔
176
      initializeRepository(gitUrl);
3✔
177
    }
178
  }
1✔
179

180
  private String getOrAskSettingsUrl() {
181

182
    String repository = this.settingsRepo.getValue();
5✔
183
    repository = handleDefaultRepository(repository);
4✔
184
    String userPromt;
185
    String defaultUrl;
186
    if (isCodeRepository()) {
3✔
187
      userPromt = "Code repository URL:";
2✔
188
      defaultUrl = null;
2✔
189
      this.context.info(MESSAGE_CODE_REPO_URL);
5✔
190
    } else {
191
      userPromt = "Settings URL [" + IdeContext.DEFAULT_SETTINGS_REPO_URL + "]:";
2✔
192
      defaultUrl = IdeContext.DEFAULT_SETTINGS_REPO_URL;
2✔
193
      this.context.info(MESSAGE_SETTINGS_REPO_URL, this.context.getSettingsPath());
12✔
194
    }
195
    while (repository == null || repository.isBlank()) {
5!
196
      repository = this.context.askForInput(userPromt, defaultUrl);
6✔
197
      repository = handleDefaultRepository(repository);
5✔
198
    }
199
    return repository;
2✔
200
  }
201

202
  private String handleDefaultRepository(String repository) {
203
    if ("-".equals(repository)) {
4✔
204
      if (isCodeRepository()) {
3✔
205
        this.context.warning("'-' is found after '--code'. This is invalid.");
4✔
206
        repository = null;
3✔
207
      } else {
208
        this.context.info("'-' was found for settings repository, the default settings repository '{}' will be used.", IdeContext.DEFAULT_SETTINGS_REPO_URL);
10✔
209
        repository = IdeContext.DEFAULT_SETTINGS_REPO_URL;
2✔
210
      }
211
    }
212
    return repository;
2✔
213
  }
214

215
  private void checkProjectNameConvention(String projectName) {
216
    boolean isSettingsRepo = projectName.contains(IdeContext.SETTINGS_REPOSITORY_KEYWORD);
4✔
217
    boolean codeRepository = isCodeRepository();
3✔
218
    if (isSettingsRepo == codeRepository) {
3✔
219
      String warningTemplate;
220
      if (codeRepository) {
2✔
221
        warningTemplate = """
3✔
222
            Your git URL is pointing to the project name {} that contains the keyword '{}'.
223
            Therefore we assume that you did a mistake by adding the '--code' option to the ide project creation.
224
            Do you really want to create the project?""";
225
      } else {
226
        warningTemplate = """
2✔
227
            Your git URL is pointing to the project name {} that does not contain the keyword ''{}''.
228
            Therefore we assume that you forgot to add the '--code' option to the ide project creation.
229
            Do you really want to create the project?""";
230
      }
231
      this.context.askToContinue(warningTemplate, projectName,
14✔
232
          IdeContext.SETTINGS_REPOSITORY_KEYWORD);
233
    }
234
  }
1✔
235

236
  private void initializeRepository(GitUrl gitUrl) {
237

238
    GitContext gitContext = this.context.getGitContext();
4✔
239
    Path settingsPath = this.context.getSettingsPath();
4✔
240
    Path repoPath = settingsPath;
2✔
241
    boolean codeRepository = isCodeRepository();
3✔
242
    if (codeRepository) {
2✔
243
      // clone the given code repository into IDE_HOME/workspaces/main
244
      repoPath = context.getWorkspacePath().resolve(gitUrl.getProjectName());
7✔
245
    }
246
    gitContext.pullOrClone(gitUrl, repoPath);
4✔
247
    if (codeRepository) {
2✔
248
      // check for settings folder and create symlink to IDE_HOME/settings
249
      Path settingsFolder = repoPath.resolve(IdeContext.FOLDER_SETTINGS);
4✔
250
      if (Files.exists(settingsFolder)) {
5!
251
        context.getFileAccess().symlink(settingsFolder, settingsPath);
×
252
      } else {
253
        throw new CliException("Invalid code repository " + gitUrl + ": missing a settings folder at " + settingsFolder);
9✔
254
      }
255
    }
256
    this.context.getGitContext().saveCurrentCommitId(settingsPath, this.context.getSettingsCommitIdPath());
8✔
257
  }
1✔
258

259

260
  private void updateSoftware() {
261

262
    if (this.skipTools.isTrue()) {
4✔
263
      this.context.info("Skipping installation/update of tools as specified by the user.");
4✔
264
      return;
1✔
265
    }
266
    Step step = this.context.newStep("Install or update software");
5✔
267
    step.run(() -> doUpdateSoftwareStep(step));
10✔
268
  }
1✔
269

270
  private void doUpdateSoftwareStep(Step step) {
271

272
    Set<ToolCommandlet> toolCommandlets = new HashSet<>();
4✔
273
    // installed tools in IDE_HOME/software
274
    List<Path> softwarePaths = this.context.getFileAccess().listChildren(this.context.getSoftwarePath(), Files::isDirectory);
14✔
275
    for (Path softwarePath : softwarePaths) {
10✔
276
      String toolName = softwarePath.getFileName().toString();
4✔
277
      ToolCommandlet toolCommandlet = this.context.getCommandletManager().getToolCommandlet(toolName);
6✔
278
      if (toolCommandlet != null) {
2!
279
        toolCommandlets.add(toolCommandlet);
4✔
280
      }
281
    }
1✔
282

283
    // regular tools in $IDE_TOOLS
284
    List<String> regularTools = IdeVariables.IDE_TOOLS.get(this.context);
6✔
285
    if (regularTools != null) {
2!
286
      for (String regularTool : regularTools) {
10✔
287
        toolCommandlets.add(this.context.getCommandletManager().getRequiredToolCommandlet(regularTool));
8✔
288
      }
1✔
289
    }
290

291
    // custom tools in ide-custom-tools.json
292
    for (CustomToolMetadata customTool : this.context.getCustomToolRepository().getTools()) {
9!
293
      CustomToolCommandlet customToolCommandlet = new CustomToolCommandlet(this.context, customTool);
×
294
      toolCommandlets.add(customToolCommandlet);
×
295
    }
×
296

297
    // update/install the toolCommandlets
298
    for (ToolCommandlet toolCommandlet : toolCommandlets) {
10✔
299
      this.context.newStep("Install " + toolCommandlet.getName()).run(() -> toolCommandlet.install(false));
15✔
300
    }
1✔
301
  }
1✔
302

303
  private void updateRepositories() {
304

305
    if (this.skipRepositories.isTrue()) {
4!
306
      if (this.forceRepositories.isTrue()) {
×
307
        this.context.warning("Options to skip and force repositories are incompatible and should not be combined. Ignoring --force-repositories to proceed.");
×
308
      }
309
      this.context.info("Skipping setup of repositories as specified by the user.");
×
310
      return;
×
311
    }
312
    RepositoryCommandlet repositoryCommandlet = this.context.getCommandletManager().getCommandlet(RepositoryCommandlet.class);
7✔
313
    repositoryCommandlet.reset();
2✔
314
    repositoryCommandlet.run();
2✔
315
  }
1✔
316

317
  private void createStartScripts() {
318

319
    List<String> ides = IdeVariables.CREATE_START_SCRIPTS.get(this.context);
6✔
320
    if (ides == null) {
2✔
321
      this.context.info("Variable CREATE_START_SCRIPTS is undefined - skipping start script creation.");
4✔
322
      return;
1✔
323
    }
324
    for (String ide : ides) {
10✔
325
      ToolCommandlet tool = this.context.getCommandletManager().getToolCommandlet(ide);
6✔
326
      if (tool == null) {
2!
327
        this.context.error("Undefined IDE '{}' configured in variable CREATE_START_SCRIPTS.");
×
328
      } else {
329
        createStartScript(ide);
3✔
330
      }
331
    }
1✔
332
  }
1✔
333

334
  private void createStartScript(String ide) {
335

336
    this.context.info("Creating start scripts for {}", ide);
10✔
337
    Path workspaces = this.context.getIdeHome().resolve(IdeContext.FOLDER_WORKSPACES);
6✔
338
    try (Stream<Path> childStream = Files.list(workspaces)) {
3✔
339
      Iterator<Path> iterator = childStream.iterator();
3✔
340
      while (iterator.hasNext()) {
3✔
341
        Path child = iterator.next();
4✔
342
        if (Files.isDirectory(child)) {
5!
343
          createStartScript(ide, child.getFileName().toString());
6✔
344
        }
345
      }
1✔
346
    } catch (IOException e) {
×
347
      throw new RuntimeException("Failed to list children of directory " + workspaces, e);
×
348
    }
1✔
349
  }
1✔
350

351
  private void createStartScript(String ide, String workspace) {
352

353
    Path ideHome = this.context.getIdeHome();
4✔
354
    String scriptName = ide + "-" + workspace;
4✔
355
    boolean windows = this.context.getSystemInfo().isWindows();
5✔
356
    if (windows) {
2!
357
      scriptName = scriptName + ".bat";
×
358
    } else {
359
      scriptName = scriptName + ".sh";
3✔
360
    }
361
    Path scriptPath = ideHome.resolve(scriptName);
4✔
362
    if (Files.exists(scriptPath)) {
5!
363
      return;
×
364
    }
365
    String scriptContent;
366
    if (windows) {
2!
367
      scriptContent = "@echo off\r\n"
×
368
          + "pushd %~dp0\r\n"
369
          + "cd workspaces/" + workspace + "\r\n"
370
          + "call ide " + ide + "\r\n"
371
          + "popd\r\n";
372
    } else {
373
      scriptContent = "#!/usr/bin/env bash\n"
4✔
374
          + "cd \"$(dirname \"$0\")\"\n"
375
          + "cd workspaces/" + workspace + "\n"
376
          + "ideasy " + ide + "\n";
377
    }
378
    FileAccess fileAccess = this.context.getFileAccess();
4✔
379
    fileAccess.writeFileContent(scriptContent, scriptPath);
4✔
380
    fileAccess.makeExecutable(scriptPath);
3✔
381
  }
1✔
382

383
  /**
384
   * Judge if the repository is a code repository.
385
   *
386
   * @return true when the repository is a code repository, otherwise false.
387
   */
388
  protected boolean isCodeRepository() {
389
    return false;
2✔
390
  }
391

392
}
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