Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
136 changes: 124 additions & 12 deletions src/main/java/hudson/plugins/git/util/BuildData.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,18 @@

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.model.AbstractBuild;
import hudson.model.Action;
import hudson.model.Api;
import hudson.model.Run;
import hudson.model.*;
import hudson.plugins.git.Branch;
import hudson.plugins.git.GitSCM;
import hudson.plugins.git.Revision;
import hudson.plugins.git.UserRemoteConfig;

import java.io.Serial;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

import hudson.plugins.git.browser.GitRepositoryBrowser;
import org.eclipse.jgit.lib.ObjectId;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
Expand Down Expand Up @@ -438,4 +433,121 @@

/* Package protected for easier testing */
static final Logger LOGGER = Logger.getLogger(BuildData.class.getName());
}


@Restricted(NoExternalUse.class)
@CheckForNull
public String getRepositoryBrowserUrl(@CheckForNull String remoteUrl) {
if (remoteUrl == null || remoteUrl.isEmpty()) {
return null;
}

// If already HTTP(S), it's clickable as-is
if (remoteUrl.startsWith("http://") || remoteUrl.startsWith("https://")) {
return remoteUrl;
}

// For SSH URLs, try to get browser URL
Run<?, ?> run = getOwningRun();
if (run == null) {
return null;
}

GitSCM gitScm = getGitSCM(run);
if (gitScm == null || gitScm.getBrowser() == null) {
return null;
}

// Check if this remoteUrl is configured in the SCM
List<UserRemoteConfig> remoteConfigs = gitScm.getUserRemoteConfigs();
if (remoteConfigs == null || remoteConfigs.isEmpty()) {
return null;
}

String normalizedRemoteUrl = normalize(remoteUrl);

boolean isConfiguredRemote = false;
for (UserRemoteConfig config : remoteConfigs) {
String configUrl = config.getUrl();
if (configUrl == null) {
continue;
}

// Direct match
if (configUrl.equals(remoteUrl)) {
isConfiguredRemote = true;
break;
}

// Normalized match (only if both normalize successfully)
if (normalizedRemoteUrl != null) {
String normalizedConfigUrl = normalize(configUrl);
if (normalizedConfigUrl != null && normalizedConfigUrl.equals(normalizedRemoteUrl)) {
isConfiguredRemote = true;
break;
}
}
}

if (!isConfiguredRemote) {
return null;
}

GitRepositoryBrowser browser = gitScm.getBrowser();
if (browser == null) {
return null;
}

String browserUrl = browser.getRepoUrl();
if (browserUrl != null && !browserUrl.isEmpty() &&
(browserUrl.startsWith("http://") || browserUrl.startsWith("https://"))) {
return browserUrl;
}

return null;
}

/**
* Get the GitSCM instance from the run.
* Supports both AbstractBuild (Freestyle) and WorkflowRun (Pipeline) jobs.
*
* @param run the current run
* @return GitSCM instance or null if not found
*/
@CheckForNull
private GitSCM getGitSCM(@CheckForNull Run<?, ?> run) {
if (run == null) {
return null;
}

// Try AbstractBuild (Freestyle jobs)
if (run instanceof AbstractBuild) {
AbstractBuild<?, ?> build = (AbstractBuild<?, ?>) run;
AbstractProject<?, ?> project = build.getProject();
if (project != null && project.getScm() instanceof GitSCM) {
return (GitSCM) project.getScm();
}
}

// Try WorkflowRun (Pipeline jobs) - parent is always non-null for Run
Object parent = run.getParent();

// Use reflection to check for getSCM() method
try {
java.lang.reflect.Method getScmMethod = parent.getClass().getMethod("getScm");
Object scm = getScmMethod.invoke(parent);
if (scm instanceof GitSCM) {
return (GitSCM) scm;
}
} catch (NoSuchMethodException e) {
LOGGER.log(Level.FINEST, "getSCM() method not found", e);
} catch (IllegalAccessException e) {
LOGGER.log(Level.FINEST, "Could not invoke getSCM() method", e);
} catch (InvocationTargetException e) {
LOGGER.log(Level.FINEST, "Error invoking getSCM() method", e);
}

return null;

Check warning on line 550 in src/main/java/hudson/plugins/git/util/BuildData.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 441-550 are not covered by tests
}

}
57 changes: 30 additions & 27 deletions src/main/resources/hudson/plugins/git/util/BuildData/index.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,38 @@
<j:if test="${build!=null}">
<st:include page="sidepanel" it="${build}" optional="true"/>
</j:if>
<l:main-panel>
<h1>${%Git Build Data}</h1>
<l:main-panel>
<h1>${%Git Build Data}</h1>

<strong>${%Revision}</strong>: ${it.lastBuild.SHA1.name()}
<j:if test="${!empty(it.scmName)}"><br/><strong>${%SCM}</strong>: ${it.scmName}</j:if>
<j:if test="${!empty(it.remoteUrls)}">
<j:forEach var="remoteUrl" items="${it.remoteUrls}">
<j:if test="${remoteUrl.startsWith('http')}">
<br/><strong>${%Repository}</strong>: <a href="${remoteUrl}">${remoteUrl}</a>
<strong>${%Revision}</strong>: ${it.lastBuild.SHA1.name()}
<j:if test="${!empty(it.scmName)}"><br/><strong>${%SCM}</strong>: ${it.scmName}</j:if>
<j:if test="${!empty(it.remoteUrls)}">
<j:forEach var="remoteUrl" items="${it.remoteUrls}">
<j:set var="browserUrl" value="${it.getRepositoryBrowserUrl(remoteUrl)}"/>
<j:choose>
<j:when test="${browserUrl != null and !browserUrl.isEmpty()}">
<br/><strong>${%Repository}</strong>: <a href="${browserUrl}">${remoteUrl}</a>
</j:when>
<j:otherwise>
<br/><strong>${%Repository}</strong>: ${remoteUrl}
</j:otherwise>
</j:choose>
</j:forEach>
</j:if>
<j:if test="${!remoteUrl.startsWith('http')}">
<br/><strong>${%Repository}</strong>: ${remoteUrl}
</j:if>
</j:forEach>
</j:if>
<ul>
<j:forEach var="branch" items="${it.lastBuild.revision.branches}">
<li>${branch.name}</li>
</j:forEach>
</ul>
<ul>
<j:forEach var="branch" items="${it.lastBuild.revision.branches}">
<li>${branch.name}</li>
</j:forEach>
</ul>

<h2>${%Built Branches}</h2>
<ul>
<h2>${%Built Branches}</h2>
<ul>

<j:forEach var="branch" items="${it.buildsByBranchName.keySet()}">
<li><j:choose><j:when test="${branch}==''}">(unnamed)</j:when><j:otherwise>${branch}</j:otherwise></j:choose>: ${it.buildsByBranchName.get(branch).toString()}</li>
</j:forEach>
</ul>
<j:forEach var="branch" items="${it.buildsByBranchName.keySet()}">
<li><j:choose><j:when test="${branch}==''}">(unnamed)</j:when><j:otherwise>${branch}</j:otherwise></j:choose>: ${it.buildsByBranchName.get(branch).toString()}</li>
</j:forEach>
</ul>

</l:main-panel>
</l:layout>
</j:jelly>
</l:main-panel>
</l:layout>
</j:jelly>
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler"
xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson"
xmlns:f="/lib/form" xmlns:i="jelly:fmt">
xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson"
xmlns:f="/lib/form" xmlns:i="jelly:fmt">
<t:summary icon="symbol-git-logo plugin-git">

<strong>${%Revision}</strong>: ${it.lastBuiltRevision.sha1.name()}
<j:if test="${!empty(it.scmName)}"><br/><strong>${%SCM}</strong>: ${it.scmName}</j:if>
<j:if test="${!empty(it.remoteUrls)}">
<j:forEach var="remoteUrl" items="${it.remoteUrls}">
<j:if test="${remoteUrl.startsWith('http')}">
<br/><strong>${%Repository}</strong>: <a href="${remoteUrl}">${remoteUrl}</a>
</j:if>
<j:if test="${!remoteUrl.startsWith('http')}">
<br/><strong>${%Repository}</strong>: ${remoteUrl}
</j:if>
</j:forEach>
</j:if>
<ul>
<j:forEach var="branch" items="${it.lastBuiltRevision.branches}">
<j:if test="${branch!=''}">
<li>${branch.name}</li>
</j:if>
</j:forEach>
</ul>


<strong>${%Revision}</strong>: ${it.lastBuiltRevision.sha1.name()}
<j:if test="${!empty(it.scmName)}"><br/><strong>${%SCM}</strong>: ${it.scmName}</j:if>
<j:if test="${!empty(it.remoteUrls)}">
<j:forEach var="remoteUrl" items="${it.remoteUrls}">
<j:set var="browserUrl" value="${it.getRepositoryBrowserUrl(remoteUrl)}"/>
<j:choose>
<j:when test="${browserUrl != null and !browserUrl.isEmpty()}">
<br/><strong>${%Repository}</strong>: <a href="${browserUrl}">${remoteUrl}</a>
</j:when>
<j:otherwise>
<br/><strong>${%Repository}</strong>: ${remoteUrl}
</j:otherwise>
</j:choose>
</j:forEach>
</j:if>
<ul>
<j:forEach var="branch" items="${it.lastBuiltRevision.branches}">
<j:if test="${branch!=''}">
<li>${branch.name}</li>
</j:if>
</j:forEach>
</ul>
</t:summary>
</j:jelly>
</j:jelly>
5 changes: 5 additions & 0 deletions src/test/java/hudson/plugins/git/util/BuildDataTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -576,4 +576,9 @@ void testHashCodeEmptyData() {
emptyData.remoteUrls = null;
assertEquals(emptyData.hashCode(), emptyData.hashCode());
}





}
Loading