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

devonfw / IDEasy / 23053266919

13 Mar 2026 01:34PM UTC coverage: 70.602% (-0.007%) from 70.609%
23053266919

push

github

web-flow
#1735: improve repository properties handling and logging (#1749)

4144 of 6466 branches covered (64.09%)

Branch coverage included in aggregate %.

10746 of 14624 relevant lines covered (73.48%)

3.09 hits per line

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

89.1
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
  }
1✔
68

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

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

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

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

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

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

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

130
  private static boolean isEmpty(String value) {
131

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

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

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

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

150
    return this.invalid;
3✔
151
  }
152

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

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

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

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

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

174
    return getProperty(PROPERTY_GIT_URL, true);
5✔
175
  }
176

177
  /**
178
   * @return the {@link RepositoryConfig#gitBranch()}  git branch}.
179
   */
180
  public String getGitBranch() {
181

182
    return getProperty(PROPERTY_GIT_BRANCH);
4✔
183
  }
184

185
  /**
186
   * @return the {@link RepositoryConfig#buildPath() build path}.
187
   */
188
  public String getBuildPath() {
189

190
    return getProperty(PROPERTY_BUILD_PATH);
4✔
191
  }
192

193
  /**
194
   * @return the {@link RepositoryConfig#buildCmd() build command}.
195
   */
196
  public String getBuildCmd() {
197

198
    return getProperty(PROPERTY_BUILD_CMD);
4✔
199
  }
200

201
  /**
202
   * @return the {@link RepositoryConfig#active() active flag}.
203
   */
204
  public boolean isActive() {
205

206
    return parseBoolean(getProperty(PROPERTY_ACTIVE));
5✔
207
  }
208

209
  /**
210
   * @return the IDEs where to import the repository.
211
   */
212
  public Set<String> getImports() {
213

214
    String importProperty = getProperty(PROPERTY_IMPORT);
4✔
215
    if (importProperty != null) {
2✔
216
      if (importProperty.isEmpty()) {
3!
217
        return Set.of();
×
218
      }
219
      return Arrays.stream(importProperty.split(",")).map(String::trim).collect(Collectors.toUnmodifiableSet());
10✔
220
    }
221

222
    String legacyImportProperty = getLegacyProperty(PROPERTY_ECLIPSE, PROPERTY_IMPORT);
5✔
223
    if ("import".equals(legacyImportProperty)) {
4!
224
      LOG.warn("Property {} is deprecated and should be replaced with {} (invert key and value).", PROPERTY_ECLIPSE,
×
225
          PROPERTY_IMPORT);
226
      return Set.of("eclipse");
×
227
    } else {
228
      return Set.of();
2✔
229
    }
230
  }
231

232
  /**
233
   * @return the workspaces where to clone the repository. Returns a set containing "main" as default if not specified.
234
   */
235
  public List<String> getWorkspaces() {
236

237
    String workspaceProperty = getProperty(PROPERTY_WORKSPACES, "workspace", false);
6✔
238
    if (!isEmpty(workspaceProperty)) {
3✔
239
      if (RepositoryConfig.WORKSPACE_NAME_ALL.equals(workspaceProperty)) {
4✔
240
        return List.of(workspaceProperty);
3✔
241
      }
242
      List<String> list = new ArrayList<>();
4✔
243
      Set<String> set = new HashSet<>();
4✔
244
      for (String workspace : workspaceProperty.split(",")) {
18✔
245
        workspace = workspace.trim();
3✔
246
        if (WORKSPACE_PATTERN.matcher(workspace).matches()) {
5!
247
          boolean added = set.add(workspace);
4✔
248
          if (added) {
2!
249
            list.add(workspace);
5✔
250
          } else {
251
            LOG.warn("Ignoring duplicate workspace {} from {}", workspace, workspaceProperty);
×
252
          }
253
        } else {
1✔
254
          LOG.warn("Ignoring illegal workspace {} from {}", workspace, workspaceProperty);
×
255
        }
256
      }
257
      return Collections.unmodifiableList(list);
3✔
258
    }
259
    return List.of(IdeContext.WORKSPACE_MAIN);
3✔
260
  }
261

262
  public List<RepositoryLink> getLinks() {
263

264
    String link = getProperty(PROPERTY_LINK);
4✔
265
    if (isEmpty(link)) {
3✔
266
      return List.of();
2✔
267
    }
268
    List<RepositoryLink> links = new ArrayList<>();
4✔
269
    for (String linkItem : link.split(",")) {
18✔
270
      String linkPath = linkItem;
2✔
271
      String linkTarget = "";
2✔
272
      int eqIndex = linkItem.indexOf('=');
4✔
273
      if (eqIndex > 0) {
2✔
274
        linkPath = linkItem.substring(0, eqIndex);
5✔
275
        linkTarget = linkItem.substring(eqIndex + 1);
6✔
276
      }
277
      linkPath = sanatizeRelativePath(linkPath, PROPERTY_LINK);
5✔
278
      if (!linkTarget.isEmpty()) {
3✔
279
        linkTarget = sanatizeRelativePath(linkTarget, PROPERTY_LINK_TARGET);
5✔
280
      }
281
      if ((linkPath != null) && (linkTarget != null)) {
4!
282
        links.add(new RepositoryLink(linkPath, linkTarget));
8✔
283
      }
284
    }
285
    return List.copyOf(links); // make immutable for record
3✔
286
  }
287

288
  private String sanatizeRelativePath(String path, String propertyName) {
289
    if (path == null) {
2✔
290
      return null;
2✔
291
    }
292
    String normalized = path.trim().replace('\\', '/');
6✔
293
    if (normalized.contains("..") || !PATH_PATTERN.matcher(normalized).matches()) {
9!
294
      LOG.warn("Invalid path {} from property {} of {}", path, propertyName, this.file);
×
295
      return null;
×
296
    }
297
    return normalized;
2✔
298
  }
299

300
  private static boolean parseBoolean(String value) {
301

302
    if (value == null) {
2✔
303
      return true;
2✔
304
    }
305
    return "true".equals(value.trim());
5✔
306
  }
307
}
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