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

devonfw / IDEasy / 19462251046

18 Nov 2025 10:11AM UTC coverage: 68.846% (-0.09%) from 68.94%
19462251046

push

github

web-flow
#1584: improve commandlet error handling and bash array syntax warning (#1589)

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

3528 of 5623 branches covered (62.74%)

Branch coverage included in aggregate %.

9214 of 12885 relevant lines covered (71.51%)

3.14 hits per line

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

52.91
cli/src/main/java/com/devonfw/tools/ide/git/repository/RepositoryCommandlet.java
1
package com.devonfw.tools.ide.git.repository;
2

3
import java.nio.file.Files;
4
import java.nio.file.Path;
5
import java.util.List;
6
import java.util.Set;
7

8
import com.devonfw.tools.ide.commandlet.Commandlet;
9
import com.devonfw.tools.ide.context.IdeContext;
10
import com.devonfw.tools.ide.git.GitContext;
11
import com.devonfw.tools.ide.git.GitUrl;
12
import com.devonfw.tools.ide.io.FileAccess;
13
import com.devonfw.tools.ide.property.RepositoryProperty;
14
import com.devonfw.tools.ide.step.Step;
15
import com.devonfw.tools.ide.tool.ToolCommandlet;
16
import com.devonfw.tools.ide.tool.ide.IdeToolCommandlet;
17

18
/**
19
 * {@link Commandlet} to setup one or multiple GIT repositories for development.
20
 */
21
public class RepositoryCommandlet extends Commandlet {
22

23
  /** the repository to setup. */
24
  public final RepositoryProperty repository;
25

26
  /**
27
   * The constructor.
28
   *
29
   * @param context the {@link IdeContext}.
30
   */
31
  public RepositoryCommandlet(IdeContext context) {
32

33
    super(context);
3✔
34
    addKeyword(getName());
4✔
35
    addKeyword("setup");
3✔
36
    this.repository = add(new RepositoryProperty("", false, "repository"));
11✔
37
  }
1✔
38

39
  @Override
40
  public String getName() {
41

42
    return "repository";
2✔
43
  }
44

45
  @Override
46
  public void run() {
47

48
    Path repositoryFile = this.repository.getValue();
5✔
49

50
    if (repositoryFile != null) {
2✔
51
      // Handle the case when a specific repository is provided
52
      doImportRepository(repositoryFile, true);
5✔
53
    } else {
54
      // If no specific repository is provided, check for repositories folder
55
      Path repositoriesPath = this.context.getRepositoriesPath();
4✔
56
      if (repositoriesPath == null) {
2✔
57
        this.context.warning("Cannot find folder 'repositories' nor 'projects' in your settings.");
4✔
58
        return;
1✔
59
      }
60
      List<Path> propertiesFiles = this.context.getFileAccess()
5✔
61
          .listChildren(repositoriesPath, path -> path.getFileName().toString().endsWith(".properties"));
8✔
62
      boolean forceMode = this.context.isForceMode() || this.context.isForceRepositories();
12!
63
      for (Path propertiesFile : propertiesFiles) {
10✔
64
        doImportRepository(propertiesFile, forceMode);
4✔
65
      }
1✔
66
    }
67
  }
1✔
68

69
  private void doImportRepository(Path repositoryFile, boolean forceMode) {
70

71
    String repositoryFilename = repositoryFile.getFileName().toString();
4✔
72
    final String repositoryId;
73
    if (repositoryFilename.endsWith(IdeContext.EXT_PROPERTIES)) {
4!
74
      repositoryId = repositoryFilename.substring(0, repositoryFilename.length() - IdeContext.EXT_PROPERTIES.length());
10✔
75
    } else {
76
      repositoryId = repositoryFilename;
×
77
    }
78
    this.context.newStep("Setup of repository " + repositoryId, repositoryFile).run(() -> {
18✔
79
      doImportRepository(repositoryFile, forceMode, repositoryId);
5✔
80
    });
1✔
81
  }
1✔
82

83
  private void doImportRepository(Path repositoryFile, boolean forceMode, String repositoryId) {
84
    RepositoryConfig repositoryConfig = RepositoryConfig.loadProperties(repositoryFile, this.context);
5✔
85
    if (!repositoryConfig.active()) {
3!
86
      if (forceMode) {
2✔
87
        this.context.info("Setup of repository {} is forced, hence proceeding ...", repositoryId);
11✔
88
      } else {
89
        this.context.info("Skipping repository {} because it is not active - use --force to setup all repositories ...", repositoryId);
10✔
90
        return;
1✔
91
      }
92
    }
93
    GitUrl gitUrl = repositoryConfig.asGitUrl();
3✔
94
    if (gitUrl == null) {
2!
95
      // error was already logged.
96
      return;
×
97
    }
98
    this.context.debug("Repository configuration: {}", repositoryConfig);
10✔
99
    Path repositoryPath = getRepositoryPath(repositoryConfig, repositoryId);
5✔
100
    if (Files.isDirectory(repositoryPath.resolve(GitContext.GIT_FOLDER))) {
7!
101
      this.context.info("Repository {} already exists at {}", repositoryId, repositoryPath);
×
102
      if (!(this.context.isForceMode() || this.context.isForceRepositories())) {
×
103
        this.context.info("Ignoring repository {} - use --force or --force-repositories to rerun setup.", repositoryId);
×
104
        return;
×
105
      }
106
    }
107
    Path ideStatusDir = this.context.getIdeHome().resolve(IdeContext.FOLDER_DOT_IDE);
6✔
108
    this.context.getFileAccess().mkdirs(ideStatusDir);
5✔
109
    Path repositoryCreatedStatusFile = ideStatusDir.resolve("repository." + repositoryId);
5✔
110
    if (Files.exists(repositoryCreatedStatusFile)) {
5!
111
      if (!(this.context.isForceMode() || this.context.isForceRepositories())) {
×
112
        this.context.info("Ignoring repository {} because it was already setup before - use --force or --force-repositories for recreation.", repository);
×
113
        return;
×
114
      }
115
    }
116
    boolean success = cloneOrPullRepository(repositoryPath, gitUrl, repositoryCreatedStatusFile);
6✔
117
    if (success) {
2!
118
      buildRepository(repositoryConfig, repositoryPath);
5✔
119
      importRepository(repositoryConfig, repositoryPath, repositoryId);
5✔
120
    }
121
  }
1✔
122

123
  private boolean cloneOrPullRepository(Path repositoryPath, GitUrl gitUrl, Path repositoryCreatedStatusFile) {
124

125
    FileAccess fileAccess = this.context.getFileAccess();
4✔
126
    return this.context.newStep("Clone or pull repository").run(() -> {
12✔
127
      fileAccess.mkdirs(repositoryPath);
3✔
128
      this.context.getGitContext().pullOrClone(gitUrl, repositoryPath);
6✔
129
      fileAccess.touch(repositoryCreatedStatusFile);
3✔
130
    });
1✔
131
  }
132

133
  private Path getRepositoryPath(RepositoryConfig repositoryConfig, String repositoryId) {
134
    String workspace = repositoryConfig.workspace();
3✔
135
    if (workspace == null) {
2!
136
      workspace = IdeContext.WORKSPACE_MAIN;
×
137
    }
138
    Path workspacePath = this.context.getIdeHome().resolve(IdeContext.FOLDER_WORKSPACES).resolve(workspace);
8✔
139
    String repositoryRelativePath = repositoryConfig.path();
3✔
140
    if (repositoryRelativePath == null) {
2✔
141
      repositoryRelativePath = repositoryId;
2✔
142
    }
143
    return workspacePath.resolve(repositoryRelativePath);
4✔
144
  }
145

146
  private boolean buildRepository(RepositoryConfig repositoryConfig, Path repositoryPath) {
147
    String buildCmd = repositoryConfig.buildCmd();
3✔
148
    if (buildCmd != null && !buildCmd.isEmpty()) {
2!
149
      return this.context.newStep("Build repository via: " + buildCmd).run(() -> {
×
150
        String[] command = buildCmd.split("\\s+");
×
151
        ToolCommandlet commandlet = this.context.getCommandletManager().getToolCommandlet(command[0]);
×
152
        if (commandlet == null) {
×
153
          String displayName = (command[0] == null || command[0].isBlank()) ? "<empty>" : "'" + command[0] + "'";
×
154
          this.context.error("Cannot build repository. Required tool '{}' not found. Please check your repository's build_cmd configuration value.", displayName);
×
155
          return;
×
156
        }
157
        commandlet.reset();
×
158
        for (int i = 1; i < command.length; i++) {
×
159
          commandlet.arguments.addValue(command[i]);
×
160
        }
161
        Path executionDirectory = repositoryPath;
×
162
        String path = repositoryConfig.buildPath();
×
163
        if (path != null) {
×
164
          executionDirectory = executionDirectory.resolve(path);
×
165
        }
166
        commandlet.setExecutionDirectory(executionDirectory);
×
167
        commandlet.run();
×
168
      });
×
169
    } else {
170
      this.context.debug("Build command not set. Skipping build for repository.");
4✔
171
      return true;
2✔
172
    }
173
  }
174

175
  private void importRepository(RepositoryConfig repositoryConfig, Path repositoryPath, String repositoryId) {
176

177
    Set<String> imports = repositoryConfig.imports();
3✔
178
    if ((imports == null) || imports.isEmpty()) {
5!
179
      this.context.debug("Repository {} has no IDE configured for import.", repositoryId);
10✔
180
      return;
1✔
181
    }
182
    for (String ide : imports) {
×
183
      Step step = this.context.newStep("Importing repository " + repositoryId + " into " + ide);
×
184
      step.run(() -> {
×
185
        ToolCommandlet commandlet = this.context.getCommandletManager().getToolCommandlet(ide);
×
186
        if (commandlet == null) {
×
187
          String displayName = (ide == null || ide.isBlank()) ? "<empty>" : "'" + ide + "'";
×
188
          step.error("Cannot import repository '{}'. Required IDE '{}' not found. Please check your repository's imports configuration.", repositoryId, displayName);
×
189
        } else if (commandlet instanceof IdeToolCommandlet ideCommandlet) {
×
190
          ideCommandlet.importRepository(repositoryPath);
×
191
        } else {
192
          step.error("Repository {} has import {} configured that is not an IDE!", repositoryId, ide);
×
193
        }
194
      });
×
195
    }
×
196
  }
×
197
}
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