diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java index df95c0eab..17affca08 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/CommandletManagerImpl.java @@ -67,6 +67,7 @@ public CommandletManagerImpl(IdeContext context) { add(new EditionSetCommandlet(context)); add(new EditionListCommandlet(context)); add(new VersionCommandlet(context)); + add(new RepositoryCommandlet(context)); add(new Gh(context)); add(new Helm(context)); add(new Java(context)); diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java new file mode 100644 index 000000000..f216099a4 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -0,0 +1,126 @@ +package com.devonfw.tools.ide.commandlet; + +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.property.PathProperty; +import com.devonfw.tools.ide.property.RepositoryProperty; +import com.devonfw.tools.ide.tool.ToolCommandlet; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import static com.devonfw.tools.ide.commandlet.RepositoryConfig.loadProperties; + +/** + * {@link Commandlet} to setup one or multiple GIT repositories for development. + */ +public class RepositoryCommandlet extends Commandlet { + + /** the repository to setup. */ + public final RepositoryProperty repository; + + /** + * The constructor. + * + * @param context the {@link IdeContext}. + */ + public RepositoryCommandlet(IdeContext context) { + + super(context); + addKeyword(getName()); + addKeyword("setup"); + this.repository = add(new RepositoryProperty("", false, "repository")); + } + + @Override + public String getName() { + + return "repository"; + } + + @Override + public void run() { + + Path repositoryFile = repository.getValue(); + + if (repositoryFile != null) { + // Handle the case when a specific repository is provided + doImportRepository(repositoryFile, true); + } else { + // If no specific repository is provided, check for repositories folder + Path repositoriesPath = this.context.getSettingsPath().resolve(IdeContext.FOLDER_REPOSITORIES); + Path legacyRepositoriesPath = this.context.getSettingsPath().resolve(IdeContext.FOLDER_LEGACY_REPOSITORIES); + Path repositories; + if (Files.exists(repositoriesPath)) { + repositories = repositoriesPath; + } else if (Files.exists(legacyRepositoriesPath)) { + repositories = legacyRepositoriesPath; + } else { + this.context.warning("Cannot find repositories folder nor projects folder."); + return; + } + + List propertiesFiles = this.context.getFileAccess().listChildren(repositories, + path -> path.getFileName().toString().endsWith(".properties")); + + boolean forceMode = this.context.isForceMode(); + for (Path propertiesFile : propertiesFiles) { + doImportRepository(propertiesFile, forceMode); + } + } + } + + private void doImportRepository(Path repositoryFile, boolean forceMode) { + + this.context.info("Importing repository from {} ...", repositoryFile.getFileName().toString()); + RepositoryConfig repositoryConfig = loadProperties(repositoryFile); + + if (!repositoryConfig.active()) { + this.context.info("Repository is not active by default."); + if (forceMode) { + this.context.info("Repository setup is forced, hence proceeding ..."); + } else { + this.context.info("Skipping repository - use force (-f) to setup all repositories ..."); + return; + } + } + + String repository = repositoryConfig.path(); + String gitUrl = repositoryConfig.gitUrl(); + if (repository == null || "".equals(repository) || gitUrl == null || "".equals(gitUrl)) { + this.context.warning("Invalid repository configuration {} - both 'path' and 'git-url' have to be defined." + , repositoryFile.getFileName().toString()); + return; + } + + this.context.debug(repositoryConfig.toString()); + + String workspace = repositoryConfig.workspace() != null ? repositoryConfig.workspace() : "main"; + Path workspacePath = this.context.getIdeHome().resolve("workspaces").resolve(workspace); + this.context.getFileAccess().mkdirs(workspacePath); + + Path repositoryPath = workspacePath.resolve(repository); + this.context.getGitContext().pullOrClone(gitUrl, repositoryConfig.gitBranch(), repositoryPath); + + String buildCmd = repositoryConfig.buildCmd(); + this.context.debug("Building repository with ide command: {}", buildCmd); + if (buildCmd != null && !buildCmd.isEmpty()) { + String[] command = buildCmd.split("\\s+"); + ToolCommandlet commandlet = this.context.getCommandletManager().getToolCommandlet(command[0]); + List args = new ArrayList<>(command.length - 1); + for (int i = 1; i < command.length; i++) { + args.add(command[i]); + } + commandlet.arguments.setValue(args); + commandlet.run(); + } else { + this.context.info("Build command not set. Skipping build for repository."); + } + + } +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryConfig.java b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryConfig.java new file mode 100644 index 000000000..89dea973e --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryConfig.java @@ -0,0 +1,65 @@ +package com.devonfw.tools.ide.commandlet; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Properties; +import java.util.Set; + +/** + * Represents the configuration of a repository to be used by the {@link RepositoryCommandlet}. + * + * @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 workspace Workspace to use for checkout and import. Default is main. + * @param gitUrl Git URL to use for cloning the project. + * @param gitBranch Git branch to checkout. Git default branch is default. + * @param buildPath The build path for the repository. + * @param buildCmd The command to invoke to build the repository after clone or pull. If omitted no build is triggered. + * @param imports list of IDEs where the repository will be imported to. + * @param active {@code true} to setup the repository during setup, {@code false} to skip. + */ +public record RepositoryConfig( + String path, + String workingSets, + String workspace, + String gitUrl, + String gitBranch, + String buildPath, + String buildCmd, + Set imports, + boolean active) { + public static RepositoryConfig loadProperties(Path filePath) { + + Properties properties = new Properties(); + try (InputStream input = new FileInputStream(filePath.toString())) { + properties.load(input); + } catch (IOException e) { + throw new IllegalStateException("Failed to read file: " + filePath, e); + } + + Set importsSet = getImports(properties); + + return new RepositoryConfig(properties.getProperty("path"), properties.getProperty("workingsets"), + properties.getProperty("workspace"), properties.getProperty("git_url"), properties.getProperty("git_branch"), + properties.getProperty(("build_path")), properties.getProperty("build_cmd"), importsSet, + Boolean.parseBoolean(properties.getProperty("active").trim())); + } + + private static Set getImports(Properties properties) { + + String importProperty = properties.getProperty("import"); + if (importProperty != null && !importProperty.isEmpty()) { + return Set.of(importProperty.split("\\s*,\\s*")); + } + + String legacyImportProperty = properties.getProperty("eclipse"); + if ("import".equals(legacyImportProperty)) { + return Set.of("eclipse"); + } else { + return Collections.emptySet(); + } + } +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java index a1c8466d3..30c995813 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java @@ -497,7 +497,7 @@ public UrlMetadata getUrls() { if (this.urlMetadata == null) { if (!isTest()) { - this.getGitContext().pullOrFetchAndResetIfNeeded(IDE_URLS_GIT, this.urlsPath, "origin", "master"); + this.getGitContext().pullOrFetchAndResetIfNeeded(IDE_URLS_GIT, "master", this.urlsPath, "origin"); } this.urlMetadata = new UrlMetadata(this); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/GitContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/GitContext.java index acb7e2b6d..fa3a21bff 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/GitContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/GitContext.java @@ -13,45 +13,53 @@ public interface GitContext extends IdeLogger { * Checks if the Git repository in the specified target folder needs an update by inspecting the modification time of * a magic file. * - * @param repoUrl the git remote URL to clone from. May be suffixed with a hash-sign ('#') followed by the branch name - * to check-out. + * @param repoUrl the git remote URL to clone from. * @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled. - * It is not the parent directory where git will by default create a sub-folder by default on clone but the * - * final folder that will contain the ".git" subfolder. + * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final + * folder that will contain the ".git" subfolder. */ - void pullOrCloneIfNeeded(String repoUrl, Path targetRepository); + void pullOrCloneIfNeeded(String repoUrl, String branch, Path targetRepository); /** * Attempts a git pull and reset if required. * - * @param repoUrl the git remote URL to clone from. May be suffixed with a hash-sign ('#') followed by the branch name - * to check-out. + * @param repoUrl the git remote URL to clone from. + * @param branch the branch name e.g. master. * @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled. - * It is not the parent directory where git will by default create a sub-folder by default on clone but the * - * final folder that will contain the ".git" subfolder. + * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final + * folder that will contain the ".git" subfolder. * @param remoteName the remote name e.g. origin. - * @param branchName the branch name e.g. master. */ - void pullOrFetchAndResetIfNeeded(String repoUrl, Path targetRepository, String remoteName, String branchName); + void pullOrFetchAndResetIfNeeded(String repoUrl, String branch, Path targetRepository, String remoteName); /** * Runs a git pull or a git clone. * - * @param gitRepoUrl the git remote URL to clone from. May be suffixed with a hash-sign ('#') followed by the branch - * name to check-out. + * @param gitRepoUrl the git remote URL to clone from. * @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled. - * It is not the parent directory where git will by default create a sub-folder by default on clone but the * - * final folder that will contain the ".git" subfolder. + * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final + * folder that will contain the ".git" subfolder. */ void pullOrClone(String gitRepoUrl, Path targetRepository); + /** + * Runs a git pull or a git clone. + * + * @param gitRepoUrl the git remote URL to clone from. + * @param branch the branch name e.g. master. + * @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled. + * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final + * folder that will contain the ".git" subfolder. + */ + void pullOrClone(String gitRepoUrl, String branch, Path targetRepository); + /** * Runs a git clone. Throws a CliException if in offline mode. * * @param gitRepoUrl the {@link GitUrl} to use for the repository URL. * @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled. - * It is not the parent directory where git will by default create a sub-folder by default on clone but the * - * final folder that will contain the ".git" subfolder. + * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final + * folder that will contain the ".git" subfolder. */ void clone(GitUrl gitRepoUrl, Path targetRepository); @@ -59,8 +67,8 @@ public interface GitContext extends IdeLogger { * Runs a git pull. * * @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled. - * It is not the parent directory where git will by default create a sub-folder by default on clone but the * - * final folder that will contain the ".git" subfolder. + * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final + * folder that will contain the ".git" subfolder. */ void pull(Path targetRepository); @@ -68,8 +76,8 @@ public interface GitContext extends IdeLogger { * Runs a git reset if files were modified. * * @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled. - * It is not the parent directory where git will by default create a sub-folder by default on clone but the * - * final folder that will contain the ".git" subfolder. + * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final + * folder that will contain the ".git" subfolder. * @param remoteName the remote server name. * @param branchName the name of the branch. */ @@ -79,8 +87,8 @@ public interface GitContext extends IdeLogger { * Runs a git cleanup if untracked files were found. * * @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled. - * It is not the parent directory where git will by default create a sub-folder by default on clone but the * - * final folder that will contain the ".git" subfolder. + * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final + * folder that will contain the ".git" subfolder. */ void cleanup(Path targetRepository); diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/GitContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/context/GitContextImpl.java index c21898906..1203816dc 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/GitContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/GitContextImpl.java @@ -24,7 +24,9 @@ * Implements the {@link GitContext}. */ public class GitContextImpl implements GitContext { - private static final Duration GIT_PULL_CACHE_DELAY_MILLIS = Duration.ofMillis(30 * 60 * 1000);; + private static final Duration GIT_PULL_CACHE_DELAY_MILLIS = Duration.ofMillis(30 * 60 * 1000); + + ; private final IdeContext context; @@ -40,7 +42,7 @@ public GitContextImpl(IdeContext context) { } @Override - public void pullOrCloneIfNeeded(String repoUrl, Path targetRepository) { + public void pullOrCloneIfNeeded(String repoUrl, String branch, Path targetRepository) { Path gitDirectory = targetRepository.resolve(".git"); @@ -67,13 +69,13 @@ public void pullOrCloneIfNeeded(String repoUrl, Path targetRepository) { } } else { // If the .git directory does not exist, perform git clone - pullOrClone(repoUrl, targetRepository); + pullOrClone(repoUrl, branch, targetRepository); } } - public void pullOrFetchAndResetIfNeeded(String repoUrl, Path targetRepository, String remoteName, String branchName) { + public void pullOrFetchAndResetIfNeeded(String repoUrl, String branch, Path targetRepository, String remoteName) { - pullOrCloneIfNeeded(repoUrl, targetRepository); + pullOrCloneIfNeeded(repoUrl, branch, targetRepository); if (remoteName.isEmpty()) { reset(targetRepository, "origin", "master"); @@ -87,6 +89,12 @@ public void pullOrFetchAndResetIfNeeded(String repoUrl, Path targetRepository, S @Override public void pullOrClone(String gitRepoUrl, Path targetRepository) { + pullOrClone(gitRepoUrl, null, targetRepository); + } + + @Override + public void pullOrClone(String gitRepoUrl, String branch, Path targetRepository) { + Objects.requireNonNull(targetRepository); Objects.requireNonNull(gitRepoUrl); @@ -112,17 +120,7 @@ public void pullOrClone(String gitRepoUrl, Path targetRepository) { } } } else { - String branch = ""; - int hashIndex = gitRepoUrl.indexOf("#"); - if (hashIndex != -1) { - branch = gitRepoUrl.substring(hashIndex + 1); - gitRepoUrl = gitRepoUrl.substring(0, hashIndex); - } clone(new GitUrl(gitRepoUrl, branch), targetRepository); - if (!branch.isEmpty()) { - this.processContext.addArgs("checkout", branch); - this.processContext.run(); - } } } @@ -130,8 +128,8 @@ public void pullOrClone(String gitRepoUrl, Path targetRepository) { * Handles errors which occurred during git pull. * * @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled. - * It is not the parent directory where git will by default create a sub-folder by default on clone but the * - * final folder that will contain the ".git" subfolder. + * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final + * folder that will contain the ".git" subfolder. * @param result the {@link ProcessResult} to evaluate. */ private void handleErrors(Path targetRepository, ProcessResult result) { @@ -144,8 +142,8 @@ private void handleErrors(Path targetRepository, ProcessResult result) { } else { this.context.error(message); if (this.context.isOnline()) { - this.context - .error("See above error for details. If you have local changes, please stash or revert and retry."); + this.context.error( + "See above error for details. If you have local changes, please stash or revert and retry."); } else { this.context.error( "It seems you are offline - please ensure Internet connectivity and retry or activate offline mode (-o or --offline)."); @@ -159,8 +157,8 @@ private void handleErrors(Path targetRepository, ProcessResult result) { * Lazily initializes the {@link ProcessContext}. * * @param targetRepository the {@link Path} to the target folder where the git repository should be cloned or pulled. - * It is not the parent directory where git will by default create a sub-folder by default on clone but the * - * final folder that will contain the ".git" subfolder. + * It is not the parent directory where git will by default create a sub-folder by default on clone but the * final + * folder that will contain the ".git" subfolder. */ private void initializeProcessContext(Path targetRepository) { @@ -183,11 +181,19 @@ public void clone(GitUrl gitRepoUrl, Path targetRepository) { if (this.context.isQuietMode()) { this.processContext.addArg("-q"); } - this.processContext.addArgs("--recursive", parsedUrl, "--config", "core.autocrlf=false", "."); + this.processContext.addArgs("--recursive", gitRepoUrl.url(), "--config", "core.autocrlf=false", "."); result = this.processContext.run(ProcessMode.DEFAULT_CAPTURE); if (!result.isSuccessful()) { this.context.warning("Git failed to clone {} into {}.", parsedUrl, targetRepository); } + String branch = gitRepoUrl.branch(); + if (branch != null) { + this.processContext.addArgs("checkout", branch); + result = this.processContext.run(ProcessMode.DEFAULT_CAPTURE); + if (!result.isSuccessful()) { + this.context.warning("Git failed to checkout to branch {}", branch); + } + } } else { throw new CliException("Could not clone " + parsedUrl + " to " + targetRepository + " because you are offline."); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/GitUrl.java b/cli/src/main/java/com/devonfw/tools/ide/context/GitUrl.java index daee4352f..aa678cf2f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/GitUrl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/GitUrl.java @@ -19,7 +19,7 @@ public record GitUrl(String url, String branch) { public URL parseUrl() { String parsedUrl = url; - if (!branch.isEmpty()) { + if (branch != null && !branch.isEmpty()) { parsedUrl += "#" + branch; } URL validUrl; diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java index a633a67b0..4090da8bb 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java +++ b/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java @@ -58,6 +58,12 @@ public interface IdeContext extends IdeLogger { /** The name of the bin folder where executable files are found by default. */ String FOLDER_BIN = "bin"; + /** The name of the repositories folder where properties files are stores for each repository */ + String FOLDER_REPOSITORIES = "repositories"; + + /** The name of the repositories folder where properties files are stores for each repository */ + String FOLDER_LEGACY_REPOSITORIES = "projects"; + /** The name of the Contents folder inside a MacOS app. */ String FOLDER_CONTENTS = "Contents"; diff --git a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java index 4501c938f..662e302e1 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java +++ b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java @@ -223,4 +223,13 @@ default void extract(Path archiveFile, Path targetDir, Consumer postExtrac */ List listChildren(Path dir, Predicate filter); + /** + * Finds the existing file with the specified name in the given list of directories. + * + * @param fileName The name of the file to find. + * @param searchDirs The list of directories to search for the file. + * @return The {@code Path} of the existing file, or {@code null} if the file is not found. + */ + Path findExistingFile(String fileName, List searchDirs); + } diff --git a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java index 9517ceaa6..c6d0210b5 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java @@ -786,4 +786,20 @@ public List listChildren(Path dir, Predicate filter) { } return children; } + + @Override + public Path findExistingFile(String fileName, List searchDirs) { + + for (Path dir : searchDirs) { + Path filePath = dir.resolve(fileName); + try { + if (Files.exists(filePath)) { + return filePath; + } + } catch (Exception e) { + throw new IllegalStateException("Unexpected error while checking existence of file "+filePath+" .", e); + } + } + return null; + } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java b/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java new file mode 100644 index 000000000..8059c201e --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java @@ -0,0 +1,60 @@ +package com.devonfw.tools.ide.property; + +import com.devonfw.tools.ide.context.IdeContext; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.function.Consumer; + +public class RepositoryProperty extends FileProperty { + + /** + * The constructor. + * + * @param name the {@link #getName() property name}. + * @param required the {@link #isRequired() required flag}. + * @param alias the {@link #getAlias() property alias}. + */ + public RepositoryProperty(String name, boolean required, String alias) { + + super(name, required, alias, true); + } + + /** + * The constructor. + * + * @param name the {@link #getName() property name}. + * @param required the {@link #isRequired() required flag}. + * @param alias the {@link #getAlias() property alias}. + * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}. + */ + public RepositoryProperty(String name, boolean required, String alias, Consumer validator) { + + super(name, required, alias, true, validator); + } + + public Path parse(String valueAsString, IdeContext context) { + + if (valueAsString == null) { + return null; + } + + Path repositoryFile = Path.of(valueAsString); + if (!Files.exists(repositoryFile)) { + Path repositoriesPath = context.getSettingsPath().resolve(IdeContext.FOLDER_REPOSITORIES); + Path legacyRepositoriesPath = context.getSettingsPath().resolve(IdeContext.FOLDER_LEGACY_REPOSITORIES); + String propertiesFileName = valueAsString; + if (!valueAsString.endsWith(".properties")) { + propertiesFileName += ".properties"; + } + repositoryFile = context.getFileAccess().findExistingFile(propertiesFileName, + Arrays.asList(repositoriesPath, legacyRepositoriesPath)); + } + if (repositoryFile == null) { + throw new IllegalStateException("Could not find properties file: " + valueAsString); + } + return repositoryFile; + } + +} diff --git a/cli/src/main/resources/nls/Ide.properties b/cli/src/main/resources/nls/Ide.properties index 297ae29de..3c16730e2 100644 --- a/cli/src/main/resources/nls/Ide.properties +++ b/cli/src/main/resources/nls/Ide.properties @@ -11,6 +11,7 @@ cmd-env=Print the environment variables to set and export. cmd-get-edition=Get the edition of the selected tool. cmd-get-version=Get the version of the selected tool. cmd-gh=Tool commandlet for Github CLI. +cmd-repository=setup the pre-configured git repository. cmd-gradle=Tool commandlet for Gradle (Build-Tool) cmd-helm=Tool commandlet for Helm (Kubernetes Package Manager) cmd-help=Prints this help. @@ -39,6 +40,7 @@ val-sonar-command=START|STOP|ANALYZE val-tool=The tool commandlet to select. val-version=The tool version val-set-version-version=The tool version to set. +val-repository-repository=The name of the properties file of the pre-configured git repository to setup, omit to setup all active repositories. version-banner=Current version of IDE is {} opt--batch=enable batch mode (non-interactive) opt--debug=enable debug logging diff --git a/cli/src/main/resources/nls/Ide_de.properties b/cli/src/main/resources/nls/Ide_de.properties index d7bb5ebb5..243d00ee1 100644 --- a/cli/src/main/resources/nls/Ide_de.properties +++ b/cli/src/main/resources/nls/Ide_de.properties @@ -10,6 +10,7 @@ cmd-env=Gibt die zu setztenden und exportierenden Umgebungsvariablen aus. cmd-get-edition=Zeigt die Edition des selektierten Werkzeugs an. cmd-get-version=Zeigt die Version des selektierten Werkzeugs an. cmd-gh=Werkzeug Kommando für die Github Kommandoschnittstelle. +cmd-repository=Richtet das vorkonfigurierte Git Repository ein. cmd-helm=Werkzeug Kommando für Helm (Kubernetes Package Manager) cmd-help=Zeigt diese Hilfe an. cmd-install=Installiert das selektierte Werkzeug. @@ -35,6 +36,7 @@ val-edition=Die Werkzeug Edition. val-tool=Das zu selektierende Werkzeug Kommando. val-version=Die Werkzeug Version. val-set-version-version=Die zu setztende Werkzeug Version. +val-repository-repository=Der Name der Properties-Datei des vorkonfigurierten Git Repositories zum Einrichten. Falls nicht angegeben, werden alle aktiven Projekte eingerichtet. version-banner=Die aktuelle Version der IDE ist {} opt--batch=Aktiviert den Batch-Modus (nicht-interaktive Stapelverarbeitung) opt--debug=Aktiviert Debug-Ausgaben (Fehleranalyse) diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/RepositoryCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/RepositoryCommandletTest.java new file mode 100644 index 000000000..65a67649b --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/RepositoryCommandletTest.java @@ -0,0 +1,115 @@ +package com.devonfw.tools.ide.commandlet; + +import com.devonfw.tools.ide.context.AbstractIdeContextTest; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.context.IdeTestContext; +import com.devonfw.tools.ide.log.IdeLogLevel; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Properties; + +public class RepositoryCommandletTest extends AbstractIdeContextTest { + + IdeTestContext context = newContext(PROJECT_BASIC); + + private static final String PROPERTIES_FILE = "test.properties"; + + /** + * Properties object used to write key-value pairs to the properties file "test.properties" + */ + Properties properties = new Properties(); + + @Test + public void testRunWithSpecificRepository() { + + // arrange + RepositoryCommandlet rc = context.getCommandletManager().getCommandlet(RepositoryCommandlet.class); + createPropertiesFile(); + rc.repository.setValueAsString(PROPERTIES_FILE, context); + // act + rc.run(); + // assert + assertLogMessage(context, IdeLogLevel.INFO, "Importing repository from " + PROPERTIES_FILE + " ..."); + } + + @Test + public void testRunWithNoSpecificRepositoryAndInactive() { + + // arrange + RepositoryCommandlet rc = context.getCommandletManager().getCommandlet(RepositoryCommandlet.class); + createPropertiesFile(); + // act + rc.run(); + // assert + assertLogMessage(context, IdeLogLevel.INFO, "Importing repository from " + PROPERTIES_FILE + " ..."); + assertLogMessage(context, IdeLogLevel.INFO, "Skipping repository - use force (-f) to setup all repositories ..."); + } + + @Test + public void testRunInvalidConfiguration() { + + // arrange + RepositoryCommandlet rc = context.getCommandletManager().getCommandlet(RepositoryCommandlet.class); + createPropertiesFile(); + properties.setProperty("path", ""); + properties.setProperty("git_url", ""); + saveProperties(properties); + rc.repository.setValueAsString(PROPERTIES_FILE, context); + // act + rc.run(); + // assert + assertLogMessage(context, IdeLogLevel.WARNING, + "Invalid repository configuration " + PROPERTIES_FILE + " - both 'path' and 'git-url' have to be defined."); + } + + @Test + public void testRunNoRepositoriesOrProjectsFolderFound() { + + // arrange + RepositoryCommandlet rc = context.getCommandletManager().getCommandlet(RepositoryCommandlet.class); + Path repositoriesPath = this.context.getSettingsPath().resolve(IdeContext.FOLDER_REPOSITORIES); + this.context.getFileAccess().delete(repositoriesPath); + // act + rc.run(); + // assert + assertLogMessage(context, IdeLogLevel.WARNING, "Cannot find repositories folder nor projects folder."); + } + + private void createPropertiesFile() { + + try { + properties.setProperty("path", "test"); + properties.setProperty("workingsets", "test"); + properties.setProperty("workspace", "test"); + properties.setProperty("git_url", "test"); + properties.setProperty("git_branch", "test"); + properties.setProperty("build_path", "test"); + properties.setProperty("build_cmd", ""); + properties.setProperty("active", "false"); + + Path propertiesPath = this.context.getSettingsPath().resolve(IdeContext.FOLDER_REPOSITORIES) + .resolve(PROPERTIES_FILE); + this.context.getFileAccess().mkdirs(propertiesPath.getParent()); + Files.createFile(propertiesPath); + saveProperties(properties); + } catch (IOException e) { + throw new IllegalStateException("Failed to create properties file during tests.", e); + } + + } + + private void saveProperties(Properties properties) { + + Path propertiesPath = this.context.getSettingsPath().resolve(IdeContext.FOLDER_REPOSITORIES) + .resolve(PROPERTIES_FILE); + try (var output = Files.newOutputStream(propertiesPath)) { + properties.store(output, null); + } catch (IOException e) { + throw new IllegalStateException("Failed to save properties file during tests.", e); + } + } + +} \ No newline at end of file diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/GitContextMock.java b/cli/src/test/java/com/devonfw/tools/ide/context/GitContextMock.java index f90fc6f49..7cb12d19d 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/context/GitContextMock.java +++ b/cli/src/test/java/com/devonfw/tools/ide/context/GitContextMock.java @@ -7,12 +7,12 @@ public class GitContextMock implements GitContext { @Override - public void pullOrCloneIfNeeded(String repoUrl, Path targetRepository) { + public void pullOrCloneIfNeeded(String repoUrl, String branch, Path targetRepository) { } @Override - public void pullOrFetchAndResetIfNeeded(String repoUrl, Path targetRepository, String remoteName, String branchName) { + public void pullOrFetchAndResetIfNeeded(String repoUrl, String branch, Path targetRepository, String remoteName) { } @@ -21,6 +21,11 @@ public void pullOrClone(String gitRepoUrl, Path targetRepository) { } + @Override + public void pullOrClone(String gitRepoUrl, String branch, Path targetRepository) { + + } + @Override public void clone(GitUrl gitRepoUrl, Path targetRepository) { diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/GitContextTest.java b/cli/src/test/java/com/devonfw/tools/ide/context/GitContextTest.java index 4d773fd3d..527466dbc 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/context/GitContextTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/context/GitContextTest.java @@ -33,7 +33,7 @@ public void testRunGitCloneInOfflineModeThrowsException(@TempDir Path tempDir) { GitContext gitContext = new GitContextImpl(context); // act CliException e1 = assertThrows(CliException.class, () -> { - gitContext.pullOrClone(gitRepoUrl, tempDir); + gitContext.pullOrClone(gitRepoUrl, "", tempDir); }); // assert assertThat(e1).hasMessageContaining(gitRepoUrl).hasMessageContaining(tempDir.toString()) @@ -115,7 +115,7 @@ public void testRunGitPullWithForceStartsReset(@TempDir Path tempDir) { IdeContext context = newGitContext(tempDir, errors, outs, 0, true); GitContext gitContext = new GitContextImpl(context); // act - gitContext.pullOrFetchAndResetIfNeeded(gitRepoUrl, tempDir, "origin", "master"); + gitContext.pullOrFetchAndResetIfNeeded(gitRepoUrl, "master", tempDir, "origin"); // assert assertThat(modifiedFile).hasContent("original"); } @@ -143,7 +143,7 @@ public void testRunGitPullWithForceStartsCleanup(@TempDir Path tempDir) { throw new RuntimeException(e); } // act - gitContext.pullOrFetchAndResetIfNeeded(gitRepoUrl, tempDir, "origin", "master"); + gitContext.pullOrFetchAndResetIfNeeded(gitRepoUrl, "master", tempDir, "origin"); // assert assertThat(tempDir.resolve("new-folder")).doesNotExist(); }