diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index c7377d8efd..3307bb7d2f 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -37,6 +37,7 @@ Release with new features and bugfixes: * https://github.com/devonfw/IDEasy/issues/1135[#1135]: Fix PowerShell env variable initialization on Windows by sourcing functions from the PowerShell profile * https://github.com/devonfw/IDEasy/issues/741[#741]: Add a warning message for legacy devonfw-ide settings users * https://github.com/devonfw/IDEasy/issues/1933[#1933]: Added a console panel to the GUI +* https://github.com/devonfw/IDEasy/issues/2273[#2273]: Integrate Git as global tool The full list of changes for this release can be found in https://github.com/devonfw/IDEasy/milestone/49?closed=1[milestone 2026.09.001]. 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 f98a63decd..36ec7cf149 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 @@ -32,6 +32,7 @@ import com.devonfw.tools.ide.tool.gcloganalyzer.GcLogAnalyzer; import com.devonfw.tools.ide.tool.gcviewer.GcViewer; import com.devonfw.tools.ide.tool.gh.Gh; +import com.devonfw.tools.ide.tool.git.Git; import com.devonfw.tools.ide.tool.go.Go; import com.devonfw.tools.ide.tool.graalvm.GraalVm; import com.devonfw.tools.ide.tool.gradle.Gradle; @@ -188,6 +189,7 @@ public CommandletManagerImpl(IdeContext context) { add(new Just(context)); add(new SoapUi(context)); add(new Ruff(context)); + add(new Git(context)); } /** 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 8dfcabaa17..640d2eb546 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 @@ -35,6 +35,7 @@ import com.devonfw.tools.ide.commandlet.CommandletManagerImpl; import com.devonfw.tools.ide.commandlet.ContextCommandlet; import com.devonfw.tools.ide.commandlet.EnvironmentCommandlet; +import com.devonfw.tools.ide.commandlet.InstallCommandlet; import com.devonfw.tools.ide.commandlet.UpdateCommandlet; import com.devonfw.tools.ide.commandlet.UpgradeCommandlet; import com.devonfw.tools.ide.common.SystemPath; @@ -75,6 +76,7 @@ import com.devonfw.tools.ide.tool.ToolInstallation; import com.devonfw.tools.ide.tool.custom.CustomToolRepository; import com.devonfw.tools.ide.tool.custom.CustomToolRepositoryImpl; +import com.devonfw.tools.ide.tool.git.Git; import com.devonfw.tools.ide.tool.mvn.MvnRepository; import com.devonfw.tools.ide.tool.npm.NpmRepository; import com.devonfw.tools.ide.tool.pip.PipRepository; @@ -830,10 +832,11 @@ public UrlMetadata getUrls() { if (this.urlMetadata == null) { if (!isTest()) { - getGitContext().pullOrCloneAndResetIfNeeded(IDE_URLS_GIT, getUrlsPath(), null); + updateUrlsRepository(); } this.urlMetadata = new UrlMetadata(this); } + return this.urlMetadata; } @@ -1410,7 +1413,7 @@ public void runWithoutLogging(Runnable lambda, IdeLogLevel threshold) { /** * @param cmd the potential {@link Commandlet} to {@link #apply(CliArguments, Commandlet) 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). + * {@link Commandlet} did not match, and we have to try a different candidate). */ private ValidationResult applyAndRun(CliArguments arguments, Commandlet cmd) { @@ -1440,20 +1443,24 @@ private ValidationResult applyAndRun(CliArguments arguments, Commandlet cmd) { if (!(cmd instanceof UpgradeCommandlet)) { verifyIdeMinVersion(false); } - Path settingsRepository = getSettingsGitRepository(); - if (settingsRepository != null) { - if (getGitContext().isRepositoryUpdateAvailable(settingsRepository, getSettingsCommitIdPath()) || ( - getGitContext().fetchIfNeeded(settingsRepository) && getGitContext().isRepositoryUpdateAvailable( - settingsRepository, getSettingsCommitIdPath()))) { - - // Inform the user that an update is available. The update message is suppressed if we are already running the update - String msg = determineSettingsUpdateMessage(cmd); - if (msg != null) { - IdeLogLevel.INTERACTION.log(LOG, msg); + + if (!isInstallingGit(cmd)) { + Path settingsRepository = getSettingsGitRepository(); + if (settingsRepository != null) { + if (getGitContext().isRepositoryUpdateAvailable(settingsRepository, getSettingsCommitIdPath()) || ( + getGitContext().fetchIfNeeded(settingsRepository) && getGitContext().isRepositoryUpdateAvailable( + settingsRepository, getSettingsCommitIdPath()))) { + + // Inform the user that an update is available. The update message is suppressed if we are already running the update + String msg = determineSettingsUpdateMessage(cmd); + if (msg != null) { + IdeLogLevel.INTERACTION.log(LOG, msg); + } } } } } + boolean success = ensureLicenseAgreement(cmd); if (!success) { return ValidationResultValid.get(); @@ -1970,4 +1977,28 @@ public String getDefaultWindowsGitPath() { return DEFAULT_WINDOWS_GIT_PATH; } + private boolean isInstallingGit(Commandlet cmd) { + + if (cmd instanceof InstallCommandlet installCommandlet) { + return installCommandlet.tool.getValue() instanceof Git; + } + return false; + } + + public void updateUrlsRepository() { + + GitContext gitContext = getGitContext(); + + if (gitContext.findGit() != null) { + gitContext.pullOrCloneAndResetIfNeeded(IDE_URLS_GIT, getUrlsPath(), null); + } else { + Path urlsPath = getUrlsPath(); + + if ((urlsPath != null) && Files.isDirectory(urlsPath)) { + LOG.debug("Git is not available. Using existing URL metadata without updating it."); + } else { + gitContext.findGitRequired(); + } + } + } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/git/GitContextImpl.java b/cli/src/main/java/com/devonfw/tools/ide/git/GitContextImpl.java index 49adbb59cd..e50c917d1e 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/git/GitContextImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/git/GitContextImpl.java @@ -336,9 +336,12 @@ public Path findGitRequired() { Path gitPath = findGit(); if (gitPath == null) { - String message = "Git " + IdeContext.IS_NOT_INSTALLED_BUT_REQUIRED; + String message = "Git " + IdeContext.IS_NOT_INSTALLED_BUT_REQUIRED + + ". Try running `ide install git` to install it."; + if (SystemInfoImpl.INSTANCE.isWindows()) { - message += IdeContext.PLEASE_DOWNLOAD_AND_INSTALL_GIT + ":\n " + IdeContext.WINDOWS_GIT_DOWNLOAD_URL; + message += "\nAlternatively, " + IdeContext.PLEASE_DOWNLOAD_AND_INSTALL_GIT + ":\n " + + IdeContext.WINDOWS_GIT_DOWNLOAD_URL; } throw new CliException(message); } diff --git a/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelperImpl.java b/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelperImpl.java index d2c99f9448..4999f515c3 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelperImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelperImpl.java @@ -144,16 +144,62 @@ private String findUninstallKey(String appName) { if (out == null) { continue; } + for (String line : out) { - line = line.trim(); - if (line.startsWith("HKEY_")) { - return line; // exact registry path (key) for tool + String key = getMatchingUninstallKey(line, appName); + if (key != null) { + return key; } } } + return null; } + /** + * Checks whether the given registry search result represents an uninstallation key whose display name matches the requested application. + * + * @param line the registry search result line to check. + * @param appName the application name to match. + * @return the matching uninstall registry key, or {@code null} if the line does not represent a matching application. + */ + private String getMatchingUninstallKey(String line, String appName) { + + String key = line.trim(); + if (!key.startsWith("HKEY_")) { + return null; + } + + List values = runReg("query", key); + if (values == null) { + return null; + } + + String displayName = retrieveRegString("DisplayName", values); + if (matchesAppName(displayName, appName)) { + return key; + } + + return null; + } + + /** + * Checks whether a Windows application display name matches the requested application name. + * + * @param displayName the application's display name from the Windows registry. + * @param appName the application name to match. + * @return {@code true} if the names match, {@code false} otherwise. + */ + private boolean matchesAppName(String displayName, String appName) { + + if ((displayName == null) || (appName == null)) { + return false; + } + + return displayName.equalsIgnoreCase(appName) + || displayName.toLowerCase().startsWith(appName.toLowerCase() + " "); + } + /** * Executes a Windows registry command and returns its output. * diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/GlobalToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/GlobalToolCommandlet.java index fb276ce37b..082f34914a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/GlobalToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/GlobalToolCommandlet.java @@ -134,7 +134,7 @@ protected ToolInstallation doInstall(ToolInstallRequest request) { VersionIdentifier resolvedVersion = request.getRequested().getResolvedVersion(); if (this.context.getSystemInfo().isLinux()) { // on Linux global tools are typically installed via the package manager of the OS - // if a global tool implements getNativePackages() to returns at least one NativePackage, then we will install this way. + // if a global tool implements getNativePackages() to return at least one NativePackage, then we will install this way. List commands = getInstallPackageManagerCommands(resolvedVersion); if (!commands.isEmpty()) { boolean newInstallation = runWithPackageManager(request.isSilent(), commands, NativePackageAction.INSTALL); @@ -164,7 +164,10 @@ protected ToolInstallation doInstall(ToolInstallRequest request) { fileAccess.extract(target, downloadBinaryPath); executable = fileAccess.findFirst(downloadBinaryPath, Files::isExecutable, false); } - ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.LOG_WARNING).executable(executable); + ProcessContext pc = this.context.newProcess() + .errorHandling(ProcessErrorHandling.LOG_WARNING) + .executable(executable) + .addArgs(getInstallerArguments()); int exitCode = pc.run(ProcessMode.BACKGROUND_SILENT).getExitCode(); if (tmpDir != null) { fileAccess.delete(tmpDir); @@ -314,4 +317,13 @@ public void uninstall() { LOG.error("Couldn't uninstall {} on this OS. Please uninstall manually.", this.getName()); } } + + @Override + protected boolean requiresVersionResolution() { + return !this.context.getSystemInfo().isLinux() || getNativePackages().isEmpty(); + } + + protected List getInstallerArguments() { + return List.of(); + } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java index d6b51d7319..2b5a1b0c20 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java @@ -472,10 +472,14 @@ private void completeRequestRequested(ToolInstallRequest request) { } } } - if (resolvedVersion == null) { + + if ((resolvedVersion == null) && requiresVersionResolution()) { resolvedVersion = getToolRepository().resolveVersion(this.tool, edition.edition(), version, this); } - requested.setResolvedVersion(resolvedVersion); + + if (resolvedVersion != null) { + requested.setResolvedVersion(resolvedVersion); + } } } @@ -1141,6 +1145,13 @@ protected VersionIdentifier resolveVersionWithPattern(String output, Pattern pat } } + /** + * @return {@code true} if the requested tool version has to be resolved from the tool repository. + */ + protected boolean requiresVersionResolution() { + return true; + } + /** * @deprecated directly log success message and then report success on step if not null. */ diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/git/Git.java b/cli/src/main/java/com/devonfw/tools/ide/tool/git/Git.java new file mode 100644 index 0000000000..049a7a469a --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/git/Git.java @@ -0,0 +1,56 @@ +package com.devonfw.tools.ide.tool.git; + +import java.util.List; +import java.util.Set; + +import com.devonfw.tools.ide.common.Tag; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.tool.GlobalToolCommandlet; +import com.devonfw.tools.ide.tool.NativePackage; +import com.devonfw.tools.ide.tool.NativePackageManager; + +/** + * {@link GlobalToolCommandlet} for Git. + */ +public class Git extends GlobalToolCommandlet { + + /** + * The constructor. + * + * @param context the {@link IdeContext}. + */ + public Git(IdeContext context) { + super(context, "git", Set.of(Tag.GIT)); + } + + @Override + protected String getBinaryName() { + return "git"; + } + + @Override + protected List getNativePackages() { + return List.of( + NativePackage.of(NativePackageManager.APT, "git"), + NativePackage.of(NativePackageManager.ZYPPER, "git") + ); + } + + @Override + public String getWindowsRegistryAppName() { + return "Git"; + } + + @Override + protected List getInstallerArguments() { + if (this.context.getSystemInfo().isWindows()) { + return List.of( + "/VERYSILENT", + "/NORESTART", + "/NOCANCEL", + "/SP-" + ); + } + return List.of(); + } +} diff --git a/cli/src/main/resources/nls/Help.properties b/cli/src/main/resources/nls/Help.properties index e678805cc2..11c808d70a 100644 --- a/cli/src/main/resources/nls/Help.properties +++ b/cli/src/main/resources/nls/Help.properties @@ -48,6 +48,8 @@ cmd.get-version.opt.--configured=print only the configured version cmd.get-version.opt.--installed=print only the installed version cmd.gh=Tool commandlet for GitHub CLI. cmd.gh.detail=GitHub CLI (Command Line Interface) allows to interact with GitHub repositories, issues, and pull requests from the command line. Detailed documentation can be found at https://cli.github.com/manual/ +cmd.git=Tool commandlet for Git. +cmd.git.detail=Git is a distributed version control system for tracking changes in source code. Detailed documentation can be found at https://git-scm.com/doc cmd.go=Tool commandlet for Go (programming language). cmd.go.detail=Go is an open-source programming language for building simple, reliable, and efficient software. Detailed documentation can be found at https://go.dev/doc/ cmd.graalvm=Tool commandlet for GraalVm (Java with native-image). diff --git a/cli/src/main/resources/nls/Help_de.properties b/cli/src/main/resources/nls/Help_de.properties index 8276ff9bae..616d419f3f 100644 --- a/cli/src/main/resources/nls/Help_de.properties +++ b/cli/src/main/resources/nls/Help_de.properties @@ -48,6 +48,8 @@ cmd.get-version.opt.--configured=zeigt nur die konfigurierte Version cmd.get-version.opt.--installed=zeigt nur die installierte Version cmd.gh=Werkzeug Kommando für die Github Kommandoschnittstelle. cmd.gh.detail=GitHub CLI (Command Line Interface) ermöglicht die Interaktion mit GitHub-Repositories, Issues und Pull Requests über die Befehlszeile. Detaillierte Dokumentation ist zu finden unter https://cli.github.com/manual/ +cmd.git=Werkzeug-Kommando für Git. +cmd.git.detail=Git ist ein verteiltes Versionsverwaltungssystem zum Nachverfolgen von Änderungen am Quellcode. Ausführliche Dokumentation ist unter https://git-scm.com/doc verfügbar. cmd.go=Werkzeug Kommando für Go (Programmiersprache). cmd.go.detail=Go ist eine Open-Source-Programmiersprache zum Erstellen einfacher, zuverlässiger und effizienter Software. Detaillierte Dokumentation ist zu finden unter https://go.dev/doc/ cmd.graalvm=Werkzeug Kommando für GraalVm. diff --git a/cli/src/test/java/com/devonfw/tools/ide/git/GitOperationTest.java b/cli/src/test/java/com/devonfw/tools/ide/git/GitOperationTest.java index 9dce45b5a5..bd9c303a36 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/git/GitOperationTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/git/GitOperationTest.java @@ -214,6 +214,61 @@ void testPullOrCloneSkippedIfRepoNotInitializedAndOfflineMode(@TempDir Path temp Mockito.verify(mock).pullOrClone(GIT_URL, repo); } + /** + * Verifies that existing URL metadata is reused when Git is not available but the local URL repository already exists. + */ + @Test + void testUpdateUrlsRepositoryUsesExistingUrlsWithoutGit() { + + // arrange + IdeTestContext context = newContext(PROJECT_BASIC, null, false); + GitContext mock = Mockito.mock(GitContext.class); + context.setGitContext(mock); + + Mockito.when(mock.findGit()).thenReturn(null); + + Path urlsPath = context.getUrlsPath(); + context.getFileAccess().mkdirs(urlsPath); + + // act + context.updateUrlsRepository(); + + // assert + assertThat(context).logAtDebug() + .hasMessage("Git is not available. Using existing URL metadata without updating it."); + + Mockito.verify(mock).findGit(); + Mockito.verify(mock, Mockito.never()).findGitRequired(); + Mockito.verify(mock, Mockito.never()) + .pullOrCloneAndResetIfNeeded(Mockito.any(), Mockito.any(), Mockito.any()); + } + + /** + * Verifies that Git is required when Git is not available and no local URL repository exists. + */ + @Test + void testUpdateUrlsRepositoryRequiresGitWithoutExistingUrls() { + + // arrange + IdeTestContext context = newContext(PROJECT_BASIC, null, false); + GitContext mock = Mockito.mock(GitContext.class); + context.setGitContext(mock); + + Mockito.when(mock.findGit()).thenReturn(null); + + Path urlsPath = context.getUrlsPath(); + context.getFileAccess().delete(urlsPath); + + // act + context.updateUrlsRepository(); + + // assert + Mockito.verify(mock).findGit(); + Mockito.verify(mock).findGitRequired(); + Mockito.verify(mock, Mockito.never()) + .pullOrCloneAndResetIfNeeded(Mockito.any(), Mockito.any(), Mockito.any()); + } + private Path createFakeGitRepo(Path dir, String file) throws Exception { return createFakeGitRepo(dir, file, false); diff --git a/cli/src/test/java/com/devonfw/tools/ide/os/WindowsHelperImplTest.java b/cli/src/test/java/com/devonfw/tools/ide/os/WindowsHelperImplTest.java index d2779ccaea..448528058f 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/os/WindowsHelperImplTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/os/WindowsHelperImplTest.java @@ -119,6 +119,7 @@ protected List runReg(String... args) { if (args.length >= 2 && args[0].equalsIgnoreCase("query") && args[1].endsWith("\\Uninstall\\TestApp")) { return List.of( "HKEY_LOCAL_MACHINE\\SOFTWARE\\...\\Uninstall\\TestApp", + " DisplayName REG_SZ TestApp", " DisplayVersion REG_SZ 2.0.0", " InstallLocation REG_SZ C:\\Program Files\\TestApp" ); @@ -194,6 +195,7 @@ protected List runReg(String... args) { if (args.length >= 2 && args[0].equalsIgnoreCase("query")) { return List.of( "HKEY_LOCAL_MACHINE\\...\\Uninstall\\TestApp", + " DisplayName REG_SZ TestApp", " DisplayVersion REG_SZ ", " DisplayIcon REG_SZ " ); @@ -308,4 +310,111 @@ void testUninstallApplicationDoesNothingWithoutUninstallString() { // assert assertThat(helper.getExecutedUninstallCommand()).isNull(); } + + /** + * Tests that a registry search result with a different DisplayName is ignored. + */ + @Test + void testGetAppInstallationFromRegistryIgnoresNonMatchingDisplayName() { + AbstractIdeTestContext context = new IdeTestContext(); + WindowsHelperImpl helper = new WindowsHelperImpl(context) { + @Override + protected List runReg(String... args) { + if (args.length >= 5 && "/f".equalsIgnoreCase(args[3])) { + return List.of( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\...\\Uninstall\\GitLFS"); + } + + if (args.length >= 2 + && args[0].equalsIgnoreCase("query") + && args[1].endsWith("\\Uninstall\\GitLFS")) { + return List.of( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\...\\Uninstall\\GitLFS", + " DisplayName REG_SZ GitHub Desktop"); + } + + return List.of(); + } + }; + + WindowsAppInstallation installation = + helper.getAppInstallationFromRegistry("Git"); + + assertThat(installation).isNull(); + } + + /** + * Tests that a DisplayName containing the application name followed by a suffix is accepted. + */ + @Test + void testGetAppInstallationFromRegistryMatchesDisplayNameWithSuffix() { + AbstractIdeTestContext context = new IdeTestContext(); + WindowsHelperImpl helper = new WindowsHelperImpl(context) { + @Override + protected List runReg(String... args) { + if (args.length >= 5 && "/f".equalsIgnoreCase(args[3])) { + return List.of( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\...\\Uninstall\\Git"); + } + + if (args.length >= 2 + && args[0].equalsIgnoreCase("query") + && args[1].endsWith("\\Uninstall\\Git")) { + return List.of( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\...\\Uninstall\\Git", + " DisplayName REG_SZ Git version 2.55.0", + " DisplayVersion REG_SZ 2.55.0"); + } + + return List.of(); + } + }; + + WindowsAppInstallation installation = + helper.getAppInstallationFromRegistry("Git"); + + assertThat(installation).isNotNull(); + assertThat(installation.version()).isEqualTo("2.55.0"); + } + + /** + * Tests that registry lookup continues with the next registry base path if a query fails. + */ + @Test + void testGetAppInstallationFromRegistryContinuesAfterRegistryQueryFailure() { + AbstractIdeTestContext context = new IdeTestContext(); + + WindowsHelperImpl helper = new WindowsHelperImpl(context) { + private int searchCount; + + @Override + protected List runReg(String... args) { + if (args.length >= 5 && "/f".equalsIgnoreCase(args[3])) { + this.searchCount++; + + if (this.searchCount == 1) { + return null; + } + + return List.of( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\...\\Uninstall\\TestApp"); + } + + if (args.length >= 2 && args[0].equalsIgnoreCase("query")) { + return List.of( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\...\\Uninstall\\TestApp", + " DisplayName REG_SZ TestApp", + " DisplayVersion REG_SZ 1.0"); + } + + return List.of(); + } + }; + + WindowsAppInstallation installation = + helper.getAppInstallationFromRegistry("TestApp"); + + assertThat(installation).isNotNull(); + assertThat(installation.version()).isEqualTo("1.0"); + } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/GlobalToolCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/GlobalToolCommandletTest.java index ede372170f..40fcd9536e 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/tool/GlobalToolCommandletTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/GlobalToolCommandletTest.java @@ -199,4 +199,84 @@ void testGetUninstallPackageManagerCommandsDerivesFromNativePackages() { "sudo apt -y autoremove --purge mytool", "sudo rm -f /etc/apt/sources.list.d/mytool.list"); } + + /** + * Tests that version resolution is skipped on Linux when native packages are available. + */ + @Test + void testRequiresVersionResolutionReturnsFalseForNativePackageOnLinux() { + + // arrange + IdeTestContext context = newContext(PROJECT_BASIC); + context.setSystemInfo(SystemInfoMock.LINUX_X64); + PackageManagedToolCommandlet commandlet = new PackageManagedToolCommandlet(context); + + // act + assert + assertThat(commandlet.requiresVersionResolution()).isFalse(); + } + + /** + * Tests that version resolution is still required on Windows. + */ + @Test + void testRequiresVersionResolutionReturnsTrueOnWindows() { + + // arrange + IdeTestContext context = newContext(PROJECT_BASIC); + context.setSystemInfo(SystemInfoMock.WINDOWS_X64); + PackageManagedToolCommandlet commandlet = new PackageManagedToolCommandlet(context); + + // act + assert + assertThat(commandlet.requiresVersionResolution()).isTrue(); + } + + /** + * Tests that version resolution is required on Linux when no native packages are configured. + */ + @Test + void testRequiresVersionResolutionReturnsTrueWithoutNativePackagesOnLinux() { + + // arrange + IdeTestContext context = newContext(PROJECT_BASIC); + context.setSystemInfo(SystemInfoMock.LINUX_X64); + AsyncInstallerToolCommandlet commandlet = new AsyncInstallerToolCommandlet(context); + + // act + assert + assertThat(commandlet.requiresVersionResolution()).isTrue(); + } + + /** + * Tests that global tools have no installer arguments by default. + */ + @Test + void testGetInstallerArgumentsReturnsEmptyListByDefault() { + + // arrange + IdeTestContext context = newContext(PROJECT_BASIC); + AsyncInstallerToolCommandlet commandlet = new AsyncInstallerToolCommandlet(context); + + // act + assert + assertThat(commandlet.getInstallerArguments()).isEmpty(); + } + + /** + * Tests that native package install commands can be created without a resolved version. + */ + @Test + void testGetInstallPackageManagerCommandsWithoutResolvedVersion() { + + // arrange + IdeTestContext context = newContext(PROJECT_BASIC); + context.setSystemInfo(SystemInfoMock.LINUX_X64); + PackageManagedToolCommandlet commandlet = new PackageManagedToolCommandlet(context); + + // act + List commands = + commandlet.getInstallPackageManagerCommands(null); + + // assert + assertThat(commands).hasSize(1); + assertThat(commands.getFirst().packageManager()) + .isEqualTo(NativePackageManager.APT); + } } diff --git a/documentation/LICENSE.adoc b/documentation/LICENSE.adoc index 735801f784..3c829dfcd0 100644 --- a/documentation/LICENSE.adoc +++ b/documentation/LICENSE.adoc @@ -100,6 +100,7 @@ The column `inclusion` indicates the way the component is included: |https://dotnet.microsoft.com/[Dotnet]|Optional|https://github.com/dotnet/core/blob/master/LICENSE.TXT[MIT] (https://www.microsoft.com/en-us/legal/intellectualproperty/copyright/default.aspx[Terms]) |https://github.com/pypa/pip/[Pip] |Optional|https://github.com/pypa/pip/blob/main/LICENSE.txt[MIT] |https://github.com/openshift/oc[OpenShiftCLI]|Optional|https://github.com/openshift/oc/blob/master/LICENSE[Apache 2.0] +|https://git-scm.com/[Git]|Optional|https://github.com/git/git/blob/master/COPYING[GPL 2.0] |https://github.com/cli/cli/[GitHubCLI]|Optional|https://github.com/cli/cli/blob/trunk/LICENSE[MIT] |https://quarkus.io/guides/cli-tooling[QuarkusCLI]|Optional|https://github.com/quarkusio/quarkus/blob/main/LICENSE.txt[Apache 2.0] |https://cloud.google.com/sdk/gcloud[GCloudCLI]|Optional|https://github.com/twistedpair/google-cloud-sdk/blob/master/google-cloud-sdk/LICENSE[Apache 2.0]