feat: GitLab support nested repositories (#467)

* feat: GitLab support nested repositories

Signed-off-by: Anatolii Bazko <abazko@redhat.com>

---------

Signed-off-by: Anatolii Bazko <abazko@redhat.com>
pull/469/head
Anatolii Bazko 2023-03-20 08:32:05 +02:00 committed by GitHub
parent 5a04496ded
commit 76445656a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 55 additions and 86 deletions

View File

@ -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;

View File

@ -25,8 +25,8 @@ import org.eclipse.che.api.factory.server.urlfactory.DefaultFactoryUrl;
/**
* Representation of a gitlab URL, allowing to get details from it.
*
* <p>like https://gitlab.com/<username>/<repository>
* https://gitlab.com/<username>/<repository>/-/tree/<branch>
* <p>like https://gitlab.com/path/to/repository or
* https://gitlab.com/path/to/repository/-/tree/<branch>
*
* @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";
}
}

View File

@ -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<String> gitlabUrlPatternTemplates =
List.of(
"^(?<host>%s)/(?<user>[^/]++)/(?<project>[^./]++).git",
"^(?<host>%s)/(?<user>[^/]++)/(?<project>[^/]++)/(?<repository>[^.]++).git",
"^(?<host>%s)/(?<user>[^/]++)/(?<project>[^/]++)(/)?(?<repository>[^/]++)?(/)?",
"^(?<host>%s)/(?<user>[^/]++)/(?<project>[^/]++)(/)?(?<repository>[^/]++)?/-/tree/(?<branch>[^/]++)(/)?(?<subfolder>[^/]++)?");
"^(?<host>%s)/(?<subgroups>([^/]++/?)+)/-/tree/(?<branch>[^/]++)(/)?(?<subfolder>[^/]++)?",
"^(?<host>%s)/(?<subgroups>.*)"); // a wider one, should be the last in the
// list
private final List<Pattern> 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());

View File

@ -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 =

View File

@ -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);

View File

@ -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"
}