-
Notifications
You must be signed in to change notification settings - Fork 349
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
Changes from 14 commits
f21f282
9774c15
973bed5
6e937cf
e30b193
4fd0365
9fd2b07
b331cef
3d5d541
cbb7a1a
453566b
4c2387c
4fef5be
aacea53
c2e9e9c
5fe218a
4fefa63
ce49346
5dec1dd
8d07e18
43d7957
a13a68a
f408a35
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ | |
package org.openrewrite.marker; | ||
|
||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Value; | ||
import lombok.With; | ||
import org.openrewrite.Incubating; | ||
|
@@ -30,6 +31,8 @@ | |
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; | ||
|
@@ -45,9 +48,14 @@ | |
|
||
@Value | ||
@With | ||
@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; | ||
|
||
|
@@ -67,14 +75,34 @@ public class GitProvenance implements Marker { | |
@Incubating(since = "8.9.0") | ||
List<Committer> committers; | ||
|
||
@Incubating(since = "8.33.0") | ||
@Nullable | ||
CloneUrl cloneUrl; | ||
|
||
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; | ||
cloneUrl = parseCloneUrl(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 #getCloneUrl().getOrganization()} instead } | ||
*/ | ||
@Deprecated | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 (cloneUrl != null) { | ||
return cloneUrl.getOrganization(); | ||
} | ||
if (origin == null) { | ||
return null; | ||
} | ||
|
@@ -102,6 +130,9 @@ public class GitProvenance implements Marker { | |
*/ | ||
@Deprecated | ||
public @Nullable String getOrganizationName() { | ||
if (cloneUrl != null) { | ||
return cloneUrl.getOrganization(); | ||
} | ||
if (origin == null) { | ||
return null; | ||
} | ||
|
@@ -206,9 +237,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), parseCloneUrl(remoteOriginUrl)); | ||
} | ||
|
||
static @Nullable String resolveBranchFromGitConfig(Repository repository) { | ||
|
@@ -268,7 +300,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) { | ||
|
@@ -354,6 +386,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, | ||
|
@@ -373,4 +414,5 @@ public static class Committer { | |
String email; | ||
NavigableMap<LocalDate, Integer> commitsByDay; | ||
} | ||
|
||
} |
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.Value; | ||
|
||
@Value | ||
public class AzureDevopsCloneUrl implements CloneUrl { | ||
String cloneUrl; | ||
String origin; | ||
String path; | ||
String organization; | ||
String project; | ||
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"); | ||
} | ||
this.organization = path.substring(0, path.indexOf("/")); | ||
this.project = path.substring(path.indexOf("/") + 1, path.lastIndexOf("/")); | ||
this.repositoryName = path.substring(path.lastIndexOf("/") + 1); | ||
} | ||
} |
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("/$", ""); | ||
} | ||
} |
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(); | ||
|
||
} |
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]; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/* | ||
* 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.Getter; | ||
|
||
@Getter | ||
public class GitLabScm implements Scm { | ||
private final String origin; | ||
|
||
/** | ||
* Configure a GitLab SaaS SCM instance | ||
*/ | ||
public GitLabScm() { | ||
this.origin = "gitlab.com"; | ||
} | ||
|
||
/** | ||
* Configure a self-hosted gitlab SCM and register with GitProvenance.registerScm(new GitLabScm(origin)); | ||
* | ||
* @param origin the url and path pointing to your GitLab instance. | ||
*/ | ||
public GitLabScm(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("/$", ""); | ||
} | ||
|
||
@Override | ||
public CloneUrl parseCloneUrl(String cloneUrl) { | ||
CloneUrl parsed = Scm.super.parseCloneUrl(cloneUrl); | ||
return new GitLabCloneUrl(cloneUrl, getOrigin(), parsed.getPath()); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably only calculate this when it is first used, otherwise this slows down deserialization.