From 20c22417c8f9ec7fc136c589429aa4b6815e4ffa Mon Sep 17 00:00:00 2001 From: Akash Manna Date: Mon, 29 Jun 2026 19:55:50 +0530 Subject: [PATCH] Use of Global Libraries breaks commit URLs --- .../forensics/miner/ForensicsViewModel.java | 2 +- .../util/CommitDecoratorFactory.java | 24 +++++++++++++ .../miner/ForensicsViewModelTest.java | 36 ++++++++++++++++++- 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/forensics/miner/ForensicsViewModel.java b/src/main/java/io/jenkins/plugins/forensics/miner/ForensicsViewModel.java index fab2ca02..2367dc37 100644 --- a/src/main/java/io/jenkins/plugins/forensics/miner/ForensicsViewModel.java +++ b/src/main/java/io/jenkins/plugins/forensics/miner/ForensicsViewModel.java @@ -100,7 +100,7 @@ public String getCommitsModel() { @SuppressWarnings("unused") //called by jelly view public Object getDynamic(final String link, final StaplerRequest2 request, final StaplerResponse2 response) { try { - CommitDecorator decorator = CommitDecoratorFactory.findCommitDecorator(owner); + CommitDecorator decorator = CommitDecoratorFactory.findCommitDecorator(owner, scmKey); return new FileDetailsView(owner, link, repositoryStatistics, decorator); } diff --git a/src/main/java/io/jenkins/plugins/forensics/util/CommitDecoratorFactory.java b/src/main/java/io/jenkins/plugins/forensics/util/CommitDecoratorFactory.java index 7f2ba874..adbab95f 100644 --- a/src/main/java/io/jenkins/plugins/forensics/util/CommitDecoratorFactory.java +++ b/src/main/java/io/jenkins/plugins/forensics/util/CommitDecoratorFactory.java @@ -1,5 +1,6 @@ package io.jenkins.plugins.forensics.util; +import java.util.Collection; import java.util.List; import java.util.Optional; @@ -63,6 +64,29 @@ public static CommitDecorator findCommitDecorator(final Run run) { return findCommitDecorator(new ScmResolver().getScm(run), new FilteredLog("ignored")); } + /** + * Returns a commit decorator for the specified {@link Run build} and SCM key. The SCM key is used to identify the + * correct SCM in case multiple SCMs are used (e.g., when Global Libraries are configured). This prevents using the + * wrong SCM's commit URLs (e.g., the Global Library's repository URLs instead of the actual project's URLs). + * + * @param run + * the current build + * @param scmKey + * the key of the SCM to use (a substring of the full SCM key) + * + * @return a commit decorator for the matching SCM of the specified build or a {@link NullDecorator} if no matching + * SCM is found, the SCM is not supported, or the SCM does not provide a {@link RepositoryBrowser} + * implementation + */ + public static CommitDecorator findCommitDecorator(final Run run, final String scmKey) { + var logger = new FilteredLog("ignored"); + Collection matchingScms = new ScmResolver().getScms(run, scmKey); + if (matchingScms.isEmpty()) { + return findCommitDecorator(new ScmResolver().getScm(run), logger); + } + return findCommitDecorator(matchingScms.iterator().next(), logger); + } + private static List findAllExtensions() { return new JenkinsFacade().getExtensionsFor(CommitDecoratorFactory.class); } diff --git a/src/test/java/io/jenkins/plugins/forensics/miner/ForensicsViewModelTest.java b/src/test/java/io/jenkins/plugins/forensics/miner/ForensicsViewModelTest.java index 069b0ec9..d83d920d 100644 --- a/src/test/java/io/jenkins/plugins/forensics/miner/ForensicsViewModelTest.java +++ b/src/test/java/io/jenkins/plugins/forensics/miner/ForensicsViewModelTest.java @@ -15,10 +15,12 @@ import hudson.model.Run; import io.jenkins.plugins.forensics.miner.FileStatistics.FileStatisticsBuilder; +import io.jenkins.plugins.forensics.util.CommitDecorator; import io.jenkins.plugins.forensics.util.CommitDecorator.NullDecorator; import io.jenkins.plugins.forensics.util.CommitDecoratorFactory; import static io.jenkins.plugins.forensics.assertions.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; /** @@ -105,10 +107,42 @@ void shouldThrowIOExceptionInGetDynamic() throws IOException { }); } + /** + * Verifies that {@link ForensicsViewModel#getDynamic} uses the stored {@code scmKey} when resolving the + * commit decorator. This prevents Global Libraries from hijacking commit URLs by ensuring only the + * SCM matching the stored key is consulted for link generation. + * + * @see Issue #682 + */ + @Test + void shouldUseScmKeyToResolveCorrectCommitDecoratorForGlobalLibraries() { + var repositoryStatistics = new RepositoryStatistics(); + var fileStatistics = new FileStatisticsBuilder().build(FILE_NAME); + repositoryStatistics.add(fileStatistics); + + Run owner = mock(Run.class); + var model = new ForensicsViewModel(owner, repositoryStatistics, SCM_KEY); + + CommitDecorator expectedDecorator = mock(CommitDecorator.class); + when(expectedDecorator.asLink(anyString())).thenReturn("abc"); + + try (MockedStatic commitDecoratorFactory = mockStatic(CommitDecoratorFactory.class)) { + commitDecoratorFactory + .when(() -> CommitDecoratorFactory.findCommitDecorator(eq(owner), eq(SCM_KEY))) + .thenReturn(expectedDecorator); + + Object result = model.getDynamic(createLink(), mock(StaplerRequest2.class), mock(StaplerResponse2.class)); + + assertThat(result).isInstanceOf(FileDetailsView.class); + commitDecoratorFactory.verify(() -> CommitDecoratorFactory.findCommitDecorator(eq(owner), eq(SCM_KEY))); + commitDecoratorFactory.verify(() -> CommitDecoratorFactory.findCommitDecorator(any(Run.class)), never()); + } + } + private void runWithNullDecorator(final ForensicsViewModel model, final Consumer modelConsumer) { try (MockedStatic commitDecoratorFactory = mockStatic(CommitDecoratorFactory.class)) { commitDecoratorFactory - .when(() -> CommitDecoratorFactory.findCommitDecorator(any(Run.class))) + .when(() -> CommitDecoratorFactory.findCommitDecorator(any(Run.class), anyString())) .thenReturn(new NullDecorator()); modelConsumer.accept(model);