From 76445656a9dae2e57482bccb1012101ec30cae61 Mon Sep 17 00:00:00 2001 From: Anatolii Bazko Date: Mon, 20 Mar 2023 08:32:05 +0200 Subject: [PATCH] feat: GitLab support nested repositories (#467) * feat: GitLab support nested repositories Signed-off-by: Anatolii Bazko --------- Signed-off-by: Anatolii Bazko --- .../GitlabAuthorizingFileContentProvider.java | 7 +-- .../api/factory/server/gitlab/GitlabUrl.java | 62 +++++-------------- .../server/gitlab/GitlabUrlParser.java | 28 ++++----- ...labAuthorizingFileContentProviderTest.java | 8 +-- .../GitlabFactoryParametersResolverTest.java | 2 +- .../server/gitlab/GitlabUrlParserTest.java | 34 +++++++--- 6 files changed, 55 insertions(+), 86 deletions(-) diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java index 166b0151fa..1137407b94 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java @@ -29,12 +29,7 @@ class GitlabAuthorizingFileContentProvider extends AuthorizingFileContentProvide @Override protected boolean isPublicRepository(GitlabUrl remoteFactoryUrl) { try { - urlFetcher.fetch( - remoteFactoryUrl.getHostName() - + '/' - + remoteFactoryUrl.getUsername() - + '/' - + remoteFactoryUrl.getProject()); + urlFetcher.fetch(remoteFactoryUrl.getHostName() + '/' + remoteFactoryUrl.getSubGroups()); return true; } catch (IOException e) { return false; diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java index 22edaa7451..1e581bc255 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java @@ -25,8 +25,8 @@ import org.eclipse.che.api.factory.server.urlfactory.DefaultFactoryUrl; /** * Representation of a gitlab URL, allowing to get details from it. * - *

like https://gitlab.com// - * https://gitlab.com///-/tree/ + *

like https://gitlab.com/path/to/repository or + * https://gitlab.com/path/to/repository/-/tree/ * * @author Max Shaposhnyk */ @@ -37,14 +37,14 @@ public class GitlabUrl extends DefaultFactoryUrl { /** Hostname of the gitlab URL */ private String hostName; - /** Username part of the gitlab URL */ - private String username; - /** Project part of the gitlab URL */ private String project; - /** Repository part of the gitlab URL. */ - private String repository; + /** + * Incorporates subgroups in the project path of the gitlab URL. See details at: + * https://docs.gitlab.com/ee/user/group/subgroups/ + */ + private String subGroups; /** Branch name */ private String branch; @@ -80,20 +80,6 @@ public class GitlabUrl extends DefaultFactoryUrl { return this; } - /** - * Gets username of this gitlab url - * - * @return the username part - */ - public String getUsername() { - return this.username; - } - - public GitlabUrl withUsername(String userName) { - this.username = userName; - return this; - } - /** * Gets project of this bitbucket url * @@ -103,22 +89,16 @@ public class GitlabUrl extends DefaultFactoryUrl { return this.project; } - public GitlabUrl withProject(String project) { - this.project = project; - return this; + public String getSubGroups() { + return subGroups; } - /** - * Gets repository of this gitlab url - * - * @return the repository part - */ - public String getRepository() { - return this.repository; - } + protected GitlabUrl withSubGroups(String subGroups) { + this.subGroups = subGroups; - protected GitlabUrl withRepository(String repository) { - this.repository = repository; + String[] subGroupsItems = subGroups.split("/"); + this.project = + subGroupsItems[subGroupsItems.length - 1]; // project (repository) name is the last item return this; } @@ -204,7 +184,7 @@ public class GitlabUrl extends DefaultFactoryUrl { .add(hostName) .add("api/v4/projects") // use URL-encoded path to the project as a selector instead of id - .add(geProjectIdentifier()) + .add(encode(subGroups, Charsets.UTF_8)) .add("repository") .add("files") .add(encode(fileName, Charsets.UTF_8)) @@ -217,22 +197,12 @@ public class GitlabUrl extends DefaultFactoryUrl { return resultUrl; } - private String geProjectIdentifier() { - return repository != null - ? encode(username + "/" + project + "/" + repository, Charsets.UTF_8) - : encode(username + "/" + project, Charsets.UTF_8); - } - /** * Provides location to the repository part of the full gitlab URL. * * @return location of the repository. */ protected String repositoryLocation() { - if (repository == null) { - return hostName + "/" + this.username + "/" + this.project + ".git"; - } else { - return hostName + "/" + this.username + "/" + this.project + "/" + repository + ".git"; - } + return hostName + "/" + subGroups + ".git"; } } diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParser.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParser.java index c35eef3f82..f25cbd7f8e 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParser.java +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParser.java @@ -14,6 +14,7 @@ package org.eclipse.che.api.factory.server.gitlab; import static java.lang.String.format; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static java.util.regex.Pattern.compile; +import static org.eclipse.che.commons.lang.StringUtils.trimEnd; import com.google.common.base.Splitter; import jakarta.validation.constraints.NotNull; @@ -33,7 +34,6 @@ import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.env.EnvironmentContext; -import org.eclipse.che.commons.lang.StringUtils; /** * Parser of String Gitlab URLs and provide {@link GitlabUrl} objects. @@ -46,10 +46,9 @@ public class GitlabUrlParser { private final PersonalAccessTokenManager personalAccessTokenManager; private static final List gitlabUrlPatternTemplates = List.of( - "^(?%s)/(?[^/]++)/(?[^./]++).git", - "^(?%s)/(?[^/]++)/(?[^/]++)/(?[^.]++).git", - "^(?%s)/(?[^/]++)/(?[^/]++)(/)?(?[^/]++)?(/)?", - "^(?%s)/(?[^/]++)/(?[^/]++)(/)?(?[^/]++)?/-/tree/(?[^/]++)(/)?(?[^/]++)?"); + "^(?%s)/(?([^/]++/?)+)/-/tree/(?[^/]++)(/)?(?[^/]++)?", + "^(?%s)/(?.*)"); // a wider one, should be the last in the + // list private final List gitlabUrlPatterns = new ArrayList<>(); private static final String OAUTH_PROVIDER_NAME = "gitlab"; @@ -62,7 +61,7 @@ public class GitlabUrlParser { this.personalAccessTokenManager = personalAccessTokenManager; if (gitlabEndpoints != null) { for (String gitlabEndpoint : Splitter.on(",").split(gitlabEndpoints)) { - String trimmedEndpoint = StringUtils.trimEnd(gitlabEndpoint, '/'); + String trimmedEndpoint = trimEnd(gitlabEndpoint, '/'); for (String gitlabUrlPatternTemplate : gitlabUrlPatternTemplates) { gitlabUrlPatterns.add(compile(format(gitlabUrlPatternTemplate, trimmedEndpoint))); } @@ -163,16 +162,13 @@ public class GitlabUrlParser { private GitlabUrl parse(Matcher matcher) { String host = matcher.group("host"); - String userName = matcher.group("user"); - String project = matcher.group("project"); - String repository = null; + String subGroups = trimEnd(matcher.group("subgroups"), '/'); + if (subGroups.endsWith(".git")) { + subGroups = subGroups.substring(0, subGroups.length() - 4); + } + String branch = null; String subfolder = null; - try { - repository = matcher.group("repository"); - } catch (IllegalArgumentException e) { - // ok no such group - } try { branch = matcher.group("branch"); } catch (IllegalArgumentException e) { @@ -186,9 +182,7 @@ public class GitlabUrlParser { return new GitlabUrl() .withHostName(host) - .withUsername(userName) - .withProject(project) - .withRepository(repository) + .withSubGroups(subGroups) .withBranch(branch) .withSubfolder(subfolder) .withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames()); diff --git a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProviderTest.java b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProviderTest.java index 92ea30aa7a..165621e62e 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProviderTest.java +++ b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProviderTest.java @@ -33,10 +33,7 @@ public class GitlabAuthorizingFileContentProviderTest { public void shouldExpandRelativePaths() throws Exception { URLFetcher urlFetcher = Mockito.mock(URLFetcher.class); GitlabUrl gitlabUrl = - new GitlabUrl() - .withHostName("https://gitlab.net") - .withUsername("eclipse") - .withProject("che"); + new GitlabUrl().withHostName("https://gitlab.net").withSubGroups("eclipse/che"); FileContentProvider fileContentProvider = new GitlabAuthorizingFileContentProvider(gitlabUrl, urlFetcher, personalAccessTokenManager); var personalAccessToken = new PersonalAccessToken("foo", "che", "my-token"); @@ -52,8 +49,7 @@ public class GitlabAuthorizingFileContentProviderTest { @Test public void shouldPreserveAbsolutePaths() throws Exception { URLFetcher urlFetcher = Mockito.mock(URLFetcher.class); - GitlabUrl gitlabUrl = - new GitlabUrl().withHostName("gitlab.net").withUsername("eclipse").withProject("che"); + GitlabUrl gitlabUrl = new GitlabUrl().withHostName("gitlab.net").withSubGroups("eclipse/che"); FileContentProvider fileContentProvider = new GitlabAuthorizingFileContentProvider(gitlabUrl, urlFetcher, personalAccessTokenManager); String url = diff --git a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolverTest.java b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolverTest.java index 59a6a2b9f0..6fa25c1497 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolverTest.java +++ b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolverTest.java @@ -118,7 +118,7 @@ public class GitlabFactoryParametersResolverTest { // when FactoryDto factory = (FactoryDto) gitlabFactoryParametersResolver.createFactory(params); // then - verify(urlFactoryBuilder).buildDefaultDevfile(eq("proj")); + verify(urlFactoryBuilder).buildDefaultDevfile(eq("repo")); assertEquals(factory, computedFactory); SourceDto source = factory.getDevfile().getProjects().get(0).getSource(); assertEquals(source.getLocation(), gitlabUrl); diff --git a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserTest.java b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserTest.java index 94931742b8..c9e13812d2 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserTest.java +++ b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserTest.java @@ -72,12 +72,11 @@ public class GitlabUrlParserTest { /** Compare parsing */ @Test(dataProvider = "parsing") public void checkParsing( - String url, String user, String project, String repository, String branch, String subfolder) { + String url, String project, String subGroups, String branch, String subfolder) { GitlabUrl gitlabUrl = gitlabUrlParser.parse(url); - assertEquals(gitlabUrl.getUsername(), user); assertEquals(gitlabUrl.getProject(), project); - assertEquals(gitlabUrl.getRepository(), repository); + assertEquals(gitlabUrl.getSubGroups(), subGroups); assertEquals(gitlabUrl.getBranch(), branch); assertEquals(gitlabUrl.getSubfolder(), subfolder); } @@ -124,16 +123,31 @@ public class GitlabUrlParserTest { @DataProvider(name = "parsing") public Object[][] expectedParsing() { return new Object[][] { - {"https://gitlab1.com/user/project1.git", "user", "project1", null, null, null}, - {"https://gitlab1.com/user/project/test1.git", "user", "project", "test1", null, null}, - {"https://gitlab1.com/user/project/", "user", "project", null, null, null}, - {"https://gitlab1.com/user/project/repo/", "user", "project", "repo", null, null}, - {"https://gitlab1.com/user/project/-/tree/master/", "user", "project", null, "master", null}, + {"https://gitlab1.com/user/project1.git", "project1", "user/project1", null, null}, + {"https://gitlab1.com/user/project/test1.git", "test1", "user/project/test1", null, null}, + { + "https://gitlab1.com/user/project/group1/group2/test1.git", + "test1", + "user/project/group1/group2/test1", + null, + null + }, + {"https://gitlab1.com/user/project/", "project", "user/project", null, null}, + {"https://gitlab1.com/user/project/repo/", "repo", "user/project/repo", null, null}, + { + "https://gitlab1.com/user/project/-/tree/master/", "project", "user/project", "master", null + }, { "https://gitlab1.com/user/project/repo/-/tree/foo/subfolder", - "user", - "project", "repo", + "user/project/repo", + "foo", + "subfolder" + }, + { + "https://gitlab1.com/user/project/group1/group2/repo/-/tree/foo/subfolder", + "repo", + "user/project/group1/group2/repo", "foo", "subfolder" }