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

devonfw / IDEasy / 29912345748

22 Jul 2026 10:33AM UTC coverage: 72.537% (+0.04%) from 72.496%
29912345748

Pull #2198

github

web-flow
Merge b3d1a5106 into 5b6cc3011
Pull Request #2198: Feature/2168 repository remote feature

4989 of 7600 branches covered (65.64%)

Branch coverage included in aggregate %.

12847 of 16989 relevant lines covered (75.62%)

3.2 hits per line

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

89.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_LINK_TARGET = "link (=<target>)";
37
  private static final String PROPERTY_GIT_REMOTE = "git_remote";
38
  private static final String PROPERTY_ECLIPSE = "eclipse";
39

40
  private static final Pattern REMOTE_NAME_PATTERN = Pattern.compile("[a-zA-Z]+");
3✔
41

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

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

46
  private final Path file;
47

48
  private final Properties properties;
49

50
  private boolean invalid;
51

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

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

72
  /**
73
   * @return the ID derived from the filename without {@link IdeContext#EXT_PROPERTIES}.
74
   */
75
  public String getId() {
76

77
    String filename = this.file.getFileName().toString();
5✔
78
    if (filename.endsWith(IdeContext.EXT_PROPERTIES)) {
4✔
79
      return filename.substring(0, filename.length() - IdeContext.EXT_PROPERTIES.length());
9✔
80
    }
81
    return filename;
2✔
82
  }
83

84
  /**
85
   * @param name the name of the requested property.
86
   * @return the value of the requested property or {@code null} if undefined.
87
   */
88
  public String getProperty(String name) {
89
    return getProperty(name, false);
5✔
90
  }
91

92
  /**
93
   * @param name the name of the requested property.
94
   * @param required - {@code true} if the requested property is required, {@code false} otherwise.
95
   * @return the value of the requested property or {@code null} if undefined.
96
   */
97
  public String getProperty(String name, boolean required) {
98

99
    return getProperty(name, null, required);
6✔
100
  }
101

102
  /**
103
   * @param name the name of the requested property.
104
   * @param legacyName the optional legacy property name.
105
   * @param required - {@code true} if the requested property is required, {@code false} otherwise.
106
   * @return the value of the requested property or {@code null} if undefined.
107
   */
108
  public String getProperty(String name, String legacyName, boolean required) {
109

110
    String value = doGetProperty(name, legacyName);
5✔
111
    if (isEmpty(value)) {
3✔
112
      if (required) {
2✔
113
        LOG.error("The properties file {} is invalid because the required property {} is not present. Ignoring this file.", this.file, name);
6✔
114
        this.invalid = true;
3✔
115
      }
116
      return null;
2✔
117
    }
118
    return value;
2✔
119
  }
120

121
  private String doGetProperty(String name, String legacyName) {
122

123
    String value = this.properties.getProperty(name);
5✔
124
    if (value != null) {
2✔
125
      return value;
2✔
126
    }
127
    if (legacyName != null) {
2✔
128
      value = getLegacyProperty(legacyName, name);
5✔
129
      if (value != null) {
2✔
130
        return value;
2✔
131
      }
132
    }
133
    legacyName = name.replace("_", ".");
5✔
134
    if (!legacyName.equals(name)) {
4✔
135
      value = getLegacyProperty(legacyName, name);
5✔
136
      if (value != null) {
2!
137
        return value;
×
138
      }
139
      legacyName = name.replace("_", "-");
5✔
140
      value = getLegacyProperty(legacyName, name);
5✔
141
    }
142
    return value;
2✔
143
  }
144

145
  private static boolean isEmpty(String value) {
146

147
    return (value == null) || value.isBlank();
9✔
148
  }
149

150
  private String getLegacyProperty(String legacyName, String name) {
151

152
    String value = this.properties.getProperty(legacyName);
5✔
153
    if (value != null) {
2✔
154
      LOG.warn("In the properties file {} please replace the legacy property {} with the official property {}", this.file, legacyName, name);
18✔
155
    }
156
    return value;
2✔
157
  }
158

159
  /**
160
   * @return {@code true} if these properties have been marked as invalid because a required property was requested that is not available, {@code false}
161
   *     otherwise.
162
   */
163
  public boolean isInvalid() {
164

165
    return this.invalid;
3✔
166
  }
167

168
  /**
169
   * @return the {@link RepositoryConfig#path() path}.
170
   */
171
  public String getPath() {
172

173
    return sanatizeRelativePath(getProperty(PROPERTY_PATH), PROPERTY_PATH);
7✔
174
  }
175

176
  /**
177
   * @return the {@link RepositoryConfig#workingSets() working sets}.
178
   */
179
  public String getWorkingSets() {
180

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

184
  /**
185
   * @return the {@link RepositoryConfig#gitUrl()}  git url}.
186
   */
187
  public String getGitUrl() {
188

189
    return getProperty(PROPERTY_GIT_URL, !isSettingsProperties());
10✔
190
  }
191

192
  private boolean isSettingsProperties() {
193

194
    return IdeContext.FOLDER_SETTINGS.equals(getId());
5✔
195
  }
196

197
  /**
198
   * @return the {@link RepositoryConfig#gitBranch()}  git branch}.
199
   */
200
  public String getGitBranch() {
201

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

205
  /**
206
   * @return the {@link RepositoryConfig#buildPath() build path}.
207
   */
208
  public String getBuildPath() {
209

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

213
  /**
214
   * @return the {@link RepositoryConfig#buildCmd() build command}.
215
   */
216
  public String getBuildCmd() {
217

218
    return getProperty(PROPERTY_BUILD_CMD);
4✔
219
  }
220

221
  /**
222
   * @return the {@link RepositoryConfig#active() active flag}.
223
   */
224
  public boolean isActive() {
225

226
    return parseBoolean(getProperty(PROPERTY_ACTIVE));
5✔
227
  }
228

229
  /**
230
   * @return the IDEs where to import the repository.
231
   */
232
  public Set<String> getImports() {
233

234
    String importProperty = getProperty(PROPERTY_IMPORT);
4✔
235
    if (importProperty != null) {
2✔
236
      if (importProperty.isEmpty()) {
3!
237
        return Set.of();
×
238
      }
239
      return Arrays.stream(importProperty.split(",")).map(String::trim).collect(Collectors.toUnmodifiableSet());
10✔
240
    }
241

242
    String legacyImportProperty = getLegacyProperty(PROPERTY_ECLIPSE, PROPERTY_IMPORT);
5✔
243
    if ("import".equals(legacyImportProperty)) {
4!
244
      LOG.warn("Property {} is deprecated and should be replaced with {} (invert key and value).", PROPERTY_ECLIPSE,
×
245
          PROPERTY_IMPORT);
246
      return Set.of("eclipse");
×
247
    } else {
248
      return Set.of();
2✔
249
    }
250
  }
251

252
  /**
253
   * @return the workspaces where to clone the repository. Returns a set containing "main" as default if not specified.
254
   */
255
  public List<String> getWorkspaces() {
256

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

282
  public List<RepositoryLink> getLinks() {
283

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

308
  public List<RepositoryRemote> getRemotes() {
309

310
    String remotes = getProperty(PROPERTY_GIT_REMOTE);
4✔
311
    if (isEmpty(remotes)) {
3✔
312
      return List.of();
2✔
313
    }
314
    List<RepositoryRemote> remoteList = new ArrayList<>();
4✔
315
    for (String remoteItem : remotes.split(",")) {
18✔
316
      String trimmed = remoteItem.trim();
3✔
317
      int colonIndex = trimmed.indexOf(':');
4✔
318
      if (colonIndex <= 0) {
2✔
319
        LOG.warn("Ignoring invalid git_remote entry {} from {}", trimmed, PROPERTY_GIT_REMOTE);
5✔
320
        continue;
1✔
321
      }
322
      String name = trimmed.substring(0, colonIndex).trim();
6✔
323
      String url = trimmed.substring(colonIndex + 1).trim();
7✔
324
      if (name.isBlank() || url.isBlank()) {
6!
325
        LOG.warn("Ignoring git_remote entry {} with empty name or url from {}", trimmed, PROPERTY_GIT_REMOTE);
×
326
        continue;
×
327
      }
328
      if (!REMOTE_NAME_PATTERN.matcher(name).matches()) {
5✔
329
        LOG.warn("Ignoring git_remote entry {} with invalid remote name \"{}\" — name must consist of latin letters only from {}",
17✔
330
            trimmed, name, PROPERTY_GIT_REMOTE);
331
        continue;
1✔
332
      }
333
      remoteList.add(new RepositoryRemote(name, url));
8✔
334
    }
335
    return List.copyOf(remoteList);
3✔
336
  }
337

338
  private String sanatizeRelativePath(String path, String propertyName) {
339
    if (path == null) {
2✔
340
      return null;
2✔
341
    }
342
    String normalized = path.trim().replace('\\', '/');
6✔
343
    if (normalized.contains("..") || !PATH_PATTERN.matcher(normalized).matches()) {
9!
344
      LOG.warn("Invalid path {} from property {} of {}", path, propertyName, this.file);
×
345
      return null;
×
346
    }
347
    return normalized;
2✔
348
  }
349

350
  private static boolean parseBoolean(String value) {
351

352
    if (value == null) {
2✔
353
      return true;
2✔
354
    }
355
    return "true".equals(value.trim());
5✔
356
  }
357
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc