Skip to content

Commit 60d1e7b

Browse files
committed
Merge branch '100-implement-repository-commandlet' into 102-implement-update-commandlet
# Conflicts: # cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java
2 parents 4c2291a + f847820 commit 60d1e7b

37 files changed

+1482
-69
lines changed

.github/workflows/build-pr.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ jobs:
1414
java-version: '17'
1515
- name: Build project with Maven
1616
run: mvn -B -ntp -Dstyle.color=always install
17+
- name: Coveralls GitHub Action
18+
uses: coverallsapp/[email protected]

.github/workflows/build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ jobs:
2323
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
2424
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
2525
run: mvn --settings .mvn/settings.xml -DskipTests=true -Darchetype.test.skip=true -Dmaven.install.skip=true -Dstyle.color=always -B -ntp deploy
26+
- name: Coveralls GitHub Action
27+
uses: coverallsapp/[email protected]

README.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ image:https://img.shields.io/github/license/devonfw/IDEasy.svg?label=License["Ap
1010
image:https://img.shields.io/maven-central/v/com.devonfw.tools.ide/ide-cli.svg?label=Maven%20Central["Maven Central",link=https://search.maven.org/search?q=g:com.devonfw.tools.ide]
1111
image:https://github.com/devonfw/IDEasy/actions/workflows/build.yml/badge.svg["Build Status",link="https://github.com/devonfw/IDEasy/actions/workflows/build.yml"]
1212
image:https://github.com/devonfw/IDEasy/actions/workflows/update-urls.yml/badge.svg["Update URLS Status",link="https://github.com/devonfw/IDEasy/actions/workflows/update-urls.yml"]
13+
image:https://coveralls.io/repos/github/devonfw/IDEasy/badge.svg?branch=main["Coverage Status",link="https://coveralls.io/github/devonfw/IDEasy?branch=main"]
1314

1415
toc::[]
1516

cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import static com.devonfw.tools.ide.commandlet.RepositoryConfig.loadProperties;
1818

1919
/**
20-
* {@link Commandlet} to setup a repository
20+
* {@link Commandlet} to setup one or multiple GIT repositories for development.
2121
*/
2222
public class RepositoryCommandlet extends Commandlet {
2323

@@ -46,15 +46,15 @@ public String getName() {
4646
@Override
4747
public void run() {
4848

49-
Path repositoriesPath = this.context.getSettingsPath().resolve(context.FOLDER_REPOSITORIES);
50-
Path legacyRepositoriesPath = this.context.getSettingsPath().resolve(context.FOLDER_LEGACY_REPOSITORIES);
5149
Path repositoryFile = repository.getValueAsPath(context);
5250

5351
if (repositoryFile != null) {
5452
// Handle the case when a specific repository is provided
5553
doImportRepository(repositoryFile, true);
5654
} else {
5755
// If no specific repository is provided, check for repositories folder
56+
Path repositoriesPath = this.context.getSettingsPath().resolve(IdeContext.FOLDER_REPOSITORIES);
57+
Path legacyRepositoriesPath = this.context.getSettingsPath().resolve(IdeContext.FOLDER_LEGACY_REPOSITORIES);
5858
Path repositories;
5959
if (Files.exists(repositoriesPath)) {
6060
repositories = repositoriesPath;
@@ -99,7 +99,6 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) {
9999
}
100100

101101
this.context.debug(repositoryConfig.toString());
102-
this.context.debug("Pull or clone git repository {} ...", repository);
103102

104103
String workspace = repositoryConfig.workspace() != null ? repositoryConfig.workspace() : "main";
105104
Path workspacePath = this.context.getIdeHome().resolve("workspaces").resolve(workspace);
@@ -127,10 +126,5 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) {
127126
this.context.info("Build command not set. Skipping build for repository.");
128127
}
129128

130-
if (!Files.exists(repositoryPath.resolve(".project"))) {
131-
for (String ideCommandlet : repositoryConfig.imports()) {
132-
//TODO: import repository to ideCommandlet
133-
}
134-
}
135129
}
136130
}

cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public static RepositoryConfig loadProperties(Path filePath) {
4545
return new RepositoryConfig(properties.getProperty("path"), properties.getProperty("workingsets"),
4646
properties.getProperty("workspace"), properties.getProperty("git_url"), properties.getProperty("git_branch"),
4747
properties.getProperty(("build_path")), properties.getProperty("build_cmd"), importsSet,
48-
Boolean.parseBoolean(properties.getProperty("active")));
48+
Boolean.parseBoolean(properties.getProperty("active").trim()));
4949
}
5050

5151
private static Set<String> getImports(Properties properties) {

cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,7 @@ public void gitPullOrClone(Path target, String gitRepoUrl) {
601601
if (!gitRepoUrl.startsWith("http")) {
602602
throw new IllegalArgumentException("Invalid git URL '" + gitRepoUrl + "'!");
603603
}
604+
debug("Pull or clone git repository {} ...", gitRepoUrl);
604605
ProcessContext pc = newProcess().directory(target).executable("git");
605606
if (Files.isDirectory(target.resolve(".git"))) {
606607
ProcessResult result = pc.addArg("remote").run(true);

cli/src/main/java/com/devonfw/tools/ide/environment/AbstractEnvironmentVariables.java

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public abstract class AbstractEnvironmentVariables implements EnvironmentVariabl
2828
// Variable surrounded with "${" and "}" such as "${JAVA_HOME}" 1......2........
2929
private static final Pattern VARIABLE_SYNTAX = Pattern.compile("(\\$\\{([^}]+)})");
3030

31+
private static final String SELF_REFERENCING_NOT_FOUND = "";
32+
3133
private static final int MAX_RECURSION = 9;
3234

3335
private static final String VARIABLE_PREFIX = "${";
@@ -161,36 +163,88 @@ public EnvironmentVariables resolved() {
161163
@Override
162164
public String resolve(String string, Object src) {
163165

164-
return resolve(string, src, 0, src, string);
166+
return resolve(string, src, 0, src, string, this);
165167
}
166168

167-
private String resolve(String value, Object src, int recursion, Object rootSrc, String rootValue) {
169+
/**
170+
* This method is called recursively. This allows you to resolve variables that are defined by other variables.
171+
*
172+
* @param value the {@link String} that potentially contains variables in the syntax "${«variable«}". Those will be
173+
* resolved by this method and replaced with their {@link #get(String) value}.
174+
* @param src the source where the {@link String} to resolve originates from. Should have a reasonable
175+
* {@link Object#toString() string representation} that will be used in error or log messages if a variable
176+
* could not be resolved.
177+
* @param recursion the current recursion level. This is used to interrupt endless recursion.
178+
* @param rootSrc the root source where the {@link String} to resolve originates from.
179+
* @param rootValue the root value to resolve.
180+
* @param resolvedVars this is a reference to an object of {@link EnvironmentVariablesResolved} being the lowest level
181+
* in the {@link EnvironmentVariablesType hierarchy} of variables. In case of a self-referencing variable
182+
* {@code x} the resolving has to continue one level higher in the {@link EnvironmentVariablesType hierarchy}
183+
* to avoid endless recursion. The {@link EnvironmentVariablesResolved} is then used if another variable
184+
* {@code y} must be resolved, since resolving this variable has to again start at the lowest level. For
185+
* example: For levels {@code l1, l2} with {@code l1 < l2} and {@code x=${x} foo} and {@code y=bar} defined at
186+
* level {@code l1} and {@code x=test ${y}} defined at level {@code l2}, {@code x} is first resolved at level
187+
* {@code l1} and then up the {@link EnvironmentVariablesType hierarchy} at {@code l2} to avoid endless
188+
* recursion. However, {@code y} must be resolved starting from the lowest level in the
189+
* {@link EnvironmentVariablesType hierarchy} and therefore {@link EnvironmentVariablesResolved} is used.
190+
* @return the given {@link String} with the variables resolved.
191+
*/
192+
private String resolve(String value, Object src, int recursion, Object rootSrc, String rootValue,
193+
AbstractEnvironmentVariables resolvedVars) {
168194

169195
if (value == null) {
170196
return null;
171197
}
172198
if (recursion > MAX_RECURSION) {
173-
throw new IllegalStateException("Reached maximum recursion resolving " + value + " for root valiable " + rootSrc
199+
throw new IllegalStateException("Reached maximum recursion resolving " + value + " for root variable " + rootSrc
174200
+ " with value '" + rootValue + "'.");
175201
}
176202
recursion++;
203+
177204
Matcher matcher = VARIABLE_SYNTAX.matcher(value);
178205
if (!matcher.find()) {
179206
return value;
180207
}
181208
StringBuilder sb = new StringBuilder(value.length() + EXTRA_CAPACITY);
182209
do {
183210
String variableName = matcher.group(2);
184-
String variableValue = getValue(variableName);
211+
String variableValue = resolvedVars.getValue(variableName);
185212
if (variableValue == null) {
186213
this.context.warning("Undefined variable {} in '{}={}' for root '{}={}'", variableName, src, value, rootSrc,
187214
rootValue);
188-
} else {
189-
String replacement = resolve(variableValue, variableName, recursion, rootSrc, rootValue);
215+
continue;
216+
}
217+
EnvironmentVariables lowestFound = findVariable(variableName);
218+
boolean isNotSelfReferencing = lowestFound == null || !lowestFound.getFlat(variableName).equals(value);
219+
220+
if (isNotSelfReferencing) {
221+
// looking for "variableName" starting from resolved upwards the hierarchy
222+
String replacement = resolvedVars.resolve(variableValue, variableName, recursion, rootSrc, rootValue,
223+
resolvedVars);
224+
matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
225+
} else { // is self referencing
226+
// finding next occurrence of "variableName" up the hierarchy of EnvironmentVariablesType
227+
EnvironmentVariables next = lowestFound.getParent();
228+
while (next != null) {
229+
if (next.getFlat(variableName) != null) {
230+
break;
231+
}
232+
next = next.getParent();
233+
}
234+
if (next == null) {
235+
matcher.appendReplacement(sb, Matcher.quoteReplacement(SELF_REFERENCING_NOT_FOUND));
236+
continue;
237+
}
238+
// resolving a self referencing variable one level up the hierarchy of EnvironmentVariablesType, i.e. at "next",
239+
// to avoid endless recursion
240+
String replacement = ((AbstractEnvironmentVariables) next).resolve(next.getFlat(variableName), variableName,
241+
recursion, rootSrc, rootValue, resolvedVars);
190242
matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
243+
191244
}
192245
} while (matcher.find());
193246
matcher.appendTail(sb);
247+
194248
String resolved = sb.toString();
195249
return resolved;
196250
}

cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariables.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ default EnvironmentVariables findVariable(String name) {
186186
* @param source the source where the {@link String} to resolve originates from. Should have a reasonable
187187
* {@link Object#toString() string representation} that will be used in error or log messages if a variable
188188
* could not be resolved.
189-
* @return the the given {@link String} with the variables resolved.
189+
* @return the given {@link String} with the variables resolved.
190190
* @see com.devonfw.tools.ide.tool.ide.IdeToolCommandlet
191191
*/
192192
String resolve(String string, Object source);

cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariablesPropertiesFile.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,9 @@ public String set(String name, String value, boolean export) {
222222
String oldValue = this.variables.put(name, value);
223223
boolean flagChanged = export != this.exportedVariables.contains(name);
224224
if (Objects.equals(value, oldValue) && !flagChanged) {
225-
this.context.trace("Set valiable '{}={}' caused no change in {}", name, value, this.propertiesFilePath);
225+
this.context.trace("Set variable '{}={}' caused no change in {}", name, value, this.propertiesFilePath);
226226
} else {
227-
this.context.debug("Set valiable '{}={}' in {}", name, value, this.propertiesFilePath);
227+
this.context.debug("Set variable '{}={}' in {}", name, value, this.propertiesFilePath);
228228
this.modifiedVariables.add(name);
229229
if (export && (value != null)) {
230230
this.exportedVariables.add(name);

cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,28 @@ public interface FileAccess {
6060
void move(Path source, Path targetDir);
6161

6262
/**
63-
* @param source the source {@link Path} to link to.
63+
* Creates a symbolic link. If the given {@code targetLink} already exists and is a symbolic link or a Windows
64+
* junction, it will be replaced. In case of missing privileges, Windows Junctions may be used as fallback, which must
65+
* point to absolute paths. Therefore, the created link will be absolute instead of relative.
66+
*
67+
* @param source the source {@link Path} to link to, may be relative or absolute.
6468
* @param targetLink the {@link Path} where the symbolic link shall be created pointing to {@code source}.
69+
* @param relative - {@code true} if the symbolic link shall be relative, {@code false} if it shall be absolute.
6570
*/
66-
void symlink(Path source, Path targetLink);
71+
void symlink(Path source, Path targetLink, boolean relative);
72+
73+
/**
74+
* Creates a relative symbolic link. If the given {@code targetLink} already exists and is a symbolic link or a
75+
* Windows junction, it will be replaced. In case of missing privileges, Windows Junctions may be used as fallback,
76+
* which must point to absolute paths. Therefore, the created link will be absolute instead of relative.
77+
*
78+
* @param source the source {@link Path} to link to, may be relative or absolute.
79+
* @param targetLink the {@link Path} where the symbolic link shall be created pointing to {@code source}.
80+
*/
81+
default void symlink(Path source, Path targetLink) {
82+
83+
symlink(source, targetLink, true);
84+
}
6785

6886
/**
6987
* @param source the source {@link Path file or folder} to copy.
@@ -140,4 +158,13 @@ default void copy(Path source, Path target) {
140158
*/
141159
List<Path> getChildrenInDir(Path dir, Predicate<Path> filter);
142160

161+
/**
162+
* Finds the existing file with the specified name in the given list of directories.
163+
*
164+
* @param fileName The name of the file to find.
165+
* @param searchDirs The list of directories to search for the file.
166+
* @return The {@code Path} of the existing file, or {@code null} if the file is not found.
167+
*/
168+
Path findExistingFile(String fileName, List<Path> searchDirs);
169+
143170
}

0 commit comments

Comments
 (0)