diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index b46c441af..3412d1bf7 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -18,6 +18,7 @@ The full list of changes for this release can be found in https://github.com/dev Release with new features and bugfixes: +* https://github.com/devonfw/IDEasy/issues/312[#312]: Add ability to prefer git (ssh) protocol instead of https for cloning repo * https://github.com/devonfw/IDEasy/issues/685[#685]: Upgrades and cleanup of dependencies and according license and doc * https://github.com/devonfw/IDEasy/pull/693[#693]: Setup not working on Mac * https://github.com/devonfw/IDEasy/issues/704[#704]: settings-security.xml not found 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 5cc27bcb1..23613c117 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,6 +1,5 @@ package com.devonfw.tools.ide.context; -import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; @@ -11,6 +10,7 @@ import com.devonfw.tools.ide.process.ProcessErrorHandling; import com.devonfw.tools.ide.process.ProcessMode; import com.devonfw.tools.ide.process.ProcessResult; +import com.devonfw.tools.ide.variable.IdeVariables; /** * Implements the {@link GitContext}. @@ -138,12 +138,13 @@ private void handleErrors(Path targetRepository, ProcessResult result) { @Override public void clone(GitUrl gitRepoUrl, Path targetRepository) { - URL parsedUrl = gitRepoUrl.parseUrl(); + GitUrlSyntax gitUrlSyntax = IdeVariables.PREFERRED_GIT_PROTOCOL.get(getContext()); + gitRepoUrl = gitUrlSyntax.format(gitRepoUrl); this.processContext.directory(targetRepository); ProcessResult result; if (!this.context.isOffline()) { this.context.getFileAccess().mkdirs(targetRepository); - this.context.requireOnline("git clone of " + parsedUrl); + this.context.requireOnline("git clone of " + gitRepoUrl.url()); this.processContext.addArg("clone"); if (this.context.isQuietMode()) { this.processContext.addArg("-q"); @@ -151,7 +152,7 @@ public void clone(GitUrl gitRepoUrl, Path targetRepository) { this.processContext.addArgs("--recursive", gitRepoUrl.url(), "--config", "core.autocrlf=false", "."); result = this.processContext.run(PROCESS_MODE); if (!result.isSuccessful()) { - this.context.warning("Git failed to clone {} into {}.", parsedUrl, targetRepository); + this.context.warning("Git failed to clone {} into {}.", gitRepoUrl.url(), targetRepository); } String branch = gitRepoUrl.branch(); if (branch != null) { @@ -162,7 +163,7 @@ public void clone(GitUrl gitRepoUrl, Path targetRepository) { } } } else { - throw CliOfflineException.ofClone(parsedUrl, targetRepository); + throw CliOfflineException.ofClone(gitRepoUrl.parseUrl(), 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 decd4aab8..510c07024 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 @@ -11,6 +11,16 @@ */ public record GitUrl(String url, String branch) { + /** + * Converts the Git URL based on the specified {@link GitUrlSyntax}. + * + * @param syntax the preferred {@link GitUrlSyntax} (SSH or HTTPS). + * @return the converted {@link GitUrl} or the original if no conversion is required. + */ + public GitUrl convert(GitUrlSyntax syntax) { + return syntax.format(this); + } + /** * Parses a git URL and omits the branch name if not provided. * diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/GitUrlSyntax.java b/cli/src/main/java/com/devonfw/tools/ide/context/GitUrlSyntax.java new file mode 100644 index 000000000..81f2dc50d --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/context/GitUrlSyntax.java @@ -0,0 +1,95 @@ +package com.devonfw.tools.ide.context; + +import java.util.Arrays; +import java.util.List; + +/** + * Enum representing the syntax of Git URLs, either SSH or HTTPS. Provides methods to format and convert Git URLs based on the syntax. + */ +public enum GitUrlSyntax { + + /** + * The DEFAULT Git URL syntax + */ + DEFAULT(""), + /** + * The SSH Git URL syntax (e.g., git@github.com:user/repo.git). + */ + SSH("git@"), + + /** + * The HTTPS Git URL syntax (e.g., https://github.com/user/repo.git). + */ + HTTPS("https://"); + + private final String prefix; + + private static final List DOMAINS_WITH_NO_CONVERSION = Arrays.asList("github.com"); + + GitUrlSyntax(String prefix) { + this.prefix = prefix; + } + + /** + * Formats the given Git URL according to the syntax represented by this enum constant. + *

+ * Converts the URL between SSH and HTTPS formats. For example, an HTTPS URL can be converted to its corresponding SSH URL format, and vice versa. + *

+ * + * @param gitUrl the original {@link GitUrl} to be formatted. + * @return the formatted {@link GitUrl} according to this syntax. + * @throws IllegalArgumentException if the protocol is not supported. + */ + public GitUrl format(GitUrl gitUrl) { + if (this == DEFAULT) { + return gitUrl; + } + String url = gitUrl.url(); + + // Prevent conversion for domains in the no-conversion list + if (isDomainWithNoConversion(url.toLowerCase())) { + return gitUrl; + } + + switch (this) { + case SSH -> { + if (url.startsWith(HTTPS.prefix)) { + int index = url.indexOf("/", HTTPS.prefix.length()); + if (index > 0) { + url = SSH.prefix + url.substring(HTTPS.prefix.length(), index) + ":" + url.substring(index + 1); + } + } + } + case HTTPS -> { + if (url.startsWith(SSH.prefix)) { + int index = url.indexOf(":"); + if (index > 0) { + url = HTTPS.prefix + url.substring(SSH.prefix.length(), index) + "/" + url.substring(index + 1); + } + } + } + default -> throw new IllegalArgumentException("Unsupported protocol: " + this); + } + + return new GitUrl(url, gitUrl.branch()); + } + + private boolean isDomainWithNoConversion(String url) { + + for (String domain : DOMAINS_WITH_NO_CONVERSION) { + // Check if it's an HTTPS URL for the domain + if (url.startsWith("https://" + domain + "/")) { + return true; + } + + // Check if it's an SSH URL for the domain + if (url.startsWith("git@" + domain + ":")) { + return true; + } + } + + return false; + } + + +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/variable/IdeVariables.java b/cli/src/main/java/com/devonfw/tools/ide/variable/IdeVariables.java index 62ba32ab0..b981e67cf 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/variable/IdeVariables.java +++ b/cli/src/main/java/com/devonfw/tools/ide/variable/IdeVariables.java @@ -3,6 +3,8 @@ import java.util.Collection; import java.util.List; +import com.devonfw.tools.ide.context.GitUrlSyntax; + /** * Interface (mis)used to define all the available variables. */ @@ -73,6 +75,10 @@ public interface IdeVariables { /** {@link VariableDefinition} for {@link com.devonfw.tools.ide.context.IdeContext#getProjectName() PROJECT_NAME}. */ VariableDefinitionString PROJECT_NAME = new VariableDefinitionString("PROJECT_NAME", null, c -> c.getProjectName()); + /** Preferred Git protocol (HTTPS/SSH) as defined by {@link GitUrlSyntax}. */ + VariableDefinitionEnum PREFERRED_GIT_PROTOCOL = new VariableDefinitionEnum<>("PREFERRED_GIT_PROTOCOL", null, GitUrlSyntax.class, + c -> GitUrlSyntax.DEFAULT); + /** * {@link VariableDefinition} for support of legacy variable syntax when * {@link com.devonfw.tools.ide.environment.EnvironmentVariables#resolve(String, Object, boolean) resolving variables} in configuration templates. @@ -83,7 +89,7 @@ public interface IdeVariables { /** A {@link Collection} with all pre-defined {@link VariableDefinition}s. */ Collection> VARIABLES = List.of(PATH, HOME, WORKSPACE_PATH, IDE_HOME, IDE_ROOT, WORKSPACE, IDE_TOOLS, CREATE_START_SCRIPTS, IDE_MIN_VERSION, MVN_VERSION, M2_REPO, DOCKER_EDITION, MVN_BUILD_OPTS, NPM_BUILD_OPTS, GRADLE_BUILD_OPTS, YARN_BUILD_OPTS, JASYPT_OPTS, MAVEN_ARGS, - PROJECT_NAME, IDE_VARIABLE_SYNTAX_LEGACY_SUPPORT_ENABLED); + PROJECT_NAME, IDE_VARIABLE_SYNTAX_LEGACY_SUPPORT_ENABLED, PREFERRED_GIT_PROTOCOL); /** * @param name the name of the requested {@link VariableDefinition}. diff --git a/cli/src/main/java/com/devonfw/tools/ide/variable/VariableDefinitionEnum.java b/cli/src/main/java/com/devonfw/tools/ide/variable/VariableDefinitionEnum.java new file mode 100644 index 000000000..28c9a81fc --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/variable/VariableDefinitionEnum.java @@ -0,0 +1,69 @@ +package com.devonfw.tools.ide.variable; + +import java.util.function.Function; + +import com.devonfw.tools.ide.context.IdeContext; + +/** + * Implementation of {@link VariableDefinition} for a variable with the {@link #getValueType() value type} as an Enum. + * + * @param the enum type. + */ +public class VariableDefinitionEnum> extends AbstractVariableDefinition { + + private final Class enumType; + + /** + * The constructor. + * + * @param name the {@link #getName() variable name}. + * @param enumType the class of the enum. + */ + public VariableDefinitionEnum(String name, Class enumType) { + super(name); + this.enumType = enumType; + } + + /** + * The constructor. + * + * @param name the {@link #getName() variable name}. + * @param legacyName the {@link #getLegacyName() legacy name}. + * @param enumType the class of the enum. + */ + public VariableDefinitionEnum(String name, String legacyName, Class enumType) { + super(name, legacyName); + this.enumType = enumType; + } + + /** + * The constructor. + * + * @param name the {@link #getName() variable name}. + * @param legacyName the {@link #getLegacyName() legacy name}. + * @param enumType the class of the enum. + * @param defaultValueFactory the factory {@link Function} for the {@link #getDefaultValue(IdeContext) default value}. + */ + public VariableDefinitionEnum(String name, String legacyName, Class enumType, Function defaultValueFactory) { + super(name, legacyName, defaultValueFactory); + this.enumType = enumType; + } + + @Override + public Class getValueType() { + return enumType; + } + + /** + * Converts a string value to the corresponding enum constant. Converts the string to upper case before matching with the enum values. + * + * @param value the string representation of the enum. + * @param context the context for the current operation. + * @return the corresponding enum constant. + * @throws IllegalArgumentException if the string doesn't match any enum constant. + */ + @Override + public E fromString(String value, IdeContext context) { + return Enum.valueOf(enumType, value.toUpperCase(java.util.Locale.ROOT)); + } +} diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/GitUrlSyntaxTest.java b/cli/src/test/java/com/devonfw/tools/ide/context/GitUrlSyntaxTest.java new file mode 100644 index 000000000..4687f62bd --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/context/GitUrlSyntaxTest.java @@ -0,0 +1,76 @@ +package com.devonfw.tools.ide.context; + +import org.junit.jupiter.api.Test; + +import com.devonfw.tools.ide.variable.IdeVariables; + + +/** + * Test class for verifying the behavior of Git URL conversions + */ +public class GitUrlSyntaxTest extends AbstractIdeContextTest { + + /** + * Tests the reading of property PREFERRED_GIT_PROTOCOL from ide.properties is done successfully + */ + @Test + public void testPreferredGitProtocolIsReadAsSsh() { + // Read the PREFERRED_GIT_PROTOCOL from the config file + IdeTestContext context = newContext("git"); + GitUrlSyntax preferredProtocol = IdeVariables.PREFERRED_GIT_PROTOCOL.get(context); + + // Check if the configuration value is correctly set to SSH + assertThat(GitUrlSyntax.SSH).isEqualTo(preferredProtocol); + } + + /** + * Tests the conversion of a Git URL from HTTPS to SSH protocol. + *

+ * Given a Git URL in HTTPS format, this test ensures that it is correctly converted to the SSH format using the {@link GitUrl#convert(GitUrlSyntax)} method. + */ + @Test + public void testConvertGitUrlFromHttpsToSsh() { + String url = "https://testgitdomain.com/devonfw/IDEasy.git"; + GitUrl gitUrl = new GitUrl(url, null); + + // Use the convert method with GitUrlSyntax enum + GitUrl convertedGitUrl = gitUrl.convert(GitUrlSyntax.SSH); + + String expectedSshUrl = "git@testgitdomain.com:devonfw/IDEasy.git"; + assertThat(convertedGitUrl.url()).isEqualTo(expectedSshUrl); + } + + /** + * Tests the conversion of a Git URL from SSH to HTTPS protocol. + *

+ * Given a Git URL in SSH format, this test ensures that it is correctly converted to the HTTPS format using the {@link GitUrl#convert(GitUrlSyntax)} method. + */ + @Test + public void testConvertGitUrlFromSshToHttps() { + String url = "git@testgitdomain.com:devonfw/IDEasy.git"; + GitUrl gitUrl = new GitUrl(url, null); + + // Use the convert method with GitUrlSyntax enum + GitUrl convertedGitUrl = gitUrl.convert(GitUrlSyntax.HTTPS); + + String expectedHttpsUrl = "https://testgitdomain.com/devonfw/IDEasy.git"; + assertThat(convertedGitUrl.url()).isEqualTo(expectedHttpsUrl); + } + + /** + * Tests that when a Git URL is in HTTPS format and points to the github.com domain, it remains in the original format and is not converted to SSH. + *

+ * This test ensures that the Git URL for github.com stays in HTTPS format, even if SSH is specified as the preferred protocol. + */ + @Test + public void testConvertGitUrlGitHubDomain() { + String url = "https://github.com/devonfw/IDEasy.git"; + GitUrl gitUrl = new GitUrl(url, null); + + // Attempt to convert to SSH, but it should remain in HTTPS format for github.com + GitUrl convertedGitUrl = gitUrl.convert(GitUrlSyntax.SSH); + + // The URL should remain unchanged in HTTPS format + assertThat(convertedGitUrl.url()).isEqualTo(url); + } +} diff --git a/cli/src/test/resources/ide-projects/git/project/home/.ide/.gitkeep b/cli/src/test/resources/ide-projects/git/project/home/.ide/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/cli/src/test/resources/ide-projects/git/project/settings/ide.properties b/cli/src/test/resources/ide-projects/git/project/settings/ide.properties new file mode 100644 index 000000000..3a6d0c2c0 --- /dev/null +++ b/cli/src/test/resources/ide-projects/git/project/settings/ide.properties @@ -0,0 +1 @@ +PREFERRED_GIT_PROTOCOL=SSH \ No newline at end of file diff --git a/cli/src/test/resources/ide-projects/git/project/workspaces/main/.gitkeep b/cli/src/test/resources/ide-projects/git/project/workspaces/main/.gitkeep new file mode 100644 index 000000000..e69de29bb