From e9d574f613bfc16e245e8654936b3f8804a8e094 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 8 Nov 2023 15:11:07 +0100 Subject: [PATCH 01/58] added class RepositoryCommandlet and its help function --- .../ide/commandlet/CommandletManagerImpl.java | 1 + .../ide/commandlet/RepositoryCommandlet.java | 38 +++++++++++++++++++ cli/src/main/resources/nls/Ide.properties | 2 + 3 files changed, 41 insertions(+) create mode 100644 cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java 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 089f909ba..80c42e49e 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 @@ -58,6 +58,7 @@ private CommandletManagerImpl(IdeContext context) { add(new VersionSetCommandlet(context)); add(new VersionGetCommandlet(context)); add(new VersionListCommandlet(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..dbc03edcf --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -0,0 +1,38 @@ +package com.devonfw.tools.ide.commandlet; + +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.property.StringProperty; + +public class RepositoryCommandlet extends Commandlet { + + + private StringProperty setup; + + private StringProperty project; + + /** + * The constructor. + * + * @param context the {@link IdeContext}. + */ + public RepositoryCommandlet(IdeContext context) { + + super(context); + addKeyword(getName()); + this.setup = add(new StringProperty("setup", true, "setup")); + this.project = add(new StringProperty("", false, "project")); + + + } + + @Override + public String getName() { + + return "repository"; + } + + @Override + public void run() { + + } +} diff --git a/cli/src/main/resources/nls/Ide.properties b/cli/src/main/resources/nls/Ide.properties index 7f1344f9d..3892df2af 100644 --- a/cli/src/main/resources/nls/Ide.properties +++ b/cli/src/main/resources/nls/Ide.properties @@ -5,6 +5,7 @@ options=Options: cmd-az=Tool commandlet for Azure CLI. cmd-env=Print the environment variables to set and export. cmd-get-version=Get the version of the selected tool. +cmd-repository=setup the pre-configured project. cmd-gh=Tool commandlet for Github CLI. cmd-gradle=Tool commandlet for Gradle (Build-Tool) cmd-helm=Tool commandlet for Helm (Kubernetes Package Manager) @@ -25,6 +26,7 @@ val-args=The commandline arguments to pass to the tool. val-tool=The tool commandlet to select. val-version=The tool version. val-set-version-version=The tool version to set. +val-repository-project=The pre-configured project to setup version-banner=Current version of IDE is {} opt--batch=enable batch mode (non-interactive) opt--debug=enable debug logging From 76eba655ef3cebc1d1d142340a7257258489e919 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Thu, 9 Nov 2023 20:24:50 +0100 Subject: [PATCH 02/58] implemented run method when project is given as argument --- .../ide/commandlet/RepositoryCommandlet.java | 44 ++++++++++++++++--- cli/src/main/resources/nls/Ide.properties | 2 +- 2 files changed, 40 insertions(+), 6 deletions(-) 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 index dbc03edcf..d7bf2f58b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -1,14 +1,19 @@ package com.devonfw.tools.ide.commandlet; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.property.FolderProperty; +import com.devonfw.tools.ide.property.PathProperty; import com.devonfw.tools.ide.property.StringProperty; +import java.nio.file.Files; +import java.nio.file.Path; + public class RepositoryCommandlet extends Commandlet { private StringProperty setup; - private StringProperty project; + private PathProperty project; /** * The constructor. @@ -19,10 +24,8 @@ public RepositoryCommandlet(IdeContext context) { super(context); addKeyword(getName()); - this.setup = add(new StringProperty("setup", true, "setup")); - this.project = add(new StringProperty("", false, "project")); - - + this.setup = add(new StringProperty("setup", true, null)); + this.project = add(new PathProperty("", false, "project")); } @Override @@ -34,5 +37,36 @@ public String getName() { @Override public void run() { + Path repositoriesPath = this.context.getSettingsPath().resolve("repositories"); + Path legacyRepositoriesPath = this.context.getSettingsPath().resolve("projects"); + + if (project != null) { + Path projectFile = project.getValue(); + if (!Files.exists(projectFile)) { + projectFile = repositoriesPath.resolve(projectFile); + } + if (!Files.exists(projectFile)) { + Path legacyProjectFile = legacyRepositoriesPath.resolve(projectFile); + if (Files.exists(legacyProjectFile)) { + projectFile = legacyProjectFile; + } else { + this.context.warning("Could not find " + projectFile); + return; + } + } + doImportProject(projectFile, true); + } else { + //if no project was given, check whether repositoriesPath exists, if not check the legacy repositoriesPath, if not return. + Path repositories = Files.exists(repositoriesPath) ? repositoriesPath : Files.exists(legacyRepositoriesPath) ? legacyRepositoriesPath : null; + if (repositories == null) return; + + //iterate through repositories and import all active projects + } + } + + private void doImportProject(Path projectFile, boolean force) { + + + } } diff --git a/cli/src/main/resources/nls/Ide.properties b/cli/src/main/resources/nls/Ide.properties index 3892df2af..5cb298590 100644 --- a/cli/src/main/resources/nls/Ide.properties +++ b/cli/src/main/resources/nls/Ide.properties @@ -26,7 +26,7 @@ val-args=The commandline arguments to pass to the tool. val-tool=The tool commandlet to select. val-version=The tool version. val-set-version-version=The tool version to set. -val-repository-project=The pre-configured project to setup +val-repository-project=The pre-configured project to setup, omit to setup all active projects version-banner=Current version of IDE is {} opt--batch=enable batch mode (non-interactive) opt--debug=enable debug logging From 0e16de61ebd2f9e2ffb0e001a52d5843c3a132e1 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Fri, 10 Nov 2023 05:13:51 +0100 Subject: [PATCH 03/58] implemented loadProperties --- .../ide/commandlet/RepositoryCommandlet.java | 129 +++++++++++++++--- .../ide/commandlet/RepositoryConfig.java | 12 ++ .../com/devonfw/tools/ide/io/FileAccess.java | 10 ++ .../devonfw/tools/ide/io/FileAccessImpl.java | 20 +++ cli/src/main/resources/nls/Ide.properties | 2 +- cli/src/main/resources/nls/Ide_de.properties | 3 + 6 files changed, 154 insertions(+), 22 deletions(-) create mode 100644 cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryConfig.java 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 index d7bf2f58b..09fadac33 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -1,19 +1,23 @@ package com.devonfw.tools.ide.commandlet; import com.devonfw.tools.ide.context.IdeContext; -import com.devonfw.tools.ide.property.FolderProperty; import com.devonfw.tools.ide.property.PathProperty; import com.devonfw.tools.ide.property.StringProperty; +import com.devonfw.tools.ide.tool.ToolCommandlet; +import com.devonfw.tools.ide.tool.mvn.Mvn; +import com.devonfw.tools.ide.util.FilenameUtil; +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.List; +import java.util.Properties; public class RepositoryCommandlet extends Commandlet { - - private StringProperty setup; - - private PathProperty project; + private PathProperty repository; /** * The constructor. @@ -24,8 +28,8 @@ public RepositoryCommandlet(IdeContext context) { super(context); addKeyword(getName()); - this.setup = add(new StringProperty("setup", true, null)); - this.project = add(new PathProperty("", false, "project")); + add(new StringProperty("setup", true, null)); + this.repository = add(new PathProperty("", false, "repository")); } @Override @@ -40,33 +44,116 @@ public void run() { Path repositoriesPath = this.context.getSettingsPath().resolve("repositories"); Path legacyRepositoriesPath = this.context.getSettingsPath().resolve("projects"); - if (project != null) { - Path projectFile = project.getValue(); - if (!Files.exists(projectFile)) { - projectFile = repositoriesPath.resolve(projectFile); + if (repository != null) { + // Handle the case when a specific repository is provided + Path repositoryFile = repository.getValue(); + if (!Files.exists(repositoryFile)) { + repositoryFile = repositoriesPath.resolve(repositoryFile); } - if (!Files.exists(projectFile)) { - Path legacyProjectFile = legacyRepositoriesPath.resolve(projectFile); - if (Files.exists(legacyProjectFile)) { - projectFile = legacyProjectFile; + // + repositoryFile = Path.of(repositoryFile.toString() + ".properties"); + if (!Files.exists(repositoryFile)) { + Path legacyRepositoryFile = Path.of(legacyRepositoriesPath.resolve(repositoryFile.getFileName()).toString()); + if (Files.exists(legacyRepositoryFile)) { + repositoryFile = legacyRepositoryFile; } else { - this.context.warning("Could not find " + projectFile); + this.context.warning("Could not find " + repositoryFile); return; } } - doImportProject(projectFile, true); + doImportRepository(repositoryFile, true); } else { - //if no project was given, check whether repositoriesPath exists, if not check the legacy repositoriesPath, if not return. - Path repositories = Files.exists(repositoriesPath) ? repositoriesPath : Files.exists(legacyRepositoriesPath) ? legacyRepositoriesPath : null; + // If no specific repository is provided, check for repositories folder + Path repositories = Files.exists(repositoriesPath) ? repositoriesPath : + Files.exists(legacyRepositoriesPath) ? legacyRepositoriesPath : null; + if (repositories == null) return; - //iterate through repositories and import all active projects + List propertiesFiles = this.context.getFileAccess().getFilesInDir(repositories, + path -> "properties".equals(FilenameUtil.getExtension(path.getFileName().toString()))); + + if (propertiesFiles != null) { + boolean forceMode = this.context.isForceMode(); + for (Path propertiesFile : propertiesFiles) { + doImportRepository(propertiesFile, forceMode); + } + } + } + } + + private void doImportRepository(Path repositoryFile, boolean forceMode) { + + //TODO: unnecessary? + if (!Files.exists(repositoryFile)) { + return; + } + + this.context.info("Importing from {} ...", repositoryFile.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 ..."); + } + } + + String repository = repositoryConfig.path(); + String gitUrl = repositoryConfig.gitUrl(); + if (repository == null || ( repository != null && repository.isEmpty()) || gitUrl == null || (gitUrl != null && gitUrl.isEmpty())) { + this.context.warning("Invalid repository configuration {} - both 'path' and 'git-url' have to be defined." + , repositoryFile); + return; + } + + this.context.debug(repositoryConfig.toString()); + this.context.debug("Pull or clone git repository {} ...", repository); + + String workspace = repositoryConfig.workspace() != null ? repositoryConfig.workspace() : "main"; + Path repositoryWorkspacePath = this.context.getIdeHome().resolve("workspaces").resolve(workspace); + this.context.getFileAccess().mkdirs(repositoryWorkspacePath); + + String targetGitUrl = repositoryConfig.gitUrl(); + if (repositoryConfig.gitBranch() != null && !repositoryConfig.gitBranch().isEmpty()) { + targetGitUrl = targetGitUrl + "#" + repositoryConfig.gitBranch(); + } + + Path repositoryPath = repositoryWorkspacePath.resolve(repository); + this.context.gitPullOrClone(repositoryPath, targetGitUrl); + + String buildCmd = repositoryConfig.buildCmd(); + this.context.debug("Building project with ide command: {}", buildCmd); + if (buildCmd != null && !buildCmd.isEmpty()) { + List command = List.of(buildCmd.split("\\s+")); + String commandlet = command.get(0); + command.remove(0); + ToolCommandlet tc = (ToolCommandlet) this.context.getCommandletManager().getCommandlet(commandlet); + tc.runTool(null, command.toArray(new String[0])); } } - private void doImportProject(Path projectFile, boolean force) { + private 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); + } + return new RepositoryConfig( + properties.getProperty("path"), + properties.getProperty("workingsets"), + properties.getProperty("workspace"), + properties.getProperty("git_url"), + properties.getProperty("git_branch"), + properties.getProperty("build_cmd"), + properties.getProperty("eclipse"), + Boolean.parseBoolean(properties.getProperty("active")) + ); } + } 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..5ac3b0391 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryConfig.java @@ -0,0 +1,12 @@ +package com.devonfw.tools.ide.commandlet; + +public record RepositoryConfig( + String path, + String workingSets, + String workspace, + String gitUrl, + String gitBranch, + String buildCmd, + String eclipse, + boolean active) { +} 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 82fe25090..d685facdf 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 @@ -1,6 +1,8 @@ package com.devonfw.tools.ide.io; +import java.io.IOException; import java.nio.file.Path; +import java.util.List; import java.util.function.Predicate; /** @@ -124,4 +126,12 @@ default void copy(Path source, Path target) { */ Path findFirst(Path dir, Predicate filter, boolean recursive); + /** + * Retrieves a list of file paths from the specified directory that satisfy the given predicate. + * @param dir the folder to iterate through + * @param filter the {@link Predicate} that determines whether a file should be included in the list. + * @return a list of paths that satisfy the provided {@link Predicate} or {@code null} if no match was found. + */ + public List getFilesInDir(Path dir, Predicate filter); + } 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 3a916fcd1..b59306df5 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 @@ -451,4 +451,24 @@ private Path findFirstRecursive(Path dir, Predicate filter, boolean recurs return null; } + @Override + public List getFilesInDir(Path dir, Predicate filter) { + List files = null; + try (Stream childStream = Files.list(dir)) { + Iterator iterator = childStream.iterator(); + while (iterator.hasNext()) { + Path child = iterator.next(); + if (filter.test(child)) { + if (files == null) { + files = new ArrayList<>(); + } + files.add(child); + } + } + } catch (IOException e) { + throw new IllegalStateException("Failed to iterate through " + dir, e); + } + return files; + } + } diff --git a/cli/src/main/resources/nls/Ide.properties b/cli/src/main/resources/nls/Ide.properties index 5cb298590..7b74ec4bf 100644 --- a/cli/src/main/resources/nls/Ide.properties +++ b/cli/src/main/resources/nls/Ide.properties @@ -26,7 +26,7 @@ val-args=The commandline arguments to pass to the tool. val-tool=The tool commandlet to select. val-version=The tool version. val-set-version-version=The tool version to set. -val-repository-project=The pre-configured project to setup, omit to setup all active projects +val-repository-project=The pre-configured project to setup, omit to setup all active projects. 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 37815d7a1..b1178f925 100644 --- a/cli/src/main/resources/nls/Ide_de.properties +++ b/cli/src/main/resources/nls/Ide_de.properties @@ -5,6 +5,7 @@ options=Optionen: cmd-az=Werkzeug Kommando fuer Azure Kommandoschnittstelle. cmd-env=Gibt die zu setztenden und exportierenden Umgebungsvariablen aus. cmd-get-version=Zeigt die Version des selektierten Werkzeugs an. +cmd-repository=Richtet das vorkonfigurierte Projekt ein. cmd-gh=Werkzeug Kommando für die Github Kommandoschnittstelle. cmd-helm=Werkzeug Kommando für Helm (Kubernetes Package Manager) cmd-help=Zeigt diese Hilfe an. @@ -24,6 +25,8 @@ val-args=Die Kommandozeilen-Argumente zur Übergabe an das Werkzeug. val-tool=Das zu selektierende Werkzeug Kommando. val-version=Die Werkzeug Version. val-set-version-version=Die zu setztende Werkzeug Version. +val-repository-project= Das vorkonfigurierte Projekt zum Einrichten. + version-banner=Die aktuelle Version der IDE ist {} opt--batch=Aktiviert den Batch-Modus (nicht-interaktive Stapelverarbeitung) opt--debug=Aktiviert Debug-Ausgaben (Fehleranalyse) From 69835e338e5f071091d20575e8fe26333b7f6ee7 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Sat, 11 Nov 2023 04:40:12 +0100 Subject: [PATCH 04/58] added record RepositoryConfig, implemented run method, implemented loadProperties --- .../java/com/devonfw/tools/ide/cli/Ide.java | 2 +- .../ide/commandlet/RepositoryCommandlet.java | 30 ++++++++----------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/cli/Ide.java b/cli/src/main/java/com/devonfw/tools/ide/cli/Ide.java index df6e040c4..451c435d0 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/cli/Ide.java +++ b/cli/src/main/java/com/devonfw/tools/ide/cli/Ide.java @@ -155,7 +155,7 @@ private CliArgument initContext(CliArgument first) { } /** - * @param argument the current {@link CliArgument} (position) to match. + * @param current the current {@link CliArgument} (position) to match. * @param commandlet the potential {@link Commandlet} to {@link #apply(CliArgument, Commandlet) apply} and * {@link Commandlet#run() run}. * @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully, 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 index 09fadac33..9e585490f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -4,7 +4,6 @@ import com.devonfw.tools.ide.property.PathProperty; import com.devonfw.tools.ide.property.StringProperty; import com.devonfw.tools.ide.tool.ToolCommandlet; -import com.devonfw.tools.ide.tool.mvn.Mvn; import com.devonfw.tools.ide.util.FilenameUtil; import java.io.FileInputStream; @@ -17,6 +16,7 @@ public class RepositoryCommandlet extends Commandlet { + private PathProperty repository; /** @@ -43,17 +43,15 @@ public void run() { Path repositoriesPath = this.context.getSettingsPath().resolve("repositories"); Path legacyRepositoriesPath = this.context.getSettingsPath().resolve("projects"); + Path repositoryFile = repository.getValue(); - if (repository != null) { + if (repositoryFile != null) { // Handle the case when a specific repository is provided - Path repositoryFile = repository.getValue(); if (!Files.exists(repositoryFile)) { - repositoryFile = repositoriesPath.resolve(repositoryFile); + repositoryFile = repositoriesPath.resolve(repositoryFile.toString() + ".properties"); } - // - repositoryFile = Path.of(repositoryFile.toString() + ".properties"); if (!Files.exists(repositoryFile)) { - Path legacyRepositoryFile = Path.of(legacyRepositoriesPath.resolve(repositoryFile.getFileName()).toString()); + Path legacyRepositoryFile = legacyRepositoriesPath.resolve(repositoryFile.getFileName().toString()); if (Files.exists(legacyRepositoryFile)) { repositoryFile = legacyRepositoryFile; } else { @@ -83,11 +81,6 @@ public void run() { private void doImportRepository(Path repositoryFile, boolean forceMode) { - //TODO: unnecessary? - if (!Files.exists(repositoryFile)) { - return; - } - this.context.info("Importing from {} ...", repositoryFile.toString()); RepositoryConfig repositoryConfig = loadProperties(repositoryFile); @@ -97,6 +90,7 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) { this.context.info("Repository setup is forced, hence proceeding ..."); } else { this.context.info("Skipping repository - use force (-f) to setup all repositories ..."); + return; } } @@ -126,11 +120,13 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) { String buildCmd = repositoryConfig.buildCmd(); this.context.debug("Building project with ide command: {}", buildCmd); if (buildCmd != null && !buildCmd.isEmpty()) { - List command = List.of(buildCmd.split("\\s+")); - String commandlet = command.get(0); - command.remove(0); - ToolCommandlet tc = (ToolCommandlet) this.context.getCommandletManager().getCommandlet(commandlet); - tc.runTool(null, command.toArray(new String[0])); + //TODO: build repository build path + } else { + this.context.info("Build command not set. Skipping build for repository."); + } + + if ("import".equals(repositoryConfig.eclipse()) && !Files.exists(repositoryPath.resolve(".project"))) { + //TODO: import repository to eclipse } } From 90145a6b1ee46b44659d517851957110e0a05ee8 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Mon, 13 Nov 2023 05:39:10 +0100 Subject: [PATCH 05/58] implemented build functionality, import missing --- .../ide/commandlet/RepositoryCommandlet.java | 21 ++++++++++++++++--- .../ide/commandlet/RepositoryConfig.java | 1 + .../tools/ide/tool/eclipse/Eclipse.java | 7 +++++++ 3 files changed, 26 insertions(+), 3 deletions(-) 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 index 9e585490f..04771eb91 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -1,9 +1,11 @@ package com.devonfw.tools.ide.commandlet; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.process.ProcessContext; import com.devonfw.tools.ide.property.PathProperty; import com.devonfw.tools.ide.property.StringProperty; import com.devonfw.tools.ide.tool.ToolCommandlet; +import com.devonfw.tools.ide.tool.eclipse.Eclipse; import com.devonfw.tools.ide.util.FilenameUtil; import java.io.FileInputStream; @@ -11,6 +13,7 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; import java.util.List; import java.util.Properties; @@ -96,7 +99,7 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) { String repository = repositoryConfig.path(); String gitUrl = repositoryConfig.gitUrl(); - if (repository == null || ( repository != null && repository.isEmpty()) || gitUrl == null || (gitUrl != null && gitUrl.isEmpty())) { + 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); return; @@ -109,7 +112,7 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) { Path repositoryWorkspacePath = this.context.getIdeHome().resolve("workspaces").resolve(workspace); this.context.getFileAccess().mkdirs(repositoryWorkspacePath); - String targetGitUrl = repositoryConfig.gitUrl(); + String targetGitUrl = gitUrl; if (repositoryConfig.gitBranch() != null && !repositoryConfig.gitBranch().isEmpty()) { targetGitUrl = targetGitUrl + "#" + repositoryConfig.gitBranch(); } @@ -120,13 +123,24 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) { String buildCmd = repositoryConfig.buildCmd(); this.context.debug("Building project with ide command: {}", buildCmd); if (buildCmd != null && !buildCmd.isEmpty()) { - //TODO: build repository build path + String[] command = buildCmd.split("\\s+"); + ProcessContext pc = this.context.newProcess(); + pc.executable(command[0]); + pc.addArgs(Arrays.copyOfRange(command, 1, command.length)); + Path buildPath = repositoryPath; + if (repositoryConfig.buildPath() != null) { + buildPath = buildPath.resolve(repositoryConfig.buildPath()); + } + pc.directory(buildPath); + this.context.getCommandletManager().getToolCommandlet(command[0]).install(false); + pc.run(); } else { this.context.info("Build command not set. Skipping build for repository."); } if ("import".equals(repositoryConfig.eclipse()) && !Files.exists(repositoryPath.resolve(".project"))) { //TODO: import repository to eclipse + this.context.getCommandletManager().getCommandlet(Eclipse.class).importRepository(repositoryPath, repositoryWorkspacePath, repositoryConfig.workingSets()); } } @@ -146,6 +160,7 @@ private RepositoryConfig loadProperties(Path filePath) { properties.getProperty("workspace"), properties.getProperty("git_url"), properties.getProperty("git_branch"), + properties.getProperty(("build_path")), properties.getProperty("build_cmd"), properties.getProperty("eclipse"), Boolean.parseBoolean(properties.getProperty("active")) 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 index 5ac3b0391..474c7b461 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryConfig.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryConfig.java @@ -6,6 +6,7 @@ public record RepositoryConfig( String workspace, String gitUrl, String gitBranch, + String buildPath, String buildCmd, String eclipse, boolean active) { diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java index 1d0adf78c..e28342ef5 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java @@ -104,4 +104,11 @@ private void log(IdeLogLevel level, List lines) { } } + public void importRepository(Path importPath, Path workspacePath, String workingSets) { + + //configureEclipse(); + //checkAndInstallGroovy(); + + + } } From c738a0edc3f1a00ca1c252504ff3e8819835c176 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Mon, 13 Nov 2023 05:47:53 +0100 Subject: [PATCH 06/58] minor improvements --- .../devonfw/tools/ide/commandlet/RepositoryCommandlet.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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 index 04771eb91..d9beececa 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -51,7 +51,7 @@ public void run() { if (repositoryFile != null) { // Handle the case when a specific repository is provided if (!Files.exists(repositoryFile)) { - repositoryFile = repositoriesPath.resolve(repositoryFile.toString() + ".properties"); + repositoryFile = repositoriesPath.resolve(repositoryFile.getFileName().toString() + ".properties"); } if (!Files.exists(repositoryFile)) { Path legacyRepositoryFile = legacyRepositoriesPath.resolve(repositoryFile.getFileName().toString()); @@ -112,13 +112,12 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) { Path repositoryWorkspacePath = this.context.getIdeHome().resolve("workspaces").resolve(workspace); this.context.getFileAccess().mkdirs(repositoryWorkspacePath); - String targetGitUrl = gitUrl; if (repositoryConfig.gitBranch() != null && !repositoryConfig.gitBranch().isEmpty()) { - targetGitUrl = targetGitUrl + "#" + repositoryConfig.gitBranch(); + gitUrl = gitUrl + "#" + repositoryConfig.gitBranch(); } Path repositoryPath = repositoryWorkspacePath.resolve(repository); - this.context.gitPullOrClone(repositoryPath, targetGitUrl); + this.context.gitPullOrClone(repositoryPath, gitUrl); String buildCmd = repositoryConfig.buildCmd(); this.context.debug("Building project with ide command: {}", buildCmd); From c3de09e703b6884d037017fe14cbe59a121b6c28 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Tue, 14 Nov 2023 12:22:01 +0100 Subject: [PATCH 07/58] javadoc --- .../ide/commandlet/RepositoryCommandlet.java | 18 +++++++++--------- .../tools/ide/commandlet/RepositoryConfig.java | 13 +++++++++++++ .../tools/ide/tool/eclipse/Eclipse.java | 7 ------- cli/src/main/resources/nls/Ide.properties | 4 ++-- 4 files changed, 24 insertions(+), 18 deletions(-) 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 index d9beececa..a45aa2556 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -4,8 +4,6 @@ import com.devonfw.tools.ide.process.ProcessContext; import com.devonfw.tools.ide.property.PathProperty; import com.devonfw.tools.ide.property.StringProperty; -import com.devonfw.tools.ide.tool.ToolCommandlet; -import com.devonfw.tools.ide.tool.eclipse.Eclipse; import com.devonfw.tools.ide.util.FilenameUtil; import java.io.FileInputStream; @@ -17,10 +15,13 @@ import java.util.List; import java.util.Properties; +/** + * {@link Commandlet} to setup a repository + */ public class RepositoryCommandlet extends Commandlet { - - private PathProperty repository; + /** the repository to setup. */ + public final PathProperty repository; /** * The constructor. @@ -109,20 +110,21 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) { this.context.debug("Pull or clone git repository {} ...", repository); String workspace = repositoryConfig.workspace() != null ? repositoryConfig.workspace() : "main"; - Path repositoryWorkspacePath = this.context.getIdeHome().resolve("workspaces").resolve(workspace); - this.context.getFileAccess().mkdirs(repositoryWorkspacePath); + Path workspacePath = this.context.getIdeHome().resolve("workspaces").resolve(workspace); + this.context.getFileAccess().mkdirs(workspacePath); if (repositoryConfig.gitBranch() != null && !repositoryConfig.gitBranch().isEmpty()) { gitUrl = gitUrl + "#" + repositoryConfig.gitBranch(); } - Path repositoryPath = repositoryWorkspacePath.resolve(repository); + Path repositoryPath = workspacePath.resolve(repository); this.context.gitPullOrClone(repositoryPath, gitUrl); String buildCmd = repositoryConfig.buildCmd(); this.context.debug("Building project with ide command: {}", buildCmd); if (buildCmd != null && !buildCmd.isEmpty()) { String[] command = buildCmd.split("\\s+"); + this.context.getCommandletManager().getToolCommandlet(command[0]).install(true); ProcessContext pc = this.context.newProcess(); pc.executable(command[0]); pc.addArgs(Arrays.copyOfRange(command, 1, command.length)); @@ -131,7 +133,6 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) { buildPath = buildPath.resolve(repositoryConfig.buildPath()); } pc.directory(buildPath); - this.context.getCommandletManager().getToolCommandlet(command[0]).install(false); pc.run(); } else { this.context.info("Build command not set. Skipping build for repository."); @@ -139,7 +140,6 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) { if ("import".equals(repositoryConfig.eclipse()) && !Files.exists(repositoryPath.resolve(".project"))) { //TODO: import repository to eclipse - this.context.getCommandletManager().getCommandlet(Eclipse.class).importRepository(repositoryPath, repositoryWorkspacePath, repositoryConfig.workingSets()); } } 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 index 474c7b461..e03d2765a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryConfig.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryConfig.java @@ -1,5 +1,18 @@ package com.devonfw.tools.ide.commandlet; +/** + * 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 eclipse Desired action for eclipse IDE. If equals to "import" all modules will be imported into eclipse. + * @param active {@code true} to setup the repository during setup, {@code false} to skip. + */ public record RepositoryConfig( String path, String workingSets, diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java index e28342ef5..1d0adf78c 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/Eclipse.java @@ -104,11 +104,4 @@ private void log(IdeLogLevel level, List lines) { } } - public void importRepository(Path importPath, Path workspacePath, String workingSets) { - - //configureEclipse(); - //checkAndInstallGroovy(); - - - } } diff --git a/cli/src/main/resources/nls/Ide.properties b/cli/src/main/resources/nls/Ide.properties index 7b74ec4bf..fe6032054 100644 --- a/cli/src/main/resources/nls/Ide.properties +++ b/cli/src/main/resources/nls/Ide.properties @@ -5,7 +5,7 @@ options=Options: cmd-az=Tool commandlet for Azure CLI. cmd-env=Print the environment variables to set and export. cmd-get-version=Get the version of the selected tool. -cmd-repository=setup the pre-configured project. +cmd-repository=setup the pre-configured repository. cmd-gh=Tool commandlet for Github CLI. cmd-gradle=Tool commandlet for Gradle (Build-Tool) cmd-helm=Tool commandlet for Helm (Kubernetes Package Manager) @@ -26,7 +26,7 @@ val-args=The commandline arguments to pass to the tool. val-tool=The tool commandlet to select. val-version=The tool version. val-set-version-version=The tool version to set. -val-repository-project=The pre-configured project to setup, omit to setup all active projects. +val-repository-repository=The pre-configured 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 From c0fbd26e7caa3c30f3185ca9d1199884b67e5d70 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Thu, 16 Nov 2023 11:16:51 +0100 Subject: [PATCH 08/58] fixed bug: tool installed but not found --- cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java b/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java index ac24d3a99..9219eea93 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java +++ b/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java @@ -166,6 +166,7 @@ public Path getPath(String tool) { */ public void setPath(String tool, Path path) { + this.paths.add(path); this.tool2pathMap.put(tool, path); } From 1c729aeb7dbdf226a3a9430d830ad1acd37a0a70 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Tue, 28 Nov 2023 19:14:16 +0100 Subject: [PATCH 09/58] removed useless improt --- .../com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java | 3 +++ cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) 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 index a45aa2556..5faac1273 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -23,6 +23,9 @@ public class RepositoryCommandlet extends Commandlet { /** the repository to setup. */ public final PathProperty repository; + public final Path repositoriesPath = context.getSettingsPath().resolve("repositories"); + Path legacyRepositoriesPath = this.context.getSettingsPath().resolve("projects"); + /** * The constructor. * 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 c6b9cf329..237fb8c6b 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 @@ -1,6 +1,5 @@ package com.devonfw.tools.ide.io; -import java.io.IOException; import java.nio.file.Path; import java.util.List; import java.util.function.Predicate; @@ -139,6 +138,6 @@ default void copy(Path source, Path target) { * @param filter the {@link Predicate} that determines whether a file should be included in the list. * @return a list of paths that satisfy the provided {@link Predicate} or {@code null} if no match was found. */ - public List getFilesInDir(Path dir, Predicate filter); + List getFilesInDir(Path dir, Predicate filter); } From 01cb2b94b28b198d6899d879f4dcdecdc570c882 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Tue, 28 Nov 2023 20:28:38 +0100 Subject: [PATCH 10/58] minor change --- .../devonfw/tools/ide/commandlet/RepositoryCommandlet.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 index 5faac1273..1be3a0a9a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -22,10 +22,7 @@ public class RepositoryCommandlet extends Commandlet { /** the repository to setup. */ public final PathProperty repository; - - public final Path repositoriesPath = context.getSettingsPath().resolve("repositories"); - Path legacyRepositoriesPath = this.context.getSettingsPath().resolve("projects"); - + /** * The constructor. * From db8008a07cd2482163bd57a9e3bbb9ccb6cb383e Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Tue, 28 Nov 2023 20:30:57 +0100 Subject: [PATCH 11/58] Update RepositoryCommandlet.java --- .../com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 1be3a0a9a..a45aa2556 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -22,7 +22,7 @@ public class RepositoryCommandlet extends Commandlet { /** the repository to setup. */ public final PathProperty repository; - + /** * The constructor. * From 82cc617d0804d063ad21dc2344b3edd63453766f Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Fri, 1 Dec 2023 00:37:52 +0100 Subject: [PATCH 12/58] changes after review --- .../main/java/com/devonfw/tools/ide/io/FileAccessImpl.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 4507d3781..2636f9592 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 @@ -460,15 +460,12 @@ private Path findFirstRecursive(Path dir, Predicate filter, boolean recurs @Override public List getFilesInDir(Path dir, Predicate filter) { - List files = null; + List files = new ArrayList<>(); try (Stream childStream = Files.list(dir)) { Iterator iterator = childStream.iterator(); while (iterator.hasNext()) { Path child = iterator.next(); if (filter.test(child)) { - if (files == null) { - files = new ArrayList<>(); - } files.add(child); } } From dbc3bd3ac295ce4d61daf4d0bb4c7adda9512358 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Dec 2023 01:47:59 +0100 Subject: [PATCH 13/58] Update cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Hohwiller --- .../com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index a45aa2556..89c08622c 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -85,7 +85,7 @@ public void run() { private void doImportRepository(Path repositoryFile, boolean forceMode) { - this.context.info("Importing from {} ...", repositoryFile.toString()); + this.context.info("Importing repository from {} ...", repositoryFile.getFileName().toString()); RepositoryConfig repositoryConfig = loadProperties(repositoryFile); if (!repositoryConfig.active()) { From a582601b1949393da30bf898075ffc21452f831f Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Dec 2023 01:48:08 +0100 Subject: [PATCH 14/58] Update cli/src/main/resources/nls/Ide_de.properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Hohwiller --- cli/src/main/resources/nls/Ide_de.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/resources/nls/Ide_de.properties b/cli/src/main/resources/nls/Ide_de.properties index b1178f925..1c3099a49 100644 --- a/cli/src/main/resources/nls/Ide_de.properties +++ b/cli/src/main/resources/nls/Ide_de.properties @@ -5,7 +5,7 @@ options=Optionen: cmd-az=Werkzeug Kommando fuer Azure Kommandoschnittstelle. cmd-env=Gibt die zu setztenden und exportierenden Umgebungsvariablen aus. cmd-get-version=Zeigt die Version des selektierten Werkzeugs an. -cmd-repository=Richtet das vorkonfigurierte Projekt ein. +cmd-repository=Richtet das vorkonfigurierte Git Repository ein. cmd-gh=Werkzeug Kommando für die Github Kommandoschnittstelle. cmd-helm=Werkzeug Kommando für Helm (Kubernetes Package Manager) cmd-help=Zeigt diese Hilfe an. From 7e3bdd9f892aeb03113c799bae0d70a70b422ccf Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Dec 2023 01:48:28 +0100 Subject: [PATCH 15/58] Update cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Hohwiller --- .../com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 89c08622c..95b454fb3 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -72,7 +72,7 @@ public void run() { if (repositories == null) return; List propertiesFiles = this.context.getFileAccess().getFilesInDir(repositories, - path -> "properties".equals(FilenameUtil.getExtension(path.getFileName().toString()))); + path -> path.getFileName().toString().endsWith(".properties"))); if (propertiesFiles != null) { boolean forceMode = this.context.isForceMode(); From d71f68ee1a170794f080342302e2f7f3b86d1c72 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Dec 2023 01:48:46 +0100 Subject: [PATCH 16/58] Update cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Hohwiller --- cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 237fb8c6b..ad0921581 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 @@ -136,7 +136,7 @@ default void copy(Path source, Path target) { * Retrieves a list of file paths from the specified directory that satisfy the given predicate. * @param dir the folder to iterate through * @param filter the {@link Predicate} that determines whether a file should be included in the list. - * @return a list of paths that satisfy the provided {@link Predicate} or {@code null} if no match was found. + * @return a list of paths that satisfy the provided {@link Predicate}. Will be {@link List#isEmpty() empty} if no match was found but is never {@code null}. */ List getFilesInDir(Path dir, Predicate filter); From 1e5aba8d923551f4601d873c550dfd72d1471ff0 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Dec 2023 01:49:08 +0100 Subject: [PATCH 17/58] Update cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Hohwiller --- .../com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 95b454fb3..d7076a443 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -32,7 +32,7 @@ public RepositoryCommandlet(IdeContext context) { super(context); addKeyword(getName()); - add(new StringProperty("setup", true, null)); + addKeyword("setup"); this.repository = add(new PathProperty("", false, "repository")); } From c8cf771e0216708ee2f701e55470a7e8fb15c0ab Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Dec 2023 01:49:29 +0100 Subject: [PATCH 18/58] Update cli/src/main/resources/nls/Ide.properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Hohwiller --- cli/src/main/resources/nls/Ide.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/resources/nls/Ide.properties b/cli/src/main/resources/nls/Ide.properties index fe6032054..a704a24bd 100644 --- a/cli/src/main/resources/nls/Ide.properties +++ b/cli/src/main/resources/nls/Ide.properties @@ -5,7 +5,7 @@ options=Options: cmd-az=Tool commandlet for Azure CLI. cmd-env=Print the environment variables to set and export. cmd-get-version=Get the version of the selected tool. -cmd-repository=setup the pre-configured repository. +cmd-repository=setup the pre-configured git repository. cmd-gh=Tool commandlet for Github CLI. cmd-gradle=Tool commandlet for Gradle (Build-Tool) cmd-helm=Tool commandlet for Helm (Kubernetes Package Manager) From 4b601354937eb29e9e0dad48a60ab2144140be0e Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Dec 2023 01:49:38 +0100 Subject: [PATCH 19/58] Update cli/src/main/resources/nls/Ide.properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Hohwiller --- cli/src/main/resources/nls/Ide.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/resources/nls/Ide.properties b/cli/src/main/resources/nls/Ide.properties index a704a24bd..25e31bf8d 100644 --- a/cli/src/main/resources/nls/Ide.properties +++ b/cli/src/main/resources/nls/Ide.properties @@ -26,7 +26,7 @@ val-args=The commandline arguments to pass to the tool. 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 pre-configured repository to setup, omit to setup all active repositories. +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 From 061cfa52013ee82122b995a618e0e11e9b23878d Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Dec 2023 01:49:51 +0100 Subject: [PATCH 20/58] Update cli/src/main/resources/nls/Ide_de.properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Hohwiller --- cli/src/main/resources/nls/Ide_de.properties | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cli/src/main/resources/nls/Ide_de.properties b/cli/src/main/resources/nls/Ide_de.properties index 1c3099a49..b7e15fef2 100644 --- a/cli/src/main/resources/nls/Ide_de.properties +++ b/cli/src/main/resources/nls/Ide_de.properties @@ -25,8 +25,7 @@ val-args=Die Kommandozeilen-Argumente zur Übergabe an das Werkzeug. val-tool=Das zu selektierende Werkzeug Kommando. val-version=Die Werkzeug Version. val-set-version-version=Die zu setztende Werkzeug Version. -val-repository-project= Das vorkonfigurierte Projekt zum Einrichten. - +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) From c314c08ff5a27792af81e0ba978d103144d6b2fa Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Dec 2023 01:50:29 +0100 Subject: [PATCH 21/58] reviewed changes --- .../ide/commandlet/RepositoryCommandlet.java | 25 +++++++++++-------- .../devonfw/tools/ide/context/IdeContext.java | 6 +++++ 2 files changed, 20 insertions(+), 11 deletions(-) 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 index a45aa2556..8350d21fb 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -45,8 +45,8 @@ public String getName() { @Override public void run() { - Path repositoriesPath = this.context.getSettingsPath().resolve("repositories"); - Path legacyRepositoriesPath = this.context.getSettingsPath().resolve("projects"); + Path repositoriesPath = this.context.getSettingsPath().resolve(context.FOLDER_REPOSITORIES); + Path legacyRepositoriesPath = this.context.getSettingsPath().resolve(context.FOLDER_LEGACY_REPOSITORIES); Path repositoryFile = repository.getValue(); if (repositoryFile != null) { @@ -66,19 +66,22 @@ public void run() { doImportRepository(repositoryFile, true); } else { // If no specific repository is provided, check for repositories folder - Path repositories = Files.exists(repositoriesPath) ? repositoriesPath : - Files.exists(legacyRepositoriesPath) ? legacyRepositoriesPath : null; - - if (repositories == null) return; + 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().getFilesInDir(repositories, path -> "properties".equals(FilenameUtil.getExtension(path.getFileName().toString()))); - if (propertiesFiles != null) { - boolean forceMode = this.context.isForceMode(); - for (Path propertiesFile : propertiesFiles) { - doImportRepository(propertiesFile, forceMode); - } + boolean forceMode = this.context.isForceMode(); + for (Path propertiesFile : propertiesFiles) { + doImportRepository(propertiesFile, forceMode); } } } 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 0c8d0782f..a1a584d28 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"; From 07d4a341b290da94df2ef5b33d775564b40a112a Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Dec 2023 01:51:58 +0100 Subject: [PATCH 22/58] Update cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Hohwiller --- .../ide/commandlet/RepositoryCommandlet.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) 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 index d7076a443..c280c0f5f 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -124,16 +124,13 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) { this.context.debug("Building project with ide command: {}", buildCmd); if (buildCmd != null && !buildCmd.isEmpty()) { String[] command = buildCmd.split("\\s+"); - this.context.getCommandletManager().getToolCommandlet(command[0]).install(true); - ProcessContext pc = this.context.newProcess(); - pc.executable(command[0]); - pc.addArgs(Arrays.copyOfRange(command, 1, command.length)); - Path buildPath = repositoryPath; - if (repositoryConfig.buildPath() != null) { - buildPath = buildPath.resolve(repositoryConfig.buildPath()); + ToolCommandlet commandlet = this.context.getCommandletManager().getToolCommandlet(command[0]); + List args = new java.util.ArrayList<>(command.length - 1); + for (int i = 1; i < command.length; i++) { + args.add(command[i]); } - pc.directory(buildPath); - pc.run(); + commandlet.arguments.setValue(args); + commandlet.run(); } else { this.context.info("Build command not set. Skipping build for repository."); } From 37da950e481683da8617354b5df8639d2246ec52 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Dec 2023 01:52:30 +0100 Subject: [PATCH 23/58] updates --- .../com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 86e2cfbc0..b8434b572 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -77,7 +77,7 @@ public void run() { } List propertiesFiles = this.context.getFileAccess().getFilesInDir(repositories, - path -> path.getFileName().toString().endsWith(".properties"))); + path -> path.getFileName().toString().endsWith(".properties")); boolean forceMode = this.context.isForceMode(); for (Path propertiesFile : propertiesFiles) { From 05bcde10517b55f6883a33c88d44aba586fd4471 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Dec 2023 02:15:44 +0100 Subject: [PATCH 24/58] reviewed changes --- .../tools/ide/commandlet/RepositoryCommandlet.java | 12 +++++------- .../java/com/devonfw/tools/ide/io/FileAccess.java | 2 +- .../com/devonfw/tools/ide/io/FileAccessImpl.java | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) 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 index d909a1349..f934db195 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -1,17 +1,15 @@ package com.devonfw.tools.ide.commandlet; import com.devonfw.tools.ide.context.IdeContext; -import com.devonfw.tools.ide.process.ProcessContext; import com.devonfw.tools.ide.property.PathProperty; -import com.devonfw.tools.ide.property.StringProperty; -import com.devonfw.tools.ide.util.FilenameUtil; +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.Arrays; +import java.util.ArrayList; import java.util.List; import java.util.Properties; @@ -76,7 +74,7 @@ public void run() { return; } - List propertiesFiles = this.context.getFileAccess().getFilesInDir(repositories, + List propertiesFiles = this.context.getFileAccess().getChildrenInDir(repositories, path -> path.getFileName().toString().endsWith(".properties")); boolean forceMode = this.context.isForceMode(); @@ -124,11 +122,11 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) { this.context.gitPullOrClone(repositoryPath, gitUrl); String buildCmd = repositoryConfig.buildCmd(); - this.context.debug("Building project with ide command: {}", 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 java.util.ArrayList<>(command.length - 1); + List args = new ArrayList<>(command.length - 1); for (int i = 1; i < command.length; i++) { args.add(command[i]); } 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 ad0921581..dbf787c8f 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 @@ -138,6 +138,6 @@ default void copy(Path source, Path target) { * @param filter the {@link Predicate} that determines whether a file should be included in the list. * @return a list of paths that satisfy the provided {@link Predicate}. Will be {@link List#isEmpty() empty} if no match was found but is never {@code null}. */ - List getFilesInDir(Path dir, Predicate filter); + List getChildrenInDir(Path dir, Predicate filter); } 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 2636f9592..d7611736d 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 @@ -459,7 +459,7 @@ private Path findFirstRecursive(Path dir, Predicate filter, boolean recurs } @Override - public List getFilesInDir(Path dir, Predicate filter) { + public List getChildrenInDir(Path dir, Predicate filter) { List files = new ArrayList<>(); try (Stream childStream = Files.list(dir)) { Iterator iterator = childStream.iterator(); From f2ee210c4a90507d0ae3c8694aa64efdfab5edba Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Dec 2023 03:11:30 +0100 Subject: [PATCH 25/58] reviewed changes --- .../ide/commandlet/RepositoryCommandlet.java | 32 +++----------- .../ide/commandlet/RepositoryConfig.java | 43 ++++++++++++++++++- 2 files changed, 47 insertions(+), 28 deletions(-) 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 index f934db195..5ee6a9db1 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -13,6 +13,8 @@ import java.util.List; import java.util.Properties; +import static com.devonfw.tools.ide.commandlet.RepositoryConfig.loadProperties; + /** * {@link Commandlet} to setup a repository */ @@ -136,32 +138,10 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) { this.context.info("Build command not set. Skipping build for repository."); } - if ("import".equals(repositoryConfig.eclipse()) && !Files.exists(repositoryPath.resolve(".project"))) { - //TODO: import repository to eclipse - } - } - - - private 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); + if (!Files.exists(repositoryPath.resolve(".project"))) { + for (String ideCommandlet : repositoryConfig.imports()) { + //TODO: import repository to ideCommandlet + } } - - 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"), - properties.getProperty("eclipse"), - Boolean.parseBoolean(properties.getProperty("active")) - ); } - } 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 index e03d2765a..952fde680 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryConfig.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryConfig.java @@ -1,5 +1,13 @@ 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}. * @@ -10,7 +18,7 @@ * @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 eclipse Desired action for eclipse IDE. If equals to "import" all modules will be imported into eclipse. + * @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( @@ -21,6 +29,37 @@ public record RepositoryConfig( String gitBranch, String buildPath, String buildCmd, - String eclipse, + 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"))); + } + + 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(); + } + } } From 5773121b21c79da2b690763917395f1bb9b02ea6 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Fri, 15 Dec 2023 11:31:16 +0100 Subject: [PATCH 26/58] implemented repositoryProperty --- .../ide/commandlet/RepositoryCommandlet.java | 19 ++--- .../ide/property/RepositoryProperty.java | 70 +++++++++++++++++++ 2 files changed, 74 insertions(+), 15 deletions(-) create mode 100644 cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java 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 index 5ee6a9db1..4587b2161 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -2,6 +2,7 @@ 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; @@ -21,7 +22,7 @@ public class RepositoryCommandlet extends Commandlet { /** the repository to setup. */ - public final PathProperty repository; + public final RepositoryProperty repository; /** * The constructor. @@ -33,7 +34,7 @@ public RepositoryCommandlet(IdeContext context) { super(context); addKeyword(getName()); addKeyword("setup"); - this.repository = add(new PathProperty("", false, "repository")); + this.repository = add(new RepositoryProperty("", false, "repository")); } @Override @@ -47,22 +48,10 @@ public void run() { Path repositoriesPath = this.context.getSettingsPath().resolve(context.FOLDER_REPOSITORIES); Path legacyRepositoriesPath = this.context.getSettingsPath().resolve(context.FOLDER_LEGACY_REPOSITORIES); - Path repositoryFile = repository.getValue(); + Path repositoryFile = repository.getValueAsPath(context); if (repositoryFile != null) { // Handle the case when a specific repository is provided - if (!Files.exists(repositoryFile)) { - repositoryFile = repositoriesPath.resolve(repositoryFile.getFileName().toString() + ".properties"); - } - if (!Files.exists(repositoryFile)) { - Path legacyRepositoryFile = legacyRepositoriesPath.resolve(repositoryFile.getFileName().toString()); - if (Files.exists(legacyRepositoryFile)) { - repositoryFile = legacyRepositoryFile; - } else { - this.context.warning("Could not find " + repositoryFile); - return; - } - } doImportRepository(repositoryFile, true); } else { // If no specific repository is provided, check for repositories folder 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..0d10c6144 --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java @@ -0,0 +1,70 @@ +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.function.Consumer; + +public class RepositoryProperty extends Property { + + /** + * 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) { + + this(name, required, alias, null); + } + + /** + * 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, validator); + } + + @Override + public Class getValueType() { + + return String.class; + } + + @Override + public String parse(String valueAsString) { + + return valueAsString; + } + + public Path getValueAsPath(IdeContext context) { + + Path repositoriesPath = context.getSettingsPath().resolve(context.FOLDER_REPOSITORIES); + Path legacyRepositoriesPath = context.getSettingsPath().resolve(context.FOLDER_LEGACY_REPOSITORIES); + + Path repositoryFile = Path.of(super.getValue()); + // Handle the case when a specific repository is provided + if (!Files.exists(repositoryFile)) { + repositoryFile = repositoriesPath.resolve(repositoryFile.getFileName().toString() + ".properties"); + } + if (!Files.exists(repositoryFile)) { + Path legacyRepositoryFile = legacyRepositoriesPath.resolve(repositoryFile.getFileName().toString()); + if (Files.exists(legacyRepositoryFile)) { + repositoryFile = legacyRepositoryFile; + } else { + throw new IllegalStateException("Could not find " + repositoryFile); + } + } + return repositoryFile; + } + + +} From d047d6213f59e1daf9ccb3e91de1e62d28cfad56 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Tue, 19 Dec 2023 02:21:04 +0100 Subject: [PATCH 27/58] removed useless comment --- .../java/com/devonfw/tools/ide/property/RepositoryProperty.java | 1 - 1 file changed, 1 deletion(-) 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 index 0d10c6144..2a52ec5bc 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java @@ -51,7 +51,6 @@ public Path getValueAsPath(IdeContext context) { Path legacyRepositoriesPath = context.getSettingsPath().resolve(context.FOLDER_LEGACY_REPOSITORIES); Path repositoryFile = Path.of(super.getValue()); - // Handle the case when a specific repository is provided if (!Files.exists(repositoryFile)) { repositoryFile = repositoriesPath.resolve(repositoryFile.getFileName().toString() + ".properties"); } From 48db7c38f0b79cb1400d4c1987ceb69b14eb0650 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Tue, 2 Jan 2024 02:32:33 +0100 Subject: [PATCH 28/58] added null check --- .../java/com/devonfw/tools/ide/property/RepositoryProperty.java | 2 ++ 1 file changed, 2 insertions(+) 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 index 2a52ec5bc..26f22ac9d 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java @@ -51,6 +51,8 @@ public Path getValueAsPath(IdeContext context) { Path legacyRepositoriesPath = context.getSettingsPath().resolve(context.FOLDER_LEGACY_REPOSITORIES); Path repositoryFile = Path.of(super.getValue()); + if (repositoryFile == null) return null; + if (!Files.exists(repositoryFile)) { repositoryFile = repositoriesPath.resolve(repositoryFile.getFileName().toString() + ".properties"); } From dcecb50ce1c067e4d56dec3fe57461e3e9544862 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Tue, 2 Jan 2024 02:41:32 +0100 Subject: [PATCH 29/58] Update RepositoryProperty.java --- .../devonfw/tools/ide/property/RepositoryProperty.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 index 26f22ac9d..60be91c18 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java @@ -50,8 +50,12 @@ public Path getValueAsPath(IdeContext context) { Path repositoriesPath = context.getSettingsPath().resolve(context.FOLDER_REPOSITORIES); Path legacyRepositoriesPath = context.getSettingsPath().resolve(context.FOLDER_LEGACY_REPOSITORIES); - Path repositoryFile = Path.of(super.getValue()); - if (repositoryFile == null) return null; + Path repositoryFile; + if (super.getValue() != null) { + repositoryFile = Path.of(super.getValue()); + } else { + return null; + } if (!Files.exists(repositoryFile)) { repositoryFile = repositoriesPath.resolve(repositoryFile.getFileName().toString() + ".properties"); From 4880d124b25711fe8cff4c1fef809ece7c0bf87c Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Sun, 7 Jan 2024 22:25:00 +0100 Subject: [PATCH 30/58] Update cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Hohwiller --- .../com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 4587b2161..87351f80c 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -17,7 +17,7 @@ import static com.devonfw.tools.ide.commandlet.RepositoryConfig.loadProperties; /** - * {@link Commandlet} to setup a repository + * {@link Commandlet} to setup one or multiple GIT repositories for development. */ public class RepositoryCommandlet extends Commandlet { From de061fe4398c3124136853b1f0ded2043df45c2e Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Sun, 7 Jan 2024 22:25:12 +0100 Subject: [PATCH 31/58] Update cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Hohwiller --- .../devonfw/tools/ide/commandlet/RepositoryCommandlet.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index 87351f80c..e10e75b8e 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -46,8 +46,8 @@ public String getName() { @Override public void run() { - Path repositoriesPath = this.context.getSettingsPath().resolve(context.FOLDER_REPOSITORIES); - Path legacyRepositoriesPath = this.context.getSettingsPath().resolve(context.FOLDER_LEGACY_REPOSITORIES); + Path repositoriesPath = this.context.getSettingsPath().resolve(IdeContext.FOLDER_REPOSITORIES); + Path legacyRepositoriesPath = this.context.getSettingsPath().resolve(IdeContext.FOLDER_LEGACY_REPOSITORIES); Path repositoryFile = repository.getValueAsPath(context); if (repositoryFile != null) { From e62ea03522fc39fc152148ed8a84939541011e93 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Sun, 7 Jan 2024 22:28:10 +0100 Subject: [PATCH 32/58] Update cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Hohwiller --- .../com/devonfw/tools/ide/property/RepositoryProperty.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index 60be91c18..5d5f32090 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java @@ -47,8 +47,8 @@ public String parse(String valueAsString) { public Path getValueAsPath(IdeContext context) { - Path repositoriesPath = context.getSettingsPath().resolve(context.FOLDER_REPOSITORIES); - Path legacyRepositoriesPath = context.getSettingsPath().resolve(context.FOLDER_LEGACY_REPOSITORIES); + Path repositoriesPath = context.getSettingsPath().resolve(IdeContext.FOLDER_REPOSITORIES); + Path legacyRepositoriesPath = context.getSettingsPath().resolve(IdeContext.FOLDER_LEGACY_REPOSITORIES); Path repositoryFile; if (super.getValue() != null) { From 8a35bbd1c076ee5b6ec53ea0a1f0bdb430800a6a Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Sun, 7 Jan 2024 22:28:50 +0100 Subject: [PATCH 33/58] Update cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Hohwiller --- .../com/devonfw/tools/ide/property/RepositoryProperty.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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 index 5d5f32090..a2f529305 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java @@ -50,12 +50,11 @@ public Path getValueAsPath(IdeContext context) { Path repositoriesPath = context.getSettingsPath().resolve(IdeContext.FOLDER_REPOSITORIES); Path legacyRepositoriesPath = context.getSettingsPath().resolve(IdeContext.FOLDER_LEGACY_REPOSITORIES); - Path repositoryFile; - if (super.getValue() != null) { - repositoryFile = Path.of(super.getValue()); - } else { + String value = getValue(); + if (value == null) { return null; } + Path repositoryFile = Path.of(value); if (!Files.exists(repositoryFile)) { repositoryFile = repositoriesPath.resolve(repositoryFile.getFileName().toString() + ".properties"); From e8ba8f711d69f2a57d4d8efb02a43d5fdd5b1e59 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Sun, 7 Jan 2024 22:30:44 +0100 Subject: [PATCH 34/58] Update cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Hohwiller --- .../java/com/devonfw/tools/ide/property/RepositoryProperty.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index a2f529305..bf5f9b156 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java @@ -57,7 +57,7 @@ public Path getValueAsPath(IdeContext context) { Path repositoryFile = Path.of(value); if (!Files.exists(repositoryFile)) { - repositoryFile = repositoriesPath.resolve(repositoryFile.getFileName().toString() + ".properties"); + repositoryFile = repositoriesPath.resolve(value + ".properties"); } if (!Files.exists(repositoryFile)) { Path legacyRepositoryFile = legacyRepositoriesPath.resolve(repositoryFile.getFileName().toString()); From 21f14dfcb3c592e33ab69c1acef3f0299f16d2b1 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Mon, 8 Jan 2024 01:03:13 +0100 Subject: [PATCH 35/58] reviewed changes --- .../ide/commandlet/RepositoryCommandlet.java | 10 ++------- .../ide/commandlet/RepositoryConfig.java | 2 +- .../tools/ide/context/AbstractIdeContext.java | 1 + .../com/devonfw/tools/ide/io/FileAccess.java | 9 ++++++++ .../devonfw/tools/ide/io/FileAccessImpl.java | 16 ++++++++++++++ .../ide/property/RepositoryProperty.java | 21 +++++++------------ 6 files changed, 37 insertions(+), 22 deletions(-) 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 index e10e75b8e..92c95cf25 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -46,8 +46,6 @@ public String getName() { @Override public void run() { - Path repositoriesPath = this.context.getSettingsPath().resolve(IdeContext.FOLDER_REPOSITORIES); - Path legacyRepositoriesPath = this.context.getSettingsPath().resolve(IdeContext.FOLDER_LEGACY_REPOSITORIES); Path repositoryFile = repository.getValueAsPath(context); if (repositoryFile != null) { @@ -55,6 +53,8 @@ public void run() { 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; @@ -99,7 +99,6 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) { } this.context.debug(repositoryConfig.toString()); - this.context.debug("Pull or clone git repository {} ...", repository); String workspace = repositoryConfig.workspace() != null ? repositoryConfig.workspace() : "main"; Path workspacePath = this.context.getIdeHome().resolve("workspaces").resolve(workspace); @@ -127,10 +126,5 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) { this.context.info("Build command not set. Skipping build for repository."); } - if (!Files.exists(repositoryPath.resolve(".project"))) { - for (String ideCommandlet : repositoryConfig.imports()) { - //TODO: import repository to ideCommandlet - } - } } } 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 index 952fde680..89dea973e 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryConfig.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryConfig.java @@ -45,7 +45,7 @@ public static RepositoryConfig loadProperties(Path filePath) { 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"))); + Boolean.parseBoolean(properties.getProperty("active").trim())); } private static Set getImports(Properties properties) { 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 8bda33623..286ce81cd 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 @@ -601,6 +601,7 @@ public void gitPullOrClone(Path target, String gitRepoUrl) { if (!gitRepoUrl.startsWith("http")) { throw new IllegalArgumentException("Invalid git URL '" + gitRepoUrl + "'!"); } + debug("Pull or clone git repository {} ...", gitRepoUrl); ProcessContext pc = newProcess().directory(target).executable("git"); if (Files.isDirectory(target.resolve(".git"))) { ProcessResult result = pc.addArg("remote").run(true); 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 dbf787c8f..617492da8 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 @@ -140,4 +140,13 @@ default void copy(Path source, Path target) { */ List getChildrenInDir(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 930685813..ed6f9d447 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 @@ -477,4 +477,20 @@ public List getChildrenInDir(Path dir, Predicate filter) { return files; } + @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 (SecurityException e) { + throw new IllegalStateException("SecurityException while checking file existence."); + } + } + 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 index bf5f9b156..c37a75bee 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java @@ -4,6 +4,7 @@ import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; import java.util.function.Consumer; public class RepositoryProperty extends Property { @@ -47,28 +48,22 @@ public String parse(String valueAsString) { public Path getValueAsPath(IdeContext context) { - Path repositoriesPath = context.getSettingsPath().resolve(IdeContext.FOLDER_REPOSITORIES); - Path legacyRepositoriesPath = context.getSettingsPath().resolve(IdeContext.FOLDER_LEGACY_REPOSITORIES); - String value = getValue(); if (value == null) { return null; } + Path repositoryFile = Path.of(value); - if (!Files.exists(repositoryFile)) { - repositoryFile = repositoriesPath.resolve(value + ".properties"); + Path repositoriesPath = context.getSettingsPath().resolve(IdeContext.FOLDER_REPOSITORIES); + Path legacyRepositoriesPath = context.getSettingsPath().resolve(IdeContext.FOLDER_LEGACY_REPOSITORIES); + repositoryFile = context.getFileAccess().findExistingFile(value + ".properties", + Arrays.asList(repositoriesPath, legacyRepositoriesPath)); } - if (!Files.exists(repositoryFile)) { - Path legacyRepositoryFile = legacyRepositoriesPath.resolve(repositoryFile.getFileName().toString()); - if (Files.exists(legacyRepositoryFile)) { - repositoryFile = legacyRepositoryFile; - } else { - throw new IllegalStateException("Could not find " + repositoryFile); - } + if (repositoryFile == null) { + throw new IllegalStateException("Could not find " + value); } return repositoryFile; } - } From bc7b48ad7463b89cd66b11252e5dcfeabdeaa7af Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Thu, 25 Jan 2024 12:17:09 +0100 Subject: [PATCH 36/58] wrote test for repositoryCom --- .../commandlet/RepositoryCommandletTest.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 cli/src/test/java/com/devonfw/tools/ide/commandlet/RepositoryCommandletTest.java 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..6ff0738b7 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/RepositoryCommandletTest.java @@ -0,0 +1,32 @@ +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 org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test of {@link RepositoryCommandlet} + */ +class RepositoryCommandletTest extends AbstractIdeContextTest { + + @Test + public void runWithSpecifiedRepository() { + + //arrange + String path = "workspaces/foo-test/my-git-repo"; + IdeTestContext context = newContext("basic", path, true); + RepositoryCommandlet repositoryCommandlet = context.getCommandletManager().getCommandlet(RepositoryCommandlet.class); + repositoryCommandlet.repository.setValueAsString("repo", context); + //act + repositoryCommandlet.run(); + //assert + + + + + } + +} \ No newline at end of file From e21f222c1a67342cc667363006296290332d225f Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Fri, 26 Jan 2024 04:20:11 +0100 Subject: [PATCH 37/58] changed repositoryProperty to FileProperty inheritance --- .../ide/commandlet/RepositoryCommandlet.java | 4 +- .../ide/property/RepositoryProperty.java | 39 ++++++++----------- .../commandlet/RepositoryCommandletTest.java | 32 --------------- 3 files changed, 18 insertions(+), 57 deletions(-) delete mode 100644 cli/src/test/java/com/devonfw/tools/ide/commandlet/RepositoryCommandletTest.java 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 index 92c95cf25..66a16c23d 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -34,7 +34,7 @@ public RepositoryCommandlet(IdeContext context) { super(context); addKeyword(getName()); addKeyword("setup"); - this.repository = add(new RepositoryProperty("", false, "repository")); + this.repository = add(new RepositoryProperty("", false, "repository", true)); } @Override @@ -46,7 +46,7 @@ public String getName() { @Override public void run() { - Path repositoryFile = repository.getValueAsPath(context); + Path repositoryFile = repository.getValue(); if (repositoryFile != null) { // Handle the case when a specific repository is provided 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 index c841c6169..0439a0fb5 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java @@ -7,7 +7,7 @@ import java.util.Arrays; import java.util.function.Consumer; -public class RepositoryProperty extends Property { +public class RepositoryProperty extends FileProperty { /** * The constructor. @@ -15,10 +15,11 @@ public class RepositoryProperty extends Property { * @param name the {@link #getName() property name}. * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. + * @param mustExist the {@link #isPathRequiredToExist() required to exist flag}. */ - public RepositoryProperty(String name, boolean required, String alias) { + public RepositoryProperty(String name, boolean required, String alias, boolean mustExist) { - this(name, required, alias, null); + super(name, required, alias, mustExist); } /** @@ -27,41 +28,33 @@ public RepositoryProperty(String name, boolean required, String alias) { * @param name the {@link #getName() property name}. * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. + * @param mustExist the {@link #isPathRequiredToExist() required to exist flag}. * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}. */ - public RepositoryProperty(String name, boolean required, String alias, Consumer validator) { + public RepositoryProperty(String name, boolean required, String alias, boolean mustExist, Consumer validator) { - super(name, required, alias, validator); + super(name, required, alias, mustExist, validator); } - @Override - public Class getValueType() { + public Path parse(String valueAsString, IdeContext context) { - return String.class; - } - - @Override - public String parse(String valueAsString, IdeContext context) { - - return null; - } - - public Path getValueAsPath(IdeContext context) { - - String value = getValue(); - if (value == null) { + if (valueAsString == null) { return null; } - Path repositoryFile = Path.of(value); + 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); - repositoryFile = context.getFileAccess().findExistingFile(value + ".properties", + 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 " + value); + throw new IllegalStateException("Could not find properties file: " + valueAsString); } return repositoryFile; } 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 deleted file mode 100644 index 6ff0738b7..000000000 --- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/RepositoryCommandletTest.java +++ /dev/null @@ -1,32 +0,0 @@ -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 org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Test of {@link RepositoryCommandlet} - */ -class RepositoryCommandletTest extends AbstractIdeContextTest { - - @Test - public void runWithSpecifiedRepository() { - - //arrange - String path = "workspaces/foo-test/my-git-repo"; - IdeTestContext context = newContext("basic", path, true); - RepositoryCommandlet repositoryCommandlet = context.getCommandletManager().getCommandlet(RepositoryCommandlet.class); - repositoryCommandlet.repository.setValueAsString("repo", context); - //act - repositoryCommandlet.run(); - //assert - - - - - } - -} \ No newline at end of file From df05c7c2b933ea94ebb2c1d783b3e7013d43c221 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Fri, 2 Feb 2024 11:18:00 +0100 Subject: [PATCH 38/58] Update Ide_de.properties --- cli/src/main/resources/nls/Ide_de.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main/resources/nls/Ide_de.properties b/cli/src/main/resources/nls/Ide_de.properties index eabfb4b02..37f980e9b 100644 --- a/cli/src/main/resources/nls/Ide_de.properties +++ b/cli/src/main/resources/nls/Ide_de.properties @@ -8,7 +8,7 @@ cmd---version=Gibt die Version von IDEasy aus. cmd-eclipse=Werkzeug Kommando für Eclipse (IDE) cmd-env=Gibt die zu setztenden und exportierenden Umgebungsvariablen aus. cmd-get-version=Zeigt die Version des selektierten Werkzeugs an. -cmd-gh=Werkzeug Kommando für die Github Kommandoschnittstelle +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. From 7b119df7dfff3d730576e674ac64230664f42fc4 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 21 Feb 2024 15:04:22 +0100 Subject: [PATCH 39/58] Update cli/src/main/java/com/devonfw/tools/ide/io/FileAccessImpl.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Hohwiller --- .../main/java/com/devonfw/tools/ide/io/FileAccessImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 822c4087c..1fc4b1229 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 @@ -652,8 +652,8 @@ public Path findExistingFile(String fileName, List searchDirs) { if (Files.exists(filePath)) { return filePath; } - } catch (SecurityException e) { - throw new IllegalStateException("SecurityException while checking file existence."); + } catch (Exception e) { + throw new IllegalStateException("Unexpected error while checking existence of file "+filePath+" .", e); } } return null; From 7a3c3c23e3734cef6a8a927313596964d5669608 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Thu, 29 Feb 2024 05:54:12 +0100 Subject: [PATCH 40/58] added tests for repo commandlet --- .../ide/commandlet/RepositoryCommandlet.java | 2 +- .../commandlet/RepositoryCommandletTest.java | 112 ++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 cli/src/test/java/com/devonfw/tools/ide/commandlet/RepositoryCommandletTest.java 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 index bf16a0bbf..4d34de39a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -94,7 +94,7 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) { 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); + , repositoryFile.getFileName().toString()); return; } 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..66146c7e8 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/RepositoryCommandletTest.java @@ -0,0 +1,112 @@ +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("basic", null, true); + + /** + * Properties object used to write key-value pairs to the properties file "test.properties" + */ + Properties properties = new Properties(); + + 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", "test"); + + Path repositoriesPath = this.context.getSettingsPath().resolve(IdeContext.FOLDER_REPOSITORIES); + this.context.getFileAccess().mkdirs(repositoriesPath); + Path propertiesPath = repositoriesPath.resolve("test.properties"); + 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("test.properties"); + try (var output = Files.newOutputStream(propertiesPath)) { + properties.store(output, null); + } catch (IOException e) { + throw new IllegalStateException("Failed to save properties file during tests.", e); + } + } + + @Test + public void testRunWithSpecificRepository() { + + // arrange + RepositoryCommandlet rc = context.getCommandletManager().getCommandlet(RepositoryCommandlet.class); + createPropertiesFile(); + rc.repository.setValueAsString("test", context); + // act + rc.run(); + // assert + assertLogMessage(context, IdeLogLevel.INFO, "Importing repository from test.properties ..."); + } + + @Test + public void testRunWithNoSpecificRepositoryAndInactive() { + + // arrange + RepositoryCommandlet rc = context.getCommandletManager().getCommandlet(RepositoryCommandlet.class); + createPropertiesFile(); + // act + rc.run(); + // assert + assertLogMessage(context, IdeLogLevel.INFO, "Importing repository from test.properties ..."); + 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("test", context); + // act + rc.run(); + // assert + assertLogMessage(context, IdeLogLevel.WARNING, + "Invalid repository configuration test.properties - 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."); + } +} \ No newline at end of file From e5b794acc1f6a989cbc87d88edec344d1c4201f1 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Thu, 29 Feb 2024 06:26:17 +0100 Subject: [PATCH 41/58] removed my stupid fix --- cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java | 1 - 1 file changed, 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java b/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java index cbd1f89ac..a1bb433b3 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java +++ b/cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java @@ -179,7 +179,6 @@ public Path getPath(String tool) { */ public void setPath(String tool, Path path) { - this.paths.add(path); this.tool2pathMap.put(tool, path); } From 9254184b0362c03ff388c2eddcb92b4ed5d47baf Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:38:14 +0100 Subject: [PATCH 42/58] removed * import --- .../java/com/devonfw/tools/ide/merge/DirectoryMerger.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cli/src/main/java/com/devonfw/tools/ide/merge/DirectoryMerger.java b/cli/src/main/java/com/devonfw/tools/ide/merge/DirectoryMerger.java index fe3b8fd3a..612117c43 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/merge/DirectoryMerger.java +++ b/cli/src/main/java/com/devonfw/tools/ide/merge/DirectoryMerger.java @@ -3,7 +3,11 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; import org.jline.utils.Log; From 2b99a21077cff9261caee1fd8df172c9bb7c416e Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Mar 2024 04:06:26 +0100 Subject: [PATCH 43/58] Update cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Hohwiller --- .../com/devonfw/tools/ide/property/RepositoryProperty.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 index 0439a0fb5..df0c92b84 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java @@ -15,11 +15,10 @@ public class RepositoryProperty extends FileProperty { * @param name the {@link #getName() property name}. * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. - * @param mustExist the {@link #isPathRequiredToExist() required to exist flag}. */ - public RepositoryProperty(String name, boolean required, String alias, boolean mustExist) { + public RepositoryProperty(String name, boolean required, String alias) { - super(name, required, alias, mustExist); + super(name, required, alias, true); } /** From 6129a5a6275134b06b7646979c2616df8d279d4c Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Mar 2024 04:07:13 +0100 Subject: [PATCH 44/58] Update cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Hohwiller --- .../com/devonfw/tools/ide/property/RepositoryProperty.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 index df0c92b84..8059c201e 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java +++ b/cli/src/main/java/com/devonfw/tools/ide/property/RepositoryProperty.java @@ -27,12 +27,11 @@ public RepositoryProperty(String name, boolean required, String alias) { * @param name the {@link #getName() property name}. * @param required the {@link #isRequired() required flag}. * @param alias the {@link #getAlias() property alias}. - * @param mustExist the {@link #isPathRequiredToExist() required to exist flag}. * @param validator the {@link Consumer} used to {@link #validate() validate} the {@link #getValue() value}. */ - public RepositoryProperty(String name, boolean required, String alias, boolean mustExist, Consumer validator) { + public RepositoryProperty(String name, boolean required, String alias, Consumer validator) { - super(name, required, alias, mustExist, validator); + super(name, required, alias, true, validator); } public Path parse(String valueAsString, IdeContext context) { From 4b5b005365b357fd5bcb2db22dce2f8b0cff5eff Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Mar 2024 04:09:56 +0100 Subject: [PATCH 45/58] Update cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jörg Hohwiller --- .../com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 4d34de39a..762cbb79b 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -34,7 +34,7 @@ public RepositoryCommandlet(IdeContext context) { super(context); addKeyword(getName()); addKeyword("setup"); - this.repository = add(new RepositoryProperty("", false, "repository", true)); + this.repository = add(new RepositoryProperty("", false, "repository")); } @Override From d079c78ac84f9f7d9c5e793f0839ece754800132 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Mar 2024 18:28:38 +0100 Subject: [PATCH 46/58] reviewed changes --- .../ide/commandlet/RepositoryCommandlet.java | 9 ++-- .../tools/ide/context/AbstractIdeContext.java | 2 +- .../devonfw/tools/ide/context/GitContext.java | 43 ++++++++++--------- .../tools/ide/context/GitContextImpl.java | 37 ++++++++-------- .../com/devonfw/tools/ide/io/FileAccess.java | 9 ++++ .../devonfw/tools/ide/io/FileAccessImpl.java | 16 +++++++ .../tools/ide/context/GitContextMock.java | 6 +-- .../tools/ide/context/GitContextTest.java | 10 ++--- 8 files changed, 76 insertions(+), 56 deletions(-) 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 index 4d34de39a..17afc1010 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -65,7 +65,7 @@ public void run() { return; } - List propertiesFiles = this.context.getFileAccess().getChildrenInDir(repositories, + List propertiesFiles = this.context.getFileAccess().listChildren(repositories, path -> path.getFileName().toString().endsWith(".properties")); boolean forceMode = this.context.isForceMode(); @@ -104,12 +104,9 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) { Path workspacePath = this.context.getIdeHome().resolve("workspaces").resolve(workspace); this.context.getFileAccess().mkdirs(workspacePath); - if (repositoryConfig.gitBranch() != null && !repositoryConfig.gitBranch().isEmpty()) { - gitUrl = gitUrl + "#" + repositoryConfig.gitBranch(); - } - Path repositoryPath = workspacePath.resolve(repository); - this.context.getGitContext().pullOrClone(gitUrl, repositoryPath); + String branch = repositoryConfig.gitBranch() != null ? repositoryConfig.gitBranch() : ""; + this.context.getGitContext().pullOrClone(gitUrl, branch, repositoryPath); String buildCmd = repositoryConfig.buildCmd(); this.context.debug("Building repository with ide command: {}", buildCmd); 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..d514eb48e 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, "", 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..c79c7a7ea 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 @@ -14,44 +14,45 @@ public interface GitContext extends IdeLogger { * 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. + * to check-out. * @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. + * to check-out. + * @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. + * name to check-out. + * @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. */ - void pullOrClone(String gitRepoUrl, Path targetRepository); + 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 +60,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 +69,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 +80,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..6d01b0e72 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"); @@ -58,7 +60,7 @@ public void pullOrCloneIfNeeded(String repoUrl, Path targetRepository) { // Check if the file modification time is older than the delta threshold if ((currentTime - fileMTime > GIT_PULL_CACHE_DELAY_MILLIS.toMillis()) || context.isForceMode()) { - pullOrClone(repoUrl, targetRepository); + pullOrClone(repoUrl, "", targetRepository); try { Files.setLastModifiedTime(magicFilePath, FileTime.fromMillis(currentTime)); } catch (IOException e) { @@ -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"); @@ -85,7 +87,7 @@ public void pullOrFetchAndResetIfNeeded(String repoUrl, Path targetRepository, S } @Override - public void pullOrClone(String gitRepoUrl, Path targetRepository) { + public void pullOrClone(String gitRepoUrl, String branch, Path targetRepository) { Objects.requireNonNull(targetRepository); Objects.requireNonNull(gitRepoUrl); @@ -112,12 +114,6 @@ 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); @@ -130,8 +126,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 +140,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 +155,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,7 +179,8 @@ 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", "-b", gitRepoUrl.branch(), 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); 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 2cbb80b9b..e1a2d2a14 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/test/java/com/devonfw/tools/ide/context/GitContextMock.java b/cli/src/test/java/com/devonfw/tools/ide/context/GitContextMock.java index f90fc6f49..15681af20 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,17 +7,17 @@ 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) { } @Override - public void pullOrClone(String gitRepoUrl, Path targetRepository) { + public void pullOrClone(String gitRepoUrl, String branch, 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..090f0445b 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()) @@ -55,7 +55,7 @@ public void testRunGitClone(@TempDir Path tempDir) { IdeContext context = newGitContext(tempDir, errors, outs, 0, true); GitContext gitContext = new GitContextImpl(context); // act - gitContext.pullOrClone(gitRepoUrl, tempDir); + gitContext.pullOrClone(gitRepoUrl, "", tempDir); // assert assertThat(tempDir.resolve(".git").resolve("url")).hasContent(gitRepoUrl); } @@ -78,7 +78,7 @@ public void testRunGitPullWithoutForce(@TempDir Path tempDir) { Path gitFolderPath = tempDir.resolve(".git"); fileAccess.mkdirs(gitFolderPath); // act - gitContext.pullOrClone(gitRepoUrl, tempDir); + gitContext.pullOrClone(gitRepoUrl, "", tempDir); // assert assertThat(tempDir.resolve(".git").resolve("update")).hasContent(currentDate.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(); } From 7a9bb0b5d6881fdc717f5bbc96157af0e7cf2d5d Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Mar 2024 18:36:10 +0100 Subject: [PATCH 47/58] minor change --- .../devonfw/tools/ide/commandlet/RepositoryCommandlet.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 index 917f9cb62..d85cdcc9a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -105,7 +105,10 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) { this.context.getFileAccess().mkdirs(workspacePath); Path repositoryPath = workspacePath.resolve(repository); - String branch = repositoryConfig.gitBranch() != null ? repositoryConfig.gitBranch() : ""; + String branch = ""; + if (repositoryConfig.gitBranch() != null) { + branch = repositoryConfig.gitBranch(); + } this.context.getGitContext().pullOrClone(gitUrl, branch, repositoryPath); String buildCmd = repositoryConfig.buildCmd(); From 4236eaec8d5a6da996617dcd4f3f30427fcf0ff1 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Mar 2024 18:59:18 +0100 Subject: [PATCH 48/58] crlf diff --- cli/src/main/java/com/devonfw/tools/ide/io/FileAccess.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 e1a2d2a14..e49770f0c 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 @@ -226,9 +226,9 @@ default void extract(Path archiveFile, Path targetDir, Consumer postExtrac /** * 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. + * @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); From 862eabc16f421a5206ab662dc4271b8732dac65b Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Mar 2024 19:30:54 +0100 Subject: [PATCH 49/58] diff war --- .../com/devonfw/tools/ide/io/FileAccess.java | 470 +++++++++--------- 1 file changed, 235 insertions(+), 235 deletions(-) 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 e49770f0c..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 @@ -1,235 +1,235 @@ -package com.devonfw.tools.ide.io; - -import java.nio.file.Path; -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Predicate; - -/** - * Interface that gives access to various operations on files. - */ -public interface FileAccess { - - /** - * Downloads a file from an arbitrary location. - * - * @param url the location of the binary file to download. May also be a local or remote path to copy from. - * @param targetFile the {@link Path} to the target file to download to. Should not already exists. Missing parent - * directories will be created automatically. - */ - void download(String url, Path targetFile); - - /** - * Creates the entire {@link Path} as directories if not already existing. - * - * @param directory the {@link Path} to - * {@link java.nio.file.Files#createDirectories(Path, java.nio.file.attribute.FileAttribute...) create}. - */ - void mkdirs(Path directory); - - /** - * @param file the {@link Path} to check. - * @return {@code true} if the given {@code file} points to an existing file, {@code false} otherwise (the given - * {@link Path} does not exist or is a directory). - */ - boolean isFile(Path file); - - /** - * @param folder the {@link Path} to check. - * @return {@code true} if the given {@code folder} points to an existing directory, {@code false} otherwise (a - * warning is logged in this case). - */ - boolean isExpectedFolder(Path folder); - - /** - * @param file the {@link Path} to compute the checksum of. - * @return the computed checksum (SHA-266). - */ - String checksum(Path file); - - /** - * Moves the given {@link Path} to the backup. - * - * @param fileOrFolder the {@link Path} to move to the backup (soft-deletion). - */ - void backup(Path fileOrFolder); - - /** - * @param source the source {@link Path file or folder} to move. - * @param targetDir the {@link Path} with the directory to move {@code source} into. - */ - void move(Path source, Path targetDir); - - /** - * Creates a symbolic link. If the given {@code targetLink} already exists and is a symbolic link or a Windows - * junction, it will be replaced. In case of missing privileges, Windows Junctions may be used as fallback, which must - * point to absolute paths. Therefore, the created link will be absolute instead of relative. - * - * @param source the source {@link Path} to link to, may be relative or absolute. - * @param targetLink the {@link Path} where the symbolic link shall be created pointing to {@code source}. - * @param relative - {@code true} if the symbolic link shall be relative, {@code false} if it shall be absolute. - */ - void symlink(Path source, Path targetLink, boolean relative); - - /** - * Creates a relative symbolic link. If the given {@code targetLink} already exists and is a symbolic link or a - * Windows junction, it will be replaced. In case of missing privileges, Windows Junctions may be used as fallback, - * which must point to absolute paths. Therefore, the created link will be absolute instead of relative. - * - * @param source the source {@link Path} to link to, may be relative or absolute. - * @param targetLink the {@link Path} where the symbolic link shall be created pointing to {@code source}. - */ - default void symlink(Path source, Path targetLink) { - - symlink(source, targetLink, true); - } - - /** - * @param source the source {@link Path file or folder} to copy. - * @param target the {@link Path} to copy {@code source} to. See {@link #copy(Path, Path, FileCopyMode)} for details. - * will always ensure that in the end you will find the same content of {@code source} in {@code target}. - */ - default void copy(Path source, Path target) { - - copy(source, target, FileCopyMode.COPY_TREE_FAIL_IF_EXISTS); - } - - /** - * @param source the source {@link Path file or folder} to copy. - * @param target the {@link Path} to copy {@code source} to. Unlike the Linux {@code cp} command this method will not - * take the filename of {@code source} and copy that to {@code target} in case that is an existing folder. Instead it - * will always be simple and stupid and just copy from {@code source} to {@code target}. Therefore the result is - * always clear and easy to predict and understand. Also you can easily rename a file to copy. While - * {@code cp my-file target} may lead to a different result than {@code cp my-file target/} this method will always - * ensure that in the end you will find the same content of {@code source} in {@code target}. - * @param fileOnly - {@code true} if {@code fileOrFolder} is expected to be a file and an exception shall be thrown if - * it is a directory, {@code false} otherwise (copy recursively). - */ - void copy(Path source, Path target, FileCopyMode fileOnly); - - /** - * @param archiveFile the {@link Path} to the file to extract. - * @param targetDir the {@link Path} to the directory where to extract the {@code archiveFile} to. - */ - default void extract(Path archiveFile, Path targetDir) { - - extract(archiveFile, targetDir, null); - } - - /** - * @param archiveFile the {@link Path} to the archive file to extract. - * @param targetDir the {@link Path} to the directory where to extract the {@code archiveFile}. - * @param postExtractHook the {@link Consumer} to be called after the extraction on the final folder before it is - * moved to {@code targetDir}. - */ - default void extract(Path archiveFile, Path targetDir, Consumer postExtractHook) { - - extract(archiveFile, targetDir, postExtractHook, true); - } - - /** - * @param archiveFile the {@link Path} to the archive file to extract. - * @param targetDir the {@link Path} to the directory where to extract the {@code archiveFile}. - * @param postExtractHook the {@link Consumer} to be called after the extraction on the final folder before it is - * moved to {@code targetDir}. - * @param extract {@code true} if the {@code archiveFile} should be extracted (default), {@code false} otherwise. - */ - void extract(Path archiveFile, Path targetDir, Consumer postExtractHook, boolean extract); - - /** - * Extracts a ZIP file what is the common archive format on Windows. Initially invented by PKZIP for MS-DOS and also - * famous from WinZIP software for Windows. - * - * @param file the ZIP file to extract. - * @param targetDir the {@link Path} with the directory to unzip to. - */ - void extractZip(Path file, Path targetDir); - - /** - * @param file the ZIP file to extract. - * @param targetDir the {@link Path} with the directory to unzip to. - * @param compression the {@link TarCompression} to use. - */ - void extractTar(Path file, Path targetDir, TarCompression compression); - - /** - * Extracts an Apple DMG (Disk Image) file that is similar to an ISO image. DMG files are commonly used for software - * releases on MacOS. Double-clicking such files on MacOS mounts them and show the application together with a - * symbolic link to the central applications folder and some help instructions. The user then copies the application - * to the applications folder via drag and drop in order to perform the installation. - * - * @param file the DMG file to extract. - * @param targetDir the target directory where to extract the contents to. - */ - void extractDmg(Path file, Path targetDir); - - /** - * Extracts an MSI (Microsoft Installer) file. MSI files are commonly used for software releases on Windows that allow - * an installation wizard and easy later uninstallation. - * - * @param file the MSI file to extract. - * @param targetDir the target directory where to extract the contents to. - */ - void extractMsi(Path file, Path targetDir); - - /** - * Extracts an Apple PKG (Package) file. PKG files are used instead of {@link #extractDmg(Path, Path) DMG files} if - * additional changes have to be performed like drivers to be installed. Similar to what - * {@link #extractMsi(Path, Path) MSI} is on Windows. PKG files are internally a xar based archive with a specific - * structure. - * - * @param file the PKG file to extract. - * @param targetDir the target directory where to extract the contents to. - */ - void extractPkg(Path file, Path targetDir); - - /** - * @param path the {@link Path} to convert. - * @return the absolute and physical {@link Path} (without symbolic links). - */ - Path toRealPath(Path path); - - /** - * Deletes the given {@link Path} idempotent and recursive. - * - * @param path the {@link Path} to delete. - */ - void delete(Path path); - - /** - * Creates a new temporary directory. ATTENTION: The user of this method is responsible to do house-keeping and - * {@link #delete(Path) delete} it after the work is done. - * - * @param name the default name of the temporary directory to create. A prefix or suffix may be added to ensure - * uniqueness. - * @return the {@link Path} to the newly created and unique temporary directory. - */ - Path createTempDir(String name); - - /** - * @param dir the folder to search. - * @param filter the {@link Predicate} used to find the {@link Predicate#test(Object) match}. - * @param recursive - {@code true} to search recursive in all sub-folders, {@code false} otherwise. - * @return the first child {@link Path} matching the given {@link Predicate} or {@code null} if no match was found. - */ - Path findFirst(Path dir, Predicate filter, boolean recursive); - - /** - * @param dir the {@link Path} to the directory where to list the children. - * @param filter the {@link Predicate} used to {@link Predicate#test(Object) decide} which children to include (if - * {@code true} is returned). - * @return all children of the given {@link Path} that match the given {@link Predicate}. Will be the empty list of - * the given {@link Path} is not an existing directory. - */ - 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); - -} +package com.devonfw.tools.ide.io; + +import java.nio.file.Path; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Predicate; + +/** + * Interface that gives access to various operations on files. + */ +public interface FileAccess { + + /** + * Downloads a file from an arbitrary location. + * + * @param url the location of the binary file to download. May also be a local or remote path to copy from. + * @param targetFile the {@link Path} to the target file to download to. Should not already exists. Missing parent + * directories will be created automatically. + */ + void download(String url, Path targetFile); + + /** + * Creates the entire {@link Path} as directories if not already existing. + * + * @param directory the {@link Path} to + * {@link java.nio.file.Files#createDirectories(Path, java.nio.file.attribute.FileAttribute...) create}. + */ + void mkdirs(Path directory); + + /** + * @param file the {@link Path} to check. + * @return {@code true} if the given {@code file} points to an existing file, {@code false} otherwise (the given + * {@link Path} does not exist or is a directory). + */ + boolean isFile(Path file); + + /** + * @param folder the {@link Path} to check. + * @return {@code true} if the given {@code folder} points to an existing directory, {@code false} otherwise (a + * warning is logged in this case). + */ + boolean isExpectedFolder(Path folder); + + /** + * @param file the {@link Path} to compute the checksum of. + * @return the computed checksum (SHA-266). + */ + String checksum(Path file); + + /** + * Moves the given {@link Path} to the backup. + * + * @param fileOrFolder the {@link Path} to move to the backup (soft-deletion). + */ + void backup(Path fileOrFolder); + + /** + * @param source the source {@link Path file or folder} to move. + * @param targetDir the {@link Path} with the directory to move {@code source} into. + */ + void move(Path source, Path targetDir); + + /** + * Creates a symbolic link. If the given {@code targetLink} already exists and is a symbolic link or a Windows + * junction, it will be replaced. In case of missing privileges, Windows Junctions may be used as fallback, which must + * point to absolute paths. Therefore, the created link will be absolute instead of relative. + * + * @param source the source {@link Path} to link to, may be relative or absolute. + * @param targetLink the {@link Path} where the symbolic link shall be created pointing to {@code source}. + * @param relative - {@code true} if the symbolic link shall be relative, {@code false} if it shall be absolute. + */ + void symlink(Path source, Path targetLink, boolean relative); + + /** + * Creates a relative symbolic link. If the given {@code targetLink} already exists and is a symbolic link or a + * Windows junction, it will be replaced. In case of missing privileges, Windows Junctions may be used as fallback, + * which must point to absolute paths. Therefore, the created link will be absolute instead of relative. + * + * @param source the source {@link Path} to link to, may be relative or absolute. + * @param targetLink the {@link Path} where the symbolic link shall be created pointing to {@code source}. + */ + default void symlink(Path source, Path targetLink) { + + symlink(source, targetLink, true); + } + + /** + * @param source the source {@link Path file or folder} to copy. + * @param target the {@link Path} to copy {@code source} to. See {@link #copy(Path, Path, FileCopyMode)} for details. + * will always ensure that in the end you will find the same content of {@code source} in {@code target}. + */ + default void copy(Path source, Path target) { + + copy(source, target, FileCopyMode.COPY_TREE_FAIL_IF_EXISTS); + } + + /** + * @param source the source {@link Path file or folder} to copy. + * @param target the {@link Path} to copy {@code source} to. Unlike the Linux {@code cp} command this method will not + * take the filename of {@code source} and copy that to {@code target} in case that is an existing folder. Instead it + * will always be simple and stupid and just copy from {@code source} to {@code target}. Therefore the result is + * always clear and easy to predict and understand. Also you can easily rename a file to copy. While + * {@code cp my-file target} may lead to a different result than {@code cp my-file target/} this method will always + * ensure that in the end you will find the same content of {@code source} in {@code target}. + * @param fileOnly - {@code true} if {@code fileOrFolder} is expected to be a file and an exception shall be thrown if + * it is a directory, {@code false} otherwise (copy recursively). + */ + void copy(Path source, Path target, FileCopyMode fileOnly); + + /** + * @param archiveFile the {@link Path} to the file to extract. + * @param targetDir the {@link Path} to the directory where to extract the {@code archiveFile} to. + */ + default void extract(Path archiveFile, Path targetDir) { + + extract(archiveFile, targetDir, null); + } + + /** + * @param archiveFile the {@link Path} to the archive file to extract. + * @param targetDir the {@link Path} to the directory where to extract the {@code archiveFile}. + * @param postExtractHook the {@link Consumer} to be called after the extraction on the final folder before it is + * moved to {@code targetDir}. + */ + default void extract(Path archiveFile, Path targetDir, Consumer postExtractHook) { + + extract(archiveFile, targetDir, postExtractHook, true); + } + + /** + * @param archiveFile the {@link Path} to the archive file to extract. + * @param targetDir the {@link Path} to the directory where to extract the {@code archiveFile}. + * @param postExtractHook the {@link Consumer} to be called after the extraction on the final folder before it is + * moved to {@code targetDir}. + * @param extract {@code true} if the {@code archiveFile} should be extracted (default), {@code false} otherwise. + */ + void extract(Path archiveFile, Path targetDir, Consumer postExtractHook, boolean extract); + + /** + * Extracts a ZIP file what is the common archive format on Windows. Initially invented by PKZIP for MS-DOS and also + * famous from WinZIP software for Windows. + * + * @param file the ZIP file to extract. + * @param targetDir the {@link Path} with the directory to unzip to. + */ + void extractZip(Path file, Path targetDir); + + /** + * @param file the ZIP file to extract. + * @param targetDir the {@link Path} with the directory to unzip to. + * @param compression the {@link TarCompression} to use. + */ + void extractTar(Path file, Path targetDir, TarCompression compression); + + /** + * Extracts an Apple DMG (Disk Image) file that is similar to an ISO image. DMG files are commonly used for software + * releases on MacOS. Double-clicking such files on MacOS mounts them and show the application together with a + * symbolic link to the central applications folder and some help instructions. The user then copies the application + * to the applications folder via drag and drop in order to perform the installation. + * + * @param file the DMG file to extract. + * @param targetDir the target directory where to extract the contents to. + */ + void extractDmg(Path file, Path targetDir); + + /** + * Extracts an MSI (Microsoft Installer) file. MSI files are commonly used for software releases on Windows that allow + * an installation wizard and easy later uninstallation. + * + * @param file the MSI file to extract. + * @param targetDir the target directory where to extract the contents to. + */ + void extractMsi(Path file, Path targetDir); + + /** + * Extracts an Apple PKG (Package) file. PKG files are used instead of {@link #extractDmg(Path, Path) DMG files} if + * additional changes have to be performed like drivers to be installed. Similar to what + * {@link #extractMsi(Path, Path) MSI} is on Windows. PKG files are internally a xar based archive with a specific + * structure. + * + * @param file the PKG file to extract. + * @param targetDir the target directory where to extract the contents to. + */ + void extractPkg(Path file, Path targetDir); + + /** + * @param path the {@link Path} to convert. + * @return the absolute and physical {@link Path} (without symbolic links). + */ + Path toRealPath(Path path); + + /** + * Deletes the given {@link Path} idempotent and recursive. + * + * @param path the {@link Path} to delete. + */ + void delete(Path path); + + /** + * Creates a new temporary directory. ATTENTION: The user of this method is responsible to do house-keeping and + * {@link #delete(Path) delete} it after the work is done. + * + * @param name the default name of the temporary directory to create. A prefix or suffix may be added to ensure + * uniqueness. + * @return the {@link Path} to the newly created and unique temporary directory. + */ + Path createTempDir(String name); + + /** + * @param dir the folder to search. + * @param filter the {@link Predicate} used to find the {@link Predicate#test(Object) match}. + * @param recursive - {@code true} to search recursive in all sub-folders, {@code false} otherwise. + * @return the first child {@link Path} matching the given {@link Predicate} or {@code null} if no match was found. + */ + Path findFirst(Path dir, Predicate filter, boolean recursive); + + /** + * @param dir the {@link Path} to the directory where to list the children. + * @param filter the {@link Predicate} used to {@link Predicate#test(Object) decide} which children to include (if + * {@code true} is returned). + * @return all children of the given {@link Path} that match the given {@link Predicate}. Will be the empty list of + * the given {@link Path} is not an existing directory. + */ + 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); + +} From de2c3f9020dbee99dc0aca785f1b07c449fece54 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:04:23 +0100 Subject: [PATCH 50/58] solved minor bug --- .../tools/ide/context/GitContextImpl.java | 576 +++++++++--------- 1 file changed, 292 insertions(+), 284 deletions(-) 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 6d01b0e72..c7d06718b 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 @@ -1,284 +1,292 @@ -package com.devonfw.tools.ide.context; - -import java.io.IOException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.attribute.FileTime; -import java.time.Duration; -import java.util.AbstractMap; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import com.devonfw.tools.ide.cli.CliException; -import com.devonfw.tools.ide.log.IdeLogLevel; -import com.devonfw.tools.ide.log.IdeSubLogger; -import com.devonfw.tools.ide.process.ProcessContext; -import com.devonfw.tools.ide.process.ProcessErrorHandling; -import com.devonfw.tools.ide.process.ProcessMode; -import com.devonfw.tools.ide.process.ProcessResult; - -/** - * Implements the {@link GitContext}. - */ -public class GitContextImpl implements GitContext { - private static final Duration GIT_PULL_CACHE_DELAY_MILLIS = Duration.ofMillis(30 * 60 * 1000); - - ; - - private final IdeContext context; - - private ProcessContext processContext; - - /** - * @param context the {@link IdeContext context}. - */ - public GitContextImpl(IdeContext context) { - - this.context = context; - - } - - @Override - public void pullOrCloneIfNeeded(String repoUrl, String branch, Path targetRepository) { - - Path gitDirectory = targetRepository.resolve(".git"); - - // Check if the .git directory exists - if (Files.isDirectory(gitDirectory)) { - Path magicFilePath = gitDirectory.resolve("HEAD"); - long currentTime = System.currentTimeMillis(); - // Get the modification time of the magic file - long fileMTime; - try { - fileMTime = Files.getLastModifiedTime(magicFilePath).toMillis(); - } catch (IOException e) { - throw new IllegalStateException("Could not read " + magicFilePath, e); - } - - // Check if the file modification time is older than the delta threshold - if ((currentTime - fileMTime > GIT_PULL_CACHE_DELAY_MILLIS.toMillis()) || context.isForceMode()) { - pullOrClone(repoUrl, "", targetRepository); - try { - Files.setLastModifiedTime(magicFilePath, FileTime.fromMillis(currentTime)); - } catch (IOException e) { - throw new IllegalStateException("Could not read or write in " + magicFilePath, e); - } - } - } else { - // If the .git directory does not exist, perform git clone - pullOrClone(repoUrl, branch, targetRepository); - } - } - - public void pullOrFetchAndResetIfNeeded(String repoUrl, String branch, Path targetRepository, String remoteName) { - - pullOrCloneIfNeeded(repoUrl, branch, targetRepository); - - if (remoteName.isEmpty()) { - reset(targetRepository, "origin", "master"); - } else { - reset(targetRepository, remoteName, "master"); - } - - cleanup(targetRepository); - } - - @Override - public void pullOrClone(String gitRepoUrl, String branch, Path targetRepository) { - - Objects.requireNonNull(targetRepository); - Objects.requireNonNull(gitRepoUrl); - - if (!gitRepoUrl.startsWith("http")) { - throw new IllegalArgumentException("Invalid git URL '" + gitRepoUrl + "'!"); - } - - initializeProcessContext(targetRepository); - if (Files.isDirectory(targetRepository.resolve(".git"))) { - // checks for remotes - ProcessResult result = this.processContext.addArg("remote").run(ProcessMode.DEFAULT_CAPTURE); - List remotes = result.getOut(); - if (remotes.isEmpty()) { - String message = targetRepository - + " is a local git repository with no remote - if you did this for testing, you may continue...\n" - + "Do you want to ignore the problem and continue anyhow?"; - this.context.askToContinue(message); - } else { - this.processContext.errorHandling(ProcessErrorHandling.WARNING); - - if (!this.context.isOffline()) { - pull(targetRepository); - } - } - } else { - clone(new GitUrl(gitRepoUrl, branch), targetRepository); - if (!branch.isEmpty()) { - this.processContext.addArgs("checkout", branch); - this.processContext.run(); - } - } - } - - /** - * 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. - * @param result the {@link ProcessResult} to evaluate. - */ - private void handleErrors(Path targetRepository, ProcessResult result) { - - if (!result.isSuccessful()) { - String message = "Failed to update git repository at " + targetRepository; - if (this.context.isOffline()) { - this.context.warning(message); - this.context.interaction("Continuing as we are in offline mode - results may be outdated!"); - } 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."); - } else { - this.context.error( - "It seems you are offline - please ensure Internet connectivity and retry or activate offline mode (-o or --offline)."); - } - this.context.askToContinue("Typically you should abort and fix the problem. Do you want to continue anyways?"); - } - } - } - - /** - * 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. - */ - private void initializeProcessContext(Path targetRepository) { - - if (this.processContext == null) { - this.processContext = this.context.newProcess().directory(targetRepository).executable("git") - .withEnvVar("GIT_TERMINAL_PROMPT", "0"); - } - } - - @Override - public void clone(GitUrl gitRepoUrl, Path targetRepository) { - - URL parsedUrl = gitRepoUrl.parseUrl(); - initializeProcessContext(targetRepository); - ProcessResult result; - if (!this.context.isOffline()) { - this.context.getFileAccess().mkdirs(targetRepository); - this.context.requireOnline("git clone of " + parsedUrl); - this.processContext.addArg("clone"); - if (this.context.isQuietMode()) { - this.processContext.addArg("-q"); - } - this.processContext.addArgs("--recursive", "-b", gitRepoUrl.branch(), 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); - } - } else { - throw new CliException("Could not clone " + parsedUrl + " to " + targetRepository + " because you are offline."); - } - } - - @Override - public void pull(Path targetRepository) { - - initializeProcessContext(targetRepository); - ProcessResult result; - // pull from remote - result = this.processContext.addArg("--no-pager").addArg("pull").run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.isSuccessful()) { - Map remoteAndBranchName = retrieveRemoteAndBranchName(); - context.warning("Git pull for {}/{} failed for repository {}.", remoteAndBranchName.get("remote"), - remoteAndBranchName.get("branch"), targetRepository); - handleErrors(targetRepository, result); - } - } - - private Map retrieveRemoteAndBranchName() { - - Map remoteAndBranchName = new HashMap<>(); - ProcessResult remoteResult = this.processContext.addArg("branch").addArg("-vv").run(ProcessMode.DEFAULT_CAPTURE); - List remotes = remoteResult.getOut(); - if (!remotes.isEmpty()) { - for (String remote : remotes) { - if (remote.startsWith("*")) { - String checkedOutBranch = remote.substring(remote.indexOf("[") + 1, remote.indexOf("]")); - remoteAndBranchName.put("remote", checkedOutBranch.substring(0, checkedOutBranch.indexOf("/"))); - // check if current repo is behind remote and omit message - if (checkedOutBranch.contains(":")) { - remoteAndBranchName.put("branch", - checkedOutBranch.substring(checkedOutBranch.indexOf("/") + 1, checkedOutBranch.indexOf(":"))); - } else { - remoteAndBranchName.put("branch", checkedOutBranch.substring(checkedOutBranch.indexOf("/") + 1)); - } - - } - } - } else { - return Map.ofEntries(new AbstractMap.SimpleEntry<>("remote", "unknown"), - new AbstractMap.SimpleEntry<>("branch", "unknown")); - } - - return remoteAndBranchName; - } - - @Override - public void reset(Path targetRepository, String remoteName, String branchName) { - - initializeProcessContext(targetRepository); - ProcessResult result; - // check for changed files - result = this.processContext.addArg("diff-index").addArg("--quiet").addArg("HEAD").run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.isSuccessful()) { - // reset to origin/master - context.warning("Git has detected modified files -- attempting to reset {} to '{}/{}'.", targetRepository, - remoteName, branchName); - result = this.processContext.addArg("reset").addArg("--hard").addArg(remoteName + "/" + branchName) - .run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.isSuccessful()) { - context.warning("Git failed to reset {} to '{}/{}'.", remoteName, branchName, targetRepository); - handleErrors(targetRepository, result); - } - } - } - - @Override - public void cleanup(Path targetRepository) { - - initializeProcessContext(targetRepository); - ProcessResult result; - // check for untracked files - result = this.processContext.addArg("ls-files").addArg("--other").addArg("--directory").addArg("--exclude-standard") - .run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.getOut().isEmpty()) { - // delete untracked files - context.warning("Git detected untracked files in {} and is attempting a cleanup.", targetRepository); - result = this.processContext.addArg("clean").addArg("-df").run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.isSuccessful()) { - context.warning("Git failed to clean the repository {}.", targetRepository); - } - } - } - - @Override - public IdeSubLogger level(IdeLogLevel level) { - - return null; - } -} +package com.devonfw.tools.ide.context; + +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.time.Duration; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import com.devonfw.tools.ide.cli.CliException; +import com.devonfw.tools.ide.log.IdeLogLevel; +import com.devonfw.tools.ide.log.IdeSubLogger; +import com.devonfw.tools.ide.process.ProcessContext; +import com.devonfw.tools.ide.process.ProcessErrorHandling; +import com.devonfw.tools.ide.process.ProcessMode; +import com.devonfw.tools.ide.process.ProcessResult; + +/** + * Implements the {@link GitContext}. + */ +public class GitContextImpl implements GitContext { + private static final Duration GIT_PULL_CACHE_DELAY_MILLIS = Duration.ofMillis(30 * 60 * 1000); + + ; + + private final IdeContext context; + + private ProcessContext processContext; + + /** + * @param context the {@link IdeContext context}. + */ + public GitContextImpl(IdeContext context) { + + this.context = context; + + } + + @Override + public void pullOrCloneIfNeeded(String repoUrl, String branch, Path targetRepository) { + + Path gitDirectory = targetRepository.resolve(".git"); + + // Check if the .git directory exists + if (Files.isDirectory(gitDirectory)) { + Path magicFilePath = gitDirectory.resolve("HEAD"); + long currentTime = System.currentTimeMillis(); + // Get the modification time of the magic file + long fileMTime; + try { + fileMTime = Files.getLastModifiedTime(magicFilePath).toMillis(); + } catch (IOException e) { + throw new IllegalStateException("Could not read " + magicFilePath, e); + } + + // Check if the file modification time is older than the delta threshold + if ((currentTime - fileMTime > GIT_PULL_CACHE_DELAY_MILLIS.toMillis()) || context.isForceMode()) { + pullOrClone(repoUrl, "", targetRepository); + try { + Files.setLastModifiedTime(magicFilePath, FileTime.fromMillis(currentTime)); + } catch (IOException e) { + throw new IllegalStateException("Could not read or write in " + magicFilePath, e); + } + } + } else { + // If the .git directory does not exist, perform git clone + pullOrClone(repoUrl, branch, targetRepository); + } + } + + public void pullOrFetchAndResetIfNeeded(String repoUrl, String branch, Path targetRepository, String remoteName) { + + pullOrCloneIfNeeded(repoUrl, branch, targetRepository); + + if (remoteName.isEmpty()) { + reset(targetRepository, "origin", "master"); + } else { + reset(targetRepository, remoteName, "master"); + } + + cleanup(targetRepository); + } + + @Override + public void pullOrClone(String gitRepoUrl, String branch, Path targetRepository) { + + Objects.requireNonNull(targetRepository); + Objects.requireNonNull(gitRepoUrl); + + if (!gitRepoUrl.startsWith("http")) { + throw new IllegalArgumentException("Invalid git URL '" + gitRepoUrl + "'!"); + } + + initializeProcessContext(targetRepository); + if (Files.isDirectory(targetRepository.resolve(".git"))) { + // checks for remotes + ProcessResult result = this.processContext.addArg("remote").run(ProcessMode.DEFAULT_CAPTURE); + List remotes = result.getOut(); + if (remotes.isEmpty()) { + String message = targetRepository + + " is a local git repository with no remote - if you did this for testing, you may continue...\n" + + "Do you want to ignore the problem and continue anyhow?"; + this.context.askToContinue(message); + } else { + this.processContext.errorHandling(ProcessErrorHandling.WARNING); + + if (!this.context.isOffline()) { + pull(targetRepository); + } + } + } else { + clone(new GitUrl(gitRepoUrl, branch), targetRepository); + if (!branch.isEmpty()) { + this.processContext.addArgs("checkout", branch); + this.processContext.run(); + } + } + } + + /** + * 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. + * @param result the {@link ProcessResult} to evaluate. + */ + private void handleErrors(Path targetRepository, ProcessResult result) { + + if (!result.isSuccessful()) { + String message = "Failed to update git repository at " + targetRepository; + if (this.context.isOffline()) { + this.context.warning(message); + this.context.interaction("Continuing as we are in offline mode - results may be outdated!"); + } 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."); + } else { + this.context.error( + "It seems you are offline - please ensure Internet connectivity and retry or activate offline mode (-o or --offline)."); + } + this.context.askToContinue("Typically you should abort and fix the problem. Do you want to continue anyways?"); + } + } + } + + /** + * 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. + */ + private void initializeProcessContext(Path targetRepository) { + + if (this.processContext == null) { + this.processContext = this.context.newProcess().directory(targetRepository).executable("git") + .withEnvVar("GIT_TERMINAL_PROMPT", "0"); + } + } + + @Override + public void clone(GitUrl gitRepoUrl, Path targetRepository) { + + URL parsedUrl = gitRepoUrl.parseUrl(); + initializeProcessContext(targetRepository); + ProcessResult result; + if (!this.context.isOffline()) { + this.context.getFileAccess().mkdirs(targetRepository); + this.context.requireOnline("git clone of " + parsedUrl); + this.processContext.addArg("clone"); + if (this.context.isQuietMode()) { + this.processContext.addArg("-q"); + } + 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); + this.processContext.run(); + 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."); + } + } + + @Override + public void pull(Path targetRepository) { + + initializeProcessContext(targetRepository); + ProcessResult result; + // pull from remote + result = this.processContext.addArg("--no-pager").addArg("pull").run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.isSuccessful()) { + Map remoteAndBranchName = retrieveRemoteAndBranchName(); + context.warning("Git pull for {}/{} failed for repository {}.", remoteAndBranchName.get("remote"), + remoteAndBranchName.get("branch"), targetRepository); + handleErrors(targetRepository, result); + } + } + + private Map retrieveRemoteAndBranchName() { + + Map remoteAndBranchName = new HashMap<>(); + ProcessResult remoteResult = this.processContext.addArg("branch").addArg("-vv").run(ProcessMode.DEFAULT_CAPTURE); + List remotes = remoteResult.getOut(); + if (!remotes.isEmpty()) { + for (String remote : remotes) { + if (remote.startsWith("*")) { + String checkedOutBranch = remote.substring(remote.indexOf("[") + 1, remote.indexOf("]")); + remoteAndBranchName.put("remote", checkedOutBranch.substring(0, checkedOutBranch.indexOf("/"))); + // check if current repo is behind remote and omit message + if (checkedOutBranch.contains(":")) { + remoteAndBranchName.put("branch", + checkedOutBranch.substring(checkedOutBranch.indexOf("/") + 1, checkedOutBranch.indexOf(":"))); + } else { + remoteAndBranchName.put("branch", checkedOutBranch.substring(checkedOutBranch.indexOf("/") + 1)); + } + + } + } + } else { + return Map.ofEntries(new AbstractMap.SimpleEntry<>("remote", "unknown"), + new AbstractMap.SimpleEntry<>("branch", "unknown")); + } + + return remoteAndBranchName; + } + + @Override + public void reset(Path targetRepository, String remoteName, String branchName) { + + initializeProcessContext(targetRepository); + ProcessResult result; + // check for changed files + result = this.processContext.addArg("diff-index").addArg("--quiet").addArg("HEAD").run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.isSuccessful()) { + // reset to origin/master + context.warning("Git has detected modified files -- attempting to reset {} to '{}/{}'.", targetRepository, + remoteName, branchName); + result = this.processContext.addArg("reset").addArg("--hard").addArg(remoteName + "/" + branchName) + .run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.isSuccessful()) { + context.warning("Git failed to reset {} to '{}/{}'.", remoteName, branchName, targetRepository); + handleErrors(targetRepository, result); + } + } + } + + @Override + public void cleanup(Path targetRepository) { + + initializeProcessContext(targetRepository); + ProcessResult result; + // check for untracked files + result = this.processContext.addArg("ls-files").addArg("--other").addArg("--directory").addArg("--exclude-standard") + .run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.getOut().isEmpty()) { + // delete untracked files + context.warning("Git detected untracked files in {} and is attempting a cleanup.", targetRepository); + result = this.processContext.addArg("clean").addArg("-df").run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.isSuccessful()) { + context.warning("Git failed to clean the repository {}.", targetRepository); + } + } + } + + @Override + public IdeSubLogger level(IdeLogLevel level) { + + return null; + } +} From 6a1eefcc6a0021f8d170255f4c3172373ec8a351 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:30:26 +0100 Subject: [PATCH 51/58] cleanup --- .../java/com/devonfw/tools/ide/context/GitContextImpl.java | 5 ----- 1 file changed, 5 deletions(-) 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 c7d06718b..9813bdd5e 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 @@ -115,10 +115,6 @@ public void pullOrClone(String gitRepoUrl, String branch, Path targetRepository) } } else { clone(new GitUrl(gitRepoUrl, branch), targetRepository); - if (!branch.isEmpty()) { - this.processContext.addArgs("checkout", branch); - this.processContext.run(); - } } } @@ -187,7 +183,6 @@ public void clone(GitUrl gitRepoUrl, Path targetRepository) { String branch = gitRepoUrl.branch(); if (branch != null) { this.processContext.addArgs("checkout", branch); - this.processContext.run(); result = this.processContext.run(ProcessMode.DEFAULT_CAPTURE); if (!result.isSuccessful()) { this.context.warning("Git failed to checkout to branch {}", branch); From 8a06af51e5683157484b09c0d44703f8490fd558 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:38:55 +0100 Subject: [PATCH 52/58] again crlf switching .. --- .../tools/ide/context/AbstractIdeContext.java | 1716 ++++++++--------- 1 file changed, 858 insertions(+), 858 deletions(-) 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 d514eb48e..9d60ccfa1 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 @@ -1,858 +1,858 @@ -package com.devonfw.tools.ide.context; - -import com.devonfw.tools.ide.cli.CliArgument; -import com.devonfw.tools.ide.cli.CliArguments; -import com.devonfw.tools.ide.cli.CliException; -import com.devonfw.tools.ide.commandlet.Commandlet; -import com.devonfw.tools.ide.commandlet.CommandletManager; -import com.devonfw.tools.ide.commandlet.CommandletManagerImpl; -import com.devonfw.tools.ide.commandlet.ContextCommandlet; -import com.devonfw.tools.ide.commandlet.HelpCommandlet; -import com.devonfw.tools.ide.common.SystemPath; -import com.devonfw.tools.ide.completion.CompletionCandidate; -import com.devonfw.tools.ide.completion.CompletionCandidateCollector; -import com.devonfw.tools.ide.completion.CompletionCandidateCollectorDefault; -import com.devonfw.tools.ide.environment.AbstractEnvironmentVariables; -import com.devonfw.tools.ide.environment.EnvironmentVariables; -import com.devonfw.tools.ide.environment.EnvironmentVariablesType; -import com.devonfw.tools.ide.io.FileAccess; -import com.devonfw.tools.ide.io.FileAccessImpl; -import com.devonfw.tools.ide.log.IdeLogLevel; -import com.devonfw.tools.ide.log.IdeSubLogger; -import com.devonfw.tools.ide.log.IdeSubLoggerNone; -import com.devonfw.tools.ide.merge.DirectoryMerger; -import com.devonfw.tools.ide.os.SystemInfo; -import com.devonfw.tools.ide.os.SystemInfoImpl; -import com.devonfw.tools.ide.process.ProcessContext; -import com.devonfw.tools.ide.process.ProcessContextImpl; -import com.devonfw.tools.ide.process.ProcessResult; -import com.devonfw.tools.ide.property.Property; -import com.devonfw.tools.ide.repo.CustomToolRepository; -import com.devonfw.tools.ide.repo.CustomToolRepositoryImpl; -import com.devonfw.tools.ide.repo.DefaultToolRepository; -import com.devonfw.tools.ide.repo.ToolRepository; -import com.devonfw.tools.ide.url.model.UrlMetadata; - -import java.io.IOException; -import java.net.InetAddress; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.function.Function; - -/** - * Abstract base implementation of {@link IdeContext}. - */ -public abstract class AbstractIdeContext implements IdeContext { - - private static final String IDE_URLS_GIT = "https://github.com/devonfw/ide-urls.git"; - - private final Map loggers; - - private final Path ideHome; - - private final Path ideRoot; - - private final Path confPath; - - private final Path settingsPath; - - private final Path softwarePath; - - private final Path softwareRepositoryPath; - - private final Path pluginsPath; - - private final Path workspacePath; - - private final String workspaceName; - - private final Path urlsPath; - - private final Path tempPath; - - private final Path tempDownloadPath; - - private final Path cwd; - - private final Path downloadPath; - - private final Path toolRepository; - - private final Path userHome; - - private final Path userHomeIde; - - private final SystemPath path; - - private final SystemInfo systemInfo; - - private final EnvironmentVariables variables; - - private final FileAccess fileAccess; - - private final CommandletManager commandletManager; - - private final ToolRepository defaultToolRepository; - - private final CustomToolRepository customToolRepository; - - private final DirectoryMerger workspaceMerger; - - private final Function loggerFactory; - - private boolean offlineMode; - - private boolean forceMode; - - private boolean batchMode; - - private boolean quietMode; - - private Locale locale; - - private UrlMetadata urlMetadata; - - private Path defaultExecutionDirectory; - - /** - * The constructor. - * - * @param minLogLevel the minimum {@link IdeLogLevel} to enable. Should be {@link IdeLogLevel#INFO} by default. - * @param factory the {@link Function} to create {@link IdeSubLogger} per {@link IdeLogLevel}. - * @param userDir the optional {@link Path} to current working directory. - * @param toolRepository @param toolRepository the {@link ToolRepository} of the context. If it is set to {@code null} - * {@link DefaultToolRepository} will be used. - */ - public AbstractIdeContext(IdeLogLevel minLogLevel, Function factory, Path userDir, - ToolRepository toolRepository) { - - super(); - this.loggerFactory = factory; - this.loggers = new HashMap<>(); - setLogLevel(minLogLevel); - this.systemInfo = SystemInfoImpl.INSTANCE; - this.commandletManager = new CommandletManagerImpl(this); - this.fileAccess = new FileAccessImpl(this); - String workspace = WORKSPACE_MAIN; - if (userDir == null) { - this.cwd = Path.of(System.getProperty("user.dir")); - } else { - this.cwd = userDir.toAbsolutePath(); - } - // detect IDE_HOME and WORKSPACE - Path currentDir = this.cwd; - String name1 = ""; - String name2 = ""; - while (currentDir != null) { - trace("Looking for IDE_HOME in {}", currentDir); - if (isIdeHome(currentDir)) { - if (FOLDER_WORKSPACES.equals(name1)) { - workspace = name2; - } - break; - } - name2 = name1; - int nameCount = currentDir.getNameCount(); - if (nameCount >= 1) { - name1 = currentDir.getName(nameCount - 1).toString(); - } - currentDir = getParentPath(currentDir); - } - // detection completed, initializing variables - this.ideHome = currentDir; - this.workspaceName = workspace; - if (this.ideHome == null) { - info(getMessageIdeHomeNotFound()); - this.workspacePath = null; - this.ideRoot = null; - this.confPath = null; - this.settingsPath = null; - this.softwarePath = null; - this.pluginsPath = null; - } else { - debug(getMessageIdeHomeFound()); - this.workspacePath = this.ideHome.resolve(FOLDER_WORKSPACES).resolve(this.workspaceName); - Path ideRootPath = this.ideHome.getParent(); - String root = null; - if (!isTest()) { - root = System.getenv("IDE_ROOT"); - } - if (root != null) { - Path rootPath = Path.of(root); - if (Files.isDirectory(rootPath)) { - if (!ideRootPath.equals(rootPath)) { - warning( - "Variable IDE_ROOT is set to '{}' but for your project '{}' the path '{}' would have been expected.", - root, this.ideHome.getFileName(), ideRootPath); - } - ideRootPath = rootPath; - } else { - warning("Variable IDE_ROOT is not set to a valid directory '{}'." + root); - ideRootPath = null; - } - } - this.ideRoot = ideRootPath; - this.confPath = this.ideHome.resolve(FOLDER_CONF); - this.settingsPath = this.ideHome.resolve(FOLDER_SETTINGS); - this.softwarePath = this.ideHome.resolve(FOLDER_SOFTWARE); - this.pluginsPath = this.ideHome.resolve(FOLDER_PLUGINS); - } - if (this.ideRoot == null) { - this.toolRepository = null; - this.urlsPath = null; - this.tempPath = null; - this.tempDownloadPath = null; - this.softwareRepositoryPath = null; - } else { - Path ideBase = this.ideRoot.resolve(FOLDER_IDE); - this.toolRepository = ideBase.resolve("software"); - this.urlsPath = ideBase.resolve("urls"); - this.tempPath = ideBase.resolve("tmp"); - this.tempDownloadPath = this.tempPath.resolve(FOLDER_DOWNLOADS); - this.softwareRepositoryPath = ideBase.resolve(FOLDER_SOFTWARE); - if (Files.isDirectory(this.tempPath)) { - // TODO delete all files older than 1 day here... - } else { - this.fileAccess.mkdirs(this.tempDownloadPath); - } - } - if (isTest()) { - // only for testing... - if (this.ideHome == null) { - this.userHome = Path.of("/non-existing-user-home-for-testing"); - } else { - this.userHome = this.ideHome.resolve("home"); - } - } else { - this.userHome = Path.of(System.getProperty("user.home")); - } - this.userHomeIde = this.userHome.resolve(".ide"); - this.downloadPath = this.userHome.resolve("Downloads/ide"); - this.variables = createVariables(); - this.path = computeSystemPath(); - - if (toolRepository == null) { - this.defaultToolRepository = new DefaultToolRepository(this); - } else { - this.defaultToolRepository = toolRepository; - } - - this.customToolRepository = CustomToolRepositoryImpl.of(this); - this.workspaceMerger = new DirectoryMerger(this); - } - - private String getMessageIdeHomeFound() { - - return "IDE environment variables have been set for " + this.ideHome + " in workspace " + this.workspaceName; - } - - private String getMessageIdeHomeNotFound() { - - return "You are not inside an IDE installation: " + this.cwd; - } - - /** - * @return the status message about the {@link #getIdeHome() IDE_HOME} detection and environment variable - * initialization. - */ - public String getMessageIdeHome() { - - if (this.ideHome == null) { - return getMessageIdeHomeNotFound(); - } - return getMessageIdeHomeFound(); - } - - /** - * @return {@code true} if this is a test context for JUnits, {@code false} otherwise. - */ - public boolean isTest() { - - return isMock(); - } - - /** - * @return {@code true} if this is a mock context for JUnits, {@code false} otherwise. - */ - public boolean isMock() { - - return false; - } - - private SystemPath computeSystemPath() { - - return new SystemPath(this); - } - - private boolean isIdeHome(Path dir) { - - if (!Files.isDirectory(dir.resolve("workspaces"))) { - return false; - } else if (!Files.isDirectory(dir.resolve("settings"))) { - return false; - } - return true; - } - - private Path getParentPath(Path dir) { - - try { - Path linkDir = dir.toRealPath(); - if (!dir.equals(linkDir)) { - return linkDir; - } else { - return dir.getParent(); - } - } catch (IOException e) { - throw new IllegalStateException(e); - } - - } - - private EnvironmentVariables createVariables() { - - AbstractEnvironmentVariables system = EnvironmentVariables.ofSystem(this); - AbstractEnvironmentVariables user = extendVariables(system, this.userHomeIde, EnvironmentVariablesType.USER); - AbstractEnvironmentVariables settings = extendVariables(user, this.settingsPath, EnvironmentVariablesType.SETTINGS); - // TODO should we keep this workspace properties? Was this feature ever used? - AbstractEnvironmentVariables workspace = extendVariables(settings, this.workspacePath, - EnvironmentVariablesType.WORKSPACE); - AbstractEnvironmentVariables conf = extendVariables(workspace, this.confPath, EnvironmentVariablesType.CONF); - return conf.resolved(); - } - - private AbstractEnvironmentVariables extendVariables(AbstractEnvironmentVariables envVariables, Path propertiesPath, - EnvironmentVariablesType type) { - - Path propertiesFile = null; - if (propertiesPath == null) { - trace("Configuration directory for type {} does not exist.", type); - } else if (Files.isDirectory(propertiesPath)) { - propertiesFile = propertiesPath.resolve(EnvironmentVariables.DEFAULT_PROPERTIES); - boolean legacySupport = (type != EnvironmentVariablesType.USER); - if (legacySupport && !Files.exists(propertiesFile)) { - Path legacyFile = propertiesPath.resolve(EnvironmentVariables.LEGACY_PROPERTIES); - if (Files.exists(legacyFile)) { - propertiesFile = legacyFile; - } - } - } else { - debug("Configuration directory {} does not exist.", propertiesPath); - } - return envVariables.extend(propertiesFile, type); - } - - @Override - public SystemInfo getSystemInfo() { - - return this.systemInfo; - } - - @Override - public FileAccess getFileAccess() { - - return this.fileAccess; - } - - @Override - public CommandletManager getCommandletManager() { - - return this.commandletManager; - } - - @Override - public ToolRepository getDefaultToolRepository() { - - return this.defaultToolRepository; - } - - @Override - public CustomToolRepository getCustomToolRepository() { - - return this.customToolRepository; - } - - @Override - public Path getIdeHome() { - - return this.ideHome; - } - - @Override - public Path getIdeRoot() { - - return this.ideRoot; - } - - @Override - public Path getCwd() { - - return this.cwd; - } - - @Override - public Path getTempPath() { - - return this.tempPath; - } - - @Override - public Path getTempDownloadPath() { - - return this.tempDownloadPath; - } - - @Override - public Path getUserHome() { - - return this.userHome; - } - - @Override - public Path getUserHomeIde() { - - return this.userHomeIde; - } - - @Override - public Path getSettingsPath() { - - return this.settingsPath; - } - - @Override - public Path getConfPath() { - - return this.confPath; - } - - @Override - public Path getSoftwarePath() { - - return this.softwarePath; - } - - @Override - public Path getSoftwareRepositoryPath() { - - return this.softwareRepositoryPath; - } - - @Override - public Path getPluginsPath() { - - return this.pluginsPath; - } - - @Override - public String getWorkspaceName() { - - return this.workspaceName; - } - - @Override - public Path getWorkspacePath() { - - return this.workspacePath; - } - - @Override - public Path getDownloadPath() { - - return this.downloadPath; - } - - @Override - public Path getUrlsPath() { - - return this.urlsPath; - } - - @Override - public Path getToolRepositoryPath() { - - return this.toolRepository; - } - - @Override - public SystemPath getPath() { - - return this.path; - } - - @Override - public EnvironmentVariables getVariables() { - - return this.variables; - } - - @Override - public UrlMetadata getUrls() { - - if (this.urlMetadata == null) { - if (!isTest()) { - this.getGitContext().pullOrFetchAndResetIfNeeded(IDE_URLS_GIT, "", this.urlsPath, "origin"); - } - this.urlMetadata = new UrlMetadata(this); - } - return this.urlMetadata; - } - - @Override - public boolean isQuietMode() { - - return this.quietMode; - } - - /** - * @param quietMode new value of {@link #isQuietMode()}. - */ - public void setQuietMode(boolean quietMode) { - - this.quietMode = quietMode; - } - - @Override - public boolean isBatchMode() { - - return this.batchMode; - } - - /** - * @param batchMode new value of {@link #isBatchMode()}. - */ - public void setBatchMode(boolean batchMode) { - - this.batchMode = batchMode; - } - - @Override - public boolean isForceMode() { - - return this.forceMode; - } - - /** - * @param forceMode new value of {@link #isForceMode()}. - */ - public void setForceMode(boolean forceMode) { - - this.forceMode = forceMode; - } - - @Override - public boolean isOfflineMode() { - - return this.offlineMode; - } - - /** - * @param offlineMode new value of {@link #isOfflineMode()}. - */ - public void setOfflineMode(boolean offlineMode) { - - this.offlineMode = offlineMode; - } - - @Override - public boolean isOnline() { - - boolean online = false; - try { - int timeout = 1000; - online = InetAddress.getByName("github.com").isReachable(timeout); - } catch (Exception ignored) { - - } - return online; - } - - @Override - public Locale getLocale() { - - if (this.locale == null) { - return Locale.getDefault(); - } - return this.locale; - } - - /** - * @param locale new value of {@link #getLocale()}. - */ - public void setLocale(Locale locale) { - - this.locale = locale; - } - - @Override - public DirectoryMerger getWorkspaceMerger() { - - return this.workspaceMerger; - } - - /** - * @return the {@link #defaultExecutionDirectory} the directory in which a command process is executed. - */ - public Path getDefaultExecutionDirectory() { - - return this.defaultExecutionDirectory; - } - - /** - * @param defaultExecutionDirectory new value of {@link #getDefaultExecutionDirectory()}. - */ - public void setDefaultExecutionDirectory(Path defaultExecutionDirectory) { - - if (defaultExecutionDirectory != null) { - this.defaultExecutionDirectory = defaultExecutionDirectory; - } - } - - @Override - public GitContext getGitContext() { - - return new GitContextImpl(this); - } - - @Override - public ProcessContext newProcess() { - - ProcessContext processContext = createProcessContext(); - if (this.defaultExecutionDirectory != null) { - processContext.directory(this.defaultExecutionDirectory); - } - return processContext; - } - - /** - * @return a new instance of {@link ProcessContext}. - * @see #newProcess() - */ - protected ProcessContext createProcessContext() { - - return new ProcessContextImpl(this); - } - - @Override - public IdeSubLogger level(IdeLogLevel level) { - - IdeSubLogger logger = this.loggers.get(level); - Objects.requireNonNull(logger); - return logger; - } - - @SuppressWarnings("unchecked") - @Override - public O question(String question, O... options) { - - assert (options.length >= 2); - interaction(question); - Map mapping = new HashMap<>(options.length); - int i = 0; - for (O option : options) { - i++; - String key = "" + option; - addMapping(mapping, key, option); - String numericKey = Integer.toString(i); - if (numericKey.equals(key)) { - trace("Options should not be numeric: " + key); - } else { - addMapping(mapping, numericKey, option); - } - interaction("Option " + numericKey + ": " + key); - } - O option = null; - if (isBatchMode()) { - if (isForceMode()) { - option = options[0]; - interaction("" + option); - } - } else { - while (option == null) { - String answer = readLine(); - option = mapping.get(answer); - if (option == null) { - warning("Invalid answer: '" + answer + "' - please try again."); - } - } - } - return option; - } - - /** - * @return the input from the end-user (e.g. read from the console). - */ - protected abstract String readLine(); - - private static void addMapping(Map mapping, String key, O option) { - - O duplicate = mapping.put(key, option); - if (duplicate != null) { - throw new IllegalArgumentException("Duplicated option " + key); - } - } - - /** - * Sets the log level. - * - * @param logLevel {@link IdeLogLevel} - */ - public void setLogLevel(IdeLogLevel logLevel) { - - for (IdeLogLevel level : IdeLogLevel.values()) { - IdeSubLogger logger; - if (level.ordinal() < logLevel.ordinal()) { - logger = new IdeSubLoggerNone(level); - } else { - logger = this.loggerFactory.apply(level); - } - this.loggers.put(level, logger); - } - } - - /** - * Finds the matching {@link Commandlet} to run, applies {@link CliArguments} to its - * {@link Commandlet#getProperties() properties} and will execute it. - * - * @param arguments the {@link CliArgument}. - * @return the return code of the execution. - */ - public int run(CliArguments arguments) { - - CliArgument current = arguments.current(); - if (!current.isEnd()) { - String keyword = current.get(); - Commandlet firstCandidate = this.commandletManager.getCommandletByFirstKeyword(keyword); - boolean matches; - if (firstCandidate != null) { - matches = applyAndRun(arguments.copy(), firstCandidate); - if (matches) { - return ProcessResult.SUCCESS; - } - } - for (Commandlet cmd : this.commandletManager.getCommandlets()) { - if (cmd != firstCandidate) { - matches = applyAndRun(arguments.copy(), cmd); - if (matches) { - return ProcessResult.SUCCESS; - } - } - } - error("Invalid arguments: {}", current.getArgs()); - } - this.commandletManager.getCommandlet(HelpCommandlet.class).run(); - return 1; - } - - /** - * @param cmd the potential {@link Commandlet} to - * {@link #apply(CliArguments, Commandlet, CompletionCandidateCollector) apply} and {@link Commandlet#run() run}. - * @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully, - * {@code false} otherwise (the {@link Commandlet} did not match and we have to try a different candidate). - */ - private boolean applyAndRun(CliArguments arguments, Commandlet cmd) { - - boolean matches = apply(arguments, cmd, null); - if (matches) { - matches = cmd.validate(); - } - if (matches) { - debug("Running commandlet {}", cmd); - if (cmd.isIdeHomeRequired() && (this.ideHome == null)) { - throw new CliException(getMessageIdeHomeNotFound()); - } - cmd.run(); - } else { - trace("Commandlet did not match"); - } - return matches; - } - - /** - * @param arguments the {@link CliArguments#ofCompletion(String...) completion arguments}. - * @param includeContextOptions to include the options of {@link ContextCommandlet}. - * @return the {@link List} of {@link CompletionCandidate}s to suggest. - */ - public List complete(CliArguments arguments, boolean includeContextOptions) { - - CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault(this); - if (arguments.current().isStart()) { - arguments.next(); - } - if (includeContextOptions) { - ContextCommandlet cc = new ContextCommandlet(); - for (Property property : cc.getProperties()) { - assert (property.isOption()); - property.apply(arguments, this, cc, collector); - } - } - CliArgument current = arguments.current(); - if (!current.isEnd()) { - String keyword = current.get(); - Commandlet firstCandidate = this.commandletManager.getCommandletByFirstKeyword(keyword); - boolean matches = false; - if (firstCandidate != null) { - matches = apply(arguments.copy(), firstCandidate, collector); - } else if (current.isCombinedShortOption()) { - collector.add(keyword, null, null, null); - } - if (!matches) { - for (Commandlet cmd : this.commandletManager.getCommandlets()) { - if (cmd != firstCandidate) { - apply(arguments.copy(), cmd, collector); - } - } - } - } - return collector.getSortedCandidates(); - } - - /** - * @param arguments the {@link CliArguments} to apply. Will be {@link CliArguments#next() consumed} as they are - * matched. Consider passing a {@link CliArguments#copy() copy} as needed. - * @param cmd the potential {@link Commandlet} to match. - * @param collector the {@link CompletionCandidateCollector}. - * @return {@code true} if the given {@link Commandlet} matches to the given {@link CliArgument}(s) and those have - * been applied (set in the {@link Commandlet} and {@link Commandlet#validate() validated}), {@code false} otherwise - * (the {@link Commandlet} did not match and we have to try a different candidate). - */ - public boolean apply(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) { - - trace("Trying to match arguments to commandlet {}", cmd.getName()); - CliArgument currentArgument = arguments.current(); - Iterator> propertyIterator; - if (currentArgument.isCompletion()) { - propertyIterator = cmd.getProperties().iterator(); - } else { - propertyIterator = cmd.getValues().iterator(); - } - while (!currentArgument.isEnd()) { - trace("Trying to match argument '{}'", currentArgument); - Property property = null; - if (!arguments.isEndOptions()) { - property = cmd.getOption(currentArgument.getKey()); - } - if (property == null) { - if (!propertyIterator.hasNext()) { - trace("No option or next value found"); - return false; - } - property = propertyIterator.next(); - } - trace("Next property candidate to match argument is {}", property); - boolean matches = property.apply(arguments, this, cmd, collector); - if (!matches || currentArgument.isCompletion()) { - return false; - } - currentArgument = arguments.current(); - } - return true; - } - -} +package com.devonfw.tools.ide.context; + +import com.devonfw.tools.ide.cli.CliArgument; +import com.devonfw.tools.ide.cli.CliArguments; +import com.devonfw.tools.ide.cli.CliException; +import com.devonfw.tools.ide.commandlet.Commandlet; +import com.devonfw.tools.ide.commandlet.CommandletManager; +import com.devonfw.tools.ide.commandlet.CommandletManagerImpl; +import com.devonfw.tools.ide.commandlet.ContextCommandlet; +import com.devonfw.tools.ide.commandlet.HelpCommandlet; +import com.devonfw.tools.ide.common.SystemPath; +import com.devonfw.tools.ide.completion.CompletionCandidate; +import com.devonfw.tools.ide.completion.CompletionCandidateCollector; +import com.devonfw.tools.ide.completion.CompletionCandidateCollectorDefault; +import com.devonfw.tools.ide.environment.AbstractEnvironmentVariables; +import com.devonfw.tools.ide.environment.EnvironmentVariables; +import com.devonfw.tools.ide.environment.EnvironmentVariablesType; +import com.devonfw.tools.ide.io.FileAccess; +import com.devonfw.tools.ide.io.FileAccessImpl; +import com.devonfw.tools.ide.log.IdeLogLevel; +import com.devonfw.tools.ide.log.IdeSubLogger; +import com.devonfw.tools.ide.log.IdeSubLoggerNone; +import com.devonfw.tools.ide.merge.DirectoryMerger; +import com.devonfw.tools.ide.os.SystemInfo; +import com.devonfw.tools.ide.os.SystemInfoImpl; +import com.devonfw.tools.ide.process.ProcessContext; +import com.devonfw.tools.ide.process.ProcessContextImpl; +import com.devonfw.tools.ide.process.ProcessResult; +import com.devonfw.tools.ide.property.Property; +import com.devonfw.tools.ide.repo.CustomToolRepository; +import com.devonfw.tools.ide.repo.CustomToolRepositoryImpl; +import com.devonfw.tools.ide.repo.DefaultToolRepository; +import com.devonfw.tools.ide.repo.ToolRepository; +import com.devonfw.tools.ide.url.model.UrlMetadata; + +import java.io.IOException; +import java.net.InetAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +/** + * Abstract base implementation of {@link IdeContext}. + */ +public abstract class AbstractIdeContext implements IdeContext { + + private static final String IDE_URLS_GIT = "https://github.com/devonfw/ide-urls.git"; + + private final Map loggers; + + private final Path ideHome; + + private final Path ideRoot; + + private final Path confPath; + + private final Path settingsPath; + + private final Path softwarePath; + + private final Path softwareRepositoryPath; + + private final Path pluginsPath; + + private final Path workspacePath; + + private final String workspaceName; + + private final Path urlsPath; + + private final Path tempPath; + + private final Path tempDownloadPath; + + private final Path cwd; + + private final Path downloadPath; + + private final Path toolRepository; + + private final Path userHome; + + private final Path userHomeIde; + + private final SystemPath path; + + private final SystemInfo systemInfo; + + private final EnvironmentVariables variables; + + private final FileAccess fileAccess; + + private final CommandletManager commandletManager; + + private final ToolRepository defaultToolRepository; + + private final CustomToolRepository customToolRepository; + + private final DirectoryMerger workspaceMerger; + + private final Function loggerFactory; + + private boolean offlineMode; + + private boolean forceMode; + + private boolean batchMode; + + private boolean quietMode; + + private Locale locale; + + private UrlMetadata urlMetadata; + + private Path defaultExecutionDirectory; + + /** + * The constructor. + * + * @param minLogLevel the minimum {@link IdeLogLevel} to enable. Should be {@link IdeLogLevel#INFO} by default. + * @param factory the {@link Function} to create {@link IdeSubLogger} per {@link IdeLogLevel}. + * @param userDir the optional {@link Path} to current working directory. + * @param toolRepository @param toolRepository the {@link ToolRepository} of the context. If it is set to {@code null} + * {@link DefaultToolRepository} will be used. + */ + public AbstractIdeContext(IdeLogLevel minLogLevel, Function factory, Path userDir, + ToolRepository toolRepository) { + + super(); + this.loggerFactory = factory; + this.loggers = new HashMap<>(); + setLogLevel(minLogLevel); + this.systemInfo = SystemInfoImpl.INSTANCE; + this.commandletManager = new CommandletManagerImpl(this); + this.fileAccess = new FileAccessImpl(this); + String workspace = WORKSPACE_MAIN; + if (userDir == null) { + this.cwd = Path.of(System.getProperty("user.dir")); + } else { + this.cwd = userDir.toAbsolutePath(); + } + // detect IDE_HOME and WORKSPACE + Path currentDir = this.cwd; + String name1 = ""; + String name2 = ""; + while (currentDir != null) { + trace("Looking for IDE_HOME in {}", currentDir); + if (isIdeHome(currentDir)) { + if (FOLDER_WORKSPACES.equals(name1)) { + workspace = name2; + } + break; + } + name2 = name1; + int nameCount = currentDir.getNameCount(); + if (nameCount >= 1) { + name1 = currentDir.getName(nameCount - 1).toString(); + } + currentDir = getParentPath(currentDir); + } + // detection completed, initializing variables + this.ideHome = currentDir; + this.workspaceName = workspace; + if (this.ideHome == null) { + info(getMessageIdeHomeNotFound()); + this.workspacePath = null; + this.ideRoot = null; + this.confPath = null; + this.settingsPath = null; + this.softwarePath = null; + this.pluginsPath = null; + } else { + debug(getMessageIdeHomeFound()); + this.workspacePath = this.ideHome.resolve(FOLDER_WORKSPACES).resolve(this.workspaceName); + Path ideRootPath = this.ideHome.getParent(); + String root = null; + if (!isTest()) { + root = System.getenv("IDE_ROOT"); + } + if (root != null) { + Path rootPath = Path.of(root); + if (Files.isDirectory(rootPath)) { + if (!ideRootPath.equals(rootPath)) { + warning( + "Variable IDE_ROOT is set to '{}' but for your project '{}' the path '{}' would have been expected.", + root, this.ideHome.getFileName(), ideRootPath); + } + ideRootPath = rootPath; + } else { + warning("Variable IDE_ROOT is not set to a valid directory '{}'." + root); + ideRootPath = null; + } + } + this.ideRoot = ideRootPath; + this.confPath = this.ideHome.resolve(FOLDER_CONF); + this.settingsPath = this.ideHome.resolve(FOLDER_SETTINGS); + this.softwarePath = this.ideHome.resolve(FOLDER_SOFTWARE); + this.pluginsPath = this.ideHome.resolve(FOLDER_PLUGINS); + } + if (this.ideRoot == null) { + this.toolRepository = null; + this.urlsPath = null; + this.tempPath = null; + this.tempDownloadPath = null; + this.softwareRepositoryPath = null; + } else { + Path ideBase = this.ideRoot.resolve(FOLDER_IDE); + this.toolRepository = ideBase.resolve("software"); + this.urlsPath = ideBase.resolve("urls"); + this.tempPath = ideBase.resolve("tmp"); + this.tempDownloadPath = this.tempPath.resolve(FOLDER_DOWNLOADS); + this.softwareRepositoryPath = ideBase.resolve(FOLDER_SOFTWARE); + if (Files.isDirectory(this.tempPath)) { + // TODO delete all files older than 1 day here... + } else { + this.fileAccess.mkdirs(this.tempDownloadPath); + } + } + if (isTest()) { + // only for testing... + if (this.ideHome == null) { + this.userHome = Path.of("/non-existing-user-home-for-testing"); + } else { + this.userHome = this.ideHome.resolve("home"); + } + } else { + this.userHome = Path.of(System.getProperty("user.home")); + } + this.userHomeIde = this.userHome.resolve(".ide"); + this.downloadPath = this.userHome.resolve("Downloads/ide"); + this.variables = createVariables(); + this.path = computeSystemPath(); + + if (toolRepository == null) { + this.defaultToolRepository = new DefaultToolRepository(this); + } else { + this.defaultToolRepository = toolRepository; + } + + this.customToolRepository = CustomToolRepositoryImpl.of(this); + this.workspaceMerger = new DirectoryMerger(this); + } + + private String getMessageIdeHomeFound() { + + return "IDE environment variables have been set for " + this.ideHome + " in workspace " + this.workspaceName; + } + + private String getMessageIdeHomeNotFound() { + + return "You are not inside an IDE installation: " + this.cwd; + } + + /** + * @return the status message about the {@link #getIdeHome() IDE_HOME} detection and environment variable + * initialization. + */ + public String getMessageIdeHome() { + + if (this.ideHome == null) { + return getMessageIdeHomeNotFound(); + } + return getMessageIdeHomeFound(); + } + + /** + * @return {@code true} if this is a test context for JUnits, {@code false} otherwise. + */ + public boolean isTest() { + + return isMock(); + } + + /** + * @return {@code true} if this is a mock context for JUnits, {@code false} otherwise. + */ + public boolean isMock() { + + return false; + } + + private SystemPath computeSystemPath() { + + return new SystemPath(this); + } + + private boolean isIdeHome(Path dir) { + + if (!Files.isDirectory(dir.resolve("workspaces"))) { + return false; + } else if (!Files.isDirectory(dir.resolve("settings"))) { + return false; + } + return true; + } + + private Path getParentPath(Path dir) { + + try { + Path linkDir = dir.toRealPath(); + if (!dir.equals(linkDir)) { + return linkDir; + } else { + return dir.getParent(); + } + } catch (IOException e) { + throw new IllegalStateException(e); + } + + } + + private EnvironmentVariables createVariables() { + + AbstractEnvironmentVariables system = EnvironmentVariables.ofSystem(this); + AbstractEnvironmentVariables user = extendVariables(system, this.userHomeIde, EnvironmentVariablesType.USER); + AbstractEnvironmentVariables settings = extendVariables(user, this.settingsPath, EnvironmentVariablesType.SETTINGS); + // TODO should we keep this workspace properties? Was this feature ever used? + AbstractEnvironmentVariables workspace = extendVariables(settings, this.workspacePath, + EnvironmentVariablesType.WORKSPACE); + AbstractEnvironmentVariables conf = extendVariables(workspace, this.confPath, EnvironmentVariablesType.CONF); + return conf.resolved(); + } + + private AbstractEnvironmentVariables extendVariables(AbstractEnvironmentVariables envVariables, Path propertiesPath, + EnvironmentVariablesType type) { + + Path propertiesFile = null; + if (propertiesPath == null) { + trace("Configuration directory for type {} does not exist.", type); + } else if (Files.isDirectory(propertiesPath)) { + propertiesFile = propertiesPath.resolve(EnvironmentVariables.DEFAULT_PROPERTIES); + boolean legacySupport = (type != EnvironmentVariablesType.USER); + if (legacySupport && !Files.exists(propertiesFile)) { + Path legacyFile = propertiesPath.resolve(EnvironmentVariables.LEGACY_PROPERTIES); + if (Files.exists(legacyFile)) { + propertiesFile = legacyFile; + } + } + } else { + debug("Configuration directory {} does not exist.", propertiesPath); + } + return envVariables.extend(propertiesFile, type); + } + + @Override + public SystemInfo getSystemInfo() { + + return this.systemInfo; + } + + @Override + public FileAccess getFileAccess() { + + return this.fileAccess; + } + + @Override + public CommandletManager getCommandletManager() { + + return this.commandletManager; + } + + @Override + public ToolRepository getDefaultToolRepository() { + + return this.defaultToolRepository; + } + + @Override + public CustomToolRepository getCustomToolRepository() { + + return this.customToolRepository; + } + + @Override + public Path getIdeHome() { + + return this.ideHome; + } + + @Override + public Path getIdeRoot() { + + return this.ideRoot; + } + + @Override + public Path getCwd() { + + return this.cwd; + } + + @Override + public Path getTempPath() { + + return this.tempPath; + } + + @Override + public Path getTempDownloadPath() { + + return this.tempDownloadPath; + } + + @Override + public Path getUserHome() { + + return this.userHome; + } + + @Override + public Path getUserHomeIde() { + + return this.userHomeIde; + } + + @Override + public Path getSettingsPath() { + + return this.settingsPath; + } + + @Override + public Path getConfPath() { + + return this.confPath; + } + + @Override + public Path getSoftwarePath() { + + return this.softwarePath; + } + + @Override + public Path getSoftwareRepositoryPath() { + + return this.softwareRepositoryPath; + } + + @Override + public Path getPluginsPath() { + + return this.pluginsPath; + } + + @Override + public String getWorkspaceName() { + + return this.workspaceName; + } + + @Override + public Path getWorkspacePath() { + + return this.workspacePath; + } + + @Override + public Path getDownloadPath() { + + return this.downloadPath; + } + + @Override + public Path getUrlsPath() { + + return this.urlsPath; + } + + @Override + public Path getToolRepositoryPath() { + + return this.toolRepository; + } + + @Override + public SystemPath getPath() { + + return this.path; + } + + @Override + public EnvironmentVariables getVariables() { + + return this.variables; + } + + @Override + public UrlMetadata getUrls() { + + if (this.urlMetadata == null) { + if (!isTest()) { + this.getGitContext().pullOrFetchAndResetIfNeeded(IDE_URLS_GIT, "master", this.urlsPath, "origin"); + } + this.urlMetadata = new UrlMetadata(this); + } + return this.urlMetadata; + } + + @Override + public boolean isQuietMode() { + + return this.quietMode; + } + + /** + * @param quietMode new value of {@link #isQuietMode()}. + */ + public void setQuietMode(boolean quietMode) { + + this.quietMode = quietMode; + } + + @Override + public boolean isBatchMode() { + + return this.batchMode; + } + + /** + * @param batchMode new value of {@link #isBatchMode()}. + */ + public void setBatchMode(boolean batchMode) { + + this.batchMode = batchMode; + } + + @Override + public boolean isForceMode() { + + return this.forceMode; + } + + /** + * @param forceMode new value of {@link #isForceMode()}. + */ + public void setForceMode(boolean forceMode) { + + this.forceMode = forceMode; + } + + @Override + public boolean isOfflineMode() { + + return this.offlineMode; + } + + /** + * @param offlineMode new value of {@link #isOfflineMode()}. + */ + public void setOfflineMode(boolean offlineMode) { + + this.offlineMode = offlineMode; + } + + @Override + public boolean isOnline() { + + boolean online = false; + try { + int timeout = 1000; + online = InetAddress.getByName("github.com").isReachable(timeout); + } catch (Exception ignored) { + + } + return online; + } + + @Override + public Locale getLocale() { + + if (this.locale == null) { + return Locale.getDefault(); + } + return this.locale; + } + + /** + * @param locale new value of {@link #getLocale()}. + */ + public void setLocale(Locale locale) { + + this.locale = locale; + } + + @Override + public DirectoryMerger getWorkspaceMerger() { + + return this.workspaceMerger; + } + + /** + * @return the {@link #defaultExecutionDirectory} the directory in which a command process is executed. + */ + public Path getDefaultExecutionDirectory() { + + return this.defaultExecutionDirectory; + } + + /** + * @param defaultExecutionDirectory new value of {@link #getDefaultExecutionDirectory()}. + */ + public void setDefaultExecutionDirectory(Path defaultExecutionDirectory) { + + if (defaultExecutionDirectory != null) { + this.defaultExecutionDirectory = defaultExecutionDirectory; + } + } + + @Override + public GitContext getGitContext() { + + return new GitContextImpl(this); + } + + @Override + public ProcessContext newProcess() { + + ProcessContext processContext = createProcessContext(); + if (this.defaultExecutionDirectory != null) { + processContext.directory(this.defaultExecutionDirectory); + } + return processContext; + } + + /** + * @return a new instance of {@link ProcessContext}. + * @see #newProcess() + */ + protected ProcessContext createProcessContext() { + + return new ProcessContextImpl(this); + } + + @Override + public IdeSubLogger level(IdeLogLevel level) { + + IdeSubLogger logger = this.loggers.get(level); + Objects.requireNonNull(logger); + return logger; + } + + @SuppressWarnings("unchecked") + @Override + public O question(String question, O... options) { + + assert (options.length >= 2); + interaction(question); + Map mapping = new HashMap<>(options.length); + int i = 0; + for (O option : options) { + i++; + String key = "" + option; + addMapping(mapping, key, option); + String numericKey = Integer.toString(i); + if (numericKey.equals(key)) { + trace("Options should not be numeric: " + key); + } else { + addMapping(mapping, numericKey, option); + } + interaction("Option " + numericKey + ": " + key); + } + O option = null; + if (isBatchMode()) { + if (isForceMode()) { + option = options[0]; + interaction("" + option); + } + } else { + while (option == null) { + String answer = readLine(); + option = mapping.get(answer); + if (option == null) { + warning("Invalid answer: '" + answer + "' - please try again."); + } + } + } + return option; + } + + /** + * @return the input from the end-user (e.g. read from the console). + */ + protected abstract String readLine(); + + private static void addMapping(Map mapping, String key, O option) { + + O duplicate = mapping.put(key, option); + if (duplicate != null) { + throw new IllegalArgumentException("Duplicated option " + key); + } + } + + /** + * Sets the log level. + * + * @param logLevel {@link IdeLogLevel} + */ + public void setLogLevel(IdeLogLevel logLevel) { + + for (IdeLogLevel level : IdeLogLevel.values()) { + IdeSubLogger logger; + if (level.ordinal() < logLevel.ordinal()) { + logger = new IdeSubLoggerNone(level); + } else { + logger = this.loggerFactory.apply(level); + } + this.loggers.put(level, logger); + } + } + + /** + * Finds the matching {@link Commandlet} to run, applies {@link CliArguments} to its + * {@link Commandlet#getProperties() properties} and will execute it. + * + * @param arguments the {@link CliArgument}. + * @return the return code of the execution. + */ + public int run(CliArguments arguments) { + + CliArgument current = arguments.current(); + if (!current.isEnd()) { + String keyword = current.get(); + Commandlet firstCandidate = this.commandletManager.getCommandletByFirstKeyword(keyword); + boolean matches; + if (firstCandidate != null) { + matches = applyAndRun(arguments.copy(), firstCandidate); + if (matches) { + return ProcessResult.SUCCESS; + } + } + for (Commandlet cmd : this.commandletManager.getCommandlets()) { + if (cmd != firstCandidate) { + matches = applyAndRun(arguments.copy(), cmd); + if (matches) { + return ProcessResult.SUCCESS; + } + } + } + error("Invalid arguments: {}", current.getArgs()); + } + this.commandletManager.getCommandlet(HelpCommandlet.class).run(); + return 1; + } + + /** + * @param cmd the potential {@link Commandlet} to + * {@link #apply(CliArguments, Commandlet, CompletionCandidateCollector) apply} and {@link Commandlet#run() run}. + * @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully, + * {@code false} otherwise (the {@link Commandlet} did not match and we have to try a different candidate). + */ + private boolean applyAndRun(CliArguments arguments, Commandlet cmd) { + + boolean matches = apply(arguments, cmd, null); + if (matches) { + matches = cmd.validate(); + } + if (matches) { + debug("Running commandlet {}", cmd); + if (cmd.isIdeHomeRequired() && (this.ideHome == null)) { + throw new CliException(getMessageIdeHomeNotFound()); + } + cmd.run(); + } else { + trace("Commandlet did not match"); + } + return matches; + } + + /** + * @param arguments the {@link CliArguments#ofCompletion(String...) completion arguments}. + * @param includeContextOptions to include the options of {@link ContextCommandlet}. + * @return the {@link List} of {@link CompletionCandidate}s to suggest. + */ + public List complete(CliArguments arguments, boolean includeContextOptions) { + + CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault(this); + if (arguments.current().isStart()) { + arguments.next(); + } + if (includeContextOptions) { + ContextCommandlet cc = new ContextCommandlet(); + for (Property property : cc.getProperties()) { + assert (property.isOption()); + property.apply(arguments, this, cc, collector); + } + } + CliArgument current = arguments.current(); + if (!current.isEnd()) { + String keyword = current.get(); + Commandlet firstCandidate = this.commandletManager.getCommandletByFirstKeyword(keyword); + boolean matches = false; + if (firstCandidate != null) { + matches = apply(arguments.copy(), firstCandidate, collector); + } else if (current.isCombinedShortOption()) { + collector.add(keyword, null, null, null); + } + if (!matches) { + for (Commandlet cmd : this.commandletManager.getCommandlets()) { + if (cmd != firstCandidate) { + apply(arguments.copy(), cmd, collector); + } + } + } + } + return collector.getSortedCandidates(); + } + + /** + * @param arguments the {@link CliArguments} to apply. Will be {@link CliArguments#next() consumed} as they are + * matched. Consider passing a {@link CliArguments#copy() copy} as needed. + * @param cmd the potential {@link Commandlet} to match. + * @param collector the {@link CompletionCandidateCollector}. + * @return {@code true} if the given {@link Commandlet} matches to the given {@link CliArgument}(s) and those have + * been applied (set in the {@link Commandlet} and {@link Commandlet#validate() validated}), {@code false} otherwise + * (the {@link Commandlet} did not match and we have to try a different candidate). + */ + public boolean apply(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) { + + trace("Trying to match arguments to commandlet {}", cmd.getName()); + CliArgument currentArgument = arguments.current(); + Iterator> propertyIterator; + if (currentArgument.isCompletion()) { + propertyIterator = cmd.getProperties().iterator(); + } else { + propertyIterator = cmd.getValues().iterator(); + } + while (!currentArgument.isEnd()) { + trace("Trying to match argument '{}'", currentArgument); + Property property = null; + if (!arguments.isEndOptions()) { + property = cmd.getOption(currentArgument.getKey()); + } + if (property == null) { + if (!propertyIterator.hasNext()) { + trace("No option or next value found"); + return false; + } + property = propertyIterator.next(); + } + trace("Next property candidate to match argument is {}", property); + boolean matches = property.apply(arguments, this, cmd, collector); + if (!matches || currentArgument.isCompletion()) { + return false; + } + currentArgument = arguments.current(); + } + return true; + } + +} From b7e76d07ed6ac3b57aa129779e614035559dd36b Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:41:29 +0100 Subject: [PATCH 53/58] Update AbstractIdeContext.java --- .../tools/ide/context/AbstractIdeContext.java | 1716 ++++++++--------- 1 file changed, 858 insertions(+), 858 deletions(-) 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 9d60ccfa1..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 @@ -1,858 +1,858 @@ -package com.devonfw.tools.ide.context; - -import com.devonfw.tools.ide.cli.CliArgument; -import com.devonfw.tools.ide.cli.CliArguments; -import com.devonfw.tools.ide.cli.CliException; -import com.devonfw.tools.ide.commandlet.Commandlet; -import com.devonfw.tools.ide.commandlet.CommandletManager; -import com.devonfw.tools.ide.commandlet.CommandletManagerImpl; -import com.devonfw.tools.ide.commandlet.ContextCommandlet; -import com.devonfw.tools.ide.commandlet.HelpCommandlet; -import com.devonfw.tools.ide.common.SystemPath; -import com.devonfw.tools.ide.completion.CompletionCandidate; -import com.devonfw.tools.ide.completion.CompletionCandidateCollector; -import com.devonfw.tools.ide.completion.CompletionCandidateCollectorDefault; -import com.devonfw.tools.ide.environment.AbstractEnvironmentVariables; -import com.devonfw.tools.ide.environment.EnvironmentVariables; -import com.devonfw.tools.ide.environment.EnvironmentVariablesType; -import com.devonfw.tools.ide.io.FileAccess; -import com.devonfw.tools.ide.io.FileAccessImpl; -import com.devonfw.tools.ide.log.IdeLogLevel; -import com.devonfw.tools.ide.log.IdeSubLogger; -import com.devonfw.tools.ide.log.IdeSubLoggerNone; -import com.devonfw.tools.ide.merge.DirectoryMerger; -import com.devonfw.tools.ide.os.SystemInfo; -import com.devonfw.tools.ide.os.SystemInfoImpl; -import com.devonfw.tools.ide.process.ProcessContext; -import com.devonfw.tools.ide.process.ProcessContextImpl; -import com.devonfw.tools.ide.process.ProcessResult; -import com.devonfw.tools.ide.property.Property; -import com.devonfw.tools.ide.repo.CustomToolRepository; -import com.devonfw.tools.ide.repo.CustomToolRepositoryImpl; -import com.devonfw.tools.ide.repo.DefaultToolRepository; -import com.devonfw.tools.ide.repo.ToolRepository; -import com.devonfw.tools.ide.url.model.UrlMetadata; - -import java.io.IOException; -import java.net.InetAddress; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.function.Function; - -/** - * Abstract base implementation of {@link IdeContext}. - */ -public abstract class AbstractIdeContext implements IdeContext { - - private static final String IDE_URLS_GIT = "https://github.com/devonfw/ide-urls.git"; - - private final Map loggers; - - private final Path ideHome; - - private final Path ideRoot; - - private final Path confPath; - - private final Path settingsPath; - - private final Path softwarePath; - - private final Path softwareRepositoryPath; - - private final Path pluginsPath; - - private final Path workspacePath; - - private final String workspaceName; - - private final Path urlsPath; - - private final Path tempPath; - - private final Path tempDownloadPath; - - private final Path cwd; - - private final Path downloadPath; - - private final Path toolRepository; - - private final Path userHome; - - private final Path userHomeIde; - - private final SystemPath path; - - private final SystemInfo systemInfo; - - private final EnvironmentVariables variables; - - private final FileAccess fileAccess; - - private final CommandletManager commandletManager; - - private final ToolRepository defaultToolRepository; - - private final CustomToolRepository customToolRepository; - - private final DirectoryMerger workspaceMerger; - - private final Function loggerFactory; - - private boolean offlineMode; - - private boolean forceMode; - - private boolean batchMode; - - private boolean quietMode; - - private Locale locale; - - private UrlMetadata urlMetadata; - - private Path defaultExecutionDirectory; - - /** - * The constructor. - * - * @param minLogLevel the minimum {@link IdeLogLevel} to enable. Should be {@link IdeLogLevel#INFO} by default. - * @param factory the {@link Function} to create {@link IdeSubLogger} per {@link IdeLogLevel}. - * @param userDir the optional {@link Path} to current working directory. - * @param toolRepository @param toolRepository the {@link ToolRepository} of the context. If it is set to {@code null} - * {@link DefaultToolRepository} will be used. - */ - public AbstractIdeContext(IdeLogLevel minLogLevel, Function factory, Path userDir, - ToolRepository toolRepository) { - - super(); - this.loggerFactory = factory; - this.loggers = new HashMap<>(); - setLogLevel(minLogLevel); - this.systemInfo = SystemInfoImpl.INSTANCE; - this.commandletManager = new CommandletManagerImpl(this); - this.fileAccess = new FileAccessImpl(this); - String workspace = WORKSPACE_MAIN; - if (userDir == null) { - this.cwd = Path.of(System.getProperty("user.dir")); - } else { - this.cwd = userDir.toAbsolutePath(); - } - // detect IDE_HOME and WORKSPACE - Path currentDir = this.cwd; - String name1 = ""; - String name2 = ""; - while (currentDir != null) { - trace("Looking for IDE_HOME in {}", currentDir); - if (isIdeHome(currentDir)) { - if (FOLDER_WORKSPACES.equals(name1)) { - workspace = name2; - } - break; - } - name2 = name1; - int nameCount = currentDir.getNameCount(); - if (nameCount >= 1) { - name1 = currentDir.getName(nameCount - 1).toString(); - } - currentDir = getParentPath(currentDir); - } - // detection completed, initializing variables - this.ideHome = currentDir; - this.workspaceName = workspace; - if (this.ideHome == null) { - info(getMessageIdeHomeNotFound()); - this.workspacePath = null; - this.ideRoot = null; - this.confPath = null; - this.settingsPath = null; - this.softwarePath = null; - this.pluginsPath = null; - } else { - debug(getMessageIdeHomeFound()); - this.workspacePath = this.ideHome.resolve(FOLDER_WORKSPACES).resolve(this.workspaceName); - Path ideRootPath = this.ideHome.getParent(); - String root = null; - if (!isTest()) { - root = System.getenv("IDE_ROOT"); - } - if (root != null) { - Path rootPath = Path.of(root); - if (Files.isDirectory(rootPath)) { - if (!ideRootPath.equals(rootPath)) { - warning( - "Variable IDE_ROOT is set to '{}' but for your project '{}' the path '{}' would have been expected.", - root, this.ideHome.getFileName(), ideRootPath); - } - ideRootPath = rootPath; - } else { - warning("Variable IDE_ROOT is not set to a valid directory '{}'." + root); - ideRootPath = null; - } - } - this.ideRoot = ideRootPath; - this.confPath = this.ideHome.resolve(FOLDER_CONF); - this.settingsPath = this.ideHome.resolve(FOLDER_SETTINGS); - this.softwarePath = this.ideHome.resolve(FOLDER_SOFTWARE); - this.pluginsPath = this.ideHome.resolve(FOLDER_PLUGINS); - } - if (this.ideRoot == null) { - this.toolRepository = null; - this.urlsPath = null; - this.tempPath = null; - this.tempDownloadPath = null; - this.softwareRepositoryPath = null; - } else { - Path ideBase = this.ideRoot.resolve(FOLDER_IDE); - this.toolRepository = ideBase.resolve("software"); - this.urlsPath = ideBase.resolve("urls"); - this.tempPath = ideBase.resolve("tmp"); - this.tempDownloadPath = this.tempPath.resolve(FOLDER_DOWNLOADS); - this.softwareRepositoryPath = ideBase.resolve(FOLDER_SOFTWARE); - if (Files.isDirectory(this.tempPath)) { - // TODO delete all files older than 1 day here... - } else { - this.fileAccess.mkdirs(this.tempDownloadPath); - } - } - if (isTest()) { - // only for testing... - if (this.ideHome == null) { - this.userHome = Path.of("/non-existing-user-home-for-testing"); - } else { - this.userHome = this.ideHome.resolve("home"); - } - } else { - this.userHome = Path.of(System.getProperty("user.home")); - } - this.userHomeIde = this.userHome.resolve(".ide"); - this.downloadPath = this.userHome.resolve("Downloads/ide"); - this.variables = createVariables(); - this.path = computeSystemPath(); - - if (toolRepository == null) { - this.defaultToolRepository = new DefaultToolRepository(this); - } else { - this.defaultToolRepository = toolRepository; - } - - this.customToolRepository = CustomToolRepositoryImpl.of(this); - this.workspaceMerger = new DirectoryMerger(this); - } - - private String getMessageIdeHomeFound() { - - return "IDE environment variables have been set for " + this.ideHome + " in workspace " + this.workspaceName; - } - - private String getMessageIdeHomeNotFound() { - - return "You are not inside an IDE installation: " + this.cwd; - } - - /** - * @return the status message about the {@link #getIdeHome() IDE_HOME} detection and environment variable - * initialization. - */ - public String getMessageIdeHome() { - - if (this.ideHome == null) { - return getMessageIdeHomeNotFound(); - } - return getMessageIdeHomeFound(); - } - - /** - * @return {@code true} if this is a test context for JUnits, {@code false} otherwise. - */ - public boolean isTest() { - - return isMock(); - } - - /** - * @return {@code true} if this is a mock context for JUnits, {@code false} otherwise. - */ - public boolean isMock() { - - return false; - } - - private SystemPath computeSystemPath() { - - return new SystemPath(this); - } - - private boolean isIdeHome(Path dir) { - - if (!Files.isDirectory(dir.resolve("workspaces"))) { - return false; - } else if (!Files.isDirectory(dir.resolve("settings"))) { - return false; - } - return true; - } - - private Path getParentPath(Path dir) { - - try { - Path linkDir = dir.toRealPath(); - if (!dir.equals(linkDir)) { - return linkDir; - } else { - return dir.getParent(); - } - } catch (IOException e) { - throw new IllegalStateException(e); - } - - } - - private EnvironmentVariables createVariables() { - - AbstractEnvironmentVariables system = EnvironmentVariables.ofSystem(this); - AbstractEnvironmentVariables user = extendVariables(system, this.userHomeIde, EnvironmentVariablesType.USER); - AbstractEnvironmentVariables settings = extendVariables(user, this.settingsPath, EnvironmentVariablesType.SETTINGS); - // TODO should we keep this workspace properties? Was this feature ever used? - AbstractEnvironmentVariables workspace = extendVariables(settings, this.workspacePath, - EnvironmentVariablesType.WORKSPACE); - AbstractEnvironmentVariables conf = extendVariables(workspace, this.confPath, EnvironmentVariablesType.CONF); - return conf.resolved(); - } - - private AbstractEnvironmentVariables extendVariables(AbstractEnvironmentVariables envVariables, Path propertiesPath, - EnvironmentVariablesType type) { - - Path propertiesFile = null; - if (propertiesPath == null) { - trace("Configuration directory for type {} does not exist.", type); - } else if (Files.isDirectory(propertiesPath)) { - propertiesFile = propertiesPath.resolve(EnvironmentVariables.DEFAULT_PROPERTIES); - boolean legacySupport = (type != EnvironmentVariablesType.USER); - if (legacySupport && !Files.exists(propertiesFile)) { - Path legacyFile = propertiesPath.resolve(EnvironmentVariables.LEGACY_PROPERTIES); - if (Files.exists(legacyFile)) { - propertiesFile = legacyFile; - } - } - } else { - debug("Configuration directory {} does not exist.", propertiesPath); - } - return envVariables.extend(propertiesFile, type); - } - - @Override - public SystemInfo getSystemInfo() { - - return this.systemInfo; - } - - @Override - public FileAccess getFileAccess() { - - return this.fileAccess; - } - - @Override - public CommandletManager getCommandletManager() { - - return this.commandletManager; - } - - @Override - public ToolRepository getDefaultToolRepository() { - - return this.defaultToolRepository; - } - - @Override - public CustomToolRepository getCustomToolRepository() { - - return this.customToolRepository; - } - - @Override - public Path getIdeHome() { - - return this.ideHome; - } - - @Override - public Path getIdeRoot() { - - return this.ideRoot; - } - - @Override - public Path getCwd() { - - return this.cwd; - } - - @Override - public Path getTempPath() { - - return this.tempPath; - } - - @Override - public Path getTempDownloadPath() { - - return this.tempDownloadPath; - } - - @Override - public Path getUserHome() { - - return this.userHome; - } - - @Override - public Path getUserHomeIde() { - - return this.userHomeIde; - } - - @Override - public Path getSettingsPath() { - - return this.settingsPath; - } - - @Override - public Path getConfPath() { - - return this.confPath; - } - - @Override - public Path getSoftwarePath() { - - return this.softwarePath; - } - - @Override - public Path getSoftwareRepositoryPath() { - - return this.softwareRepositoryPath; - } - - @Override - public Path getPluginsPath() { - - return this.pluginsPath; - } - - @Override - public String getWorkspaceName() { - - return this.workspaceName; - } - - @Override - public Path getWorkspacePath() { - - return this.workspacePath; - } - - @Override - public Path getDownloadPath() { - - return this.downloadPath; - } - - @Override - public Path getUrlsPath() { - - return this.urlsPath; - } - - @Override - public Path getToolRepositoryPath() { - - return this.toolRepository; - } - - @Override - public SystemPath getPath() { - - return this.path; - } - - @Override - public EnvironmentVariables getVariables() { - - return this.variables; - } - - @Override - public UrlMetadata getUrls() { - - if (this.urlMetadata == null) { - if (!isTest()) { - this.getGitContext().pullOrFetchAndResetIfNeeded(IDE_URLS_GIT, "master", this.urlsPath, "origin"); - } - this.urlMetadata = new UrlMetadata(this); - } - return this.urlMetadata; - } - - @Override - public boolean isQuietMode() { - - return this.quietMode; - } - - /** - * @param quietMode new value of {@link #isQuietMode()}. - */ - public void setQuietMode(boolean quietMode) { - - this.quietMode = quietMode; - } - - @Override - public boolean isBatchMode() { - - return this.batchMode; - } - - /** - * @param batchMode new value of {@link #isBatchMode()}. - */ - public void setBatchMode(boolean batchMode) { - - this.batchMode = batchMode; - } - - @Override - public boolean isForceMode() { - - return this.forceMode; - } - - /** - * @param forceMode new value of {@link #isForceMode()}. - */ - public void setForceMode(boolean forceMode) { - - this.forceMode = forceMode; - } - - @Override - public boolean isOfflineMode() { - - return this.offlineMode; - } - - /** - * @param offlineMode new value of {@link #isOfflineMode()}. - */ - public void setOfflineMode(boolean offlineMode) { - - this.offlineMode = offlineMode; - } - - @Override - public boolean isOnline() { - - boolean online = false; - try { - int timeout = 1000; - online = InetAddress.getByName("github.com").isReachable(timeout); - } catch (Exception ignored) { - - } - return online; - } - - @Override - public Locale getLocale() { - - if (this.locale == null) { - return Locale.getDefault(); - } - return this.locale; - } - - /** - * @param locale new value of {@link #getLocale()}. - */ - public void setLocale(Locale locale) { - - this.locale = locale; - } - - @Override - public DirectoryMerger getWorkspaceMerger() { - - return this.workspaceMerger; - } - - /** - * @return the {@link #defaultExecutionDirectory} the directory in which a command process is executed. - */ - public Path getDefaultExecutionDirectory() { - - return this.defaultExecutionDirectory; - } - - /** - * @param defaultExecutionDirectory new value of {@link #getDefaultExecutionDirectory()}. - */ - public void setDefaultExecutionDirectory(Path defaultExecutionDirectory) { - - if (defaultExecutionDirectory != null) { - this.defaultExecutionDirectory = defaultExecutionDirectory; - } - } - - @Override - public GitContext getGitContext() { - - return new GitContextImpl(this); - } - - @Override - public ProcessContext newProcess() { - - ProcessContext processContext = createProcessContext(); - if (this.defaultExecutionDirectory != null) { - processContext.directory(this.defaultExecutionDirectory); - } - return processContext; - } - - /** - * @return a new instance of {@link ProcessContext}. - * @see #newProcess() - */ - protected ProcessContext createProcessContext() { - - return new ProcessContextImpl(this); - } - - @Override - public IdeSubLogger level(IdeLogLevel level) { - - IdeSubLogger logger = this.loggers.get(level); - Objects.requireNonNull(logger); - return logger; - } - - @SuppressWarnings("unchecked") - @Override - public O question(String question, O... options) { - - assert (options.length >= 2); - interaction(question); - Map mapping = new HashMap<>(options.length); - int i = 0; - for (O option : options) { - i++; - String key = "" + option; - addMapping(mapping, key, option); - String numericKey = Integer.toString(i); - if (numericKey.equals(key)) { - trace("Options should not be numeric: " + key); - } else { - addMapping(mapping, numericKey, option); - } - interaction("Option " + numericKey + ": " + key); - } - O option = null; - if (isBatchMode()) { - if (isForceMode()) { - option = options[0]; - interaction("" + option); - } - } else { - while (option == null) { - String answer = readLine(); - option = mapping.get(answer); - if (option == null) { - warning("Invalid answer: '" + answer + "' - please try again."); - } - } - } - return option; - } - - /** - * @return the input from the end-user (e.g. read from the console). - */ - protected abstract String readLine(); - - private static void addMapping(Map mapping, String key, O option) { - - O duplicate = mapping.put(key, option); - if (duplicate != null) { - throw new IllegalArgumentException("Duplicated option " + key); - } - } - - /** - * Sets the log level. - * - * @param logLevel {@link IdeLogLevel} - */ - public void setLogLevel(IdeLogLevel logLevel) { - - for (IdeLogLevel level : IdeLogLevel.values()) { - IdeSubLogger logger; - if (level.ordinal() < logLevel.ordinal()) { - logger = new IdeSubLoggerNone(level); - } else { - logger = this.loggerFactory.apply(level); - } - this.loggers.put(level, logger); - } - } - - /** - * Finds the matching {@link Commandlet} to run, applies {@link CliArguments} to its - * {@link Commandlet#getProperties() properties} and will execute it. - * - * @param arguments the {@link CliArgument}. - * @return the return code of the execution. - */ - public int run(CliArguments arguments) { - - CliArgument current = arguments.current(); - if (!current.isEnd()) { - String keyword = current.get(); - Commandlet firstCandidate = this.commandletManager.getCommandletByFirstKeyword(keyword); - boolean matches; - if (firstCandidate != null) { - matches = applyAndRun(arguments.copy(), firstCandidate); - if (matches) { - return ProcessResult.SUCCESS; - } - } - for (Commandlet cmd : this.commandletManager.getCommandlets()) { - if (cmd != firstCandidate) { - matches = applyAndRun(arguments.copy(), cmd); - if (matches) { - return ProcessResult.SUCCESS; - } - } - } - error("Invalid arguments: {}", current.getArgs()); - } - this.commandletManager.getCommandlet(HelpCommandlet.class).run(); - return 1; - } - - /** - * @param cmd the potential {@link Commandlet} to - * {@link #apply(CliArguments, Commandlet, CompletionCandidateCollector) apply} and {@link Commandlet#run() run}. - * @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully, - * {@code false} otherwise (the {@link Commandlet} did not match and we have to try a different candidate). - */ - private boolean applyAndRun(CliArguments arguments, Commandlet cmd) { - - boolean matches = apply(arguments, cmd, null); - if (matches) { - matches = cmd.validate(); - } - if (matches) { - debug("Running commandlet {}", cmd); - if (cmd.isIdeHomeRequired() && (this.ideHome == null)) { - throw new CliException(getMessageIdeHomeNotFound()); - } - cmd.run(); - } else { - trace("Commandlet did not match"); - } - return matches; - } - - /** - * @param arguments the {@link CliArguments#ofCompletion(String...) completion arguments}. - * @param includeContextOptions to include the options of {@link ContextCommandlet}. - * @return the {@link List} of {@link CompletionCandidate}s to suggest. - */ - public List complete(CliArguments arguments, boolean includeContextOptions) { - - CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault(this); - if (arguments.current().isStart()) { - arguments.next(); - } - if (includeContextOptions) { - ContextCommandlet cc = new ContextCommandlet(); - for (Property property : cc.getProperties()) { - assert (property.isOption()); - property.apply(arguments, this, cc, collector); - } - } - CliArgument current = arguments.current(); - if (!current.isEnd()) { - String keyword = current.get(); - Commandlet firstCandidate = this.commandletManager.getCommandletByFirstKeyword(keyword); - boolean matches = false; - if (firstCandidate != null) { - matches = apply(arguments.copy(), firstCandidate, collector); - } else if (current.isCombinedShortOption()) { - collector.add(keyword, null, null, null); - } - if (!matches) { - for (Commandlet cmd : this.commandletManager.getCommandlets()) { - if (cmd != firstCandidate) { - apply(arguments.copy(), cmd, collector); - } - } - } - } - return collector.getSortedCandidates(); - } - - /** - * @param arguments the {@link CliArguments} to apply. Will be {@link CliArguments#next() consumed} as they are - * matched. Consider passing a {@link CliArguments#copy() copy} as needed. - * @param cmd the potential {@link Commandlet} to match. - * @param collector the {@link CompletionCandidateCollector}. - * @return {@code true} if the given {@link Commandlet} matches to the given {@link CliArgument}(s) and those have - * been applied (set in the {@link Commandlet} and {@link Commandlet#validate() validated}), {@code false} otherwise - * (the {@link Commandlet} did not match and we have to try a different candidate). - */ - public boolean apply(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) { - - trace("Trying to match arguments to commandlet {}", cmd.getName()); - CliArgument currentArgument = arguments.current(); - Iterator> propertyIterator; - if (currentArgument.isCompletion()) { - propertyIterator = cmd.getProperties().iterator(); - } else { - propertyIterator = cmd.getValues().iterator(); - } - while (!currentArgument.isEnd()) { - trace("Trying to match argument '{}'", currentArgument); - Property property = null; - if (!arguments.isEndOptions()) { - property = cmd.getOption(currentArgument.getKey()); - } - if (property == null) { - if (!propertyIterator.hasNext()) { - trace("No option or next value found"); - return false; - } - property = propertyIterator.next(); - } - trace("Next property candidate to match argument is {}", property); - boolean matches = property.apply(arguments, this, cmd, collector); - if (!matches || currentArgument.isCompletion()) { - return false; - } - currentArgument = arguments.current(); - } - return true; - } - -} +package com.devonfw.tools.ide.context; + +import com.devonfw.tools.ide.cli.CliArgument; +import com.devonfw.tools.ide.cli.CliArguments; +import com.devonfw.tools.ide.cli.CliException; +import com.devonfw.tools.ide.commandlet.Commandlet; +import com.devonfw.tools.ide.commandlet.CommandletManager; +import com.devonfw.tools.ide.commandlet.CommandletManagerImpl; +import com.devonfw.tools.ide.commandlet.ContextCommandlet; +import com.devonfw.tools.ide.commandlet.HelpCommandlet; +import com.devonfw.tools.ide.common.SystemPath; +import com.devonfw.tools.ide.completion.CompletionCandidate; +import com.devonfw.tools.ide.completion.CompletionCandidateCollector; +import com.devonfw.tools.ide.completion.CompletionCandidateCollectorDefault; +import com.devonfw.tools.ide.environment.AbstractEnvironmentVariables; +import com.devonfw.tools.ide.environment.EnvironmentVariables; +import com.devonfw.tools.ide.environment.EnvironmentVariablesType; +import com.devonfw.tools.ide.io.FileAccess; +import com.devonfw.tools.ide.io.FileAccessImpl; +import com.devonfw.tools.ide.log.IdeLogLevel; +import com.devonfw.tools.ide.log.IdeSubLogger; +import com.devonfw.tools.ide.log.IdeSubLoggerNone; +import com.devonfw.tools.ide.merge.DirectoryMerger; +import com.devonfw.tools.ide.os.SystemInfo; +import com.devonfw.tools.ide.os.SystemInfoImpl; +import com.devonfw.tools.ide.process.ProcessContext; +import com.devonfw.tools.ide.process.ProcessContextImpl; +import com.devonfw.tools.ide.process.ProcessResult; +import com.devonfw.tools.ide.property.Property; +import com.devonfw.tools.ide.repo.CustomToolRepository; +import com.devonfw.tools.ide.repo.CustomToolRepositoryImpl; +import com.devonfw.tools.ide.repo.DefaultToolRepository; +import com.devonfw.tools.ide.repo.ToolRepository; +import com.devonfw.tools.ide.url.model.UrlMetadata; + +import java.io.IOException; +import java.net.InetAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +/** + * Abstract base implementation of {@link IdeContext}. + */ +public abstract class AbstractIdeContext implements IdeContext { + + private static final String IDE_URLS_GIT = "https://github.com/devonfw/ide-urls.git"; + + private final Map loggers; + + private final Path ideHome; + + private final Path ideRoot; + + private final Path confPath; + + private final Path settingsPath; + + private final Path softwarePath; + + private final Path softwareRepositoryPath; + + private final Path pluginsPath; + + private final Path workspacePath; + + private final String workspaceName; + + private final Path urlsPath; + + private final Path tempPath; + + private final Path tempDownloadPath; + + private final Path cwd; + + private final Path downloadPath; + + private final Path toolRepository; + + private final Path userHome; + + private final Path userHomeIde; + + private final SystemPath path; + + private final SystemInfo systemInfo; + + private final EnvironmentVariables variables; + + private final FileAccess fileAccess; + + private final CommandletManager commandletManager; + + private final ToolRepository defaultToolRepository; + + private final CustomToolRepository customToolRepository; + + private final DirectoryMerger workspaceMerger; + + private final Function loggerFactory; + + private boolean offlineMode; + + private boolean forceMode; + + private boolean batchMode; + + private boolean quietMode; + + private Locale locale; + + private UrlMetadata urlMetadata; + + private Path defaultExecutionDirectory; + + /** + * The constructor. + * + * @param minLogLevel the minimum {@link IdeLogLevel} to enable. Should be {@link IdeLogLevel#INFO} by default. + * @param factory the {@link Function} to create {@link IdeSubLogger} per {@link IdeLogLevel}. + * @param userDir the optional {@link Path} to current working directory. + * @param toolRepository @param toolRepository the {@link ToolRepository} of the context. If it is set to {@code null} + * {@link DefaultToolRepository} will be used. + */ + public AbstractIdeContext(IdeLogLevel minLogLevel, Function factory, Path userDir, + ToolRepository toolRepository) { + + super(); + this.loggerFactory = factory; + this.loggers = new HashMap<>(); + setLogLevel(minLogLevel); + this.systemInfo = SystemInfoImpl.INSTANCE; + this.commandletManager = new CommandletManagerImpl(this); + this.fileAccess = new FileAccessImpl(this); + String workspace = WORKSPACE_MAIN; + if (userDir == null) { + this.cwd = Path.of(System.getProperty("user.dir")); + } else { + this.cwd = userDir.toAbsolutePath(); + } + // detect IDE_HOME and WORKSPACE + Path currentDir = this.cwd; + String name1 = ""; + String name2 = ""; + while (currentDir != null) { + trace("Looking for IDE_HOME in {}", currentDir); + if (isIdeHome(currentDir)) { + if (FOLDER_WORKSPACES.equals(name1)) { + workspace = name2; + } + break; + } + name2 = name1; + int nameCount = currentDir.getNameCount(); + if (nameCount >= 1) { + name1 = currentDir.getName(nameCount - 1).toString(); + } + currentDir = getParentPath(currentDir); + } + // detection completed, initializing variables + this.ideHome = currentDir; + this.workspaceName = workspace; + if (this.ideHome == null) { + info(getMessageIdeHomeNotFound()); + this.workspacePath = null; + this.ideRoot = null; + this.confPath = null; + this.settingsPath = null; + this.softwarePath = null; + this.pluginsPath = null; + } else { + debug(getMessageIdeHomeFound()); + this.workspacePath = this.ideHome.resolve(FOLDER_WORKSPACES).resolve(this.workspaceName); + Path ideRootPath = this.ideHome.getParent(); + String root = null; + if (!isTest()) { + root = System.getenv("IDE_ROOT"); + } + if (root != null) { + Path rootPath = Path.of(root); + if (Files.isDirectory(rootPath)) { + if (!ideRootPath.equals(rootPath)) { + warning( + "Variable IDE_ROOT is set to '{}' but for your project '{}' the path '{}' would have been expected.", + root, this.ideHome.getFileName(), ideRootPath); + } + ideRootPath = rootPath; + } else { + warning("Variable IDE_ROOT is not set to a valid directory '{}'." + root); + ideRootPath = null; + } + } + this.ideRoot = ideRootPath; + this.confPath = this.ideHome.resolve(FOLDER_CONF); + this.settingsPath = this.ideHome.resolve(FOLDER_SETTINGS); + this.softwarePath = this.ideHome.resolve(FOLDER_SOFTWARE); + this.pluginsPath = this.ideHome.resolve(FOLDER_PLUGINS); + } + if (this.ideRoot == null) { + this.toolRepository = null; + this.urlsPath = null; + this.tempPath = null; + this.tempDownloadPath = null; + this.softwareRepositoryPath = null; + } else { + Path ideBase = this.ideRoot.resolve(FOLDER_IDE); + this.toolRepository = ideBase.resolve("software"); + this.urlsPath = ideBase.resolve("urls"); + this.tempPath = ideBase.resolve("tmp"); + this.tempDownloadPath = this.tempPath.resolve(FOLDER_DOWNLOADS); + this.softwareRepositoryPath = ideBase.resolve(FOLDER_SOFTWARE); + if (Files.isDirectory(this.tempPath)) { + // TODO delete all files older than 1 day here... + } else { + this.fileAccess.mkdirs(this.tempDownloadPath); + } + } + if (isTest()) { + // only for testing... + if (this.ideHome == null) { + this.userHome = Path.of("/non-existing-user-home-for-testing"); + } else { + this.userHome = this.ideHome.resolve("home"); + } + } else { + this.userHome = Path.of(System.getProperty("user.home")); + } + this.userHomeIde = this.userHome.resolve(".ide"); + this.downloadPath = this.userHome.resolve("Downloads/ide"); + this.variables = createVariables(); + this.path = computeSystemPath(); + + if (toolRepository == null) { + this.defaultToolRepository = new DefaultToolRepository(this); + } else { + this.defaultToolRepository = toolRepository; + } + + this.customToolRepository = CustomToolRepositoryImpl.of(this); + this.workspaceMerger = new DirectoryMerger(this); + } + + private String getMessageIdeHomeFound() { + + return "IDE environment variables have been set for " + this.ideHome + " in workspace " + this.workspaceName; + } + + private String getMessageIdeHomeNotFound() { + + return "You are not inside an IDE installation: " + this.cwd; + } + + /** + * @return the status message about the {@link #getIdeHome() IDE_HOME} detection and environment variable + * initialization. + */ + public String getMessageIdeHome() { + + if (this.ideHome == null) { + return getMessageIdeHomeNotFound(); + } + return getMessageIdeHomeFound(); + } + + /** + * @return {@code true} if this is a test context for JUnits, {@code false} otherwise. + */ + public boolean isTest() { + + return isMock(); + } + + /** + * @return {@code true} if this is a mock context for JUnits, {@code false} otherwise. + */ + public boolean isMock() { + + return false; + } + + private SystemPath computeSystemPath() { + + return new SystemPath(this); + } + + private boolean isIdeHome(Path dir) { + + if (!Files.isDirectory(dir.resolve("workspaces"))) { + return false; + } else if (!Files.isDirectory(dir.resolve("settings"))) { + return false; + } + return true; + } + + private Path getParentPath(Path dir) { + + try { + Path linkDir = dir.toRealPath(); + if (!dir.equals(linkDir)) { + return linkDir; + } else { + return dir.getParent(); + } + } catch (IOException e) { + throw new IllegalStateException(e); + } + + } + + private EnvironmentVariables createVariables() { + + AbstractEnvironmentVariables system = EnvironmentVariables.ofSystem(this); + AbstractEnvironmentVariables user = extendVariables(system, this.userHomeIde, EnvironmentVariablesType.USER); + AbstractEnvironmentVariables settings = extendVariables(user, this.settingsPath, EnvironmentVariablesType.SETTINGS); + // TODO should we keep this workspace properties? Was this feature ever used? + AbstractEnvironmentVariables workspace = extendVariables(settings, this.workspacePath, + EnvironmentVariablesType.WORKSPACE); + AbstractEnvironmentVariables conf = extendVariables(workspace, this.confPath, EnvironmentVariablesType.CONF); + return conf.resolved(); + } + + private AbstractEnvironmentVariables extendVariables(AbstractEnvironmentVariables envVariables, Path propertiesPath, + EnvironmentVariablesType type) { + + Path propertiesFile = null; + if (propertiesPath == null) { + trace("Configuration directory for type {} does not exist.", type); + } else if (Files.isDirectory(propertiesPath)) { + propertiesFile = propertiesPath.resolve(EnvironmentVariables.DEFAULT_PROPERTIES); + boolean legacySupport = (type != EnvironmentVariablesType.USER); + if (legacySupport && !Files.exists(propertiesFile)) { + Path legacyFile = propertiesPath.resolve(EnvironmentVariables.LEGACY_PROPERTIES); + if (Files.exists(legacyFile)) { + propertiesFile = legacyFile; + } + } + } else { + debug("Configuration directory {} does not exist.", propertiesPath); + } + return envVariables.extend(propertiesFile, type); + } + + @Override + public SystemInfo getSystemInfo() { + + return this.systemInfo; + } + + @Override + public FileAccess getFileAccess() { + + return this.fileAccess; + } + + @Override + public CommandletManager getCommandletManager() { + + return this.commandletManager; + } + + @Override + public ToolRepository getDefaultToolRepository() { + + return this.defaultToolRepository; + } + + @Override + public CustomToolRepository getCustomToolRepository() { + + return this.customToolRepository; + } + + @Override + public Path getIdeHome() { + + return this.ideHome; + } + + @Override + public Path getIdeRoot() { + + return this.ideRoot; + } + + @Override + public Path getCwd() { + + return this.cwd; + } + + @Override + public Path getTempPath() { + + return this.tempPath; + } + + @Override + public Path getTempDownloadPath() { + + return this.tempDownloadPath; + } + + @Override + public Path getUserHome() { + + return this.userHome; + } + + @Override + public Path getUserHomeIde() { + + return this.userHomeIde; + } + + @Override + public Path getSettingsPath() { + + return this.settingsPath; + } + + @Override + public Path getConfPath() { + + return this.confPath; + } + + @Override + public Path getSoftwarePath() { + + return this.softwarePath; + } + + @Override + public Path getSoftwareRepositoryPath() { + + return this.softwareRepositoryPath; + } + + @Override + public Path getPluginsPath() { + + return this.pluginsPath; + } + + @Override + public String getWorkspaceName() { + + return this.workspaceName; + } + + @Override + public Path getWorkspacePath() { + + return this.workspacePath; + } + + @Override + public Path getDownloadPath() { + + return this.downloadPath; + } + + @Override + public Path getUrlsPath() { + + return this.urlsPath; + } + + @Override + public Path getToolRepositoryPath() { + + return this.toolRepository; + } + + @Override + public SystemPath getPath() { + + return this.path; + } + + @Override + public EnvironmentVariables getVariables() { + + return this.variables; + } + + @Override + public UrlMetadata getUrls() { + + if (this.urlMetadata == null) { + if (!isTest()) { + this.getGitContext().pullOrFetchAndResetIfNeeded(IDE_URLS_GIT, "master", this.urlsPath, "origin"); + } + this.urlMetadata = new UrlMetadata(this); + } + return this.urlMetadata; + } + + @Override + public boolean isQuietMode() { + + return this.quietMode; + } + + /** + * @param quietMode new value of {@link #isQuietMode()}. + */ + public void setQuietMode(boolean quietMode) { + + this.quietMode = quietMode; + } + + @Override + public boolean isBatchMode() { + + return this.batchMode; + } + + /** + * @param batchMode new value of {@link #isBatchMode()}. + */ + public void setBatchMode(boolean batchMode) { + + this.batchMode = batchMode; + } + + @Override + public boolean isForceMode() { + + return this.forceMode; + } + + /** + * @param forceMode new value of {@link #isForceMode()}. + */ + public void setForceMode(boolean forceMode) { + + this.forceMode = forceMode; + } + + @Override + public boolean isOfflineMode() { + + return this.offlineMode; + } + + /** + * @param offlineMode new value of {@link #isOfflineMode()}. + */ + public void setOfflineMode(boolean offlineMode) { + + this.offlineMode = offlineMode; + } + + @Override + public boolean isOnline() { + + boolean online = false; + try { + int timeout = 1000; + online = InetAddress.getByName("github.com").isReachable(timeout); + } catch (Exception ignored) { + + } + return online; + } + + @Override + public Locale getLocale() { + + if (this.locale == null) { + return Locale.getDefault(); + } + return this.locale; + } + + /** + * @param locale new value of {@link #getLocale()}. + */ + public void setLocale(Locale locale) { + + this.locale = locale; + } + + @Override + public DirectoryMerger getWorkspaceMerger() { + + return this.workspaceMerger; + } + + /** + * @return the {@link #defaultExecutionDirectory} the directory in which a command process is executed. + */ + public Path getDefaultExecutionDirectory() { + + return this.defaultExecutionDirectory; + } + + /** + * @param defaultExecutionDirectory new value of {@link #getDefaultExecutionDirectory()}. + */ + public void setDefaultExecutionDirectory(Path defaultExecutionDirectory) { + + if (defaultExecutionDirectory != null) { + this.defaultExecutionDirectory = defaultExecutionDirectory; + } + } + + @Override + public GitContext getGitContext() { + + return new GitContextImpl(this); + } + + @Override + public ProcessContext newProcess() { + + ProcessContext processContext = createProcessContext(); + if (this.defaultExecutionDirectory != null) { + processContext.directory(this.defaultExecutionDirectory); + } + return processContext; + } + + /** + * @return a new instance of {@link ProcessContext}. + * @see #newProcess() + */ + protected ProcessContext createProcessContext() { + + return new ProcessContextImpl(this); + } + + @Override + public IdeSubLogger level(IdeLogLevel level) { + + IdeSubLogger logger = this.loggers.get(level); + Objects.requireNonNull(logger); + return logger; + } + + @SuppressWarnings("unchecked") + @Override + public O question(String question, O... options) { + + assert (options.length >= 2); + interaction(question); + Map mapping = new HashMap<>(options.length); + int i = 0; + for (O option : options) { + i++; + String key = "" + option; + addMapping(mapping, key, option); + String numericKey = Integer.toString(i); + if (numericKey.equals(key)) { + trace("Options should not be numeric: " + key); + } else { + addMapping(mapping, numericKey, option); + } + interaction("Option " + numericKey + ": " + key); + } + O option = null; + if (isBatchMode()) { + if (isForceMode()) { + option = options[0]; + interaction("" + option); + } + } else { + while (option == null) { + String answer = readLine(); + option = mapping.get(answer); + if (option == null) { + warning("Invalid answer: '" + answer + "' - please try again."); + } + } + } + return option; + } + + /** + * @return the input from the end-user (e.g. read from the console). + */ + protected abstract String readLine(); + + private static void addMapping(Map mapping, String key, O option) { + + O duplicate = mapping.put(key, option); + if (duplicate != null) { + throw new IllegalArgumentException("Duplicated option " + key); + } + } + + /** + * Sets the log level. + * + * @param logLevel {@link IdeLogLevel} + */ + public void setLogLevel(IdeLogLevel logLevel) { + + for (IdeLogLevel level : IdeLogLevel.values()) { + IdeSubLogger logger; + if (level.ordinal() < logLevel.ordinal()) { + logger = new IdeSubLoggerNone(level); + } else { + logger = this.loggerFactory.apply(level); + } + this.loggers.put(level, logger); + } + } + + /** + * Finds the matching {@link Commandlet} to run, applies {@link CliArguments} to its + * {@link Commandlet#getProperties() properties} and will execute it. + * + * @param arguments the {@link CliArgument}. + * @return the return code of the execution. + */ + public int run(CliArguments arguments) { + + CliArgument current = arguments.current(); + if (!current.isEnd()) { + String keyword = current.get(); + Commandlet firstCandidate = this.commandletManager.getCommandletByFirstKeyword(keyword); + boolean matches; + if (firstCandidate != null) { + matches = applyAndRun(arguments.copy(), firstCandidate); + if (matches) { + return ProcessResult.SUCCESS; + } + } + for (Commandlet cmd : this.commandletManager.getCommandlets()) { + if (cmd != firstCandidate) { + matches = applyAndRun(arguments.copy(), cmd); + if (matches) { + return ProcessResult.SUCCESS; + } + } + } + error("Invalid arguments: {}", current.getArgs()); + } + this.commandletManager.getCommandlet(HelpCommandlet.class).run(); + return 1; + } + + /** + * @param cmd the potential {@link Commandlet} to + * {@link #apply(CliArguments, Commandlet, CompletionCandidateCollector) apply} and {@link Commandlet#run() run}. + * @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully, + * {@code false} otherwise (the {@link Commandlet} did not match and we have to try a different candidate). + */ + private boolean applyAndRun(CliArguments arguments, Commandlet cmd) { + + boolean matches = apply(arguments, cmd, null); + if (matches) { + matches = cmd.validate(); + } + if (matches) { + debug("Running commandlet {}", cmd); + if (cmd.isIdeHomeRequired() && (this.ideHome == null)) { + throw new CliException(getMessageIdeHomeNotFound()); + } + cmd.run(); + } else { + trace("Commandlet did not match"); + } + return matches; + } + + /** + * @param arguments the {@link CliArguments#ofCompletion(String...) completion arguments}. + * @param includeContextOptions to include the options of {@link ContextCommandlet}. + * @return the {@link List} of {@link CompletionCandidate}s to suggest. + */ + public List complete(CliArguments arguments, boolean includeContextOptions) { + + CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault(this); + if (arguments.current().isStart()) { + arguments.next(); + } + if (includeContextOptions) { + ContextCommandlet cc = new ContextCommandlet(); + for (Property property : cc.getProperties()) { + assert (property.isOption()); + property.apply(arguments, this, cc, collector); + } + } + CliArgument current = arguments.current(); + if (!current.isEnd()) { + String keyword = current.get(); + Commandlet firstCandidate = this.commandletManager.getCommandletByFirstKeyword(keyword); + boolean matches = false; + if (firstCandidate != null) { + matches = apply(arguments.copy(), firstCandidate, collector); + } else if (current.isCombinedShortOption()) { + collector.add(keyword, null, null, null); + } + if (!matches) { + for (Commandlet cmd : this.commandletManager.getCommandlets()) { + if (cmd != firstCandidate) { + apply(arguments.copy(), cmd, collector); + } + } + } + } + return collector.getSortedCandidates(); + } + + /** + * @param arguments the {@link CliArguments} to apply. Will be {@link CliArguments#next() consumed} as they are + * matched. Consider passing a {@link CliArguments#copy() copy} as needed. + * @param cmd the potential {@link Commandlet} to match. + * @param collector the {@link CompletionCandidateCollector}. + * @return {@code true} if the given {@link Commandlet} matches to the given {@link CliArgument}(s) and those have + * been applied (set in the {@link Commandlet} and {@link Commandlet#validate() validated}), {@code false} otherwise + * (the {@link Commandlet} did not match and we have to try a different candidate). + */ + public boolean apply(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) { + + trace("Trying to match arguments to commandlet {}", cmd.getName()); + CliArgument currentArgument = arguments.current(); + Iterator> propertyIterator; + if (currentArgument.isCompletion()) { + propertyIterator = cmd.getProperties().iterator(); + } else { + propertyIterator = cmd.getValues().iterator(); + } + while (!currentArgument.isEnd()) { + trace("Trying to match argument '{}'", currentArgument); + Property property = null; + if (!arguments.isEndOptions()) { + property = cmd.getOption(currentArgument.getKey()); + } + if (property == null) { + if (!propertyIterator.hasNext()) { + trace("No option or next value found"); + return false; + } + property = propertyIterator.next(); + } + trace("Next property candidate to match argument is {}", property); + boolean matches = property.apply(arguments, this, cmd, collector); + if (!matches || currentArgument.isCompletion()) { + return false; + } + currentArgument = arguments.current(); + } + return true; + } + +} From 6765d89dbb4ca9056814048f51858839ce5a5bf8 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:42:36 +0100 Subject: [PATCH 54/58] Update GitContextImpl.java --- .../tools/ide/context/GitContextImpl.java | 574 +++++++++--------- 1 file changed, 287 insertions(+), 287 deletions(-) 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 9813bdd5e..91022a728 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 @@ -1,287 +1,287 @@ -package com.devonfw.tools.ide.context; - -import java.io.IOException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.attribute.FileTime; -import java.time.Duration; -import java.util.AbstractMap; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import com.devonfw.tools.ide.cli.CliException; -import com.devonfw.tools.ide.log.IdeLogLevel; -import com.devonfw.tools.ide.log.IdeSubLogger; -import com.devonfw.tools.ide.process.ProcessContext; -import com.devonfw.tools.ide.process.ProcessErrorHandling; -import com.devonfw.tools.ide.process.ProcessMode; -import com.devonfw.tools.ide.process.ProcessResult; - -/** - * Implements the {@link GitContext}. - */ -public class GitContextImpl implements GitContext { - private static final Duration GIT_PULL_CACHE_DELAY_MILLIS = Duration.ofMillis(30 * 60 * 1000); - - ; - - private final IdeContext context; - - private ProcessContext processContext; - - /** - * @param context the {@link IdeContext context}. - */ - public GitContextImpl(IdeContext context) { - - this.context = context; - - } - - @Override - public void pullOrCloneIfNeeded(String repoUrl, String branch, Path targetRepository) { - - Path gitDirectory = targetRepository.resolve(".git"); - - // Check if the .git directory exists - if (Files.isDirectory(gitDirectory)) { - Path magicFilePath = gitDirectory.resolve("HEAD"); - long currentTime = System.currentTimeMillis(); - // Get the modification time of the magic file - long fileMTime; - try { - fileMTime = Files.getLastModifiedTime(magicFilePath).toMillis(); - } catch (IOException e) { - throw new IllegalStateException("Could not read " + magicFilePath, e); - } - - // Check if the file modification time is older than the delta threshold - if ((currentTime - fileMTime > GIT_PULL_CACHE_DELAY_MILLIS.toMillis()) || context.isForceMode()) { - pullOrClone(repoUrl, "", targetRepository); - try { - Files.setLastModifiedTime(magicFilePath, FileTime.fromMillis(currentTime)); - } catch (IOException e) { - throw new IllegalStateException("Could not read or write in " + magicFilePath, e); - } - } - } else { - // If the .git directory does not exist, perform git clone - pullOrClone(repoUrl, branch, targetRepository); - } - } - - public void pullOrFetchAndResetIfNeeded(String repoUrl, String branch, Path targetRepository, String remoteName) { - - pullOrCloneIfNeeded(repoUrl, branch, targetRepository); - - if (remoteName.isEmpty()) { - reset(targetRepository, "origin", "master"); - } else { - reset(targetRepository, remoteName, "master"); - } - - cleanup(targetRepository); - } - - @Override - public void pullOrClone(String gitRepoUrl, String branch, Path targetRepository) { - - Objects.requireNonNull(targetRepository); - Objects.requireNonNull(gitRepoUrl); - - if (!gitRepoUrl.startsWith("http")) { - throw new IllegalArgumentException("Invalid git URL '" + gitRepoUrl + "'!"); - } - - initializeProcessContext(targetRepository); - if (Files.isDirectory(targetRepository.resolve(".git"))) { - // checks for remotes - ProcessResult result = this.processContext.addArg("remote").run(ProcessMode.DEFAULT_CAPTURE); - List remotes = result.getOut(); - if (remotes.isEmpty()) { - String message = targetRepository - + " is a local git repository with no remote - if you did this for testing, you may continue...\n" - + "Do you want to ignore the problem and continue anyhow?"; - this.context.askToContinue(message); - } else { - this.processContext.errorHandling(ProcessErrorHandling.WARNING); - - if (!this.context.isOffline()) { - pull(targetRepository); - } - } - } else { - clone(new GitUrl(gitRepoUrl, branch), 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. - * @param result the {@link ProcessResult} to evaluate. - */ - private void handleErrors(Path targetRepository, ProcessResult result) { - - if (!result.isSuccessful()) { - String message = "Failed to update git repository at " + targetRepository; - if (this.context.isOffline()) { - this.context.warning(message); - this.context.interaction("Continuing as we are in offline mode - results may be outdated!"); - } 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."); - } else { - this.context.error( - "It seems you are offline - please ensure Internet connectivity and retry or activate offline mode (-o or --offline)."); - } - this.context.askToContinue("Typically you should abort and fix the problem. Do you want to continue anyways?"); - } - } - } - - /** - * 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. - */ - private void initializeProcessContext(Path targetRepository) { - - if (this.processContext == null) { - this.processContext = this.context.newProcess().directory(targetRepository).executable("git") - .withEnvVar("GIT_TERMINAL_PROMPT", "0"); - } - } - - @Override - public void clone(GitUrl gitRepoUrl, Path targetRepository) { - - URL parsedUrl = gitRepoUrl.parseUrl(); - initializeProcessContext(targetRepository); - ProcessResult result; - if (!this.context.isOffline()) { - this.context.getFileAccess().mkdirs(targetRepository); - this.context.requireOnline("git clone of " + parsedUrl); - this.processContext.addArg("clone"); - if (this.context.isQuietMode()) { - this.processContext.addArg("-q"); - } - 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."); - } - } - - @Override - public void pull(Path targetRepository) { - - initializeProcessContext(targetRepository); - ProcessResult result; - // pull from remote - result = this.processContext.addArg("--no-pager").addArg("pull").run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.isSuccessful()) { - Map remoteAndBranchName = retrieveRemoteAndBranchName(); - context.warning("Git pull for {}/{} failed for repository {}.", remoteAndBranchName.get("remote"), - remoteAndBranchName.get("branch"), targetRepository); - handleErrors(targetRepository, result); - } - } - - private Map retrieveRemoteAndBranchName() { - - Map remoteAndBranchName = new HashMap<>(); - ProcessResult remoteResult = this.processContext.addArg("branch").addArg("-vv").run(ProcessMode.DEFAULT_CAPTURE); - List remotes = remoteResult.getOut(); - if (!remotes.isEmpty()) { - for (String remote : remotes) { - if (remote.startsWith("*")) { - String checkedOutBranch = remote.substring(remote.indexOf("[") + 1, remote.indexOf("]")); - remoteAndBranchName.put("remote", checkedOutBranch.substring(0, checkedOutBranch.indexOf("/"))); - // check if current repo is behind remote and omit message - if (checkedOutBranch.contains(":")) { - remoteAndBranchName.put("branch", - checkedOutBranch.substring(checkedOutBranch.indexOf("/") + 1, checkedOutBranch.indexOf(":"))); - } else { - remoteAndBranchName.put("branch", checkedOutBranch.substring(checkedOutBranch.indexOf("/") + 1)); - } - - } - } - } else { - return Map.ofEntries(new AbstractMap.SimpleEntry<>("remote", "unknown"), - new AbstractMap.SimpleEntry<>("branch", "unknown")); - } - - return remoteAndBranchName; - } - - @Override - public void reset(Path targetRepository, String remoteName, String branchName) { - - initializeProcessContext(targetRepository); - ProcessResult result; - // check for changed files - result = this.processContext.addArg("diff-index").addArg("--quiet").addArg("HEAD").run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.isSuccessful()) { - // reset to origin/master - context.warning("Git has detected modified files -- attempting to reset {} to '{}/{}'.", targetRepository, - remoteName, branchName); - result = this.processContext.addArg("reset").addArg("--hard").addArg(remoteName + "/" + branchName) - .run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.isSuccessful()) { - context.warning("Git failed to reset {} to '{}/{}'.", remoteName, branchName, targetRepository); - handleErrors(targetRepository, result); - } - } - } - - @Override - public void cleanup(Path targetRepository) { - - initializeProcessContext(targetRepository); - ProcessResult result; - // check for untracked files - result = this.processContext.addArg("ls-files").addArg("--other").addArg("--directory").addArg("--exclude-standard") - .run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.getOut().isEmpty()) { - // delete untracked files - context.warning("Git detected untracked files in {} and is attempting a cleanup.", targetRepository); - result = this.processContext.addArg("clean").addArg("-df").run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.isSuccessful()) { - context.warning("Git failed to clean the repository {}.", targetRepository); - } - } - } - - @Override - public IdeSubLogger level(IdeLogLevel level) { - - return null; - } -} +package com.devonfw.tools.ide.context; + +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.time.Duration; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import com.devonfw.tools.ide.cli.CliException; +import com.devonfw.tools.ide.log.IdeLogLevel; +import com.devonfw.tools.ide.log.IdeSubLogger; +import com.devonfw.tools.ide.process.ProcessContext; +import com.devonfw.tools.ide.process.ProcessErrorHandling; +import com.devonfw.tools.ide.process.ProcessMode; +import com.devonfw.tools.ide.process.ProcessResult; + +/** + * Implements the {@link GitContext}. + */ +public class GitContextImpl implements GitContext { + private static final Duration GIT_PULL_CACHE_DELAY_MILLIS = Duration.ofMillis(30 * 60 * 1000); + + ; + + private final IdeContext context; + + private ProcessContext processContext; + + /** + * @param context the {@link IdeContext context}. + */ + public GitContextImpl(IdeContext context) { + + this.context = context; + + } + + @Override + public void pullOrCloneIfNeeded(String repoUrl, String branch, Path targetRepository) { + + Path gitDirectory = targetRepository.resolve(".git"); + + // Check if the .git directory exists + if (Files.isDirectory(gitDirectory)) { + Path magicFilePath = gitDirectory.resolve("HEAD"); + long currentTime = System.currentTimeMillis(); + // Get the modification time of the magic file + long fileMTime; + try { + fileMTime = Files.getLastModifiedTime(magicFilePath).toMillis(); + } catch (IOException e) { + throw new IllegalStateException("Could not read " + magicFilePath, e); + } + + // Check if the file modification time is older than the delta threshold + if ((currentTime - fileMTime > GIT_PULL_CACHE_DELAY_MILLIS.toMillis()) || context.isForceMode()) { + pullOrClone(repoUrl, "", targetRepository); + try { + Files.setLastModifiedTime(magicFilePath, FileTime.fromMillis(currentTime)); + } catch (IOException e) { + throw new IllegalStateException("Could not read or write in " + magicFilePath, e); + } + } + } else { + // If the .git directory does not exist, perform git clone + pullOrClone(repoUrl, branch, targetRepository); + } + } + + public void pullOrFetchAndResetIfNeeded(String repoUrl, String branch, Path targetRepository, String remoteName) { + + pullOrCloneIfNeeded(repoUrl, branch, targetRepository); + + if (remoteName.isEmpty()) { + reset(targetRepository, "origin", "master"); + } else { + reset(targetRepository, remoteName, "master"); + } + + cleanup(targetRepository); + } + + @Override + public void pullOrClone(String gitRepoUrl, String branch, Path targetRepository) { + + Objects.requireNonNull(targetRepository); + Objects.requireNonNull(gitRepoUrl); + + if (!gitRepoUrl.startsWith("http")) { + throw new IllegalArgumentException("Invalid git URL '" + gitRepoUrl + "'!"); + } + + initializeProcessContext(targetRepository); + if (Files.isDirectory(targetRepository.resolve(".git"))) { + // checks for remotes + ProcessResult result = this.processContext.addArg("remote").run(ProcessMode.DEFAULT_CAPTURE); + List remotes = result.getOut(); + if (remotes.isEmpty()) { + String message = targetRepository + + " is a local git repository with no remote - if you did this for testing, you may continue...\n" + + "Do you want to ignore the problem and continue anyhow?"; + this.context.askToContinue(message); + } else { + this.processContext.errorHandling(ProcessErrorHandling.WARNING); + + if (!this.context.isOffline()) { + pull(targetRepository); + } + } + } else { + clone(new GitUrl(gitRepoUrl, branch), 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. + * @param result the {@link ProcessResult} to evaluate. + */ + private void handleErrors(Path targetRepository, ProcessResult result) { + + if (!result.isSuccessful()) { + String message = "Failed to update git repository at " + targetRepository; + if (this.context.isOffline()) { + this.context.warning(message); + this.context.interaction("Continuing as we are in offline mode - results may be outdated!"); + } 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."); + } else { + this.context.error( + "It seems you are offline - please ensure Internet connectivity and retry or activate offline mode (-o or --offline)."); + } + this.context.askToContinue("Typically you should abort and fix the problem. Do you want to continue anyways?"); + } + } + } + + /** + * 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. + */ + private void initializeProcessContext(Path targetRepository) { + + if (this.processContext == null) { + this.processContext = this.context.newProcess().directory(targetRepository).executable("git") + .withEnvVar("GIT_TERMINAL_PROMPT", "0"); + } + } + + @Override + public void clone(GitUrl gitRepoUrl, Path targetRepository) { + + URL parsedUrl = gitRepoUrl.parseUrl(); + initializeProcessContext(targetRepository); + ProcessResult result; + if (!this.context.isOffline()) { + this.context.getFileAccess().mkdirs(targetRepository); + this.context.requireOnline("git clone of " + parsedUrl); + this.processContext.addArg("clone"); + if (this.context.isQuietMode()) { + this.processContext.addArg("-q"); + } + 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."); + } + } + + @Override + public void pull(Path targetRepository) { + + initializeProcessContext(targetRepository); + ProcessResult result; + // pull from remote + result = this.processContext.addArg("--no-pager").addArg("pull").run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.isSuccessful()) { + Map remoteAndBranchName = retrieveRemoteAndBranchName(); + context.warning("Git pull for {}/{} failed for repository {}.", remoteAndBranchName.get("remote"), + remoteAndBranchName.get("branch"), targetRepository); + handleErrors(targetRepository, result); + } + } + + private Map retrieveRemoteAndBranchName() { + + Map remoteAndBranchName = new HashMap<>(); + ProcessResult remoteResult = this.processContext.addArg("branch").addArg("-vv").run(ProcessMode.DEFAULT_CAPTURE); + List remotes = remoteResult.getOut(); + if (!remotes.isEmpty()) { + for (String remote : remotes) { + if (remote.startsWith("*")) { + String checkedOutBranch = remote.substring(remote.indexOf("[") + 1, remote.indexOf("]")); + remoteAndBranchName.put("remote", checkedOutBranch.substring(0, checkedOutBranch.indexOf("/"))); + // check if current repo is behind remote and omit message + if (checkedOutBranch.contains(":")) { + remoteAndBranchName.put("branch", + checkedOutBranch.substring(checkedOutBranch.indexOf("/") + 1, checkedOutBranch.indexOf(":"))); + } else { + remoteAndBranchName.put("branch", checkedOutBranch.substring(checkedOutBranch.indexOf("/") + 1)); + } + + } + } + } else { + return Map.ofEntries(new AbstractMap.SimpleEntry<>("remote", "unknown"), + new AbstractMap.SimpleEntry<>("branch", "unknown")); + } + + return remoteAndBranchName; + } + + @Override + public void reset(Path targetRepository, String remoteName, String branchName) { + + initializeProcessContext(targetRepository); + ProcessResult result; + // check for changed files + result = this.processContext.addArg("diff-index").addArg("--quiet").addArg("HEAD").run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.isSuccessful()) { + // reset to origin/master + context.warning("Git has detected modified files -- attempting to reset {} to '{}/{}'.", targetRepository, + remoteName, branchName); + result = this.processContext.addArg("reset").addArg("--hard").addArg(remoteName + "/" + branchName) + .run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.isSuccessful()) { + context.warning("Git failed to reset {} to '{}/{}'.", remoteName, branchName, targetRepository); + handleErrors(targetRepository, result); + } + } + } + + @Override + public void cleanup(Path targetRepository) { + + initializeProcessContext(targetRepository); + ProcessResult result; + // check for untracked files + result = this.processContext.addArg("ls-files").addArg("--other").addArg("--directory").addArg("--exclude-standard") + .run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.getOut().isEmpty()) { + // delete untracked files + context.warning("Git detected untracked files in {} and is attempting a cleanup.", targetRepository); + result = this.processContext.addArg("clean").addArg("-df").run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.isSuccessful()) { + context.warning("Git failed to clean the repository {}.", targetRepository); + } + } + } + + @Override + public IdeSubLogger level(IdeLogLevel level) { + + return null; + } +} From 6a8bf0e891d15217faddf72e206fa6721e1c4e60 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:43:18 +0100 Subject: [PATCH 55/58] Update GitContextImpl.java --- .../tools/ide/context/GitContextImpl.java | 574 +++++++++--------- 1 file changed, 287 insertions(+), 287 deletions(-) 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 91022a728..9813bdd5e 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 @@ -1,287 +1,287 @@ -package com.devonfw.tools.ide.context; - -import java.io.IOException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.attribute.FileTime; -import java.time.Duration; -import java.util.AbstractMap; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import com.devonfw.tools.ide.cli.CliException; -import com.devonfw.tools.ide.log.IdeLogLevel; -import com.devonfw.tools.ide.log.IdeSubLogger; -import com.devonfw.tools.ide.process.ProcessContext; -import com.devonfw.tools.ide.process.ProcessErrorHandling; -import com.devonfw.tools.ide.process.ProcessMode; -import com.devonfw.tools.ide.process.ProcessResult; - -/** - * Implements the {@link GitContext}. - */ -public class GitContextImpl implements GitContext { - private static final Duration GIT_PULL_CACHE_DELAY_MILLIS = Duration.ofMillis(30 * 60 * 1000); - - ; - - private final IdeContext context; - - private ProcessContext processContext; - - /** - * @param context the {@link IdeContext context}. - */ - public GitContextImpl(IdeContext context) { - - this.context = context; - - } - - @Override - public void pullOrCloneIfNeeded(String repoUrl, String branch, Path targetRepository) { - - Path gitDirectory = targetRepository.resolve(".git"); - - // Check if the .git directory exists - if (Files.isDirectory(gitDirectory)) { - Path magicFilePath = gitDirectory.resolve("HEAD"); - long currentTime = System.currentTimeMillis(); - // Get the modification time of the magic file - long fileMTime; - try { - fileMTime = Files.getLastModifiedTime(magicFilePath).toMillis(); - } catch (IOException e) { - throw new IllegalStateException("Could not read " + magicFilePath, e); - } - - // Check if the file modification time is older than the delta threshold - if ((currentTime - fileMTime > GIT_PULL_CACHE_DELAY_MILLIS.toMillis()) || context.isForceMode()) { - pullOrClone(repoUrl, "", targetRepository); - try { - Files.setLastModifiedTime(magicFilePath, FileTime.fromMillis(currentTime)); - } catch (IOException e) { - throw new IllegalStateException("Could not read or write in " + magicFilePath, e); - } - } - } else { - // If the .git directory does not exist, perform git clone - pullOrClone(repoUrl, branch, targetRepository); - } - } - - public void pullOrFetchAndResetIfNeeded(String repoUrl, String branch, Path targetRepository, String remoteName) { - - pullOrCloneIfNeeded(repoUrl, branch, targetRepository); - - if (remoteName.isEmpty()) { - reset(targetRepository, "origin", "master"); - } else { - reset(targetRepository, remoteName, "master"); - } - - cleanup(targetRepository); - } - - @Override - public void pullOrClone(String gitRepoUrl, String branch, Path targetRepository) { - - Objects.requireNonNull(targetRepository); - Objects.requireNonNull(gitRepoUrl); - - if (!gitRepoUrl.startsWith("http")) { - throw new IllegalArgumentException("Invalid git URL '" + gitRepoUrl + "'!"); - } - - initializeProcessContext(targetRepository); - if (Files.isDirectory(targetRepository.resolve(".git"))) { - // checks for remotes - ProcessResult result = this.processContext.addArg("remote").run(ProcessMode.DEFAULT_CAPTURE); - List remotes = result.getOut(); - if (remotes.isEmpty()) { - String message = targetRepository - + " is a local git repository with no remote - if you did this for testing, you may continue...\n" - + "Do you want to ignore the problem and continue anyhow?"; - this.context.askToContinue(message); - } else { - this.processContext.errorHandling(ProcessErrorHandling.WARNING); - - if (!this.context.isOffline()) { - pull(targetRepository); - } - } - } else { - clone(new GitUrl(gitRepoUrl, branch), 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. - * @param result the {@link ProcessResult} to evaluate. - */ - private void handleErrors(Path targetRepository, ProcessResult result) { - - if (!result.isSuccessful()) { - String message = "Failed to update git repository at " + targetRepository; - if (this.context.isOffline()) { - this.context.warning(message); - this.context.interaction("Continuing as we are in offline mode - results may be outdated!"); - } 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."); - } else { - this.context.error( - "It seems you are offline - please ensure Internet connectivity and retry or activate offline mode (-o or --offline)."); - } - this.context.askToContinue("Typically you should abort and fix the problem. Do you want to continue anyways?"); - } - } - } - - /** - * 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. - */ - private void initializeProcessContext(Path targetRepository) { - - if (this.processContext == null) { - this.processContext = this.context.newProcess().directory(targetRepository).executable("git") - .withEnvVar("GIT_TERMINAL_PROMPT", "0"); - } - } - - @Override - public void clone(GitUrl gitRepoUrl, Path targetRepository) { - - URL parsedUrl = gitRepoUrl.parseUrl(); - initializeProcessContext(targetRepository); - ProcessResult result; - if (!this.context.isOffline()) { - this.context.getFileAccess().mkdirs(targetRepository); - this.context.requireOnline("git clone of " + parsedUrl); - this.processContext.addArg("clone"); - if (this.context.isQuietMode()) { - this.processContext.addArg("-q"); - } - 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."); - } - } - - @Override - public void pull(Path targetRepository) { - - initializeProcessContext(targetRepository); - ProcessResult result; - // pull from remote - result = this.processContext.addArg("--no-pager").addArg("pull").run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.isSuccessful()) { - Map remoteAndBranchName = retrieveRemoteAndBranchName(); - context.warning("Git pull for {}/{} failed for repository {}.", remoteAndBranchName.get("remote"), - remoteAndBranchName.get("branch"), targetRepository); - handleErrors(targetRepository, result); - } - } - - private Map retrieveRemoteAndBranchName() { - - Map remoteAndBranchName = new HashMap<>(); - ProcessResult remoteResult = this.processContext.addArg("branch").addArg("-vv").run(ProcessMode.DEFAULT_CAPTURE); - List remotes = remoteResult.getOut(); - if (!remotes.isEmpty()) { - for (String remote : remotes) { - if (remote.startsWith("*")) { - String checkedOutBranch = remote.substring(remote.indexOf("[") + 1, remote.indexOf("]")); - remoteAndBranchName.put("remote", checkedOutBranch.substring(0, checkedOutBranch.indexOf("/"))); - // check if current repo is behind remote and omit message - if (checkedOutBranch.contains(":")) { - remoteAndBranchName.put("branch", - checkedOutBranch.substring(checkedOutBranch.indexOf("/") + 1, checkedOutBranch.indexOf(":"))); - } else { - remoteAndBranchName.put("branch", checkedOutBranch.substring(checkedOutBranch.indexOf("/") + 1)); - } - - } - } - } else { - return Map.ofEntries(new AbstractMap.SimpleEntry<>("remote", "unknown"), - new AbstractMap.SimpleEntry<>("branch", "unknown")); - } - - return remoteAndBranchName; - } - - @Override - public void reset(Path targetRepository, String remoteName, String branchName) { - - initializeProcessContext(targetRepository); - ProcessResult result; - // check for changed files - result = this.processContext.addArg("diff-index").addArg("--quiet").addArg("HEAD").run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.isSuccessful()) { - // reset to origin/master - context.warning("Git has detected modified files -- attempting to reset {} to '{}/{}'.", targetRepository, - remoteName, branchName); - result = this.processContext.addArg("reset").addArg("--hard").addArg(remoteName + "/" + branchName) - .run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.isSuccessful()) { - context.warning("Git failed to reset {} to '{}/{}'.", remoteName, branchName, targetRepository); - handleErrors(targetRepository, result); - } - } - } - - @Override - public void cleanup(Path targetRepository) { - - initializeProcessContext(targetRepository); - ProcessResult result; - // check for untracked files - result = this.processContext.addArg("ls-files").addArg("--other").addArg("--directory").addArg("--exclude-standard") - .run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.getOut().isEmpty()) { - // delete untracked files - context.warning("Git detected untracked files in {} and is attempting a cleanup.", targetRepository); - result = this.processContext.addArg("clean").addArg("-df").run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.isSuccessful()) { - context.warning("Git failed to clean the repository {}.", targetRepository); - } - } - } - - @Override - public IdeSubLogger level(IdeLogLevel level) { - - return null; - } -} +package com.devonfw.tools.ide.context; + +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.time.Duration; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import com.devonfw.tools.ide.cli.CliException; +import com.devonfw.tools.ide.log.IdeLogLevel; +import com.devonfw.tools.ide.log.IdeSubLogger; +import com.devonfw.tools.ide.process.ProcessContext; +import com.devonfw.tools.ide.process.ProcessErrorHandling; +import com.devonfw.tools.ide.process.ProcessMode; +import com.devonfw.tools.ide.process.ProcessResult; + +/** + * Implements the {@link GitContext}. + */ +public class GitContextImpl implements GitContext { + private static final Duration GIT_PULL_CACHE_DELAY_MILLIS = Duration.ofMillis(30 * 60 * 1000); + + ; + + private final IdeContext context; + + private ProcessContext processContext; + + /** + * @param context the {@link IdeContext context}. + */ + public GitContextImpl(IdeContext context) { + + this.context = context; + + } + + @Override + public void pullOrCloneIfNeeded(String repoUrl, String branch, Path targetRepository) { + + Path gitDirectory = targetRepository.resolve(".git"); + + // Check if the .git directory exists + if (Files.isDirectory(gitDirectory)) { + Path magicFilePath = gitDirectory.resolve("HEAD"); + long currentTime = System.currentTimeMillis(); + // Get the modification time of the magic file + long fileMTime; + try { + fileMTime = Files.getLastModifiedTime(magicFilePath).toMillis(); + } catch (IOException e) { + throw new IllegalStateException("Could not read " + magicFilePath, e); + } + + // Check if the file modification time is older than the delta threshold + if ((currentTime - fileMTime > GIT_PULL_CACHE_DELAY_MILLIS.toMillis()) || context.isForceMode()) { + pullOrClone(repoUrl, "", targetRepository); + try { + Files.setLastModifiedTime(magicFilePath, FileTime.fromMillis(currentTime)); + } catch (IOException e) { + throw new IllegalStateException("Could not read or write in " + magicFilePath, e); + } + } + } else { + // If the .git directory does not exist, perform git clone + pullOrClone(repoUrl, branch, targetRepository); + } + } + + public void pullOrFetchAndResetIfNeeded(String repoUrl, String branch, Path targetRepository, String remoteName) { + + pullOrCloneIfNeeded(repoUrl, branch, targetRepository); + + if (remoteName.isEmpty()) { + reset(targetRepository, "origin", "master"); + } else { + reset(targetRepository, remoteName, "master"); + } + + cleanup(targetRepository); + } + + @Override + public void pullOrClone(String gitRepoUrl, String branch, Path targetRepository) { + + Objects.requireNonNull(targetRepository); + Objects.requireNonNull(gitRepoUrl); + + if (!gitRepoUrl.startsWith("http")) { + throw new IllegalArgumentException("Invalid git URL '" + gitRepoUrl + "'!"); + } + + initializeProcessContext(targetRepository); + if (Files.isDirectory(targetRepository.resolve(".git"))) { + // checks for remotes + ProcessResult result = this.processContext.addArg("remote").run(ProcessMode.DEFAULT_CAPTURE); + List remotes = result.getOut(); + if (remotes.isEmpty()) { + String message = targetRepository + + " is a local git repository with no remote - if you did this for testing, you may continue...\n" + + "Do you want to ignore the problem and continue anyhow?"; + this.context.askToContinue(message); + } else { + this.processContext.errorHandling(ProcessErrorHandling.WARNING); + + if (!this.context.isOffline()) { + pull(targetRepository); + } + } + } else { + clone(new GitUrl(gitRepoUrl, branch), 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. + * @param result the {@link ProcessResult} to evaluate. + */ + private void handleErrors(Path targetRepository, ProcessResult result) { + + if (!result.isSuccessful()) { + String message = "Failed to update git repository at " + targetRepository; + if (this.context.isOffline()) { + this.context.warning(message); + this.context.interaction("Continuing as we are in offline mode - results may be outdated!"); + } 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."); + } else { + this.context.error( + "It seems you are offline - please ensure Internet connectivity and retry or activate offline mode (-o or --offline)."); + } + this.context.askToContinue("Typically you should abort and fix the problem. Do you want to continue anyways?"); + } + } + } + + /** + * 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. + */ + private void initializeProcessContext(Path targetRepository) { + + if (this.processContext == null) { + this.processContext = this.context.newProcess().directory(targetRepository).executable("git") + .withEnvVar("GIT_TERMINAL_PROMPT", "0"); + } + } + + @Override + public void clone(GitUrl gitRepoUrl, Path targetRepository) { + + URL parsedUrl = gitRepoUrl.parseUrl(); + initializeProcessContext(targetRepository); + ProcessResult result; + if (!this.context.isOffline()) { + this.context.getFileAccess().mkdirs(targetRepository); + this.context.requireOnline("git clone of " + parsedUrl); + this.processContext.addArg("clone"); + if (this.context.isQuietMode()) { + this.processContext.addArg("-q"); + } + 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."); + } + } + + @Override + public void pull(Path targetRepository) { + + initializeProcessContext(targetRepository); + ProcessResult result; + // pull from remote + result = this.processContext.addArg("--no-pager").addArg("pull").run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.isSuccessful()) { + Map remoteAndBranchName = retrieveRemoteAndBranchName(); + context.warning("Git pull for {}/{} failed for repository {}.", remoteAndBranchName.get("remote"), + remoteAndBranchName.get("branch"), targetRepository); + handleErrors(targetRepository, result); + } + } + + private Map retrieveRemoteAndBranchName() { + + Map remoteAndBranchName = new HashMap<>(); + ProcessResult remoteResult = this.processContext.addArg("branch").addArg("-vv").run(ProcessMode.DEFAULT_CAPTURE); + List remotes = remoteResult.getOut(); + if (!remotes.isEmpty()) { + for (String remote : remotes) { + if (remote.startsWith("*")) { + String checkedOutBranch = remote.substring(remote.indexOf("[") + 1, remote.indexOf("]")); + remoteAndBranchName.put("remote", checkedOutBranch.substring(0, checkedOutBranch.indexOf("/"))); + // check if current repo is behind remote and omit message + if (checkedOutBranch.contains(":")) { + remoteAndBranchName.put("branch", + checkedOutBranch.substring(checkedOutBranch.indexOf("/") + 1, checkedOutBranch.indexOf(":"))); + } else { + remoteAndBranchName.put("branch", checkedOutBranch.substring(checkedOutBranch.indexOf("/") + 1)); + } + + } + } + } else { + return Map.ofEntries(new AbstractMap.SimpleEntry<>("remote", "unknown"), + new AbstractMap.SimpleEntry<>("branch", "unknown")); + } + + return remoteAndBranchName; + } + + @Override + public void reset(Path targetRepository, String remoteName, String branchName) { + + initializeProcessContext(targetRepository); + ProcessResult result; + // check for changed files + result = this.processContext.addArg("diff-index").addArg("--quiet").addArg("HEAD").run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.isSuccessful()) { + // reset to origin/master + context.warning("Git has detected modified files -- attempting to reset {} to '{}/{}'.", targetRepository, + remoteName, branchName); + result = this.processContext.addArg("reset").addArg("--hard").addArg(remoteName + "/" + branchName) + .run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.isSuccessful()) { + context.warning("Git failed to reset {} to '{}/{}'.", remoteName, branchName, targetRepository); + handleErrors(targetRepository, result); + } + } + } + + @Override + public void cleanup(Path targetRepository) { + + initializeProcessContext(targetRepository); + ProcessResult result; + // check for untracked files + result = this.processContext.addArg("ls-files").addArg("--other").addArg("--directory").addArg("--exclude-standard") + .run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.getOut().isEmpty()) { + // delete untracked files + context.warning("Git detected untracked files in {} and is attempting a cleanup.", targetRepository); + result = this.processContext.addArg("clean").addArg("-df").run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.isSuccessful()) { + context.warning("Git failed to clean the repository {}.", targetRepository); + } + } + } + + @Override + public IdeSubLogger level(IdeLogLevel level) { + + return null; + } +} From 7b4bc5712e93ad8ad1d83a446019d526692bedb8 Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:44:00 +0100 Subject: [PATCH 56/58] Update GitContextImpl.java --- .../tools/ide/context/GitContextImpl.java | 574 +++++++++--------- 1 file changed, 287 insertions(+), 287 deletions(-) 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 9813bdd5e..91022a728 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 @@ -1,287 +1,287 @@ -package com.devonfw.tools.ide.context; - -import java.io.IOException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.attribute.FileTime; -import java.time.Duration; -import java.util.AbstractMap; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import com.devonfw.tools.ide.cli.CliException; -import com.devonfw.tools.ide.log.IdeLogLevel; -import com.devonfw.tools.ide.log.IdeSubLogger; -import com.devonfw.tools.ide.process.ProcessContext; -import com.devonfw.tools.ide.process.ProcessErrorHandling; -import com.devonfw.tools.ide.process.ProcessMode; -import com.devonfw.tools.ide.process.ProcessResult; - -/** - * Implements the {@link GitContext}. - */ -public class GitContextImpl implements GitContext { - private static final Duration GIT_PULL_CACHE_DELAY_MILLIS = Duration.ofMillis(30 * 60 * 1000); - - ; - - private final IdeContext context; - - private ProcessContext processContext; - - /** - * @param context the {@link IdeContext context}. - */ - public GitContextImpl(IdeContext context) { - - this.context = context; - - } - - @Override - public void pullOrCloneIfNeeded(String repoUrl, String branch, Path targetRepository) { - - Path gitDirectory = targetRepository.resolve(".git"); - - // Check if the .git directory exists - if (Files.isDirectory(gitDirectory)) { - Path magicFilePath = gitDirectory.resolve("HEAD"); - long currentTime = System.currentTimeMillis(); - // Get the modification time of the magic file - long fileMTime; - try { - fileMTime = Files.getLastModifiedTime(magicFilePath).toMillis(); - } catch (IOException e) { - throw new IllegalStateException("Could not read " + magicFilePath, e); - } - - // Check if the file modification time is older than the delta threshold - if ((currentTime - fileMTime > GIT_PULL_CACHE_DELAY_MILLIS.toMillis()) || context.isForceMode()) { - pullOrClone(repoUrl, "", targetRepository); - try { - Files.setLastModifiedTime(magicFilePath, FileTime.fromMillis(currentTime)); - } catch (IOException e) { - throw new IllegalStateException("Could not read or write in " + magicFilePath, e); - } - } - } else { - // If the .git directory does not exist, perform git clone - pullOrClone(repoUrl, branch, targetRepository); - } - } - - public void pullOrFetchAndResetIfNeeded(String repoUrl, String branch, Path targetRepository, String remoteName) { - - pullOrCloneIfNeeded(repoUrl, branch, targetRepository); - - if (remoteName.isEmpty()) { - reset(targetRepository, "origin", "master"); - } else { - reset(targetRepository, remoteName, "master"); - } - - cleanup(targetRepository); - } - - @Override - public void pullOrClone(String gitRepoUrl, String branch, Path targetRepository) { - - Objects.requireNonNull(targetRepository); - Objects.requireNonNull(gitRepoUrl); - - if (!gitRepoUrl.startsWith("http")) { - throw new IllegalArgumentException("Invalid git URL '" + gitRepoUrl + "'!"); - } - - initializeProcessContext(targetRepository); - if (Files.isDirectory(targetRepository.resolve(".git"))) { - // checks for remotes - ProcessResult result = this.processContext.addArg("remote").run(ProcessMode.DEFAULT_CAPTURE); - List remotes = result.getOut(); - if (remotes.isEmpty()) { - String message = targetRepository - + " is a local git repository with no remote - if you did this for testing, you may continue...\n" - + "Do you want to ignore the problem and continue anyhow?"; - this.context.askToContinue(message); - } else { - this.processContext.errorHandling(ProcessErrorHandling.WARNING); - - if (!this.context.isOffline()) { - pull(targetRepository); - } - } - } else { - clone(new GitUrl(gitRepoUrl, branch), 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. - * @param result the {@link ProcessResult} to evaluate. - */ - private void handleErrors(Path targetRepository, ProcessResult result) { - - if (!result.isSuccessful()) { - String message = "Failed to update git repository at " + targetRepository; - if (this.context.isOffline()) { - this.context.warning(message); - this.context.interaction("Continuing as we are in offline mode - results may be outdated!"); - } 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."); - } else { - this.context.error( - "It seems you are offline - please ensure Internet connectivity and retry or activate offline mode (-o or --offline)."); - } - this.context.askToContinue("Typically you should abort and fix the problem. Do you want to continue anyways?"); - } - } - } - - /** - * 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. - */ - private void initializeProcessContext(Path targetRepository) { - - if (this.processContext == null) { - this.processContext = this.context.newProcess().directory(targetRepository).executable("git") - .withEnvVar("GIT_TERMINAL_PROMPT", "0"); - } - } - - @Override - public void clone(GitUrl gitRepoUrl, Path targetRepository) { - - URL parsedUrl = gitRepoUrl.parseUrl(); - initializeProcessContext(targetRepository); - ProcessResult result; - if (!this.context.isOffline()) { - this.context.getFileAccess().mkdirs(targetRepository); - this.context.requireOnline("git clone of " + parsedUrl); - this.processContext.addArg("clone"); - if (this.context.isQuietMode()) { - this.processContext.addArg("-q"); - } - 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."); - } - } - - @Override - public void pull(Path targetRepository) { - - initializeProcessContext(targetRepository); - ProcessResult result; - // pull from remote - result = this.processContext.addArg("--no-pager").addArg("pull").run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.isSuccessful()) { - Map remoteAndBranchName = retrieveRemoteAndBranchName(); - context.warning("Git pull for {}/{} failed for repository {}.", remoteAndBranchName.get("remote"), - remoteAndBranchName.get("branch"), targetRepository); - handleErrors(targetRepository, result); - } - } - - private Map retrieveRemoteAndBranchName() { - - Map remoteAndBranchName = new HashMap<>(); - ProcessResult remoteResult = this.processContext.addArg("branch").addArg("-vv").run(ProcessMode.DEFAULT_CAPTURE); - List remotes = remoteResult.getOut(); - if (!remotes.isEmpty()) { - for (String remote : remotes) { - if (remote.startsWith("*")) { - String checkedOutBranch = remote.substring(remote.indexOf("[") + 1, remote.indexOf("]")); - remoteAndBranchName.put("remote", checkedOutBranch.substring(0, checkedOutBranch.indexOf("/"))); - // check if current repo is behind remote and omit message - if (checkedOutBranch.contains(":")) { - remoteAndBranchName.put("branch", - checkedOutBranch.substring(checkedOutBranch.indexOf("/") + 1, checkedOutBranch.indexOf(":"))); - } else { - remoteAndBranchName.put("branch", checkedOutBranch.substring(checkedOutBranch.indexOf("/") + 1)); - } - - } - } - } else { - return Map.ofEntries(new AbstractMap.SimpleEntry<>("remote", "unknown"), - new AbstractMap.SimpleEntry<>("branch", "unknown")); - } - - return remoteAndBranchName; - } - - @Override - public void reset(Path targetRepository, String remoteName, String branchName) { - - initializeProcessContext(targetRepository); - ProcessResult result; - // check for changed files - result = this.processContext.addArg("diff-index").addArg("--quiet").addArg("HEAD").run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.isSuccessful()) { - // reset to origin/master - context.warning("Git has detected modified files -- attempting to reset {} to '{}/{}'.", targetRepository, - remoteName, branchName); - result = this.processContext.addArg("reset").addArg("--hard").addArg(remoteName + "/" + branchName) - .run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.isSuccessful()) { - context.warning("Git failed to reset {} to '{}/{}'.", remoteName, branchName, targetRepository); - handleErrors(targetRepository, result); - } - } - } - - @Override - public void cleanup(Path targetRepository) { - - initializeProcessContext(targetRepository); - ProcessResult result; - // check for untracked files - result = this.processContext.addArg("ls-files").addArg("--other").addArg("--directory").addArg("--exclude-standard") - .run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.getOut().isEmpty()) { - // delete untracked files - context.warning("Git detected untracked files in {} and is attempting a cleanup.", targetRepository); - result = this.processContext.addArg("clean").addArg("-df").run(ProcessMode.DEFAULT_CAPTURE); - - if (!result.isSuccessful()) { - context.warning("Git failed to clean the repository {}.", targetRepository); - } - } - } - - @Override - public IdeSubLogger level(IdeLogLevel level) { - - return null; - } -} +package com.devonfw.tools.ide.context; + +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.time.Duration; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import com.devonfw.tools.ide.cli.CliException; +import com.devonfw.tools.ide.log.IdeLogLevel; +import com.devonfw.tools.ide.log.IdeSubLogger; +import com.devonfw.tools.ide.process.ProcessContext; +import com.devonfw.tools.ide.process.ProcessErrorHandling; +import com.devonfw.tools.ide.process.ProcessMode; +import com.devonfw.tools.ide.process.ProcessResult; + +/** + * Implements the {@link GitContext}. + */ +public class GitContextImpl implements GitContext { + private static final Duration GIT_PULL_CACHE_DELAY_MILLIS = Duration.ofMillis(30 * 60 * 1000); + + ; + + private final IdeContext context; + + private ProcessContext processContext; + + /** + * @param context the {@link IdeContext context}. + */ + public GitContextImpl(IdeContext context) { + + this.context = context; + + } + + @Override + public void pullOrCloneIfNeeded(String repoUrl, String branch, Path targetRepository) { + + Path gitDirectory = targetRepository.resolve(".git"); + + // Check if the .git directory exists + if (Files.isDirectory(gitDirectory)) { + Path magicFilePath = gitDirectory.resolve("HEAD"); + long currentTime = System.currentTimeMillis(); + // Get the modification time of the magic file + long fileMTime; + try { + fileMTime = Files.getLastModifiedTime(magicFilePath).toMillis(); + } catch (IOException e) { + throw new IllegalStateException("Could not read " + magicFilePath, e); + } + + // Check if the file modification time is older than the delta threshold + if ((currentTime - fileMTime > GIT_PULL_CACHE_DELAY_MILLIS.toMillis()) || context.isForceMode()) { + pullOrClone(repoUrl, "", targetRepository); + try { + Files.setLastModifiedTime(magicFilePath, FileTime.fromMillis(currentTime)); + } catch (IOException e) { + throw new IllegalStateException("Could not read or write in " + magicFilePath, e); + } + } + } else { + // If the .git directory does not exist, perform git clone + pullOrClone(repoUrl, branch, targetRepository); + } + } + + public void pullOrFetchAndResetIfNeeded(String repoUrl, String branch, Path targetRepository, String remoteName) { + + pullOrCloneIfNeeded(repoUrl, branch, targetRepository); + + if (remoteName.isEmpty()) { + reset(targetRepository, "origin", "master"); + } else { + reset(targetRepository, remoteName, "master"); + } + + cleanup(targetRepository); + } + + @Override + public void pullOrClone(String gitRepoUrl, String branch, Path targetRepository) { + + Objects.requireNonNull(targetRepository); + Objects.requireNonNull(gitRepoUrl); + + if (!gitRepoUrl.startsWith("http")) { + throw new IllegalArgumentException("Invalid git URL '" + gitRepoUrl + "'!"); + } + + initializeProcessContext(targetRepository); + if (Files.isDirectory(targetRepository.resolve(".git"))) { + // checks for remotes + ProcessResult result = this.processContext.addArg("remote").run(ProcessMode.DEFAULT_CAPTURE); + List remotes = result.getOut(); + if (remotes.isEmpty()) { + String message = targetRepository + + " is a local git repository with no remote - if you did this for testing, you may continue...\n" + + "Do you want to ignore the problem and continue anyhow?"; + this.context.askToContinue(message); + } else { + this.processContext.errorHandling(ProcessErrorHandling.WARNING); + + if (!this.context.isOffline()) { + pull(targetRepository); + } + } + } else { + clone(new GitUrl(gitRepoUrl, branch), 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. + * @param result the {@link ProcessResult} to evaluate. + */ + private void handleErrors(Path targetRepository, ProcessResult result) { + + if (!result.isSuccessful()) { + String message = "Failed to update git repository at " + targetRepository; + if (this.context.isOffline()) { + this.context.warning(message); + this.context.interaction("Continuing as we are in offline mode - results may be outdated!"); + } 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."); + } else { + this.context.error( + "It seems you are offline - please ensure Internet connectivity and retry or activate offline mode (-o or --offline)."); + } + this.context.askToContinue("Typically you should abort and fix the problem. Do you want to continue anyways?"); + } + } + } + + /** + * 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. + */ + private void initializeProcessContext(Path targetRepository) { + + if (this.processContext == null) { + this.processContext = this.context.newProcess().directory(targetRepository).executable("git") + .withEnvVar("GIT_TERMINAL_PROMPT", "0"); + } + } + + @Override + public void clone(GitUrl gitRepoUrl, Path targetRepository) { + + URL parsedUrl = gitRepoUrl.parseUrl(); + initializeProcessContext(targetRepository); + ProcessResult result; + if (!this.context.isOffline()) { + this.context.getFileAccess().mkdirs(targetRepository); + this.context.requireOnline("git clone of " + parsedUrl); + this.processContext.addArg("clone"); + if (this.context.isQuietMode()) { + this.processContext.addArg("-q"); + } + 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."); + } + } + + @Override + public void pull(Path targetRepository) { + + initializeProcessContext(targetRepository); + ProcessResult result; + // pull from remote + result = this.processContext.addArg("--no-pager").addArg("pull").run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.isSuccessful()) { + Map remoteAndBranchName = retrieveRemoteAndBranchName(); + context.warning("Git pull for {}/{} failed for repository {}.", remoteAndBranchName.get("remote"), + remoteAndBranchName.get("branch"), targetRepository); + handleErrors(targetRepository, result); + } + } + + private Map retrieveRemoteAndBranchName() { + + Map remoteAndBranchName = new HashMap<>(); + ProcessResult remoteResult = this.processContext.addArg("branch").addArg("-vv").run(ProcessMode.DEFAULT_CAPTURE); + List remotes = remoteResult.getOut(); + if (!remotes.isEmpty()) { + for (String remote : remotes) { + if (remote.startsWith("*")) { + String checkedOutBranch = remote.substring(remote.indexOf("[") + 1, remote.indexOf("]")); + remoteAndBranchName.put("remote", checkedOutBranch.substring(0, checkedOutBranch.indexOf("/"))); + // check if current repo is behind remote and omit message + if (checkedOutBranch.contains(":")) { + remoteAndBranchName.put("branch", + checkedOutBranch.substring(checkedOutBranch.indexOf("/") + 1, checkedOutBranch.indexOf(":"))); + } else { + remoteAndBranchName.put("branch", checkedOutBranch.substring(checkedOutBranch.indexOf("/") + 1)); + } + + } + } + } else { + return Map.ofEntries(new AbstractMap.SimpleEntry<>("remote", "unknown"), + new AbstractMap.SimpleEntry<>("branch", "unknown")); + } + + return remoteAndBranchName; + } + + @Override + public void reset(Path targetRepository, String remoteName, String branchName) { + + initializeProcessContext(targetRepository); + ProcessResult result; + // check for changed files + result = this.processContext.addArg("diff-index").addArg("--quiet").addArg("HEAD").run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.isSuccessful()) { + // reset to origin/master + context.warning("Git has detected modified files -- attempting to reset {} to '{}/{}'.", targetRepository, + remoteName, branchName); + result = this.processContext.addArg("reset").addArg("--hard").addArg(remoteName + "/" + branchName) + .run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.isSuccessful()) { + context.warning("Git failed to reset {} to '{}/{}'.", remoteName, branchName, targetRepository); + handleErrors(targetRepository, result); + } + } + } + + @Override + public void cleanup(Path targetRepository) { + + initializeProcessContext(targetRepository); + ProcessResult result; + // check for untracked files + result = this.processContext.addArg("ls-files").addArg("--other").addArg("--directory").addArg("--exclude-standard") + .run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.getOut().isEmpty()) { + // delete untracked files + context.warning("Git detected untracked files in {} and is attempting a cleanup.", targetRepository); + result = this.processContext.addArg("clean").addArg("-df").run(ProcessMode.DEFAULT_CAPTURE); + + if (!result.isSuccessful()) { + context.warning("Git failed to clean the repository {}.", targetRepository); + } + } + } + + @Override + public IdeSubLogger level(IdeLogLevel level) { + + return null; + } +} From b73e83d98fae2846019e4bf59861c7ccfb7a9d5f Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Fri, 15 Mar 2024 11:17:24 +0100 Subject: [PATCH 57/58] small git api improvements --- .../ide/commandlet/RepositoryCommandlet.java | 6 +----- .../devonfw/tools/ide/context/GitContext.java | 19 +++++++++++++------ .../tools/ide/context/GitContextImpl.java | 6 ++++++ .../com/devonfw/tools/ide/context/GitUrl.java | 2 +- .../tools/ide/context/GitContextMock.java | 5 +++++ .../tools/ide/context/GitContextTest.java | 4 ++-- 6 files changed, 28 insertions(+), 14 deletions(-) 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 index d85cdcc9a..f216099a4 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/commandlet/RepositoryCommandlet.java @@ -105,11 +105,7 @@ private void doImportRepository(Path repositoryFile, boolean forceMode) { this.context.getFileAccess().mkdirs(workspacePath); Path repositoryPath = workspacePath.resolve(repository); - String branch = ""; - if (repositoryConfig.gitBranch() != null) { - branch = repositoryConfig.gitBranch(); - } - this.context.getGitContext().pullOrClone(gitUrl, branch, repositoryPath); + this.context.getGitContext().pullOrClone(gitUrl, repositoryConfig.gitBranch(), repositoryPath); String buildCmd = repositoryConfig.buildCmd(); this.context.debug("Building repository with ide command: {}", buildCmd); 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 c79c7a7ea..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,8 +13,7 @@ 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. @@ -24,8 +23,7 @@ public interface GitContext extends IdeLogger { /** * 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 @@ -37,8 +35,17 @@ public interface GitContext extends IdeLogger { /** * 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. + */ + 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 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 91022a728..b1f8d3232 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 @@ -86,6 +86,12 @@ public void pullOrFetchAndResetIfNeeded(String repoUrl, String branch, Path targ cleanup(targetRepository); } + @Override + public void pullOrClone(String gitRepoUrl, Path targetRepository) { + + pullOrClone(gitRepoUrl, null, targetRepository); + } + @Override public void pullOrClone(String gitRepoUrl, String branch, Path targetRepository) { 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/test/java/com/devonfw/tools/ide/context/GitContextMock.java b/cli/src/test/java/com/devonfw/tools/ide/context/GitContextMock.java index 15681af20..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 @@ -16,6 +16,11 @@ public void pullOrFetchAndResetIfNeeded(String repoUrl, String branch, Path targ } + @Override + public void pullOrClone(String gitRepoUrl, Path targetRepository) { + + } + @Override public void pullOrClone(String gitRepoUrl, String branch, 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 090f0445b..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 @@ -55,7 +55,7 @@ public void testRunGitClone(@TempDir Path tempDir) { IdeContext context = newGitContext(tempDir, errors, outs, 0, true); GitContext gitContext = new GitContextImpl(context); // act - gitContext.pullOrClone(gitRepoUrl, "", tempDir); + gitContext.pullOrClone(gitRepoUrl, tempDir); // assert assertThat(tempDir.resolve(".git").resolve("url")).hasContent(gitRepoUrl); } @@ -78,7 +78,7 @@ public void testRunGitPullWithoutForce(@TempDir Path tempDir) { Path gitFolderPath = tempDir.resolve(".git"); fileAccess.mkdirs(gitFolderPath); // act - gitContext.pullOrClone(gitRepoUrl, "", tempDir); + gitContext.pullOrClone(gitRepoUrl, tempDir); // assert assertThat(tempDir.resolve(".git").resolve("update")).hasContent(currentDate.toString()); } From f513dfa4dc9f882f0c8ab3eac032085891571d6e Mon Sep 17 00:00:00 2001 From: salimbouch <145128725+salimbouch@users.noreply.github.com> Date: Fri, 15 Mar 2024 11:22:53 +0100 Subject: [PATCH 58/58] Update GitContextImpl.java --- .../main/java/com/devonfw/tools/ide/context/GitContextImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b1f8d3232..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 @@ -60,7 +60,7 @@ public void pullOrCloneIfNeeded(String repoUrl, String branch, Path targetReposi // Check if the file modification time is older than the delta threshold if ((currentTime - fileMTime > GIT_PULL_CACHE_DELAY_MILLIS.toMillis()) || context.isForceMode()) { - pullOrClone(repoUrl, "", targetRepository); + pullOrClone(repoUrl, targetRepository); try { Files.setLastModifiedTime(magicFilePath, FileTime.fromMillis(currentTime)); } catch (IOException e) {