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
11 changes: 7 additions & 4 deletions src/main/java/hudson/plugins/git/GitSCM.java
Original file line number Diff line number Diff line change
Expand Up @@ -2007,13 +2007,16 @@ public BuildData copyBuildData(Run build) {
BuildData base = getBuildData(build);
ScmName sn = getExtensions().get(ScmName.class);
String scmName = sn == null ? null : sn.getName();
if (base==null)
return new BuildData(scmName, getUserRemoteConfigs());
else {
BuildData buildData = base.clone();
BuildData buildData;
if (base==null) {
buildData = new BuildData(scmName, getUserRemoteConfigs());
} else {
buildData = base.clone();
buildData.setScmName(scmName);
return buildData;
Comment on lines +2014 to 2016
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical bug: The browser is only set when base is null (line 2018), but when base is not null and gets cloned (lines 2014-2016), the method returns early without setting the browser. This means that for all builds after the first one, the browser will not be persisted in the BuildData, and the clickable links feature will not work.

The setBrowser call on line 2018 should be moved before the early return on line 2016, or the return statement should be removed and the setBrowser call should be placed after the if-else block to ensure the browser is always set on the BuildData before returning.

Suggested change
buildData = base.clone();
buildData.setScmName(scmName);
return buildData;
buildData = base.clone();
buildData.setScmName(scmName);

Copilot uses AI. Check for mistakes.
}
buildData.setBrowser(getBrowser());
return buildData;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
return url;
}

public final URL getUrl() throws IOException {
public URL getUrl() throws IOException {
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getUrl() method visibility was changed from 'final' to non-final. This is a breaking API change that allows subclasses to override this method. While this change appears intentional to allow the StubGitRepositoryBrowser test implementation to override it, there's no clear justification in the PR description for why this API change is necessary.

The existing implementation already allows configuration through the constructor, and the test stub could work by passing the baseUrl to the super constructor instead of overriding getUrl(). Consider whether this breaking change is truly necessary, or if the test can be refactored to avoid it.

Suggested change
public URL getUrl() throws IOException {
public final URL getUrl() throws IOException {

Copilot uses AI. Check for mistakes.
String u = url;
StaplerRequest2 req = Stapler.getCurrentRequest2();
if (req != null) {
Expand Down Expand Up @@ -107,6 +107,17 @@
return null;
}

/**
* Determines the link to the given ref.
*
* @param ref The specific ref to link to
* @return null if the browser doesn't have any meaningful URL for a ref
*/
@CheckForNull
public URL getRefLink(String ref) throws IOException {
return null;

Check warning on line 118 in src/main/java/hudson/plugins/git/browser/GitRepositoryBrowser.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 118 is not covered by tests
}

/**
* Determines whether a URL should be normalized
* Overridden in the rare case where it shouldn't
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/hudson/plugins/git/browser/GithubWeb.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@
return new URL(url, url.getPath()+"commit/" + changeSet.getId());
}

/**
* Determines the link to the given ref.
* @param ref The specific ref to link to
* @return null if the browser doesn't have any meaningful URL for a ref
*/
@Override
public URL getRefLink(String ref) throws IOException {
URL url = getUrl();
return new URL(url, url.getPath() + "tree/" + ref);

Check warning on line 47 in src/main/java/hudson/plugins/git/browser/GithubWeb.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 46-47 are not covered by tests
}

/**
* Creates a link to the file diff.
* http://[GitHib URL]/commit/573670a3bb1f3b939e87f1dee3e99b6bfe281fcb#diff-N
Expand Down
76 changes: 73 additions & 3 deletions src/main/java/hudson/plugins/git/util/BuildData.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
import hudson.plugins.git.Branch;
import hudson.plugins.git.Revision;
import hudson.plugins.git.UserRemoteConfig;
import hudson.plugins.git.browser.GitRepositoryBrowser;

import java.io.IOException;
import java.io.Serial;
import java.io.Serializable;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
Expand Down Expand Up @@ -78,7 +81,14 @@
public Set<String> remoteUrls = new HashSet<>();

/**
* Allow disambiguation of the action url when multiple {@link BuildData} actions present.
* The Git console browser (if any) used to link to the repository.
*/
@CheckForNull
public GitRepositoryBrowser browser;
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The newly added 'browser' field is declared as public, unlike other public fields in this class (buildsByBranchName, lastBuild, scmName, remoteUrls) which are annotated with @SuppressFBWarnings for "PA_PUBLIC_PRIMITIVE_ATTRIBUTE". While browser is not a primitive type, having a public mutable field is generally discouraged for API design.

Consider making this field private and only exposing it through the getBrowser() and setBrowser() methods, similar to how the 'index' field is handled. This would be more consistent with modern Java best practices and prevent external modification of the browser reference.

Suggested change
public GitRepositoryBrowser browser;
private GitRepositoryBrowser browser;

Copilot uses AI. Check for mistakes.

/**
* Allow disambiguation of the action url when multiple {@link BuildData}
* actions present.
*/
@CheckForNull
private Integer index;
Expand Down Expand Up @@ -288,6 +298,65 @@
remoteUrls.add(remoteUrl);
}

public void setBrowser(GitRepositoryBrowser browser) {
this.browser = browser;

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

View check run for this annotation

ci.jenkins.io / SpotBugs

PA_PUBLIC_PRIMITIVE_ATTRIBUTE

NORMAL: Primitive field hudson.plugins.git.util.BuildData.browser is public and set from inside the class, which makes it too exposed. Consider making it private to limit external accessibility.
Raw output
no message found
}

public GitRepositoryBrowser getBrowser() {
return browser;
}

public String getCommitUrl(String commitId) {
if (browser == null || commitId == null) {

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 310 is only partially covered, 2 branches are missing
return null;

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 311 is not covered by tests
}
try {
URL url = browser.getChangeSetLink(commitId);
if (url == null) {

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 315 is only partially covered, one branch is missing
return null;

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 316 is not covered by tests
}
return url.toExternalForm().replace(".git/commit/", "/commit/");
} catch (IOException e) {
return null;

Check warning on line 320 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 319-320 are not covered by tests
}
}

public String getRefUrl(String ref) {
if (browser == null || ref == null) {

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 325 is only partially covered, 2 branches are missing
return null;

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 326 is not covered by tests
}
// Normalization
if (ref.startsWith("refs/remotes/")) {
String[] parts = ref.split("/", 4);
if (parts.length == 4) {

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 331 is only partially covered, one branch is missing
ref = parts[3];
}
} else if (ref.startsWith("refs/heads/")) {

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 334 is only partially covered, one branch is missing
Comment on lines +329 to +334
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ref normalization logic assumes that refs starting with "refs/remotes/" will always have exactly 4 parts when split by "/" (line 330-332). However, if a ref like "refs/remotes/origin" (only 3 parts) is passed, the normalization will be silently skipped and the full ref will be passed to the browser.

Consider handling this edge case more explicitly, either by:

  1. Logging a warning when the split doesn't produce the expected number of parts
  2. Falling back to a safe default normalization
  3. Documenting the expected ref format in a comment

Similarly, what happens if someone has a branch name containing slashes, like "refs/remotes/origin/feature/sub-feature"? The current logic would only strip "refs/remotes/origin/" leaving "feature/sub-feature", which might be correct, but should be documented.

Suggested change
if (ref.startsWith("refs/remotes/")) {
String[] parts = ref.split("/", 4);
if (parts.length == 4) {
ref = parts[3];
}
} else if (ref.startsWith("refs/heads/")) {
if (ref.startsWith("refs/remotes/")) {
// Expected format: refs/remotes/<remote>/<branch-path>
// where <branch-path> may itself contain '/' (e.g. feature/sub-feature).
// We strip the "refs/remotes/<remote>/" prefix and keep the branch path.
String[] parts = ref.split("/", 4);
if (parts.length == 4) {
ref = parts[3];
} else {
// Unexpected remote ref format; skip normalization but log for diagnostics.
Logger.getLogger(BuildData.class.getName()).log(
Level.FINE,
"Unexpected remote ref format ''{0}''; skipping normalization",
ref
);
}
} else if (ref.startsWith("refs/heads/")) {
// Expected format: refs/heads/<branch-path>; strip the "refs/heads/" prefix.

Copilot uses AI. Check for mistakes.
ref = ref.substring(11);
}
Comment on lines +328 to +336
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ref normalization logic only handles 'refs/remotes/' and 'refs/heads/' prefixes, but Git has other ref types like 'refs/tags/', 'refs/pull/', etc. If a tag or pull request ref is passed, it will be passed to the browser as-is with the full ref path.

Consider whether this is intentional behavior, or if other ref types should also be normalized. At minimum, add a comment documenting which ref types are normalized and why others are left as-is.

Copilot uses AI. Check for mistakes.
try {
URL url = browser.getRefLink(ref);
if (url == null) {

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 339 is only partially covered, one branch is missing
return null;

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 340 is not covered by tests
}
return url.toExternalForm().replace(".git/tree/", "/tree/");
} catch (IOException e) {
return null;

Check warning on line 344 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 343-344 are not covered by tests
}
}

public String getRepositoryUrl() {
if (browser == null) {

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 349 is only partially covered, one branch is missing
return null;

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 350 is not covered by tests
}
try {
URL url = browser.getUrl();
return url == null ? null : url.toExternalForm();

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

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 354 is only partially covered, one branch is missing
} catch (IOException e) {
return null;

Check warning on line 356 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 355-356 are not covered by tests
}
}
Comment on lines +309 to +358
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The URL strings returned by getCommitUrl, getRefUrl, and getRepositoryUrl are used directly in href attributes in the Jelly template without any explicit XSS protection. While Jelly's escape-by-default='true' at line 1 of summary.jelly should provide protection, the URL generation methods perform string replacements (e.g., replace(".git/commit/", "/commit/")) on URL strings.

If a malicious repository browser implementation returns a URL containing special characters or JavaScript, the string replacement could potentially create an XSS vulnerability. Consider validating that the returned URLs have safe schemes (http/https) and don't contain JavaScript or other dangerous protocols.

Copilot uses AI. Check for mistakes.
Comment on lines +309 to +358
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Javadoc: The public methods getCommitUrl, getRefUrl, and getRepositoryUrl lack Javadoc comments. Since these are new public API methods that will be called from Jelly templates and potentially from other code, they should have proper documentation explaining:

  1. What they return
  2. When they return null
  3. What the parameters represent
  4. Whether the returned URLs are sanitized/normalized

For example, getCommitUrl should document that it returns null if browser is null, if commitId is null, if browser.getChangeSetLink throws IOException, or if the browser returns null.

Copilot uses AI. Check for mistakes.

@Exported
public Set<String> getRemoteUrls() {
return remoteUrls;
Expand Down Expand Up @@ -428,12 +497,13 @@

return Objects.equals(remoteUrls, that.remoteUrls)
&& Objects.equals(buildsByBranchName, that.buildsByBranchName)
&& Objects.equals(lastBuild, that.lastBuild);
&& Objects.equals(lastBuild, that.lastBuild)
&& Objects.equals(browser, that.browser);
}

@Override
public int hashCode() {
return Objects.hash(remoteUrls, buildsByBranchName, lastBuild);
return Objects.hash(remoteUrls, buildsByBranchName, lastBuild, browser);
}

/* Package protected for easier testing */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,48 @@
xmlns:f="/lib/form" xmlns:i="jelly:fmt">
<t:summary icon="symbol-git-logo plugin-git">

<strong>${%Revision}</strong>: ${it.lastBuiltRevision.sha1.name()}
<j:set var="browser" value="${it.browser}"/>
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable: The 'browser' variable is set on line 7 but never used in the template. The code directly accesses 'it.browser' through the method calls (getCommitUrl, getRefUrl, getRepositoryUrl), so this variable declaration is unnecessary and can be removed.

Suggested change
<j:set var="browser" value="${it.browser}"/>

Copilot uses AI. Check for mistakes.
<j:set var="revisionLink" value="${it.getCommitUrl(it.lastBuiltRevision.sha1.name())}"/>
<j:choose>
<j:when test="${revisionLink!=null}">
<strong>${%Revision}</strong>: <a href="${revisionLink}">${it.lastBuiltRevision.sha1.name()}</a>
</j:when>
<j:otherwise>
<strong>${%Revision}</strong>: ${it.lastBuiltRevision.sha1.name()}
</j:otherwise>
</j:choose>

<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:set var="repoLink" value="${it.repositoryUrl}"/>
<j:choose>
<j:when test="${repoLink!=null}">
<br/><strong>${%Repository}</strong>: <a href="${repoLink}">${remoteUrl}</a>
</j:when>
<j:when test="${remoteUrl.startsWith('http')}">
<br/><strong>${%Repository}</strong>: <a href="${remoteUrl}">${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:set var="branchLink" value="${it.getRefUrl(branch.name)}"/>
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation: This line uses 12 spaces for indentation while the surrounding choose/when/otherwise blocks use 16 spaces (lines 10-15, 23-31, 40-45). This should be indented with 16 spaces to match the other j:set statements at the same nesting level.

Suggested change
<j:set var="branchLink" value="${it.getRefUrl(branch.name)}"/>
<j:set var="branchLink" value="${it.getRefUrl(branch.name)}"/>

Copilot uses AI. Check for mistakes.
<j:choose>
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation: This line uses 20 spaces for indentation while it should use 16 spaces to match the indentation of the j:when and j:otherwise tags at lines 40 and 43.

Suggested change
<j:choose>
<j:choose>

Copilot uses AI. Check for mistakes.
<j:when test="${branchLink!=null}">
<li><a href="${branchLink}">${branch.name}</a></li>
</j:when>
<j:otherwise>
<li>${branch.name}</li>
</j:otherwise>
</j:choose>
</j:if>
</j:forEach>
</ul>


</t:summary>
</j:jelly>
18 changes: 18 additions & 0 deletions src/test/java/hudson/plugins/git/util/BuildDataBrowserTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package hudson.plugins.git.util;

import hudson.plugins.git.browser.GithubWeb;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class BuildDataBrowserTest {

@Test
public void testBrowserPersistence() {
BuildData data = new BuildData();
assertNull(data.getBrowser());

GithubWeb browser = new GithubWeb("https://github.com/jenkinsci/git-plugin");
data.setBrowser(browser);
assertEquals(browser, data.getBrowser());
}
}
93 changes: 92 additions & 1 deletion src/test/java/hudson/plugins/git/util/BuildDataTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
import hudson.plugins.git.Branch;
import hudson.plugins.git.Revision;
import hudson.plugins.git.UserRemoteConfig;
import hudson.plugins.git.GitChangeSet;
import hudson.plugins.git.browser.GitRepositoryBrowser;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Random;
Expand Down Expand Up @@ -576,4 +580,91 @@ void testHashCodeEmptyData() {
emptyData.remoteUrls = null;
assertEquals(emptyData.hashCode(), emptyData.hashCode());
}
}

@Test
void testSshRepositoryLink() throws IOException {
BuildData buildData = new BuildData();
// Add an SSH remote URL to simulate JENKINS-75285 scenario
buildData.addRemoteUrl("git@github.com:user/repo.git");

// Configure a browser that returns a valid HTTP URL
buildData.setBrowser(new StubGitRepositoryBrowser("https://github.com/user/repo/"));

// Verify getRepositoryUrl returns the HTTP URL despite the SSH remote
assertEquals("https://github.com/user/repo/", buildData.getRepositoryUrl());
}

@Test
void testCommitUrlSanitization() throws IOException {
BuildData buildData = new BuildData();
buildData.setBrowser(new StubGitRepositoryBrowser("https://github.com/user/repo/"));

// Stub returns URL with .git, verify getCommitUrl strips it
assertEquals("https://github.com/user/repo/commit/123456", buildData.getCommitUrl("123456"));
}

@Test
void testRefUrlNormalization() throws IOException {
BuildData buildData = new BuildData();
buildData.setBrowser(new StubGitRepositoryBrowser("https://github.com/user/repo/"));

// Test refs/remotes/origin/main -> main
assertEquals("https://github.com/user/repo/tree/main", buildData.getRefUrl("refs/remotes/origin/main"));

// Test refs/heads/feature -> feature
assertEquals("https://github.com/user/repo/tree/feature", buildData.getRefUrl("refs/heads/feature"));
}
Comment on lines +584 to +616
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage for null/error cases: The tests only cover the happy path where the browser returns valid URLs. There are no tests for:

  1. What happens when browser is null (should return null)
  2. What happens when commitId/ref is null (should return null)
  3. What happens when browser.getChangeSetLink() throws IOException (should return null)
  4. What happens when browser.getRefLink() returns null (should return null)

These null-safety behaviors are important for the Jelly template's conditional rendering. Consider adding test cases to verify these edge cases work as expected.

Copilot uses AI. Check for mistakes.

/**
* Stub implementation of GitRepositoryBrowser for testing purposes.
*/
private static class StubGitRepositoryBrowser extends GitRepositoryBrowser {
private static final long serialVersionUID = 1L;
private final String baseUrl;

StubGitRepositoryBrowser(String baseUrl) {
this.baseUrl = baseUrl;
}

@Override
public URL getUrl() throws IOException {
return new URL(baseUrl);
}
Comment on lines +626 to +632
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The StubGitRepositoryBrowser constructor doesn't call super(baseUrl), so the parent class's 'url' field is never initialized. This means getRepoUrl() would return null and the base class's getUrl() would fail if called. While the stub overrides getUrl() to return the baseUrl directly, this creates an inconsistent state.

For proper testing, the constructor should call super(baseUrl) instead of storing baseUrl in a separate field, or the constructor should be changed to:

StubGitRepositoryBrowser(String baseUrl) {
    super(baseUrl);
    this.baseUrl = baseUrl;
}

This ensures the base class is properly initialized even if it's not strictly necessary for these tests.

Suggested change
this.baseUrl = baseUrl;
}
@Override
public URL getUrl() throws IOException {
return new URL(baseUrl);
}
super(baseUrl);
this.baseUrl = baseUrl;
}
@Override
public URL getUrl() throws IOException {
return new URL(baseUrl);

Copilot uses AI. Check for mistakes.

// Standard abstract method implementation
@Override
public URL getChangeSetLink(GitChangeSet changeSet) throws IOException {
return new URL(baseUrl.replaceAll("/$", "") + ".git/commit/" + changeSet.getId());
}

// Standard abstract method implementation
@Override
public URL getDiffLink(GitChangeSet.Path path) throws IOException {
return null;
}

// Standard abstract method implementation
@Override
public URL getFileLink(GitChangeSet.Path path) throws IOException {
return null;
}

// Custom method for BuildData (removed @Override to avoid errors if not in base class)
public URL getChangeSetLink(String changeSetId) throws IOException {
return new URL(baseUrl.replaceAll("/$", "") + ".git/commit/" + changeSetId);
}

// Custom method for BuildData (removed @Override to avoid errors if not in base class)
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing @OverRide annotation. The method getRefLink is overriding the base class method from GitRepositoryBrowser (added in this same PR at line 117). While the comment on line 652 explains why @OverRide was removed, now that the base class has this method, the @OverRide annotation should be added for consistency and to catch signature changes.

Suggested change
// Custom method for BuildData (removed @Override to avoid errors if not in base class)
// Custom method for BuildData
@Override

Copilot uses AI. Check for mistakes.
Comment on lines +653 to +657
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getChangeSetLink(String) method at line 653 doesn't call the superclass method. The base class GitRepositoryBrowser.getChangeSetLink(String) at line 103-108 checks if the commitId is null or blank before delegating to getChangeSetLink(GitChangeSet). This stub bypasses that null/blank check and creates a different implementation pattern.

While this works for testing, it would be better to add @OverRide and ensure the stub properly extends the base class behavior, or remove this method entirely and rely on the base class implementation which will call getChangeSetLink(GitChangeSet) anyway.

Suggested change
public URL getChangeSetLink(String changeSetId) throws IOException {
return new URL(baseUrl.replaceAll("/$", "") + ".git/commit/" + changeSetId);
}
// Custom method for BuildData (removed @Override to avoid errors if not in base class)

Copilot uses AI. Check for mistakes.
public URL getRefLink(String ref) throws IOException {
return new URL(baseUrl.replaceAll("/$", "") + ".git/tree/" + ref);
}
Comment on lines +621 to +660
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test stub intentionally adds .git to URLs (lines 637, 654, 659) to test sanitization, but this creates misleading tests because the actual GitRepositoryBrowser implementations (like GithubWeb) don't add .git to their URLs. The sanitization logic in BuildData (lines 318, 342) is therefore removing something that real browsers never add in the first place.

This makes the tests not representative of actual browser behavior. Consider either:

  1. Verifying that real GitRepositoryBrowser implementations actually need this .git sanitization, or
  2. Removing the .git sanitization logic if it's not needed for real browsers

If the sanitization is genuinely needed for some browsers, the test should document which browsers add .git and why this is necessary.

Copilot uses AI. Check for mistakes.

public URL getDiffLink(String path, String changeSetId) throws IOException {
return null;
}

public URL getFileLink(String path) throws IOException {
return null;
}
}
}
Loading