Skip to content

Commit

Permalink
#312: Added ability to prefer git (ssh) protocol instead of https for…
Browse files Browse the repository at this point in the history
… cloning repos (#724)
  • Loading branch information
alfeilex authored Nov 11, 2024
1 parent 1e5e98d commit 55db46d
Show file tree
Hide file tree
Showing 10 changed files with 265 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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}.
Expand Down Expand Up @@ -138,20 +138,21 @@ 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");
}
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) {
Expand All @@ -162,7 +163,7 @@ public void clone(GitUrl gitRepoUrl, Path targetRepository) {
}
}
} else {
throw CliOfflineException.ofClone(parsedUrl, targetRepository);
throw CliOfflineException.ofClone(gitRepoUrl.parseUrl(), targetRepository);
}
}

Expand Down
10 changes: 10 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/context/GitUrl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
95 changes: 95 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/context/GitUrlSyntax.java
Original file line number Diff line number Diff line change
@@ -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., [email protected]: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<String> 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.
* <p>
* 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.
* </p>
*
* @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;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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<GitUrlSyntax> 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.
Expand All @@ -83,7 +89,7 @@ public interface IdeVariables {
/** A {@link Collection} with all pre-defined {@link VariableDefinition}s. */
Collection<VariableDefinition<?>> 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}.
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <E> the enum type.
*/
public class VariableDefinitionEnum<E extends Enum<E>> extends AbstractVariableDefinition<E> {

private final Class<E> enumType;

/**
* The constructor.
*
* @param name the {@link #getName() variable name}.
* @param enumType the class of the enum.
*/
public VariableDefinitionEnum(String name, Class<E> 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<E> 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<E> enumType, Function<IdeContext, E> defaultValueFactory) {
super(name, legacyName, defaultValueFactory);
this.enumType = enumType;
}

@Override
public Class<E> 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));
}
}
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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 = "[email protected]:devonfw/IDEasy.git";
assertThat(convertedGitUrl.url()).isEqualTo(expectedSshUrl);
}

/**
* Tests the conversion of a Git URL from SSH to HTTPS protocol.
* <p>
* 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 = "[email protected]: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.
* <p>
* 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);
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PREFERRED_GIT_PROTOCOL=SSH
Empty file.

0 comments on commit 55db46d

Please sign in to comment.