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 1 commit
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,6 +16,7 @@
package org.openrewrite.marker;


import lombok.AllArgsConstructor;
import lombok.Value;
import lombok.With;
import org.openrewrite.Incubating;
Expand All @@ -30,6 +31,7 @@
import org.openrewrite.marker.ci.BuildEnvironment;
import org.openrewrite.marker.ci.IncompleteGitConfigException;
import org.openrewrite.marker.ci.JenkinsBuildEnvironment;
import org.openrewrite.scm.*;

import java.io.File;
import java.io.IOException;
Expand All @@ -45,9 +47,21 @@

@Value
@With
@AllArgsConstructor
public class GitProvenance implements Marker {
private static final Set<Scm> KNOWN_SCMS = new LinkedHashSet<>(Arrays.asList(
new SimpleScm("github.com"),
new SimpleScm("bitbucket.org"),
new GroupPathScm("gitlab.com"),
new AzureDevOpsScm()
));

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 +81,34 @@ public class GitProvenance implements Marker {
@Incubating(since = "8.9.0")
List<Committer> committers;

@Incubating(since = "8.33.0")
@Nullable
ScmUrlComponents scmUrlComponents;

public GitProvenance(UUID id, @Nullable String origin, @Nullable String branch, @Nullable String change, @Nullable AutoCRLF autocrlf, @Nullable EOL eol, @Nullable List<Committer> committers) {
this.id = id;
this.origin = origin;
this.branch = branch;
this.change = change;
this.autocrlf = autocrlf;
this.eol = eol;
this.committers = committers;
scmUrlComponents = determineScmUrlComponents(origin);
}

/**
* 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 #getScmUrlComponents().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) {
if (scmUrlComponents != null) {
return scmUrlComponents.getOrganization();
}
if (origin == null) {
return null;
}
Expand Down Expand Up @@ -102,6 +136,9 @@ public class GitProvenance implements Marker {
*/
@Deprecated
public @Nullable String getOrganizationName() {
if (scmUrlComponents != null) {
return scmUrlComponents.getOrganization();
}
if (origin == null) {
return null;
}
Expand Down Expand Up @@ -206,9 +243,10 @@ 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));
getCommitters(repository), determineScmUrlComponents(remoteOriginUrl));
}

static @Nullable String resolveBranchFromGitConfig(Repository repository) {
Expand Down Expand Up @@ -268,7 +306,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 +392,27 @@ private static String hideSensitiveInformation(String url) {
return url;
}

public static void registerScm(Scm scm) {
KNOWN_SCMS.add(scm);
}

@Nullable
public static ScmUrlComponents determineScmUrlComponents(@Nullable String cloneUrl) {
if (cloneUrl == null) {
return null;
}
Scm selected = null;
for (Scm scm : KNOWN_SCMS) {
if (scm.belongsToScm(cloneUrl)) {
selected = scm;
}
}
if (selected == null) {
selected = new UnknownScm(cloneUrl);
}
return selected.determineScmUrlComponents(cloneUrl);
}

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

}
38 changes: 38 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,38 @@
package org.openrewrite.scm;
pstreef marked this conversation as resolved.
Show resolved Hide resolved

import org.openrewrite.internal.lang.Nullable;

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 String determineRepositoryName(String path) {
return path.substring(path.lastIndexOf("/") + 1);
}

@Nullable
@Override
public String determineProject(String path) {
return path.substring(path.indexOf("/") + 1, path.lastIndexOf("/"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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("/$", "");
}
}
38 changes: 38 additions & 0 deletions rewrite-core/src/main/java/org/openrewrite/scm/GroupPathScm.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.openrewrite.scm;
pstreef marked this conversation as resolved.
Show resolved Hide resolved

import lombok.Getter;
import org.openrewrite.internal.lang.Nullable;

/**
* Can be used for GitLab
*/
@Getter
public class GroupPathScm implements Scm {
private final String origin;

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

@Override
public @Nullable String determineGroupPath(String path) {
return path.substring(0, path.lastIndexOf("/"));
}

@Override
public String determineRepositoryName(String path) {
return path.substring(path.lastIndexOf("/") + 1);
}

@Override
public String cleanHostAndPath(String url) {
UrlComponents uri = UrlComponents.parse(url);
String hostAndPath = uri.getHost() + uri.maybePort() + "/" + uri.getPath()
.replaceFirst("^/", "")
.replaceFirst("\\.git$", "");
return hostAndPath.replaceFirst("/$", "");
}
}
43 changes: 43 additions & 0 deletions rewrite-core/src/main/java/org/openrewrite/scm/Scm.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.openrewrite.scm;
pstreef marked this conversation as resolved.
Show resolved Hide resolved

import org.openrewrite.internal.lang.Nullable;

public interface Scm extends Comparable<Scm> {
String getOrigin();

String cleanHostAndPath(String cloneUrl);

default boolean belongsToScm(String cloneUrl) {
return cleanHostAndPath(cloneUrl).startsWith(getOrigin());
}

@Nullable
default String determineOrganization(String path) {
return path.substring(0, path.indexOf("/"));
}

@Nullable
default String determineGroupPath(String path) {
return null;
}

@Nullable
default String determineProject(String path) {
return null;
}

default String determineRepositoryName(String path) {
return path.substring(path.indexOf("/") + 1);
}

default ScmUrlComponents determineScmUrlComponents(String cloneUrl) {
String path = cleanHostAndPath(cloneUrl).substring(getOrigin().length() + 1);
return new ScmUrlComponents(getOrigin(), determineOrganization(path), determineGroupPath(path), determineProject(path), determineRepositoryName(path));
}

@Override
default int compareTo(Scm o) {
return getOrigin().compareTo(o.getOrigin());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.openrewrite.scm;
pstreef marked this conversation as resolved.
Show resolved Hide resolved

import lombok.Builder;
import lombok.Value;
import org.openrewrite.internal.lang.Nullable;

@Value
@Builder
public class ScmUrlComponents {

@Nullable
String origin;

@Nullable
String organization;

@Nullable
String groupPath;

@Nullable
String project;

String repositoryName;

public String getPath() {
if (groupPath != null) {
return groupPath + "/" + repositoryName;
}
if (organization != null) {
if (project != null) {
return organization + "/" + project + "/" + repositoryName;
}
return organization + "/" + repositoryName;
}
return repositoryName;
}
}
27 changes: 27 additions & 0 deletions rewrite-core/src/main/java/org/openrewrite/scm/SimpleScm.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.openrewrite.scm;
pstreef marked this conversation as resolved.
Show resolved Hide resolved

import lombok.Getter;

/**
* Simple as in just split by origin/path, and where the path is organization/repositoryName, like GitHub
*/
@Getter
public class SimpleScm implements Scm {
private final String origin;

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

@Override
public String cleanHostAndPath(String url) {
UrlComponents uri = UrlComponents.parse(url);
String hostAndPath = uri.getHost() + uri.maybePort() + "/" + uri.getPath()
.replaceFirst("^/", "")
.replaceFirst("\\.git$", "");
return hostAndPath.replaceFirst("/$", "");
}
}
30 changes: 30 additions & 0 deletions rewrite-core/src/main/java/org/openrewrite/scm/UnknownScm.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.openrewrite.scm;
pstreef marked this conversation as resolved.
Show resolved Hide resolved

import lombok.Getter;

import java.util.Arrays;

/**
* Fallback that assumes the last 2 path segments of a cloneUrl contains the repository path, not the same as SimpleScm
* which splits on a known origin.
*/
@Getter
public class UnknownScm extends SimpleScm {
public UnknownScm(String cloneUrl) {
super(cloneUrlToOrigin(cloneUrl));
}

private static String cloneUrlToOrigin(String cloneUrl) {
UrlComponents uri = UrlComponents.parse(cloneUrl);
String fullPath = uri.getPath()
.replaceFirst("^/", "")
.replaceFirst("\\.git$", "");

String[] segments = fullPath.split("/");
if (segments.length <= 2) {
return uri.getHost() + uri.maybePort();
}
String contextPath = String.join("/", Arrays.copyOfRange(segments, 0, segments.length - 2));
return uri.getHost() + uri.maybePort() + "/" + contextPath;
}
}
Loading
Loading