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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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].

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -188,6 +189,7 @@ public CommandletManagerImpl(IdeContext context) {
add(new Just(context));
add(new SoapUi(context));
add(new Ruff(context));
add(new Git(context));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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) {

Expand Down Expand Up @@ -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()))) {

Comment thread
cap-juan marked this conversation as resolved.
// 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();
Expand Down Expand Up @@ -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();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
52 changes: 49 additions & 3 deletions cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelperImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> 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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<PackageManagerCommand> commands = getInstallPackageManagerCommands(resolvedVersion);
if (!commands.isEmpty()) {
boolean newInstallation = runWithPackageManager(request.isSilent(), commands, NativePackageAction.INSTALL);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<String> getInstallerArguments() {
return List.of();
}
}
15 changes: 13 additions & 2 deletions cli/src/main/java/com/devonfw/tools/ide/tool/ToolCommandlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}

Expand Down Expand Up @@ -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() {
Comment thread
cap-juan marked this conversation as resolved.
return true;
}

/**
* @deprecated directly log success message and then report success on step if not null.
*/
Expand Down
56 changes: 56 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/tool/git/Git.java
Original file line number Diff line number Diff line change
@@ -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 <a href="https://git-scm.com/">Git</a>.
*/
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<NativePackage> getNativePackages() {
return List.of(
NativePackage.of(NativePackageManager.APT, "git"),
NativePackage.of(NativePackageManager.ZYPPER, "git")
);
}

@Override
public String getWindowsRegistryAppName() {
return "Git";
}

@Override
protected List<String> getInstallerArguments() {
if (this.context.getSystemInfo().isWindows()) {
return List.of(
"/VERYSILENT",
"/NORESTART",
"/NOCANCEL",
"/SP-"
);
}
return List.of();
}
}
2 changes: 2 additions & 0 deletions cli/src/main/resources/nls/Help.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
2 changes: 2 additions & 0 deletions cli/src/main/resources/nls/Help_de.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading