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

devonfw / IDEasy / 23013411057

12 Mar 2026 04:48PM UTC coverage: 70.479% (+0.1%) from 70.347%
23013411057

push

github

web-flow
#1735: implement repo link feature, #1736: fix link creation (#1739)

4139 of 6464 branches covered (64.03%)

Branch coverage included in aggregate %.

10730 of 14633 relevant lines covered (73.33%)

3.09 hits per line

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

91.39
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_ECLIPSE = "eclipse";
37

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

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

42
  private final Path file;
43

44
  private final Properties properties;
45

46
  private boolean invalid;
47

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

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

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

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

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

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

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

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

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

129
  private static boolean isEmpty(String value) {
130

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

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

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

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

149
    return this.invalid;
3✔
150
  }
151

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

157
    return sanatizeRelativePath(getProperty(PROPERTY_PATH));
6✔
158
  }
159

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

261
  public List<RepositoryLink> getLinks() {
262

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

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

297
  private static boolean parseBoolean(String value) {
298

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