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

devonfw / IDEasy / 26819321012

02 Jun 2026 12:21PM UTC coverage: 71.075% (-0.02%) from 71.094%
26819321012

Pull #1983

github

web-flow
Merge f7ec9f66e into 6080ee79b
Pull Request #1983: #1937 link content from settings into workspace(s)

4533 of 7068 branches covered (64.13%)

Branch coverage included in aggregate %.

11739 of 15826 relevant lines covered (74.18%)

3.14 hits per line

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

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

3
import java.nio.file.Path;
4
import java.util.ArrayList;
5
import java.util.Arrays;
6
import java.util.Collections;
7
import java.util.HashSet;
8
import java.util.List;
9
import java.util.Properties;
10
import java.util.Set;
11
import java.util.regex.Pattern;
12
import java.util.stream.Collectors;
13

14
import org.slf4j.Logger;
15
import org.slf4j.LoggerFactory;
16

17
import com.devonfw.tools.ide.context.IdeContext;
18

19
/**
20
 * {@link Properties} for {@link RepositoryConfig}.
21
 */
22
final class RepositoryProperties {
23

24
  private static final Logger LOG = LoggerFactory.getLogger(RepositoryProperties.class);
3✔
25

26
  private static final String PROPERTY_PATH = "path";
27
  private static final String PROPERTY_WORKING_SETS = "workingsets";
28
  private static final String PROPERTY_WORKSPACES = "workspaces";
29
  private static final String PROPERTY_GIT_URL = "git_url";
30
  private static final String PROPERTY_BUILD_PATH = "build_path";
31
  private static final String PROPERTY_BUILD_CMD = "build_cmd";
32
  private static final String PROPERTY_ACTIVE = "active";
33
  private static final String PROPERTY_GIT_BRANCH = "git_branch";
34
  private static final String PROPERTY_IMPORT = "import";
35
  private static final String PROPERTY_LINK = "link";
36
  private static final String PROPERTY_LINK_TARGET = "link (=<target>)";
37
  private static final String PROPERTY_ECLIPSE = "eclipse";
38

39
  private static final Pattern PATH_PATTERN = Pattern.compile("[a-zA-Z0-9_.$/-]+");
3✔
40

41
  private static final Pattern WORKSPACE_PATTERN = Pattern.compile("[a-zA-Z][a-zA-Z0-9_.-]+");
4✔
42

43
  private final Path file;
44

45
  private final Properties properties;
46

47
  private boolean invalid;
48

49
  /**
50
   * The constructor.
51
   *
52
   * @param file the {@link Path} to the properties file.
53
   * @param context the {@link IdeContext}.
54
   */
55
  public RepositoryProperties(Path file, IdeContext context) {
56
    this(file, context.getFileAccess().readProperties(file));
7✔
57
  }
1✔
58

59
  /**
60
   * @param file the {@link Path} to the properties file.
61
   * @param properties the actual {@link Properties} loaded from the file.
62
   */
63
  RepositoryProperties(Path file, Properties properties) {
64
    super();
2✔
65
    this.file = file;
3✔
66
    this.properties = properties;
3✔
67
    validateRepositoryProperties();
2✔
68
  }
1✔
69

70
  /**
71
   * @param name the name of the requested property.
72
   * @return the value of the requested property or {@code null} if undefined.
73
   */
74
  public String getProperty(String name) {
75
    return getProperty(name, false);
5✔
76
  }
77

78
  /**
79
   * @param name the name of the requested property.
80
   * @param required - {@code true} if the requested property is required, {@code false} otherwise.
81
   * @return the value of the requested property or {@code null} if undefined.
82
   */
83
  public String getProperty(String name, boolean required) {
84

85
    return getProperty(name, null, required);
6✔
86
  }
87

88
  /**
89
   * @param name the name of the requested property.
90
   * @param legacyName the optional legacy property name.
91
   * @param required - {@code true} if the requested property is required, {@code false} otherwise.
92
   * @return the value of the requested property or {@code null} if undefined.
93
   */
94
  public String getProperty(String name, String legacyName, boolean required) {
95

96
    String value = doGetProperty(name, legacyName);
5✔
97
    if (isEmpty(value)) {
3✔
98
      if (required) {
2!
99
        LOG.error("The properties file {} is invalid because the required property {} is not present. Ignoring this file.", this.file, name);
×
100
        this.invalid = true;
×
101
      }
102
      return null;
2✔
103
    }
104
    return value;
2✔
105
  }
106

107
  private String doGetProperty(String name, String legacyName) {
108

109
    String value = this.properties.getProperty(name);
5✔
110
    if (value != null) {
2✔
111
      return value;
2✔
112
    }
113
    if (legacyName != null) {
2✔
114
      value = getLegacyProperty(legacyName, name);
5✔
115
      if (value != null) {
2✔
116
        return value;
2✔
117
      }
118
    }
119
    legacyName = name.replace("_", ".");
5✔
120
    if (!legacyName.equals(name)) {
4✔
121
      value = getLegacyProperty(legacyName, name);
5✔
122
      if (value != null) {
2!
123
        return value;
×
124
      }
125
      legacyName = name.replace("_", "-");
5✔
126
      value = getLegacyProperty(legacyName, name);
5✔
127
    }
128
    return value;
2✔
129
  }
130

131
  private static boolean isEmpty(String value) {
132

133
    return (value == null) || value.isBlank();
9✔
134
  }
135

136
  private String getLegacyProperty(String legacyName, String name) {
137

138
    String value = this.properties.getProperty(legacyName);
5✔
139
    if (value != null) {
2✔
140
      LOG.warn("In the properties file {} please replace the legacy property {} with the official property {}", this.file, legacyName, name);
18✔
141
    }
142
    return value;
2✔
143
  }
144

145
  /**
146
   * @return {@code true} if these properties have been marked as invalid because a required property was requested that is not available, {@code false}
147
   *     otherwise.
148
   */
149
  public boolean isInvalid() {
150

151
    return this.invalid;
3✔
152
  }
153

154
  /**
155
   * @return the {@link RepositoryConfig#path() path}.
156
   */
157
  public String getPath() {
158

159
    return sanatizeRelativePath(getProperty(PROPERTY_PATH), PROPERTY_PATH);
7✔
160
  }
161

162
  /**
163
   * @return the {@link RepositoryConfig#workingSets() working sets}.
164
   */
165
  public String getWorkingSets() {
166

167
    return getProperty(PROPERTY_WORKING_SETS);
4✔
168
  }
169

170
  /**
171
   * @return the {@link RepositoryConfig#gitUrl()}  git url}.
172
   */
173
  public String getGitUrl() {
174

175
    return getProperty(PROPERTY_GIT_URL, false);
5✔
176
  }
177

178
  /**
179
   * @return the repository identifier derived from the filename.
180
   */
181
  public String getRepositoryId() {
182
    String filename = this.file.getFileName().toString();
5✔
183
    if (filename.endsWith(IdeContext.EXT_PROPERTIES)) {
4!
184
      return filename.substring(0, filename.length() - IdeContext.EXT_PROPERTIES.length());
9✔
185
    }
186
    return filename;
×
187
  }
188

189
  /**
190
   * @return the {@link RepositoryConfig#gitBranch()}  git branch}.
191
   */
192
  public String getGitBranch() {
193

194
    return getProperty(PROPERTY_GIT_BRANCH);
4✔
195
  }
196

197
  /**
198
   * @return the {@link RepositoryConfig#buildPath() build path}.
199
   */
200
  public String getBuildPath() {
201

202
    return getProperty(PROPERTY_BUILD_PATH);
4✔
203
  }
204

205
  /**
206
   * @return the {@link RepositoryConfig#buildCmd() build command}.
207
   */
208
  public String getBuildCmd() {
209

210
    return getProperty(PROPERTY_BUILD_CMD);
4✔
211
  }
212

213
  /**
214
   * @return the {@link RepositoryConfig#active() active flag}.
215
   */
216
  public boolean isActive() {
217

218
    return parseBoolean(getProperty(PROPERTY_ACTIVE));
5✔
219
  }
220

221
  /**
222
   * @return the IDEs where to import the repository.
223
   */
224
  public Set<String> getImports() {
225

226
    String importProperty = getProperty(PROPERTY_IMPORT);
4✔
227
    if (importProperty != null) {
2✔
228
      if (importProperty.isEmpty()) {
3!
229
        return Set.of();
×
230
      }
231
      return Arrays.stream(importProperty.split(",")).map(String::trim).collect(Collectors.toUnmodifiableSet());
10✔
232
    }
233

234
    String legacyImportProperty = getLegacyProperty(PROPERTY_ECLIPSE, PROPERTY_IMPORT);
5✔
235
    if ("import".equals(legacyImportProperty)) {
4!
236
      LOG.warn("Property {} is deprecated and should be replaced with {} (invert key and value).", PROPERTY_ECLIPSE,
×
237
          PROPERTY_IMPORT);
238
      return Set.of("eclipse");
×
239
    } else {
240
      return Set.of();
2✔
241
    }
242
  }
243

244
  /**
245
   * @return the workspaces where to clone the repository. Returns a set containing "main" as default if not specified.
246
   */
247
  public List<String> getWorkspaces() {
248

249
    String workspaceProperty = getProperty(PROPERTY_WORKSPACES, "workspace", false);
6✔
250
    if (!isEmpty(workspaceProperty)) {
3✔
251
      if (RepositoryConfig.WORKSPACE_NAME_ALL.equals(workspaceProperty)) {
4✔
252
        return List.of(workspaceProperty);
3✔
253
      }
254
      List<String> list = new ArrayList<>();
4✔
255
      Set<String> set = new HashSet<>();
4✔
256
      for (String workspace : workspaceProperty.split(",")) {
18✔
257
        workspace = workspace.trim();
3✔
258
        if (WORKSPACE_PATTERN.matcher(workspace).matches()) {
5!
259
          boolean added = set.add(workspace);
4✔
260
          if (added) {
2!
261
            list.add(workspace);
5✔
262
          } else {
263
            LOG.warn("Ignoring duplicate workspace {} from {}", workspace, workspaceProperty);
×
264
          }
265
        } else {
1✔
266
          LOG.warn("Ignoring illegal workspace {} from {}", workspace, workspaceProperty);
×
267
        }
268
      }
269
      return Collections.unmodifiableList(list);
3✔
270
    }
271
    return List.of(IdeContext.WORKSPACE_MAIN);
3✔
272
  }
273

274
  public List<RepositoryLink> getLinks() {
275

276
    String link = getProperty(PROPERTY_LINK);
4✔
277
    if (isEmpty(link)) {
3✔
278
      return List.of();
2✔
279
    }
280
    List<RepositoryLink> links = new ArrayList<>();
4✔
281
    for (String linkItem : link.split(",")) {
18✔
282
      String linkPath = linkItem;
2✔
283
      String linkTarget = "";
2✔
284
      int eqIndex = linkItem.indexOf('=');
4✔
285
      if (eqIndex > 0) {
2✔
286
        linkPath = linkItem.substring(0, eqIndex);
5✔
287
        linkTarget = linkItem.substring(eqIndex + 1);
6✔
288
      }
289
      linkPath = sanatizeRelativePath(linkPath, PROPERTY_LINK);
5✔
290
      if (!linkTarget.isEmpty()) {
3✔
291
        linkTarget = sanatizeRelativePath(linkTarget, PROPERTY_LINK_TARGET);
5✔
292
      }
293
      if ((linkPath != null) && (linkTarget != null)) {
4!
294
        links.add(new RepositoryLink(linkPath, linkTarget));
8✔
295
      }
296
    }
297
    return List.copyOf(links); // make immutable for record
3✔
298
  }
299

300
  private String sanatizeRelativePath(String path, String propertyName) {
301
    if (path == null) {
2✔
302
      return null;
2✔
303
    }
304
    String normalized = path.trim().replace('\\', '/');
6✔
305
    if (normalized.contains("..") || !PATH_PATTERN.matcher(normalized).matches()) {
9!
306
      LOG.warn("Invalid path {} from property {} of {}", path, propertyName, this.file);
×
307
      return null;
×
308
    }
309
    return normalized;
2✔
310
  }
311

312
  private static boolean parseBoolean(String value) {
313

314
    if (value == null) {
2✔
315
      return true;
2✔
316
    }
317
    return "true".equals(value.trim());
5✔
318
  }
319

320
  /**
321
   * Validates that non-settings repositories must have a git_url.
322
   * Settings repositories can optionally omit git_url to enable linking from settings.
323
   */
324
  private void validateRepositoryProperties() {
325
    String gitUrl = getProperty(PROPERTY_GIT_URL, false);
5✔
326
    String id = getRepositoryId();
3✔
327

328
    boolean isSettingsRepository = IdeContext.SETTINGS_REPOSITORY_KEYWORD.equals(id);
4✔
329
    boolean hasGitUrl = gitUrl != null && !gitUrl.isBlank();
9!
330

331
    if (!isSettingsRepository && !hasGitUrl) {
4✔
332
      LOG.error(
6✔
333
          "The properties file {} is invalid: repository '{}' requires a git_url property or must be named 'settings.properties' for virtual linking.",
334
          this.file, id);
335
      this.invalid = true;
3✔
336
    }
337
  }
1✔
338
}
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