diff --git a/src/main/java/hudson/plugins/git/GitSCM.java b/src/main/java/hudson/plugins/git/GitSCM.java index ceab12095a..50a012995f 100644 --- a/src/main/java/hudson/plugins/git/GitSCM.java +++ b/src/main/java/hudson/plugins/git/GitSCM.java @@ -1663,6 +1663,7 @@ public static final class DescriptorImpl extends SCMDescriptor { private boolean allowSecondFetch; private boolean disableGitToolChooser; private boolean addGitTagAction; + private String globalUrlRegEx; public DescriptorImpl() { super(GitSCM.class, GitRepositoryBrowser.class); @@ -1772,6 +1773,29 @@ public String getGlobalConfigEmail() { return Util.fixEmptyAndTrim(globalConfigEmail); } + /** + * Global setting for Regular Expression + * @return url regex + */ + public String getGlobalUrlRegEx(){ return globalUrlRegEx; } + + public void setGlobalUrlRegEx(String globalUrlRegEx){ + if (globalUrlRegEx == null || globalUrlRegEx.trim().isEmpty()) { + // If the user doesn't provide a value, use the default one + globalUrlRegEx = getDefaultGlobalUrlRegEx(); + } + this.globalUrlRegEx = globalUrlRegEx; + } + + public String getDefaultGlobalUrlRegEx() { + return "(.*github.*?[/:](?[^/]+)/(?[^/]+?)(?:\\.git)?$)"+"&&&"+ + "(.*gitlab.*?[/:](?[^/]+)/(?[^/]+?)(?:\\.git)?$)"+"&&&"+ + "(.*?//(?\\w+).*visualstudio.*?/(?[^/]+?)(?:\\.git)?/?$)"+"&&&"+ + "(.*bitbucket.*?[/:](?[^/]+)/(?[^/]+?)(?:\\.git)?$)"+"&&&"+ + "(.*assembla.com[:/](?[^/]+?)(?:\\.git)?$)"+"&&&"+ + "(git@git.*?[:/](?[^/]+)/(?[^/]+?)(?:\\.git)?$)"; + } + /** * Global setting to be used to set GIT_COMMITTER_EMAIL and GIT_AUTHOR_EMAIL. * @param globalConfigEmail user.email value to be assigned diff --git a/src/main/java/hudson/plugins/git/util/BuildData.java b/src/main/java/hudson/plugins/git/util/BuildData.java index ba4cb6d783..257673ef6e 100644 --- a/src/main/java/hudson/plugins/git/util/BuildData.java +++ b/src/main/java/hudson/plugins/git/util/BuildData.java @@ -7,6 +7,7 @@ import hudson.model.Api; import hudson.model.Run; import hudson.plugins.git.Branch; +import hudson.plugins.git.GitSCM; import hudson.plugins.git.Revision; import hudson.plugins.git.UserRemoteConfig; import java.io.Serializable; @@ -26,10 +27,14 @@ import org.kohsuke.stapler.export.ExportedBean; import static hudson.Util.fixNull; +import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * Captures the Git related information for a build. * @@ -293,6 +298,88 @@ public boolean hasBeenReferenced(String remoteUrl) { return remoteUrls.contains(remoteUrl); } + protected GitSCM.DescriptorImpl getDescriptorImpl(){ + return new GitSCM.DescriptorImpl(); + } + /** + * Extracts the repository name from a given Git remote URL. + * This method uses a global regular expression defined in the Jenkins Git plugin + * configuration (GitSCM.DescriptorImpl). If the global regex is not defined or empty, + * it defaults to the plugin's default regular expression. The method checks if the regex + * pattern contains a named capturing group 'repo' and attempts to extract it. + * + * @param remoteUrl The Git remote URL to parse. + * @return The repository name if matched and extracted successfully; otherwise, null. + */ + public String getRepoName(String remoteUrl) throws MalformedURLException { + GitSCM.DescriptorImpl descriptor = getDescriptorImpl(); + String globalRegex = descriptor.getGlobalUrlRegEx(); + + if (globalRegex == null || globalRegex.isEmpty()) { + globalRegex = descriptor.getDefaultGlobalUrlRegEx(); + } + String[] regexps = globalRegex.split("&&&"); + for (String regex : regexps) { + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(remoteUrl); + + if (matcher.matches()) { + if (regex.contains("(?")) { // Check if regex contains the 'repo' named group + try { + return matcher.group("repo"); + } catch (IllegalArgumentException | IllegalStateException e) { + // Return null if there is an error extracting the 'repo' group + return null; + } + } else { + // Return null if regex does not contain the 'repo' named group + return null; + } + } + } + // Return null if no matching repository name is found in the URL + return null; + } + /** + * Extracts the repository name from a given Git remote URL. + * This method uses a global regular expression defined in the Jenkins Git plugin + * configuration (GitSCM.DescriptorImpl). If the global regex is not defined or empty, + * it defaults to the plugin's default regular expression. The method checks if the regex + * pattern contains a named capturing group 'repo' and attempts to extract it. + * + * @param remoteUrl The Git remote URL to parse. + * @return The repository name if matched and extracted successfully; otherwise, null. + */ + public String getOrganizationName(String remoteUrl) { + GitSCM.DescriptorImpl descriptor = getDescriptorImpl(); + String globalRegex = descriptor.getGlobalUrlRegEx(); + if (globalRegex == null || globalRegex.isEmpty()) { + globalRegex = descriptor.getDefaultGlobalUrlRegEx(); + } + String[] regexps = globalRegex.split("&&&"); + for (String regex : regexps) { + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(remoteUrl); + + if (matcher.matches()) { + // Check if the pattern includes the 'org' group + if (regex.contains("(?")) { + try { + return matcher.group("org"); + } catch (IllegalArgumentException | IllegalStateException e) { + // Return null if there is an error extracting the 'org' group + return null; + } + } else { + // Return null if regex does not contain the 'org' named group + return null; + } + } + } + // Return null if no matching org name is found in the URL + return null; + } + @Override public BuildData clone() { BuildData clone; diff --git a/src/main/resources/hudson/plugins/git/GitSCM/global.jelly b/src/main/resources/hudson/plugins/git/GitSCM/global.jelly index 2536b5ca71..f34054e1c8 100644 --- a/src/main/resources/hudson/plugins/git/GitSCM/global.jelly +++ b/src/main/resources/hudson/plugins/git/GitSCM/global.jelly @@ -8,6 +8,9 @@ + + + diff --git a/src/main/resources/hudson/plugins/git/util/BuildData/index.jelly b/src/main/resources/hudson/plugins/git/util/BuildData/index.jelly index 7684a80e6f..03167e1e3b 100644 --- a/src/main/resources/hudson/plugins/git/util/BuildData/index.jelly +++ b/src/main/resources/hudson/plugins/git/util/BuildData/index.jelly @@ -18,6 +18,12 @@
${%Repository}: ${remoteUrl}
+ +
${%Organization Name}: ${it.getOrganizationName(remoteUrl)} +
+ +
${%Repository Name}: ${it.getRepoName(remoteUrl)} +
    diff --git a/src/main/resources/hudson/plugins/git/util/BuildData/summary.jelly b/src/main/resources/hudson/plugins/git/util/BuildData/summary.jelly index 6b49d2f7c2..553bb09a6c 100644 --- a/src/main/resources/hudson/plugins/git/util/BuildData/summary.jelly +++ b/src/main/resources/hudson/plugins/git/util/BuildData/summary.jelly @@ -14,6 +14,12 @@
    ${%Repository}: ${remoteUrl}
    + +
    ${%Organization Name}: ${it.getOrganizationName(remoteUrl)} +
    + +
    ${%Repository Name}: ${it.getRepoName(remoteUrl)} +
      diff --git a/src/test/java/hudson/plugins/git/util/BuildDataTest.java b/src/test/java/hudson/plugins/git/util/BuildDataTest.java index fb47137281..c1527f0066 100644 --- a/src/test/java/hudson/plugins/git/util/BuildDataTest.java +++ b/src/test/java/hudson/plugins/git/util/BuildDataTest.java @@ -21,9 +21,20 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; + import org.junit.Before; import org.junit.Test; import org.jvnet.hudson.test.Issue; +import java.net.MalformedURLException; +import org.mockito.MockedStatic; +import jenkins.model.Jenkins; +import hudson.plugins.git.GitSCM; +import java.util.List; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + /** * @author Mark Waite @@ -33,7 +44,7 @@ public class BuildDataTest { private BuildData data; private final ObjectId sha1 = ObjectId.fromString("929e92e3adaff2e6e1d752a8168c1598890fe84c"); private final String remoteUrl = "https://github.com/jenkinsci/git-plugin"; - + private GitSCM.DescriptorImpl descriptor; @Before public void setUp() throws Exception { data = new BuildData(); @@ -578,4 +589,96 @@ public void testHashCodeEmptyData() { emptyData.remoteUrls = null; assertEquals(emptyData.hashCode(), emptyData.hashCode()); } + + // Helper method for GitOrgRepoName-related setup + private void setupGitOrgRepoNameMock() throws MalformedURLException { + + MockedStatic mockedJenkins = mockStatic(Jenkins.class); + Jenkins mockJenkins = mock(Jenkins.class); + mockedJenkins.when(Jenkins::getInstanceOrNull).thenReturn(mockJenkins); + + // Create a mock for the GitSCM.DescriptorImpl class + descriptor = mock(GitSCM.DescriptorImpl.class); + // Setup the behavior for the mock descriptor when Jenkins.getDescriptor is called + when(mockJenkins.getDescriptor(GitSCM.class)).thenReturn(descriptor); + String mockRegexPattern = + "(.*github.*?[/:](?[^/]+)/(?[^/]+?)(?:\\.git)?$)" + + "&&&" + + "(.*gitlab.*?[/:](?[^/]+)/(?[^/]+?)(?:\\.git)?$)" + + "&&&" + + "(.*?//(?\\w+).*visualstudio.*?/(?[^/]+?)(?:\\.git)?/?$)" + + "&&&" + + "(.*bitbucket.*?[/:](?[^/]+)/(?[^/]+?)(?:\\.git)?$)" + + "&&&" + + "(.*assembla.com[:/](?[^/]+?)(?:\\.git)?$)"+ + "&&&" + + "(git@git.*?[:/](?[^/]+)/(?[^/]+?)(?:\\.git)?$)"; + when(descriptor.getGlobalUrlRegEx()).thenReturn(mockRegexPattern); + data = spy(new BuildData() { + @Override + protected GitSCM.DescriptorImpl getDescriptorImpl() { + return descriptor; + } + }); + } + + @Test + public void testOrganizationAndRepoNameExtraction() throws MalformedURLException { + setupGitOrgRepoNameMock(); + List testUrls = new ArrayList<>(); + testUrls.add(new TestUrl( "https://github.com/mohdishaq786/Backend_challenge_stage2.git","mohdishaq786","Backend_challenge_stage2")); + testUrls.add(new TestUrl("git@bitbucket.org:markewaite/tasks.git", "markewaite", "tasks")); + testUrls.add(new TestUrl("git@bitbucket.org:markewaite/bin.git", "markewaite", "bin")); + testUrls.add(new TestUrl("https://markewaite@bitbucket.org/markewaite/tasks.git", "markewaite", "tasks")); + testUrls.add(new TestUrl("https://markewaite@bitbucket.org/markewaite/git-client-plugin.git", "markewaite", "git-client-plugin")); + testUrls.add(new TestUrl("https://markewaite@bitbucket.org/markewaite/bin.git", "markewaite", "bin")); + testUrls.add(new TestUrl("https://MarkEWaite:also-a-password@gitlab.com/MarkEWaite/tasks.git", "MarkEWaite", "tasks")); + testUrls.add(new TestUrl("https://MarkEWaite:another-password@github.com/MarkEWaite/tasks.git", "MarkEWaite", "tasks")); + testUrls.add(new TestUrl("https://MarkEWaite:yes-this-is-a-password@github.com/MarkEWaite/bin.git", "MarkEWaite", "bin")); + testUrls.add(new TestUrl("https://gitlab.com/MarkEWaite/tasks.git", "MarkEWaite", "tasks")); + testUrls.add(new TestUrl("https://gitlab.com/MarkEWaite/tasks", "MarkEWaite", "tasks")); + testUrls.add(new TestUrl("https://gitlab.com/MarkEWaite/bin", "MarkEWaite", "bin")); + testUrls.add(new TestUrl("https://github.com/MarkEWaite/tasks.git", "MarkEWaite", "tasks")); + testUrls.add(new TestUrl("git@github.com:MarkEWaite/bin.git", "MarkEWaite", "bin")); + testUrls.add(new TestUrl("git@gitlab.com:MarkEWaite/tasks.git", "MarkEWaite", "tasks")); + testUrls.add(new TestUrl("git@github.com:MarkEWaite/tasks.git", "MarkEWaite", "tasks")); + testUrls.add(new TestUrl("https://bitbucket.org/markewaite/bin.git", "markewaite", "bin")); + testUrls.add(new TestUrl("https://bitbucket.org/markewaite/git-client-plugin.git", "markewaite", "git-client-plugin")); + testUrls.add(new TestUrl("https://bitbucket.org/markewaite/tasks.git", "markewaite", "tasks")); + testUrls.add(new TestUrl("https://github.com/MarkEWaite/bin.git", "MarkEWaite", "bin")); + testUrls.add(new TestUrl("https://markwaite.visualstudio.com/_git/elisp", "markwaite", "elisp")); + testUrls.add(new TestUrl("https://markwaite.visualstudio.com/DefaultCollection/_git/", "markwaite", "_git")); + testUrls.add(new TestUrl("https://markwaite.visualstudio.com/DefaultCollection/elisp/_git/elisp", "markwaite", "elisp")); + testUrls.add(new TestUrl("https://git.assembla.com/git-plugin.bin.git", null, "git-plugin.bin")); + testUrls.add(new TestUrl("git@git.assembla.com:git-plugin.bin.git", null, "git-plugin.bin")); + testUrls.add(new TestUrl("ssh://markwaite@vs-ssh.visualstudio.com:22/DefaultCollection/_ssh/elisp", "markwaite", "elisp")); + testUrls.add(new TestUrl("ssh://git@github.com/MarkEWaite/tasks.git", "MarkEWaite", "tasks")); + testUrls.add(new TestUrl("ssh://git.assembla.com/git-plugin.bin.git", null, "git-plugin.bin")); + + + + for (TestUrl testUrl : testUrls) { + String repoName = data.getRepoName(testUrl.remoteUrl); + String orgName = data.getOrganizationName(testUrl.remoteUrl); + + assertEquals("Repo name mismatch for URL: " + testUrl.remoteUrl, testUrl.expectedRepoName, repoName); + assertEquals("Org name mismatch for URL: " + testUrl.remoteUrl, testUrl.expectedOrgName, orgName); + + } + } + + // Helper class to hold test URLs and expected results + private static class TestUrl { + String remoteUrl; + String expectedOrgName; + String expectedRepoName; + + TestUrl(String remoteUrl, String expectedOrgName, String expectedRepoName) { + this.remoteUrl = remoteUrl; + this.expectedOrgName = expectedOrgName; + this.expectedRepoName = expectedRepoName; + } + } + + } diff --git a/src/test/java/jenkins/plugins/git/GitSCMJCasCCompatibilityTest.java b/src/test/java/jenkins/plugins/git/GitSCMJCasCCompatibilityTest.java index 1b84d2f82a..441e688416 100644 --- a/src/test/java/jenkins/plugins/git/GitSCMJCasCCompatibilityTest.java +++ b/src/test/java/jenkins/plugins/git/GitSCMJCasCCompatibilityTest.java @@ -14,6 +14,9 @@ protected void assertConfiguredAsExpected(RestartableJenkinsRule restartableJenk GitSCM.DescriptorImpl gitSCM = (GitSCM.DescriptorImpl) restartableJenkinsRule.j.jenkins.getScm(GitSCM.class.getSimpleName()); assertEquals("user_name", gitSCM.getGlobalConfigName()); assertEquals("me@mail.com", gitSCM.getGlobalConfigEmail()); + assertEquals("Global URL RegEx not configured correctly", + "(.*github.*?[/:](?[^/]+)/(?[^/]+?)(?:\\.git)?$)", + gitSCM.getGlobalUrlRegEx()); assertTrue("Allow second fetch setting not honored", gitSCM.isAllowSecondFetch()); assertTrue("Show entire commit summary setting not honored", gitSCM.isShowEntireCommitSummaryInChanges()); assertTrue("Hide credentials setting not honored", gitSCM.isHideCredentials()); diff --git a/src/test/resources/jenkins/plugins/git/gitscm-casc.yaml b/src/test/resources/jenkins/plugins/git/gitscm-casc.yaml index 7120c21f9a..f01320ce63 100644 --- a/src/test/resources/jenkins/plugins/git/gitscm-casc.yaml +++ b/src/test/resources/jenkins/plugins/git/gitscm-casc.yaml @@ -8,3 +8,4 @@ unclassified: addGitTagAction: true showEntireCommitSummaryInChanges: true useExistingAccountWithSameEmail: false + globalUrlRegEx: "(.*github.*?[/:](?[^/]+)/(?[^/]+?)(?:\\.git)?$)"