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

devonfw / IDEasy / 14313798819

07 Apr 2025 03:55PM UTC coverage: 67.315% (-0.1%) from 67.432%
14313798819

push

github

web-flow
#1007: Implemented granular force options for update command (#1180)

Co-authored-by: jan-vcapgemini <59438728+jan-vcapgemini@users.noreply.github.com>
Co-authored-by: Jörg Hohwiller <hohwille@users.noreply.github.com>

3066 of 4964 branches covered (61.76%)

Branch coverage included in aggregate %.

7901 of 11328 relevant lines covered (69.75%)

3.05 hits per line

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

79.72
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.context.AbstractIdeContext;
13
import com.devonfw.tools.ide.context.IdeContext;
14
import com.devonfw.tools.ide.git.GitContext;
15
import com.devonfw.tools.ide.git.GitUrl;
16
import com.devonfw.tools.ide.git.repository.RepositoryCommandlet;
17
import com.devonfw.tools.ide.io.FileAccess;
18
import com.devonfw.tools.ide.property.FlagProperty;
19
import com.devonfw.tools.ide.property.StringProperty;
20
import com.devonfw.tools.ide.step.Step;
21
import com.devonfw.tools.ide.tool.CustomToolCommandlet;
22
import com.devonfw.tools.ide.tool.ToolCommandlet;
23
import com.devonfw.tools.ide.tool.repository.CustomToolMetadata;
24
import com.devonfw.tools.ide.variable.IdeVariables;
25

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

31
  /** {@link StringProperty} for the settings repository URL. */
32
  public final StringProperty settingsRepo;
33

34
  /** {@link FlagProperty} for skipping installation/updating of tools. */
35
  public final FlagProperty skipTools;
36

37
  /** {@link FlagProperty} for skipping the setup of git repositories. */
38
  public final FlagProperty skipRepositories;
39

40
  /** {@link FlagProperty} to force the update of the settings git repository. */
41
  public final FlagProperty forcePull;
42

43
  /** {@link FlagProperty} to force the installation/update of plugins. */
44
  public final FlagProperty forcePlugins;
45

46
  /** {@link FlagProperty} to force the setup of git repositories. */
47
  public final FlagProperty forceRepositories;
48

49
  /**
50
   * The constructor.
51
   *
52
   * @param context the {@link IdeContext}.
53
   */
54
  public AbstractUpdateCommandlet(IdeContext context) {
55

56
    super(context);
3✔
57
    addKeyword(getName());
4✔
58
    this.skipTools = add(new FlagProperty("--skip-tools"));
9✔
59
    this.skipRepositories = add(new FlagProperty("--skip-repositories"));
9✔
60
    this.forcePull = add(new FlagProperty("--force-pull"));
9✔
61
    this.forcePlugins = add(new FlagProperty("--force-plugins"));
9✔
62
    this.forceRepositories = add(new FlagProperty("--force-repositories"));
9✔
63
    this.settingsRepo = new StringProperty("", false, "settingsRepository");
8✔
64
  }
1✔
65

66
  @Override
67
  public void run() {
68

69
    this.context.setForcePull(forcePull.isTrue());
6✔
70
    this.context.setForcePlugins(forcePlugins.isTrue());
6✔
71
    this.context.setForceRepositories(forceRepositories.isTrue());
6✔
72

73
    if (!this.context.isSettingsRepositorySymlinkOrJunction() || this.context.isForceMode() || forcePull.isTrue()) {
4!
74
      updateSettings();
2✔
75
    }
76
    updateConf();
2✔
77
    reloadContext();
2✔
78

79
    updateSoftware();
2✔
80
    updateRepositories();
2✔
81
    createStartScripts();
2✔
82
  }
1✔
83

84
  private void reloadContext() {
85

86
    ((AbstractIdeContext) this.context).reload();
4✔
87
  }
1✔
88

89
  private void updateConf() {
90

91
    Path templatesFolder = this.context.getSettingsPath().resolve(IdeContext.FOLDER_TEMPLATES);
6✔
92
    if (!Files.exists(templatesFolder)) {
5✔
93
      Path legacyTemplatesFolder = this.context.getSettingsPath().resolve(IdeContext.FOLDER_LEGACY_TEMPLATES);
6✔
94
      if (Files.exists(legacyTemplatesFolder)) {
5!
95
        templatesFolder = legacyTemplatesFolder;
×
96
      } else {
97
        this.context.warning("Templates folder is missing in settings repository.");
4✔
98
        return;
1✔
99
      }
100
    }
101

102
    try (Step step = this.context.newStep("Copy configuration templates", templatesFolder)) {
11✔
103
      setupConf(templatesFolder, this.context.getIdeHome());
6✔
104
      step.success();
2✔
105
    }
106
  }
1✔
107

108
  private void setupConf(Path template, Path conf) {
109

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

113
      String basename = child.getFileName().toString();
4✔
114
      Path confPath = conf.resolve(basename);
4✔
115

116
      if (Files.isDirectory(child)) {
5✔
117
        if (!Files.isDirectory(confPath)) {
5!
118
          this.context.getFileAccess().mkdirs(confPath);
5✔
119
        }
120
        setupConf(child, confPath);
5✔
121
      } else if (Files.isRegularFile(child)) {
5!
122
        if (Files.isRegularFile(confPath)) {
5!
123
          this.context.debug("Configuration {} already exists - skipping to copy from {}", confPath, child);
×
124
        } else {
125
          if (!basename.equals("settings.xml")) {
4!
126
            this.context.info("Copying template {} to {}.", child, conf);
14✔
127
            this.context.getFileAccess().copy(child, conf);
6✔
128
          }
129
        }
130
      }
131
    }
1✔
132
  }
1✔
133

134
  /**
135
   * 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
136
   * latest current commit ID in the file ".commit.id".
137
   */
138
  protected void updateSettings() {
139

140
    Path settingsPath = this.context.getSettingsPath();
4✔
141
    GitContext gitContext = this.context.getGitContext();
4✔
142
    Step step = null;
2✔
143
    try {
144
      // here we do not use pullOrClone to prevent asking a pointless question for repository URL...
145
      if (Files.isDirectory(settingsPath) && !this.context.getFileAccess().isEmptyDir(settingsPath)) {
11!
146
        step = this.context.newStep("Pull settings repository");
5✔
147
        gitContext.pull(settingsPath);
4✔
148
      } else {
149
        step = this.context.newStep("Clone settings repository");
5✔
150
        // check if a settings repository is given, otherwise prompt user for a repository.
151
        String repository = this.settingsRepo.getValue();
5✔
152
        if (repository == null) {
2!
153
          String message = "Missing your settings at " + settingsPath + " and no SETTINGS_URL is defined.\n"
×
154
              + "Further details can be found here: https://github.com/devonfw/IDEasy/blob/main/documentation/settings.asciidoc\n"
155
              + "Please contact the technical lead of your project to get the SETTINGS_URL for your project.\n"
156
              + "In case you just want to test IDEasy you may simply hit return to install the default settings.\n" + "Settings URL ["
157
              + IdeContext.DEFAULT_SETTINGS_REPO_URL + "]:";
158
          repository = this.context.askForInput(message, IdeContext.DEFAULT_SETTINGS_REPO_URL);
×
159
        } else if ("-".equals(repository)) {
4!
160
          repository = IdeContext.DEFAULT_SETTINGS_REPO_URL;
×
161
        }
162
        gitContext.pullOrClone(GitUrl.of(repository), settingsPath);
5✔
163
      }
164
      this.context.getGitContext().saveCurrentCommitId(settingsPath, this.context.getSettingsCommitIdPath());
8✔
165
      step.success("Successfully updated settings repository.");
3✔
166
    } finally {
167
      if (step != null) {
2!
168
        step.close();
2✔
169
      }
170
    }
171
  }
1✔
172

173
  private void updateSoftware() {
174

175
    if (this.skipTools.isTrue()) {
4✔
176
      this.context.info("Skipping installation/update of tools as specified by the user.");
4✔
177
      return;
1✔
178
    }
179
    try (Step step = this.context.newStep("Install or update software")) {
5✔
180
      Set<ToolCommandlet> toolCommandlets = new HashSet<>();
4✔
181
      // installed tools in IDE_HOME/software
182
      List<Path> softwarePaths = this.context.getFileAccess().listChildren(this.context.getSoftwarePath(), Files::isDirectory);
14✔
183
      for (Path softwarePath : softwarePaths) {
10✔
184
        String toolName = softwarePath.getFileName().toString();
4✔
185
        ToolCommandlet toolCommandlet = this.context.getCommandletManager().getToolCommandlet(toolName);
6✔
186
        if (toolCommandlet != null) {
2!
187
          toolCommandlets.add(toolCommandlet);
4✔
188
        }
189
      }
1✔
190

191
      // regular tools in $IDE_TOOLS
192
      List<String> regularTools = IdeVariables.IDE_TOOLS.get(this.context);
6✔
193
      if (regularTools != null) {
2!
194
        for (String regularTool : regularTools) {
10✔
195
          toolCommandlets.add(this.context.getCommandletManager().getRequiredToolCommandlet(regularTool));
8✔
196
        }
1✔
197
      }
198

199
      // custom tools in ide-custom-tools.json
200
      for (CustomToolMetadata customTool : this.context.getCustomToolRepository().getTools()) {
9!
201
        CustomToolCommandlet customToolCommandlet = new CustomToolCommandlet(this.context, customTool);
×
202
        toolCommandlets.add(customToolCommandlet);
×
203
      }
×
204

205
      // update/install the toolCommandlets
206
      for (ToolCommandlet toolCommandlet : toolCommandlets) {
10✔
207
        try {
208
          toolCommandlet.install(false);
4✔
209
        } catch (Exception e) {
1✔
210
          step.error(e, "Installation of {} failed!", toolCommandlet.getName());
11✔
211
        }
1✔
212
      }
1✔
213
      step.success();
2✔
214
    }
215
  }
1✔
216

217
  private void updateRepositories() {
218

219
    if (this.skipRepositories.isTrue()) {
4!
220
      if (this.forceRepositories.isTrue()) {
×
221
        this.context.warning("Options to skip and force repositories are incompatible and should not be combined. Ignoring --force-repositories to proceed.");
×
222
      }
223
      this.context.info("Skipping setup of repositories as specified by the user.");
×
224
      return;
×
225
    }
226
    RepositoryCommandlet repositoryCommandlet = this.context.getCommandletManager().getCommandlet(RepositoryCommandlet.class);
7✔
227
    repositoryCommandlet.reset();
2✔
228
    repositoryCommandlet.run();
2✔
229
  }
1✔
230

231
  private void createStartScripts() {
232

233
    List<String> ides = IdeVariables.CREATE_START_SCRIPTS.get(this.context);
6✔
234
    if (ides == null) {
2✔
235
      this.context.info("Variable CREATE_START_SCRIPTS is undefined - skipping start script creation.");
4✔
236
      return;
1✔
237
    }
238
    for (String ide : ides) {
10✔
239
      ToolCommandlet tool = this.context.getCommandletManager().getToolCommandlet(ide);
6✔
240
      if (tool == null) {
2!
241
        this.context.error("Undefined IDE '{}' configured in variable CREATE_START_SCRIPTS.");
×
242
      } else {
243
        createStartScript(ide);
3✔
244
      }
245
    }
1✔
246
  }
1✔
247

248
  private void createStartScript(String ide) {
249

250
    this.context.info("Creating start scripts for {}", ide);
10✔
251
    Path workspaces = this.context.getIdeHome().resolve(IdeContext.FOLDER_WORKSPACES);
6✔
252
    try (Stream<Path> childStream = Files.list(workspaces)) {
3✔
253
      Iterator<Path> iterator = childStream.iterator();
3✔
254
      while (iterator.hasNext()) {
3✔
255
        Path child = iterator.next();
4✔
256
        if (Files.isDirectory(child)) {
5!
257
          createStartScript(ide, child.getFileName().toString());
6✔
258
        }
259
      }
1✔
260
    } catch (IOException e) {
×
261
      throw new RuntimeException("Failed to list children of directory " + workspaces, e);
×
262
    }
1✔
263
  }
1✔
264

265
  private void createStartScript(String ide, String workspace) {
266

267
    Path ideHome = this.context.getIdeHome();
4✔
268
    String scriptName = ide + "-" + workspace;
4✔
269
    boolean windows = this.context.getSystemInfo().isWindows();
5✔
270
    if (windows) {
2!
271
      scriptName = scriptName + ".bat";
×
272
    } else {
273
      scriptName = scriptName + ".sh";
3✔
274
    }
275
    Path scriptPath = ideHome.resolve(scriptName);
4✔
276
    if (Files.exists(scriptPath)) {
5!
277
      return;
×
278
    }
279
    String scriptContent;
280
    if (windows) {
2!
281
      scriptContent = "@echo off\r\n"
×
282
          + "pushd %~dp0\r\n"
283
          + "cd workspaces/" + workspace + "\r\n"
284
          + "call ide " + ide + "\r\n"
285
          + "popd\r\n";
286
    } else {
287
      scriptContent = "#!/usr/bin/env bash\n"
4✔
288
          + "cd \"$(dirname \"$0\")\"\n"
289
          + "cd workspaces/" + workspace + "\n"
290
          + "ideasy " + ide + "\n";
291
    }
292
    FileAccess fileAccess = this.context.getFileAccess();
4✔
293
    fileAccess.writeFileContent(scriptContent, scriptPath);
4✔
294
    fileAccess.makeExecutable(scriptPath);
3✔
295
  }
1✔
296

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

© 2025 Coveralls, Inc