diff --git a/rewrite-core/src/main/java/org/openrewrite/marker/GitProvenance.java b/rewrite-core/src/main/java/org/openrewrite/marker/GitProvenance.java index 889c4e65885..3d0a94c8e1a 100644 --- a/rewrite-core/src/main/java/org/openrewrite/marker/GitProvenance.java +++ b/rewrite-core/src/main/java/org/openrewrite/marker/GitProvenance.java @@ -51,7 +51,7 @@ public class GitProvenance implements Marker { private static final Pattern AZURE_DEVOPS_HTTP_REMOTE = Pattern.compile("https://(?:[\\w-]+@)?dev\\.azure\\.com/([^/]+)/([^/]+)/_git/(.*)"); private static final Pattern AZURE_DEVOPS_SSH_REMOTE = - Pattern.compile("git@ssh\\.dev\\.azure\\.com:v3/([^/]+)/([^/]+)/(.*)"); + Pattern.compile("(?:ssh://)?git@ssh\\.dev\\.azure\\.com:v3/([^/]+)/([^/]+)/(.*)"); UUID id; @Nullable @@ -112,32 +112,51 @@ public class GitProvenance implements Marker { */ @Deprecated public @Nullable String getOrganizationName() { - if (origin != null) { - Matcher matcher = AZURE_DEVOPS_HTTP_REMOTE.matcher(origin); - if (matcher.matches()) { - return matcher.group(1) + '/' + matcher.group(2); - } - matcher = AZURE_DEVOPS_SSH_REMOTE.matcher(origin); - if (matcher.matches()) { - return matcher.group(1) + '/' + matcher.group(2); - } + return Optional.ofNullable(origin).map(GitProvenance::getOrganizationNameFromOrigin).orElse(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) { - } + private static @Nullable String getOrganizationNameFromOrigin(String origin) { + Matcher matcher = AZURE_DEVOPS_HTTP_REMOTE.matcher(origin); + if (matcher.matches()) { + return matcher.group(1); + } + matcher = AZURE_DEVOPS_SSH_REMOTE.matcher(origin); + if (matcher.matches()) { + return matcher.group(1); + } + + 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; } - public @Nullable String getRepositoryName() { - if (origin == null) { - return null; + public @Nullable String getProjectName() { + return Optional.ofNullable(origin).map(GitProvenance::getProjectName).orElse(null); + } + + private static @Nullable String getProjectName(String origin) { + Matcher matcher = AZURE_DEVOPS_HTTP_REMOTE.matcher(origin); + if (matcher.matches()) { + return matcher.group(2); + } + matcher = AZURE_DEVOPS_SSH_REMOTE.matcher(origin); + if (matcher.matches()) { + return matcher.group(2); } + return null; + } + + public @Nullable String getRepositoryName() { + return Optional.ofNullable(origin).map(GitProvenance::getRepositoryName).orElse(null); + } + + private static @Nullable String getRepositoryName(String origin) { try { String path = new URIish(origin).getPath(); return path.substring(path.lastIndexOf('/') + 1) @@ -147,6 +166,21 @@ public class GitProvenance implements Marker { } } + public static String getRepositoryPath(String origin) { + StringBuilder builder = new StringBuilder(64); + builder.append(getOrganizationNameFromOrigin(origin)); + String projectName = getProjectName(origin); + if (projectName != null) { + builder.append('/').append(projectName); + } + String repositoryName = getRepositoryName(origin); + if (repositoryName != null) { + builder.append('/').append(repositoryName); + } + return builder.toString(); + } + + /** * @param projectDir The project directory. * @return A marker containing git provenance information. @@ -163,7 +197,8 @@ public class GitProvenance implements Marker { * determined from a {@link BuildEnvironment} marker if possible. * @return A marker containing git provenance information. */ - public static @Nullable GitProvenance fromProjectDirectory(Path projectDir, @Nullable BuildEnvironment environment) { + public static @Nullable GitProvenance fromProjectDirectory(Path projectDir, + @Nullable BuildEnvironment environment) { if (environment != null) { if (environment instanceof JenkinsBuildEnvironment) { JenkinsBuildEnvironment jenkinsBuildEnvironment = (JenkinsBuildEnvironment) environment; @@ -220,7 +255,9 @@ private static void printRequireGitDirOrWorkTreeException(Exception e) { } } - private static GitProvenance fromGitConfig(Repository repository, @Nullable String branch, @Nullable String changeset) { + private static GitProvenance fromGitConfig(Repository repository, + @Nullable String branch, + @Nullable String changeset) { if (branch == null) { branch = resolveBranchFromGitConfig(repository); } @@ -264,7 +301,8 @@ private static GitProvenance fromGitConfig(Repository repository, @Nullable Stri } - private static @Nullable String localBranchName(Repository repository, @Nullable String remoteBranch) throws IOException, GitAPIException { + private static @Nullable String localBranchName(Repository repository, + @Nullable String remoteBranch) throws IOException, GitAPIException { if (remoteBranch == null) { return null; } else if (remoteBranch.startsWith("remotes/")) { @@ -277,7 +315,7 @@ private static GitProvenance fromGitConfig(Repository repository, @Nullable Stri List remotes = git.remoteList().call(); for (RemoteConfig remote : remotes) { if (remoteBranch.startsWith(remote.getName()) && - (branch == null || branch.length() > remoteBranch.length() - remote.getName().length() - 1)) { + (branch == null || branch.length() > remoteBranch.length() - remote.getName().length() - 1)) { branch = remoteBranch.substring(remote.getName().length() + 1); // +1 for the forward slash } } @@ -340,9 +378,11 @@ private static List getCommitters(Repository repository) { Map committers = new TreeMap<>(); for (RevCommit commit : git.log().add(head).call()) { PersonIdent who = commit.getAuthorIdent(); - Committer committer = committers.computeIfAbsent(who.getEmailAddress(), + Committer committer = committers.computeIfAbsent( + who.getEmailAddress(), email -> new Committer(who.getName(), email, new TreeMap<>())); - committer.getCommitsByDay().compute(who.getWhen().toInstant().atZone(who.getTimeZone().toZoneId()) + committer.getCommitsByDay().compute( + who.getWhen().toInstant().atZone(who.getTimeZone().toZoneId()) .toLocalDate(), (day, count) -> count == null ? 1 : count + 1); } diff --git a/rewrite-core/src/test/java/org/openrewrite/marker/GitProvenanceTest.java b/rewrite-core/src/test/java/org/openrewrite/marker/GitProvenanceTest.java index 0c392886c64..5492d07c9ad 100644 --- a/rewrite-core/src/test/java/org/openrewrite/marker/GitProvenanceTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/marker/GitProvenanceTest.java @@ -39,11 +39,11 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; -import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.openrewrite.Tree.randomId; @@ -54,46 +54,54 @@ class GitProvenanceTest { private static Stream remotes() { return Stream.of( - Arguments.of("ssh://git@github.com/openrewrite/rewrite.git", "openrewrite", "rewrite"), - Arguments.of("https://github.com/openrewrite/rewrite.git", "openrewrite", "rewrite"), - Arguments.of("file:///openrewrite/rewrite.git", "openrewrite", "rewrite"), - Arguments.of("http://localhost:7990/scm/openrewrite/rewrite.git", "openrewrite", "rewrite"), - Arguments.of("http://localhost:7990/scm/some/openrewrite/rewrite.git", "openrewrite", "rewrite"), - Arguments.of("git@github.com:openrewrite/rewrite.git", "openrewrite", "rewrite"), - Arguments.of("org-12345678@github.com:openrewrite/rewrite.git", "openrewrite", "rewrite"), - Arguments.of("https://dev.azure.com/openrewrite/rewrite/_git/rewrite", "openrewrite/rewrite", "rewrite"), - Arguments.of("https://openrewrite@dev.azure.com/openrewrite/rewrite/_git/rewrite", "openrewrite/rewrite", "rewrite"), - Arguments.of("git@ssh.dev.azure.com:v3/openrewrite/rewrite/rewrite", "openrewrite/rewrite", "rewrite") + Arguments.of("ssh://git@github.com/openrewrite/rewrite.git", "openrewrite", null, "rewrite"), + Arguments.of("https://github.com/openrewrite/rewrite.git", "openrewrite", null, "rewrite"), + Arguments.of("file:///openrewrite/rewrite.git", "openrewrite", null, "rewrite"), + Arguments.of("http://localhost:7990/scm/openrewrite/rewrite.git", "openrewrite", null, "rewrite"), + Arguments.of("http://localhost:7990/scm/some/openrewrite/rewrite.git", "openrewrite", null, "rewrite"), + Arguments.of("git@github.com:openrewrite/rewrite.git", "openrewrite", null, "rewrite"), + Arguments.of("org-12345678@github.com:openrewrite/rewrite.git", "openrewrite", null, "rewrite"), + Arguments.of("https://dev.azure.com/openrewrite/rewrite/_git/rewrite", "openrewrite", "rewrite", "rewrite"), + Arguments.of( + "https://openrewrite@dev.azure.com/openrewrite/rewrite/_git/rewrite", + "openrewrite", + "rewrite", + "rewrite"), + Arguments.of("git@ssh.dev.azure.com:v3/openrewrite/rewrite/rewrite", "openrewrite", "rewrite", "rewrite"), + Arguments.of( + "ssh://git@ssh.dev.azure.com:v3/openrewrite/rewrite/rewrite", + "openrewrite", + "rewrite", + "rewrite") ); } @SuppressWarnings("deprecation") @ParameterizedTest @MethodSource("remotes") - void getOrganizationName(String remote, String org, String repo) { - assertThat(new GitProvenance(randomId(), remote, "main", "123", null, null, emptyList()).getOrganizationName()) - .isEqualTo(org); + void getRepositoryPath(String origin, String expectedOrg, String expectedProject, String expectedRepo) { + GitProvenance gitProvenance = new GitProvenance(randomId(), origin, "main", "123", null, null, List.of()); + assertThat(gitProvenance.getOrganizationName()).isEqualTo(expectedOrg); + assertThat(gitProvenance.getProjectName()).isEqualTo(expectedProject); + assertThat(gitProvenance.getRepositoryName()).isEqualTo(expectedRepo); + String expectedPath = expectedProject != null ? + expectedOrg + '/' + expectedProject + '/' + expectedRepo : + expectedOrg + '/' + expectedRepo; + assertThat(gitProvenance.getRepositoryPath(origin)).isEqualTo(expectedPath); } @ParameterizedTest @CsvSource({ "git@gitlab.acme.com:organization/subgroup/repository.git, https://gitlab.acme.com, organization/subgroup", "git@gitlab.acme.com:organization/subgroup/repository.git, git@gitlab.acme.com, organization/subgroup", - "https://dev.azure.com/organization/project/_git/repository, https://dev.azure.com, organization/project", - "https://organization@dev.azure.com/organization/project/_git/repository, https://dev.azure.com, organization/project", - "git@ssh.dev.azure.com:v3/organization/project/repository, git@ssh.dev.azure.com, organization/project" + "https://dev.azure.com/organization/project/_git/repository, https://dev.azure.com, organization", + "https://organization@dev.azure.com/organization/project/_git/repository, https://dev.azure.com, organization", + "git@ssh.dev.azure.com:v3/organization/project/repository, git@ssh.dev.azure.com, organization" }) void getOrganizationNameWithBaseUrl(String gitOrigin, String baseUrl, String organizationName) { - assertThat(new GitProvenance(randomId(), gitOrigin, "main", "123", null, null, emptyList()).getOrganizationName(baseUrl)) - .isEqualTo(organizationName); - } - - @ParameterizedTest - @MethodSource("remotes") - void getRepositoryName(String remote, String org, String repo) { - assertThat(new GitProvenance(randomId(), remote, "main", "123", null, null, emptyList()).getRepositoryName()) - .isEqualTo(repo); + GitProvenance gitProvenance = new GitProvenance(randomId(), gitOrigin, "main", "123", null, null, List.of()); + assertThat(gitProvenance.getOrganizationName(baseUrl)).isEqualTo(organizationName); } @Test @@ -207,13 +215,14 @@ private static Stream baseUrls() { @ParameterizedTest @MethodSource("baseUrls") void multiplePathSegments(String baseUrl) { - GitProvenance provenance = new GitProvenance(randomId(), + GitProvenance provenance = new GitProvenance( + randomId(), "http://gitlab.com/group/subgroup1/subgroup2/repo.git", "master", "1234567890abcdef1234567890abcdef12345678", null, null, - emptyList()); + List.of()); assertThat(provenance.getOrganizationName(baseUrl)).isEqualTo("group/subgroup1/subgroup2"); assertThat(provenance.getRepositoryName()).isEqualTo("repo"); @@ -228,7 +237,10 @@ void shallowCloneDetachedHead(@TempDir Path projectDir) throws IOException, GitA var cloneDir = projectDir.resolve("clone1"); try (var remoteRepo = fileKey.open(false)) { remoteRepo.create(true); - try (Git git = Git.cloneRepository().setURI(remoteRepo.getDirectory().getAbsolutePath()).setDirectory(cloneDir.toFile()).call()) { + try (Git git = Git.cloneRepository() + .setURI(remoteRepo.getDirectory().getAbsolutePath()) + .setDirectory(cloneDir.toFile()) + .call()) { Files.writeString(projectDir.resolve("test.txt"), "hi"); git.add().addFilepattern("*").call(); git.commit().setMessage("init").setSign(false).call(); @@ -261,7 +273,10 @@ void noLocalBranchDeriveFromRemote(@TempDir Path projectDir) throws IOException, // push an initial commit to the remote var cloneDir = projectDir.resolve("clone1"); - try (Git gitSetup = Git.cloneRepository().setURI(remoteRepo.getDirectory().getAbsolutePath()).setDirectory(cloneDir.toFile()).call()) { + try (Git gitSetup = Git.cloneRepository() + .setURI(remoteRepo.getDirectory().getAbsolutePath()) + .setDirectory(cloneDir.toFile()) + .call()) { Files.writeString(projectDir.resolve("test.txt"), "hi"); gitSetup.add().addFilepattern("*").call(); var commit = gitSetup.commit().setMessage("init").setSign(false).call(); @@ -270,8 +285,16 @@ void noLocalBranchDeriveFromRemote(@TempDir Path projectDir) throws IOException, //Now create new workspace directory, git init and then fetch from remote. var workspaceDir = projectDir.resolve("workspace"); try (Git git = Git.init().setDirectory(workspaceDir.toFile()).call()) { - git.remoteAdd().setName("origin").setUri(new URIish(remoteRepo.getDirectory().getAbsolutePath())).call(); - git.fetch().setRemote("origin").setForceUpdate(true).setTagOpt(TagOpt.FETCH_TAGS).setRefSpecs("+refs/heads/*:refs/remotes/origin/*").call(); + git.remoteAdd() + .setName("origin") + .setUri(new URIish(remoteRepo.getDirectory().getAbsolutePath())) + .call(); + git.fetch() + .setRemote("origin") + .setForceUpdate(true) + .setTagOpt(TagOpt.FETCH_TAGS) + .setRefSpecs("+refs/heads/*:refs/remotes/origin/*") + .call(); git.checkout().setName(commit.getName()).call(); } } @@ -291,7 +314,8 @@ void supportsGitHubActions(@TempDir Path projectDir) { envVars.put("GITHUB_SHA", "287364287357"); envVars.put("GITHUB_HEAD_REF", ""); - GitProvenance prov = GitProvenance.fromProjectDirectory(projectDir, + GitProvenance prov = GitProvenance.fromProjectDirectory( + projectDir, GithubActionsBuildEnvironment.build(envVars::get)); assertThat(prov).isNotNull(); assertThat(prov.getOrigin()).isEqualTo("https://github.com/octocat/Hello-World.git"); @@ -308,7 +332,8 @@ void ignoresBuildEnvironmentIfThereIsGitConfig(@TempDir Path projectDir) throws envVars.put("GITHUB_SHA", "287364287357"); envVars.put("GITHUB_HEAD_REF", ""); try (Git ignored = Git.init().setDirectory(projectDir.toFile()).setInitialBranch("main").call()) { - GitProvenance prov = GitProvenance.fromProjectDirectory(projectDir, + GitProvenance prov = GitProvenance.fromProjectDirectory( + projectDir, GithubActionsBuildEnvironment.build(envVars::get)); assertThat(prov).isNotNull(); assertThat(prov.getOrigin()).isNotEqualTo("https://github.com/octocat/Hello-World.git"); @@ -324,7 +349,8 @@ void supportsCustomBuildEnvironment(@TempDir Path projectDir) { envVars.put("CUSTOM_GIT_REF", "main"); envVars.put("CUSTOM_GIT_SHA", "287364287357"); - GitProvenance prov = GitProvenance.fromProjectDirectory(projectDir, + GitProvenance prov = GitProvenance.fromProjectDirectory( + projectDir, CustomBuildEnvironment.build(envVars::get)); assertThat(prov).isNotNull(); @@ -340,7 +366,8 @@ void supportsGitLab(@TempDir Path projectDir) { envVars.put("CI_COMMIT_REF_NAME", "main"); envVars.put("CI_COMMIT_SHA", "287364287357"); - GitProvenance prov = GitProvenance.fromProjectDirectory(projectDir, + GitProvenance prov = GitProvenance.fromProjectDirectory( + projectDir, GitlabBuildEnvironment.build(envVars::get)); assertThat(prov).isNotNull(); @@ -357,7 +384,8 @@ void supportsDrone(@TempDir Path projectDir) { envVars.put("DRONE_REMOTE_URL", "https://github.com/octocat/Hello-World.git"); envVars.put("DRONE_COMMIT_SHA", "287364287357"); - GitProvenance prov = GitProvenance.fromProjectDirectory(projectDir, + GitProvenance prov = GitProvenance.fromProjectDirectory( + projectDir, DroneBuildEnvironment.build(envVars::get)); assertThat(prov).isNotNull();