• 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

80.54
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 org.slf4j.Logger;
13
import org.slf4j.LoggerFactory;
14

15
import com.devonfw.tools.ide.context.AbstractIdeContext;
16
import com.devonfw.tools.ide.context.IdeContext;
17
import com.devonfw.tools.ide.context.IdeStartContextImpl;
18
import com.devonfw.tools.ide.git.GitContext;
19
import com.devonfw.tools.ide.git.GitUrl;
20
import com.devonfw.tools.ide.git.repository.RepositoryCommandlet;
21
import com.devonfw.tools.ide.io.FileAccess;
22
import com.devonfw.tools.ide.property.FlagProperty;
23
import com.devonfw.tools.ide.property.StringProperty;
24
import com.devonfw.tools.ide.step.Step;
25
import com.devonfw.tools.ide.tool.LocalToolCommandlet;
26
import com.devonfw.tools.ide.tool.ToolCommandlet;
27
import com.devonfw.tools.ide.tool.ToolEdition;
28
import com.devonfw.tools.ide.tool.ToolEditionAndVersion;
29
import com.devonfw.tools.ide.tool.ToolInstallRequest;
30
import com.devonfw.tools.ide.tool.custom.CustomToolCommandlet;
31
import com.devonfw.tools.ide.tool.custom.CustomToolMetadata;
32
import com.devonfw.tools.ide.tool.extra.ExtraToolInstallation;
33
import com.devonfw.tools.ide.tool.extra.ExtraTools;
34
import com.devonfw.tools.ide.tool.extra.ExtraToolsMapper;
35
import com.devonfw.tools.ide.variable.IdeVariables;
36
import com.devonfw.tools.ide.version.VersionIdentifier;
37

38
/**
39
 * Abstract {@link Commandlet} base-class for both {@link UpdateCommandlet} and {@link CreateCommandlet}.
40
 */
41
public abstract class AbstractUpdateCommandlet extends Commandlet {
42

43
  private static final Logger LOG = LoggerFactory.getLogger(AbstractUpdateCommandlet.class);
4✔
44

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

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

56
  /** {@link StringProperty} for the settings repository URL. */
57
  public final StringProperty settingsRepo;
58

59
  /** {@link FlagProperty} for skipping installation/updating of tools. */
60
  public final FlagProperty skipTools;
61

62
  /** {@link FlagProperty} for skipping the setup of git repositories. */
63
  public final FlagProperty skipRepositories;
64

65
  /** {@link FlagProperty} to force the update of the settings git repository. */
66
  public final FlagProperty forcePull;
67

68
  /** {@link FlagProperty} to force the installation/update of plugins. */
69
  public final FlagProperty forcePlugins;
70

71
  /** {@link FlagProperty} to force the setup of git repositories. */
72
  public final FlagProperty forceRepositories;
73

74
  /**
75
   * The constructor.
76
   *
77
   * @param context the {@link IdeContext}.
78
   */
79
  public AbstractUpdateCommandlet(IdeContext context) {
80

81
    super(context);
3✔
82
    addKeyword(getName());
4✔
83
    this.skipTools = add(new FlagProperty("--skip-tools"));
9✔
84
    this.skipRepositories = add(new FlagProperty("--skip-repositories"));
9✔
85
    this.forcePull = add(new FlagProperty("--force-pull"));
9✔
86
    this.forcePlugins = add(new FlagProperty("--force-plugins"));
9✔
87
    this.forceRepositories = add(new FlagProperty("--force-repositories"));
9✔
88
    this.settingsRepo = new StringProperty("", false, "settingsRepository");
8✔
89
  }
1✔
90

91
  @Override
92
  protected void doRun() {
93

94
    IdeStartContextImpl startContext = ((AbstractIdeContext) this.context).getStartContext();
5✔
95
    startContext.setForcePull(forcePull.isTrue());
5✔
96
    startContext.setForcePlugins(forcePlugins.isTrue());
5✔
97
    startContext.setForceRepositories(forceRepositories.isTrue());
5✔
98

99
    updateSettings();
2✔
100
    updateConf();
2✔
101
    reloadContext();
2✔
102
    this.context.verifyIdeMinVersion(true);
4✔
103

104
    updateSoftware();
2✔
105
    updateRepositories();
2✔
106
    createStartScripts();
2✔
107
  }
1✔
108

109
  private void reloadContext() {
110

111
    ((AbstractIdeContext) this.context).reload();
4✔
112
  }
1✔
113

114
  private void updateConf() {
115

116
    Path templatesFolder = this.context.getSettingsPath().resolve(IdeContext.FOLDER_TEMPLATES);
6✔
117
    if (!Files.exists(templatesFolder)) {
5✔
118
      Path legacyTemplatesFolder = this.context.getSettingsPath().resolve(IdeContext.FOLDER_LEGACY_TEMPLATES);
6✔
119
      if (Files.exists(legacyTemplatesFolder)) {
5!
120
        templatesFolder = legacyTemplatesFolder;
×
121
      } else {
122
        LOG.warn("Templates folder is missing in settings repository.");
3✔
123
        return;
1✔
124
      }
125
    }
126

127
    Step step = this.context.newStep("Copy configuration templates", templatesFolder);
11✔
128
    final Path finalTemplatesFolder = templatesFolder;
2✔
129
    step.run(() -> setupConf(finalTemplatesFolder, this.context.getIdeHome()));
13✔
130
  }
1✔
131

132
  private void setupConf(Path template, Path conf) {
133

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

137
      String basename = child.getFileName().toString();
4✔
138
      Path confPath = conf.resolve(basename);
4✔
139

140
      if (Files.isDirectory(child)) {
5✔
141
        if (!Files.isDirectory(confPath)) {
5!
142
          this.context.getFileAccess().mkdirs(confPath);
5✔
143
        }
144
        setupConf(child, confPath);
5✔
145
      } else if (Files.isRegularFile(child)) {
5!
146
        if (Files.isRegularFile(confPath)) {
5!
147
          LOG.debug("Configuration {} already exists - skipping to copy from {}", confPath, child);
×
148
        } else {
149
          if (!basename.equals("settings.xml")) {
4!
150
            LOG.info("Copying template {} to {}.", child, conf);
5✔
151
            this.context.getFileAccess().copy(child, conf);
6✔
152
          }
153
        }
154
      }
155
    }
1✔
156
  }
1✔
157

158
  /**
159
   * 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
160
   * latest current commit ID in the file ".commit.id".
161
   */
162
  protected void updateSettings() {
163

164
    boolean codeRepository = this.context.isSettingsCodeRepository();
4✔
165
    if (codeRepository && !(this.context.isForceMode() || forcePull.isTrue())) {
2!
166
      LOG.info("Skipping git pull in settings due to code repository. Use --force-pull to enforce pulling.");
×
167
      return;
×
168
    }
169
    this.context.newStep(getStepMessage()).run(() -> updateSettingsInStep(codeRepository));
14✔
170
  }
1✔
171

172
  protected String getStepMessage() {
173

174
    return "update (pull) settings repository";
2✔
175
  }
176

177
  private void updateSettingsInStep(boolean codeRepository) {
178
    Path settingsPath = this.context.getSettingsPath();
4✔
179
    if (!codeRepository) {
2!
180
      boolean settingsRepository = this.context.getGitContext().isGitRepo(settingsPath);
6✔
181
      if (!settingsRepository) {
2✔
182
        if (Files.exists(settingsPath)) {
5!
183
          if (!this.context.getFileAccess().isEmptyDir(settingsPath)) {
×
184
            this.context.askToContinue(
×
185
                "Your settings repository seems to be broken ('.git' folder not present). "
186
                    + "We can fix this by moving  your settings the backed up. "
187
                    + "You will be asked for the settings git URL and your settings will be cloned from scratch. "
188
                    + "Do you want to proceed?"
189
            );
190
          }
191
          this.context.getFileAccess().backup(settingsPath);
×
192
        }
193
        GitUrl gitUrl = getOrAskSettingsUrl();
3✔
194
        initializeRepository(gitUrl);
3✔
195
        return;
1✔
196
      }
197
    }
198
    GitContext gitContext = this.context.getGitContext();
4✔
199
    if (gitContext.hasUntrackedFiles(settingsPath)) {
4!
200
      gitContext.pullSafelyWithStash(settingsPath);
×
201
    } else {
202
      gitContext.pull(settingsPath);
3✔
203
    }
204
    this.context.getGitContext().saveCurrentCommitId(settingsPath, this.context.getSettingsCommitIdPath());
8✔
205
  }
1✔
206

207
  private GitUrl getOrAskSettingsUrl() {
208

209
    String repository = this.settingsRepo.getValue();
5✔
210
    repository = handleDefaultRepository(repository);
4✔
211
    String userPromt = "Repository URL [" + IdeContext.DEFAULT_SETTINGS_REPO_URL + "]:";
2✔
212
    String defaultUrl = IdeContext.DEFAULT_SETTINGS_REPO_URL;
2✔
213
    LOG.info(MESSAGE_SETTINGS_REPO_URL, this.context.getSettingsPath());
6✔
214

215
    GitUrl gitUrl = null;
2✔
216
    if (repository != null) {
2✔
217
      gitUrl = GitUrl.of(repository);
3✔
218
    }
219
    while ((gitUrl == null) || !gitUrl.isValid()) {
5!
220
      repository = this.context.askForInput(userPromt, defaultUrl);
6✔
221
      repository = handleDefaultRepository(repository);
4✔
222
      gitUrl = GitUrl.of(repository);
3✔
223
      if (!gitUrl.isValid()) {
3!
224
        LOG.warn("The input URL is not valid, please try again.");
×
225
      }
226
    }
227
    return gitUrl;
2✔
228
  }
229

230
  private String handleDefaultRepository(String repository) {
231
    if ("-".equals(repository)) {
4✔
232
      LOG.info("'-' was found for the repository, the default settings repository '{}' will be used.", IdeContext.DEFAULT_SETTINGS_REPO_URL);
4✔
233
      repository = IdeContext.DEFAULT_SETTINGS_REPO_URL;
2✔
234
    }
235
    return repository;
2✔
236
  }
237

238
  private void initializeRepository(GitUrl gitUrl) {
239

240
    GitContext gitContext = this.context.getGitContext();
4✔
241
    Path settingsPath = this.context.getSettingsPath();
4✔
242
    Path repoPath = settingsPath;
2✔
243
    gitContext.pullOrClone(gitUrl, repoPath);
4✔
244
    this.context.getGitContext().saveCurrentCommitId(settingsPath, this.context.getSettingsCommitIdPath());
8✔
245
  }
1✔
246

247
  private void updateSoftware() {
248

249
    if (this.skipTools.isTrue()) {
4✔
250
      LOG.info("Skipping installation/update of tools as specified by the user.");
3✔
251
      return;
1✔
252
    }
253
    Step step = this.context.newStep("Install or update software");
5✔
254
    step.run(() -> doUpdateSoftwareStep(step));
10✔
255
  }
1✔
256

257
  private void doUpdateSoftwareStep(Step step) {
258

259
    Set<ToolCommandlet> toolCommandlets = new HashSet<>();
4✔
260
    CommandletManager commandletManager = this.context.getCommandletManager();
4✔
261
    // installed tools in IDE_HOME/software
262
    List<Path> softwarePaths = this.context.getFileAccess().listChildren(this.context.getSoftwarePath(), Files::isDirectory);
14✔
263
    for (Path softwarePath : softwarePaths) {
10✔
264
      String toolName = softwarePath.getFileName().toString();
4✔
265
      ToolCommandlet toolCommandlet = commandletManager.getToolCommandlet(toolName);
4✔
266
      if (toolCommandlet != null) {
2!
267
        toolCommandlets.add(toolCommandlet);
4✔
268
      }
269
    }
1✔
270

271
    // regular tools in $IDE_TOOLS
272
    List<String> regularTools = IdeVariables.IDE_TOOLS.get(this.context);
6✔
273
    if (regularTools != null) {
2!
274
      for (String regularTool : regularTools) {
10✔
275
        ToolCommandlet toolCommandlet = commandletManager.getToolCommandlet(regularTool);
4✔
276
        if (toolCommandlet == null) {
2!
277
          String displayName = (regularTool == null || regularTool.isBlank()) ? "<empty>" : "'" + regularTool + "'";
×
278
          LOG.error("Cannot install or update tool '{}''. No matching commandlet found. Please check your IDE_TOOLS configuration.", displayName);
×
279
        } else {
×
280
          toolCommandlets.add(toolCommandlet);
4✔
281
        }
282
      }
1✔
283
    }
284

285
    // custom tools in ide-custom-tools.json
286
    for (CustomToolMetadata customTool : this.context.getCustomToolRepository().getTools()) {
9!
287
      CustomToolCommandlet customToolCommandlet = new CustomToolCommandlet(this.context, customTool);
×
288
      toolCommandlets.add(customToolCommandlet);
×
289
    }
×
290

291
    // update/install the toolCommandlets
292
    for (ToolCommandlet toolCommandlet : toolCommandlets) {
10✔
293
      this.context.newStep("Install " + toolCommandlet.getName()).run(() -> toolCommandlet.install(false));
15✔
294
    }
1✔
295

296
    ExtraTools extraTools = ExtraToolsMapper.get().loadJsonFromFolder(this.context.getSettingsPath());
7✔
297
    if (extraTools != null) {
2✔
298
      List<String> toolNames = extraTools.getSortedToolNames();
3✔
299
      LOG.info("Found extra installation of the following tools: {}", toolNames);
4✔
300
      for (String tool : toolNames) {
10✔
301
        List<ExtraToolInstallation> installations = extraTools.getExtraInstallations(tool);
4✔
302
        this.context.newStep("Install extra version(s) of " + tool).run(() -> installExtraToolInstallations(tool, installations));
16✔
303
      }
1✔
304
    }
305
  }
1✔
306

307
  private void installExtraToolInstallations(String tool, List<ExtraToolInstallation> extraInstallations) {
308

309
    CommandletManager commandletManager = this.context.getCommandletManager();
4✔
310
    FileAccess fileAccess = this.context.getFileAccess();
4✔
311
    Path extraPath = this.context.getSoftwareExtraPath();
4✔
312
    LocalToolCommandlet toolCommandlet = commandletManager.getRequiredLocalToolCommandlet(tool);
4✔
313
    for (ExtraToolInstallation extraInstallation : extraInstallations) {
10✔
314
      ToolInstallRequest request = new ToolInstallRequest(false);
5✔
315
      String edition = extraInstallation.edition();
3✔
316
      if (edition == null) {
2✔
317
        edition = toolCommandlet.getConfiguredEdition();
3✔
318
      }
319
      ToolEdition toolEdition = new ToolEdition(tool, edition);
6✔
320
      VersionIdentifier version = extraInstallation.version();
3✔
321
      request.setRequested(new ToolEditionAndVersion(toolEdition, version));
7✔
322
      Path extraToolPath = extraPath.resolve(tool);
4✔
323
      Path toolPath = extraToolPath.resolve(extraInstallation.name());
5✔
324
      request.setToolPathForExtraInstallation(toolPath);
3✔
325
      toolCommandlet.install(request);
4✔
326
    }
1✔
327
  }
1✔
328

329
  private void updateRepositories() {
330

331
    if (this.skipRepositories.isTrue()) {
4!
332
      if (this.forceRepositories.isTrue()) {
×
333
        LOG.warn("Options to skip and force repositories are incompatible and should not be combined. Ignoring --force-repositories to proceed.");
×
334
      }
335
      LOG.info("Skipping setup of repositories as specified by the user.");
×
336
      return;
×
337
    }
338
    RepositoryCommandlet repositoryCommandlet = this.context.getCommandletManager().getCommandlet(RepositoryCommandlet.class);
7✔
339
    repositoryCommandlet.reset();
2✔
340
    repositoryCommandlet.run();
2✔
341
  }
1✔
342

343
  private void createStartScripts() {
344

345
    List<String> ides = IdeVariables.CREATE_START_SCRIPTS.get(this.context);
6✔
346
    if (ides == null) {
2✔
347
      LOG.info("Variable CREATE_START_SCRIPTS is undefined - skipping start script creation.");
3✔
348
      return;
1✔
349
    }
350
    for (String ide : ides) {
10✔
351
      ToolCommandlet tool = this.context.getCommandletManager().getToolCommandlet(ide);
6✔
352
      if (tool == null) {
2!
353
        LOG.error("Undefined IDE '{}' configured in variable CREATE_START_SCRIPTS.", ide);
×
354
      } else {
355
        createStartScript(ide);
3✔
356
      }
357
    }
1✔
358
  }
1✔
359

360
  private void createStartScript(String ide) {
361

362
    LOG.info("Creating start scripts for {}", ide);
4✔
363
    Path workspaces = this.context.getIdeHome().resolve(IdeContext.FOLDER_WORKSPACES);
6✔
364
    try (Stream<Path> childStream = Files.list(workspaces)) {
3✔
365
      Iterator<Path> iterator = childStream.iterator();
3✔
366
      while (iterator.hasNext()) {
3✔
367
        Path child = iterator.next();
4✔
368
        if (Files.isDirectory(child)) {
5!
369
          createStartScript(ide, child.getFileName().toString());
6✔
370
        }
371
      }
1✔
372
    } catch (IOException e) {
×
373
      throw new RuntimeException("Failed to list children of directory " + workspaces, e);
×
374
    }
1✔
375
  }
1✔
376

377
  private void createStartScript(String ide, String workspace) {
378

379
    Path ideHome = this.context.getIdeHome();
4✔
380
    String scriptName = ide + "-" + workspace;
4✔
381
    boolean windows = this.context.getSystemInfo().isWindows();
5✔
382
    if (windows) {
2!
383
      scriptName = scriptName + ".bat";
×
384
    } else {
385
      scriptName = scriptName + ".sh";
3✔
386
    }
387
    Path scriptPath = ideHome.resolve(scriptName);
4✔
388
    if (Files.exists(scriptPath)) {
5!
389
      return;
×
390
    }
391
    String scriptContent;
392
    if (windows) {
2!
393
      scriptContent = "@echo off\r\n" + "pushd %~dp0\r\n" + "cd workspaces/" + workspace + "\r\n" + "call ide " + ide + "\r\n" + "popd\r\n";
×
394
    } else {
395
      scriptContent = "#!/usr/bin/env bash\n" + "cd \"$(dirname \"$0\")\"\n" + "cd workspaces/" + workspace + "\n" + "ideasy " + ide + "\n";
4✔
396
    }
397
    FileAccess fileAccess = this.context.getFileAccess();
4✔
398
    fileAccess.writeFileContent(scriptContent, scriptPath);
4✔
399
    fileAccess.makeExecutable(scriptPath);
3✔
400
  }
1✔
401
}
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