Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ This file documents all notable changes to https://github.com/devonfw/IDEasy[IDE

Release with new features and bugfixes:

* https://github.com/devonfw/IDEasy/issues/1351[#1729]: Jline update fix. Installed version of Jline: 3.30.6 ProgressBar version changed to 0.10.2 Dependencies jline-terminal and jline-native were added explicitly.
* https://github.com/devonfw/IDEasy/issues/1351[#1729]: Update Jline to 3.30.6 and ProgressBar to 0.10.2
* https://github.com/devonfw/IDEasy/issues/1735[#1735]: Add repositories symlink feature for advanced AI usage
* https://github.com/devonfw/IDEasy/issues/1713[#1713]: Advanced logging and writing logfiles
* https://github.com/devonfw/IDEasy/issues/1731[#1731]: Fixed SonarQube installation path resolution

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ public abstract class AbstractIdeContext implements IdeContext, IdeLogArgFormatt

private Path workspacePath;

private Path workspacesBasePath;

private String workspaceName;

private Path cwd;
Expand Down Expand Up @@ -352,12 +354,14 @@ public void setCwd(Path userDir, String workspace, Path ideHome) {
this.workspaceName = workspace;
this.ideHome = ideHome;
if (ideHome == null) {
this.workspacesBasePath = null;
this.workspacePath = null;
this.confPath = null;
this.settingsPath = null;
this.pluginsPath = null;
} else {
this.workspacePath = this.ideHome.resolve(FOLDER_WORKSPACES).resolve(this.workspaceName);
this.workspacesBasePath = this.ideHome.resolve(FOLDER_WORKSPACES);
this.workspacePath = this.workspacesBasePath.resolve(this.workspaceName);
this.confPath = this.ideHome.resolve(FOLDER_CONF);
this.settingsPath = this.ideHome.resolve(FOLDER_SETTINGS);
this.settingsCommitIdPath = this.ideHome.resolve(IdeContext.SETTINGS_COMMIT_ID);
Expand Down Expand Up @@ -688,12 +692,27 @@ public String getWorkspaceName() {
return this.workspaceName;
}

@Override
public Path getWorkspacesBasePath() {

return this.workspacesBasePath;
}

@Override
public Path getWorkspacePath() {

return this.workspacePath;
}

@Override
public Path getWorkspacePath(String workspace) {

if (this.workspacesBasePath == null) {
throw new IllegalStateException("Failed to access workspace " + workspace + " without IDE_HOME in " + this.cwd);
}
return this.workspacesBasePath.resolve(workspace);
}

@Override
public Path getDownloadPath() {

Expand Down
12 changes: 12 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -552,12 +552,24 @@ default Path getSettingsTemplatePath() {
*/
Path getConfPath();

/**
* @return the {@link Path} to the workspaces base folder containing the individual {@link #getWorkspacePath(String) workspaces}.
* @see #getWorkspacePath(String)
*/
Path getWorkspacesBasePath();

/**
* @return the {@link Path} to the workspace.
* @see #getWorkspaceName()
*/
Path getWorkspacePath();

/**
* @param workspace the specific workspace name.
* @return the {@link Path} to the specified workspace.
*/
Path getWorkspacePath(String workspace);

/**
* @return the name of the workspace. Defaults to {@link #WORKSPACE_MAIN}.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
Expand Down Expand Up @@ -54,7 +57,10 @@ protected void doRun() {

if (repositoryFile != null) {
// Handle the case when a specific repository is provided
doImportRepository(repositoryFile, true);
RepositoryConfig config = prepareActiveRepository(repositoryFile, true);
if (config != null) {
importRepository(config);
}
} else {
// If no specific repository is provided, check for repositories folder
Path repositoriesPath = this.context.getRepositoriesPath();
Expand All @@ -65,85 +71,137 @@ protected void doRun() {
List<Path> propertiesFiles = this.context.getFileAccess()
.listChildren(repositoriesPath, path -> path.getFileName().toString().endsWith(".properties"));
boolean forceMode = this.context.isForceMode() || this.context.isForceRepositories();
Map<Path, RepositoryConfig> repositoryConfigMap = new HashMap<>(propertiesFiles.size());
for (Path propertiesFile : propertiesFiles) {
doImportRepository(propertiesFile, forceMode);
RepositoryConfig config = prepareActiveRepository(propertiesFile, forceMode);
if (config != null) {
repositoryConfigMap.put(propertiesFile, config);
}
}
for (Path propertiesFile : propertiesFiles) {
RepositoryConfig config = repositoryConfigMap.get(propertiesFile);
if (config != null) {
importRepository(config);
}
}
}
}

private void doImportRepository(Path repositoryFile, boolean forceMode) {
private RepositoryConfig prepareActiveRepository(Path repositoryFile, boolean forceMode) {

String repositoryFilename = repositoryFile.getFileName().toString();
final String repositoryId;
if (repositoryFilename.endsWith(IdeContext.EXT_PROPERTIES)) {
repositoryId = repositoryFilename.substring(0, repositoryFilename.length() - IdeContext.EXT_PROPERTIES.length());
} else {
repositoryId = repositoryFilename;
RepositoryConfig config = RepositoryConfig.loadProperties(repositoryFile, this.context);
if (config == null) {
return null;
}
this.context.newStep("Setup of repository " + repositoryId, repositoryFile).run(() -> {
doImportRepository(repositoryFile, forceMode, repositoryId);
});
}

private void doImportRepository(Path repositoryFile, boolean forceMode, String repositoryId) {
RepositoryConfig repositoryConfig = RepositoryConfig.loadProperties(repositoryFile, this.context);
if (!repositoryConfig.active()) {
if (!config.active()) {
if (forceMode) {
LOG.info("Setup of repository {} is forced, hence proceeding ...", repositoryId);
LOG.info("Setup of repository {} is forced, hence proceeding ...", config.id());
} else {
LOG.info("Skipping repository {} because it is not active - use --force to setup all repositories ...", repositoryId);
return;
LOG.info("Skipping repository {} because it is not active, use --force-repositories to setup all repositories ...", config.id());
return null;
}
}
GitUrl gitUrl = repositoryConfig.asGitUrl();
if (gitUrl == null) {
// error was already logged.
return;
// prepare workspace creation for correct resolution of *
List<String> workspaces = config.workspaces();
for (String workspace : workspaces) {
if (!RepositoryConfig.WORKSPACE_NAME_ALL.equals(workspace)) {
Path workspacePath = this.context.getWorkspacePath(workspace);
this.context.getFileAccess().mkdirs(workspacePath);
}
}
LOG.debug("Repository configuration: {}", repositoryConfig);
List<String> workspaces = repositoryConfig.workspaces();
String repositoryRelativePath = repositoryConfig.path();
return config;
}

private void importRepository(RepositoryConfig config) {

this.context.newStep("Setup of repository " + config.id()).run(() -> {
doImportRepository(config);
});
}

private void doImportRepository(RepositoryConfig config) {
LOG.debug("Repository configuration: {}", config);
String repositoryRelativePath = config.path();
if (repositoryRelativePath == null) {
repositoryRelativePath = repositoryId;
repositoryRelativePath = config.id();
}
Path ideStatusDir = this.context.getIdeHome().resolve(IdeContext.FOLDER_DOT_IDE);
FileAccess fileAccess = this.context.getFileAccess();
fileAccess.mkdirs(ideStatusDir);

List<String> workspaces = config.workspaces();
if ((workspaces.size() == 1) && (RepositoryConfig.WORKSPACE_NAME_ALL.equals(workspaces.getFirst()))) {
// if workspaces=* replace with all existing workspaces
workspaces = fileAccess.listChildren(this.context.getWorkspacesBasePath(), Files::isDirectory).stream().map(Path::getFileName).map(Path::toString)
.toList();
}
// if main is contained in workspaces, it should come first (to ensure physical cloning to main and linking to others)
if (!workspaces.getFirst().equals(IdeContext.WORKSPACE_MAIN) && workspaces.contains(IdeContext.WORKSPACE_MAIN)) {
workspaces = new ArrayList<>(workspaces); // mutable copy
workspaces.remove(IdeContext.WORKSPACE_MAIN);
workspaces.addFirst(IdeContext.WORKSPACE_MAIN);
}
Path firstRepository = null;
for (String workspaceName : workspaces) {
Path workspacePath = this.context.getIdeHome().resolve(IdeContext.FOLDER_WORKSPACES).resolve(workspaceName);
Path workspacePath = this.context.getWorkspacePath(workspaceName);
Path repositoryPath = workspacePath.resolve(repositoryRelativePath);
Path repositoryCreatedStatusFile = ideStatusDir.resolve("repository." + config.id() + "." + workspaceName);
boolean createRepository = true;
if (Files.isDirectory(repositoryPath.resolve(GitContext.GIT_FOLDER))) {
if (firstRepository == null) {
firstRepository = repositoryPath;
}
LOG.info("Repository {} already exists in workspace {} at {}", repositoryId, workspaceName, repositoryPath);
LOG.info("Repository {} already exists in workspace {} at {}", config.id(), workspaceName, repositoryPath);
if (!(this.context.isForceMode() || this.context.isForceRepositories())) {
LOG.info("Ignoring repository {} in workspace {} - use --force or --force-repositories to rerun setup.", repositoryId, workspaceName);
continue;
LOG.info("Ignoring repository {} in workspace {}, use --force-repositories to rerun setup.", config.id(), workspaceName);
createRepository = false;
}
}
Path repositoryCreatedStatusFile = ideStatusDir.resolve("repository." + repositoryId + "." + workspaceName);
if (Files.exists(repositoryCreatedStatusFile)) {
if (!(this.context.isForceMode() || this.context.isForceRepositories())) {
LOG.info("Ignoring repository {} in workspace {} because it was already setup before - use --force or --force-repositories for recreation.",
repositoryId, workspaceName);
continue;
LOG.info("Ignoring repository {} in workspace {} because it was already setup before, use --force-repositories for recreation.",
config.id(), workspaceName);
createRepository = false;
}
}
if (firstRepository == null) {
boolean success = cloneOrPullRepository(repositoryPath, gitUrl, repositoryCreatedStatusFile);
if (success) {
firstRepository = repositoryPath;
buildRepository(repositoryConfig, repositoryPath);
importRepository(repositoryConfig, repositoryPath, repositoryId);
if (createRepository) {
if (firstRepository == null) {
GitUrl gitUrl = config.asGitUrl();
boolean success = cloneOrPullRepository(repositoryPath, gitUrl, repositoryCreatedStatusFile);
if (success) {
firstRepository = repositoryPath;
buildRepository(config, repositoryPath);
importRepository(config, repositoryPath, config.id());
}
} else {
fileAccess.mkdirs(repositoryPath.getParent());
fileAccess.symlink(firstRepository, repositoryPath);
}
} else {
fileAccess.mkdirs(repositoryPath.getParent());
fileAccess.symlink(firstRepository, repositoryPath);
}
if (Files.exists(repositoryPath)) {
for (RepositoryLink link : config.links()) {
createRepositoryLink(link, repositoryPath, workspacePath);
}
}
}
}

private void createRepositoryLink(RepositoryLink link, Path repositoryPath, Path workspacePath) {
Path linkPath = workspacePath.resolve(link.link());
String target = link.target();
Path linkTargetPath;
if ((target != null) && !target.isBlank()) {
linkTargetPath = repositoryPath.resolve(target);
if (!Files.exists(linkTargetPath)) {
LOG.error("Skipping link from '{}' to '{}' because target does not exist: {}", link.link(), target, linkTargetPath);
return;
}
} else {
linkTargetPath = repositoryPath;
}
FileAccess fileAccess = this.context.getFileAccess();
fileAccess.mkdirs(linkPath.getParent());
fileAccess.symlink(linkTargetPath, linkPath);
}

private boolean cloneOrPullRepository(Path repositoryPath, GitUrl gitUrl, Path repositoryCreatedStatusFile) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
/**
* Represents the configuration of a repository to be used by the {@link RepositoryCommandlet}.
*
* @param id Identifier derived from the filename without extension.
* @param path Path into which the project is cloned. This path is relative to the workspace.
* @param workingSets The working sets associated with the repository.
* @param workingSets The working sets associated with the repository (for Eclipse import).
* @param workspaces Workspaces to use for checkout and import. Supports comma-separated values. Default is main.
* @param gitUrl Git URL to use for cloning the project.
* @param gitBranch Git branch to checkout. Git default branch is default.
Expand All @@ -22,6 +23,7 @@
* @param active {@code true} to setup the repository during setup, {@code false} to skip.
*/
public record RepositoryConfig(
String id,
String path,
String workingSets,
List<String> workspaces,
Expand All @@ -30,37 +32,11 @@ public record RepositoryConfig(
String buildPath,
String buildCmd,
Set<String> imports,
List<RepositoryLink> links,
boolean active) {

/** {@link RepositoryProperties#getProperty(String) Property name} for {@link #path()}. */
public static final String PROPERTY_PATH = "path";

/** {@link RepositoryProperties#getProperty(String) Property name} for {@link #workingSets()}. */
public static final String PROPERTY_WORKING_SETS = "workingsets";

/** {@link RepositoryProperties#getProperty(String) Property name} for {@link #workspaces()}. */
public static final String PROPERTY_WORKSPACES = "workspaces";

/** {@link RepositoryProperties#getProperty(String) Property name} for {@link #gitUrl()}. */
public static final String PROPERTY_GIT_URL = "git_url";

/** {@link RepositoryProperties#getProperty(String) Property name} for {@link #buildPath()}. */
public static final String PROPERTY_BUILD_PATH = "build_path";

/** {@link RepositoryProperties#getProperty(String) Property name} for {@link #buildCmd()}. */
public static final String PROPERTY_BUILD_CMD = "build_cmd";

/** {@link RepositoryProperties#getProperty(String) Property name} for {@link #active()}. */
public static final String PROPERTY_ACTIVE = "active";

/** {@link RepositoryProperties#getProperty(String) Property name} for {@link #gitBranch()}. */
public static final String PROPERTY_GIT_BRANCH = "git_branch";

/** {@link RepositoryProperties#getProperty(String) Property name} for {@link #imports()}. */
public static final String PROPERTY_IMPORT = "import";

/** Legacy {@link RepositoryProperties#getProperty(String) property name} for {@link #imports()}. */
public static final String PROPERTY_ECLIPSE = "eclipse";
/** Wildcard to match all workspaces. */
public static final String WORKSPACE_NAME_ALL = "*";

public RepositoryConfig {
if (workspaces == null || workspaces.isEmpty()) {
Expand All @@ -87,21 +63,19 @@ public GitUrl asGitUrl() {
public static RepositoryConfig loadProperties(Path filePath, IdeContext context) {

RepositoryProperties properties = new RepositoryProperties(filePath, context);

Set<String> importsSet = properties.getImports();
List<String> workspaces = properties.getWorkspaces();

return new RepositoryConfig(properties.getProperty(PROPERTY_PATH), properties.getProperty(PROPERTY_WORKING_SETS),
workspaces, properties.getProperty(PROPERTY_GIT_URL, true), properties.getProperty(PROPERTY_GIT_BRANCH),
properties.getProperty(PROPERTY_BUILD_PATH), properties.getProperty(PROPERTY_BUILD_CMD), importsSet,
parseBoolean(properties.getProperty(PROPERTY_ACTIVE)));
}

private static boolean parseBoolean(String value) {

if (value == null) {
return true;
String filename = filePath.getFileName().toString();
final String id;
if (filename.endsWith(IdeContext.EXT_PROPERTIES)) {
id = filename.substring(0, filename.length() - IdeContext.EXT_PROPERTIES.length());
} else {
id = filename;
}
RepositoryConfig config = new RepositoryConfig(id, properties.getPath(), properties.getWorkingSets(), properties.getWorkspaces(), properties.getGitUrl(),
properties.getGitBranch(), properties.getBuildPath(), properties.getBuildCmd(), properties.getImports(), properties.getLinks(), properties.isActive());
if (properties.isInvalid()) {
return null;
}
return "true".equals(value.trim());
return config;
}

}
Loading
Loading