Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Determine SCM and parse clone url in GitProvenance for more accurate path/origin/organiation/groups #4367

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f21f282
feat: Determine SCM in GitProvenance for more accurate path estimation
pstreef Aug 1, 2024
9774c15
Apply suggestions from code review
pstreef Aug 1, 2024
973bed5
rename gitlab scm
pstreef Aug 1, 2024
6e937cf
Comments
pstreef Aug 1, 2024
e30b193
fix tests
pstreef Aug 1, 2024
4fd0365
fix boolean
pstreef Aug 1, 2024
9fd2b07
fix git provenance tests
pstreef Aug 1, 2024
b331cef
Rename to `ScmUrlComponents` to `CloneUrl` and move logic into that
pstreef Aug 2, 2024
3d5d541
Add full cloneUrl
pstreef Aug 2, 2024
cbb7a1a
fully parse on construction
pstreef Aug 2, 2024
453566b
Update rewrite-core/src/test/java/org/openrewrite/scm/GitLabScmTest.java
pstreef Aug 2, 2024
4c2387c
Update rewrite-core/src/main/java/org/openrewrite/scm/AzureDevopsClon…
pstreef Aug 2, 2024
4fef5be
Merge branch 'main' into feat/add-scm-url-component-detection-logic
pstreef Aug 2, 2024
aacea53
Apply suggestions from code review
timtebeek Aug 2, 2024
c2e9e9c
Add missing newlines at the end of files
timtebeek Aug 2, 2024
5fe218a
Add missing newline at the end of GitLabScmTest
timtebeek Aug 2, 2024
4fefa63
refactor: Azure devops refactoring (#4378)
bryceatmoderne Aug 3, 2024
ce49346
new line?
pstreef Aug 3, 2024
5dec1dd
Lazy initialization
pstreef Aug 4, 2024
8d07e18
Lombok compiler issue
pstreef Aug 4, 2024
43d7957
Update rewrite-core/src/main/java/org/openrewrite/internal/Lazy.java
pstreef Aug 4, 2024
a13a68a
Replace Lazy with `@NonFinal` field
pstreef Aug 4, 2024
f408a35
fix message, use getters
pstreef Aug 4, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,29 @@
package org.openrewrite.marker;


import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.With;
import lombok.experimental.NonFinal;
import org.openrewrite.Incubating;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.jgit.api.Git;
import org.openrewrite.jgit.api.errors.GitAPIException;
import org.openrewrite.jgit.lib.*;
import org.openrewrite.jgit.revwalk.RevCommit;
import org.openrewrite.jgit.transport.RemoteConfig;
import org.openrewrite.jgit.transport.URIish;
import org.openrewrite.jgit.treewalk.WorkingTreeOptions;
import org.openrewrite.marker.ci.BuildEnvironment;
import org.openrewrite.marker.ci.IncompleteGitConfigException;
import org.openrewrite.marker.ci.JenkinsBuildEnvironment;
import org.openrewrite.scm.CloneUrl;
import org.openrewrite.scm.Scm;

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.time.LocalDate;
import java.util.*;
Expand All @@ -45,9 +48,15 @@

@Value
@With
@RequiredArgsConstructor
@AllArgsConstructor
public class GitProvenance implements Marker {
UUID id;

/**
* The URL of the origin remote, not to be confused with the SCM origin
* which can be found under scmUrlComponents.getOrigin() which contains the base url to the SCM server
*/
@Nullable
String origin;

Expand All @@ -67,14 +76,32 @@ public class GitProvenance implements Marker {
@Incubating(since = "8.9.0")
List<Committer> committers;

@Incubating(since = "8.33.0")
@Nullable
@NonFinal
transient CloneUrl cloneUrl = null;

private @Nullable CloneUrl getCloneUrl() {
if (cloneUrl == null) {
cloneUrl = parseCloneUrl(origin);
}
return cloneUrl;
}

/**
* Extract the organization name, including sub-organizations for git hosting services which support such a concept,
* from the origin URL. Needs to be supplied with the
*
* @param baseUrl the portion of the URL which precedes the organization
* @return the portion of the git origin URL which corresponds to the organization the git repository is organized under
* @deprecated use {@link #getCloneUrl().getOrganization()} instead }
*/
@Deprecated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it's necessary to deprecate since it is in widespread use. Fine to have a convenience method for organization on CloneUrl

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually I think we should point back to the "old" method that does not take an argument, and deprecate this one which (again) takes the origin (with scheme).

public @Nullable String getOrganizationName(String baseUrl) {
CloneUrl cloneUrl = getCloneUrl();
if (cloneUrl != null) {
return cloneUrl.getOrganization();
}
if (origin == null) {
return null;
}
Expand Down Expand Up @@ -102,31 +129,22 @@ public class GitProvenance implements Marker {
*/
@Deprecated
public @Nullable String getOrganizationName() {
if (origin == null) {
return null;
}
try {
String path = new URIish(origin).getPath();
// Strip off any trailing repository name
path = path.substring(0, path.lastIndexOf('/'));
// Strip off any leading sub organization names
return path.substring(path.lastIndexOf('/') + 1);
} catch (URISyntaxException e) {
return null;
}
return Optional.ofNullable(getCloneUrl())
.map(CloneUrl::getOrganization)
.orElse(null);
}

public @Nullable String getRepositoryName() {
if (origin == null) {
return null;
}
try {
String path = new URIish(origin).getPath();
return path.substring(path.lastIndexOf('/') + 1)
.replaceAll("\\.git$", "");
} catch (URISyntaxException e) {
return null;
}
return Optional.ofNullable(getCloneUrl())
.map(CloneUrl::getRepositoryName)
.orElse(null);
}

public static String getRepositoryPath(String origin) {
return Optional.of(origin)
.map(GitProvenance::parseCloneUrl)
.map(CloneUrl::getPath)
.orElse("");
}

/**
Expand Down Expand Up @@ -206,7 +224,8 @@ private static GitProvenance fromGitConfig(Repository repository, @Nullable Stri
if (branch == null) {
branch = resolveBranchFromGitConfig(repository);
}
return new GitProvenance(randomId(), getOrigin(repository), branch, changeset,
String remoteOriginUrl = getRemoteOriginUrl(repository);
return new GitProvenance(randomId(), remoteOriginUrl, branch, changeset,
getAutocrlf(repository), getEOF(repository),
getCommitters(repository));
}
Expand Down Expand Up @@ -268,7 +287,7 @@ private static GitProvenance fromGitConfig(Repository repository, @Nullable Stri
return branch;
}

private static @Nullable String getOrigin(Repository repository) {
private static @Nullable String getRemoteOriginUrl(Repository repository) {
Config storedConfig = repository.getConfig();
String url = storedConfig.getString("remote", "origin", "url");
if (url == null) {
Expand Down Expand Up @@ -354,6 +373,15 @@ private static String hideSensitiveInformation(String url) {
return url;
}

@Nullable
public static CloneUrl parseCloneUrl(@Nullable String cloneUrl) {
if (cloneUrl == null) {
return null;
}
Scm scm = Scm.findMatchingScm(cloneUrl);
return scm.parseCloneUrl(cloneUrl);
}

public enum AutoCRLF {
False,
True,
Expand All @@ -373,4 +401,5 @@ public static class Committer {
String email;
NavigableMap<LocalDate, Integer> commitsByDay;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.scm;

import lombok.Value;

@Value
public class AzureDevOpsCloneUrl implements CloneUrl {
String cloneUrl;
String origin;
String path;
String organization;
String repositoryName;

public AzureDevOpsCloneUrl(String cloneUrl, String origin, String path) {
this.cloneUrl = cloneUrl;
this.origin = origin;
this.path = path;
if (!path.contains("/")) {
throw new IllegalArgumentException("Azure DevOps clone url path must contain organization, project and repository");
}
String azureDevOpsOrganization = path.substring(0, path.indexOf("/"));
String azureDevOpsProject = path.substring(path.indexOf("/") + 1, path.lastIndexOf("/"));
this.repositoryName = path.substring(path.lastIndexOf("/") + 1);
this.organization = azureDevOpsOrganization + '/' + azureDevOpsProject;
}
}
46 changes: 46 additions & 0 deletions rewrite-core/src/main/java/org/openrewrite/scm/AzureDevOpsScm.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.scm;
pstreef marked this conversation as resolved.
Show resolved Hide resolved

public class AzureDevOpsScm implements Scm {

@Override
public String getOrigin() {
return "dev.azure.com";
}

@Override
public String cleanHostAndPath(String url) {
UrlComponents uri = UrlComponents.parse(url);
String host = uri.getHost();
String path = uri.getPath();
if (uri.isSsh() && host.startsWith("ssh.")) {
host = host.substring(4);
path = path.replaceFirst("v3/", "");
} else {
path = path.replaceFirst("_git/", "");
}
String hostAndPath = host + "/" + path
.replaceFirst("\\.git$", "");
return hostAndPath.replaceFirst("/$", "");
}

@Override
public CloneUrl parseCloneUrl(String cloneUrl) {
CloneUrl parsed = Scm.super.parseCloneUrl(cloneUrl);
return new AzureDevOpsCloneUrl(cloneUrl, getOrigin(), parsed.getPath());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.scm;
pstreef marked this conversation as resolved.
Show resolved Hide resolved

import lombok.Getter;

@Getter
public class BitbucketServerScm implements Scm {
private final String origin;

public BitbucketServerScm(String origin) {
if (origin.startsWith("ssh://") || origin.startsWith("http://") || origin.startsWith("https://")) {
origin = cleanHostAndPath(origin);
}
this.origin = origin;
}

@Override
public String cleanHostAndPath(String url) {
UrlComponents uri = UrlComponents.parse(url);
String hostAndPath = uri.getHost() + uri.maybePort() + "/" + uri.getPath()
.replaceFirst("^/", "")
.replaceFirst("scm/", "")
.replaceFirst("\\.git$", "");
return hostAndPath.replaceFirst("/$", "");
}
}
30 changes: 30 additions & 0 deletions rewrite-core/src/main/java/org/openrewrite/scm/CloneUrl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.scm;

public interface CloneUrl {

String getCloneUrl();

String getOrigin();

String getPath();

String getOrganization();

String getRepositoryName();

}
44 changes: 44 additions & 0 deletions rewrite-core/src/main/java/org/openrewrite/scm/GitLabCloneUrl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2024 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.scm;

import lombok.Value;

import java.util.Arrays;
import java.util.List;

@Value
public class GitLabCloneUrl implements CloneUrl {
String cloneUrl;
String origin;
String path;
List<String> groups;
String organization;
String repositoryName;

public GitLabCloneUrl(String cloneUrl, String origin, String path) {
this.cloneUrl = cloneUrl;
this.origin = origin;
this.path = path;
if (!this.path.contains("/")) {
throw new IllegalArgumentException("GitLab path must contain 1 or more groups and a repository name");
}
String[] parts = this.path.split("/");
groups = Arrays.asList(parts).subList(0, parts.length - 1);
organization = String.join("/", groups);
repositoryName = parts[parts.length - 1];
}
}
Loading
Loading