Skip to content

Commit afd2a23

Browse files
authored
[JENKINS-75555] InvalidObjectIdException when "Scan Project Now" and multibranch-build-strategy plugin is installed (#1031)
Fix building of a GitSCM by BitbucketGitSCM in case of revision from a Pull Request web hook event that contains an abbreviate 7 chars of a SHA1 commit hash.
1 parent 76255b1 commit afd2a23

File tree

11 files changed

+297
-157
lines changed

11 files changed

+297
-157
lines changed

checkstyle.xml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
</module>
99

1010
<module name="FileTabCharacter" />
11-
<module name="NewlineAtEndOfFile">
12-
</module>
11+
1312
<module name="RegexpSingleline">
1413
<property name="format" value="\s+$" />
1514
<property name="message" value="Trailing spaces are not allowed." />

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketGitSCMBuilder.java

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
*/
2424
package com.cloudbees.jenkins.plugins.bitbucket;
2525

26+
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi;
27+
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketCommit;
2628
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketHref;
2729
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepository;
2830
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketRepositoryProtocol;
@@ -33,6 +35,7 @@
3335
import com.cloudbees.jenkins.plugins.bitbucket.impl.extension.FallbackToOtherRepositoryGitSCMExtension;
3436
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketApiUtils;
3537
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentials;
38+
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.SCMUtils;
3639
import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey;
3740
import com.cloudbees.plugins.credentials.Credentials;
3841
import com.cloudbees.plugins.credentials.common.IdCredentials;
@@ -43,7 +46,11 @@
4346
import hudson.plugins.git.GitSCM;
4447
import hudson.plugins.git.browser.BitbucketServer;
4548
import hudson.plugins.git.browser.BitbucketWeb;
49+
import java.io.IOException;
4650
import java.util.List;
51+
import java.util.logging.Level;
52+
import java.util.logging.Logger;
53+
import jenkins.plugins.git.AbstractGitSCMSource;
4754
import jenkins.plugins.git.GitSCMBuilder;
4855
import jenkins.plugins.git.MergeWithGitSCMExtension;
4956
import jenkins.scm.api.SCMHead;
@@ -59,6 +66,7 @@
5966
* @since 2.2.0
6067
*/
6168
public class BitbucketGitSCMBuilder extends GitSCMBuilder<BitbucketGitSCMBuilder> {
69+
private static final Logger logger = Logger.getLogger(BitbucketGitSCMBuilder.class.getName());
6270

6371
/**
6472
* The {@link BitbucketSCMSource} who's {@link BitbucketSCMSource#getOwner()} can be used as the context for
@@ -233,18 +241,18 @@ private void withPullRequestRemote(PullRequestSCMHead head, String headName) { /
233241
withRefSpec("+refs/heads/" + branchName + ":refs/remotes/@{remote}/" + branchName); // NOSONAR
234242
}
235243
if (cloneFromMirror) {
236-
PullRequestSCMRevision pullRequestSCMRevision = (PullRequestSCMRevision) revision;
244+
PullRequestSCMRevision prRevision = (PullRequestSCMRevision) revision;
237245
String primaryRemoteName = remoteName().equals("primary") ? "primary-primary" : "primary";
238246
String cloneLink = getCloneLink(primaryCloneLinks);
239247
List<BranchWithHash> branchWithHashes;
240248
if (checkoutStrategy == ChangeRequestCheckoutStrategy.MERGE) {
241249
branchWithHashes = List.of(
242-
new BranchWithHash(branchName, pullRequestSCMRevision.getPull().getHash()),
243-
new BranchWithHash(targetBranch, pullRequestSCMRevision.getTargetImpl().getHash())
250+
new BranchWithHash(branchName, SCMUtils.getHash(prRevision.getPull())),
251+
new BranchWithHash(targetBranch, SCMUtils.getHash(prRevision))
244252
);
245253
} else {
246254
branchWithHashes = List.of(
247-
new BranchWithHash(branchName, pullRequestSCMRevision.getPull().getHash())
255+
new BranchWithHash(branchName, SCMUtils.getHash(prRevision.getPull()))
248256
);
249257
}
250258
withExtension(new FallbackToOtherRepositoryGitSCMExtension(cloneLink, primaryRemoteName, branchWithHashes));
@@ -264,7 +272,7 @@ private void withPullRequestRemote(PullRequestSCMHead head, String headName) { /
264272
}
265273
}
266274
if (head.getCheckoutStrategy() == ChangeRequestCheckoutStrategy.MERGE) {
267-
String hash = revision instanceof PullRequestSCMRevision prRevision ? prRevision.getTargetImpl().getHash() : null;
275+
String hash = revision instanceof PullRequestSCMRevision prRevision ? SCMUtils.getHash(prRevision) : null;
268276
String refSpec = "+refs/heads/" + targetBranch + ":refs/remotes/@{remote}/" + targetBranch;
269277
if (!prFromTargetRepository && scmCloud) {
270278
String upstreamRemoteName = remoteName().equals("upstream") ? "upstream-upstream" : "upstream";
@@ -338,7 +346,8 @@ public GitSCM build() {
338346
if (head instanceof PullRequestSCMHead prHead) {
339347
withHead(new SCMHead(prHead.getBranchName()));
340348
if (rev instanceof PullRequestSCMRevision prRev) {
341-
withRevision(prRev.getPull());
349+
SCMRevision revision = resolvePullRequestRevision(prHead, prRev);
350+
withRevision(revision);
342351
}
343352
}
344353
return super.build();
@@ -347,4 +356,22 @@ public GitSCM build() {
347356
withRevision(rev);
348357
}
349358
}
359+
360+
@NonNull
361+
private SCMRevision resolvePullRequestRevision(PullRequestSCMHead prHead, PullRequestSCMRevision prRev) {
362+
SCMRevision revision = prRev.getPull();
363+
String hash = SCMUtils.getHash(revision);
364+
if (hash != null && StringUtils.length(hash) != 40) { // JENKINS-75555
365+
try (BitbucketApi client = scmSource.buildBitbucketClient(prHead)) {
366+
BitbucketCommit commit = client.resolveCommit(hash);
367+
if (commit != null) {
368+
revision = new AbstractGitSCMSource.SCMRevisionImpl(prHead, commit.getHash());
369+
}
370+
} catch (IOException | InterruptedException e) {
371+
logger.log(Level.SEVERE, "Can not retrieve commit for hash " + hash, e);
372+
throw new RuntimeException(e);
373+
}
374+
}
375+
return revision;
376+
}
350377
}

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/PullRequestSCMRevision.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@
2323
*/
2424
package com.cloudbees.jenkins.plugins.bitbucket;
2525

26-
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
2726
import edu.umd.cs.findbugs.annotations.NonNull;
28-
import jenkins.plugins.git.AbstractGitSCMSource;
2927
import jenkins.scm.api.SCMRevision;
3028
import jenkins.scm.api.mixin.ChangeRequestCheckoutStrategy;
3129
import jenkins.scm.api.mixin.ChangeRequestSCMRevision;
@@ -42,7 +40,7 @@ public class PullRequestSCMRevision extends ChangeRequestSCMRevision<PullRequest
4240
* The pull head revision.
4341
*/
4442
@NonNull
45-
private final AbstractGitSCMSource.SCMRevisionImpl pull;
43+
private final SCMRevision pull;
4644

4745
/**
4846
* Constructor.
@@ -51,7 +49,7 @@ public class PullRequestSCMRevision extends ChangeRequestSCMRevision<PullRequest
5149
* @param target the target revision.
5250
* @param pull the pull revision.
5351
*/
54-
public PullRequestSCMRevision(@NonNull PullRequestSCMHead head, @NonNull AbstractGitSCMSource.SCMRevisionImpl target, @NonNull AbstractGitSCMSource.SCMRevisionImpl pull) {
52+
public PullRequestSCMRevision(@NonNull PullRequestSCMHead head, @NonNull SCMRevision target, @NonNull SCMRevision pull) {
5553
super(head, target);
5654
this.pull = pull;
5755
}
@@ -61,8 +59,8 @@ public PullRequestSCMRevision(@NonNull PullRequestSCMHead head, @NonNull Abstrac
6159
*
6260
* @return the pull revision.
6361
*/
64-
@NonNull @WithBridgeMethods(SCMRevision.class)
65-
public AbstractGitSCMSource.SCMRevisionImpl getPull() {
62+
@NonNull
63+
public SCMRevision getPull() {
6664
return pull;
6765
}
6866

@@ -78,10 +76,6 @@ public boolean equivalent(ChangeRequestSCMRevision<?> o) {
7876
return getHead().equals(other.getHead()) && pull.equals(other.pull);
7977
}
8078

81-
public AbstractGitSCMSource.SCMRevisionImpl getTargetImpl() {
82-
return (AbstractGitSCMSource.SCMRevisionImpl) getTarget();
83-
}
84-
8579
/**
8680
* {@inheritDoc}
8781
*/

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/branch/BitbucketCloudAuthor.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
*/
2424
package com.cloudbees.jenkins.plugins.bitbucket.client.branch;
2525

26+
import com.fasterxml.jackson.annotation.JsonCreator;
27+
import com.fasterxml.jackson.annotation.JsonProperty;
28+
import edu.umd.cs.findbugs.annotations.NonNull;
29+
2630
/**
2731
* Represents the author information given by Bitbucket Cloud.
2832
*
@@ -32,6 +36,11 @@
3236
public class BitbucketCloudAuthor {
3337
private String raw;
3438

39+
@JsonCreator
40+
public BitbucketCloudAuthor(@NonNull @JsonProperty("raw") String raw) {
41+
this.raw = raw;
42+
}
43+
3544
/**
3645
* Returns the raw author string provided by the commit.
3746
*

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/client/pullrequest/BitbucketCloudPullRequestCommit.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ public String getAuthor() {
5050

5151
public void setAuthor(String author) {
5252
if (this.author == null) {
53-
this.author = new BitbucketCloudAuthor();
53+
this.author = new BitbucketCloudAuthor(author);
54+
} else {
55+
this.author.setRaw(author);
5456
}
55-
this.author.setRaw(author);
5657
}
5758

5859
@Override

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPushHookProcessor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ public void process(HookEventType hookEvent, String payload, BitbucketType insta
8787
return;
8888
}
8989
} else {
90-
throw new UnsupportedOperationException("Unsupported hook event " + hookEvent);
90+
throw new UnsupportedOperationException("Hook event of type " + hookEvent + " is not supported.\n"
91+
+ "Please fill an issue at https://issues.jenkins.io to the bitbucket-branch-source-plugin component.");
9192
}
9293
} catch (final IOException e) {
9394
LOGGER.log(Level.SEVERE, "Can not read hook payload", e);

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/util/BitbucketApiUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public static boolean isCloud(BitbucketApi client) {
6666
}
6767

6868
public static boolean isCloud(@NonNull String serverURL) {
69-
return StringUtils.startsWithAny(serverURL, new String[] { BitbucketCloudEndpoint.SERVER_URL, BitbucketCloudEndpoint.BAD_SERVER_URL });
69+
return StringUtils.startsWithAny(serverURL, BitbucketCloudEndpoint.SERVER_URL, BitbucketCloudEndpoint.BAD_SERVER_URL);
7070
}
7171

7272
public static ListBoxModel getFromBitbucket(SCMSourceOwner context,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2025, Nikolas Falco
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
package com.cloudbees.jenkins.plugins.bitbucket.impl.util;
25+
26+
import edu.umd.cs.findbugs.annotations.CheckForNull;
27+
import edu.umd.cs.findbugs.annotations.Nullable;
28+
import jenkins.plugins.git.AbstractGitSCMSource.SCMRevisionImpl;
29+
import jenkins.scm.api.SCMRevision;
30+
import jenkins.scm.api.mixin.ChangeRequestSCMRevision;
31+
32+
/**
33+
* Utility class that helps to extract specific information from implementation
34+
* of SCM base classes.
35+
*
36+
* @author Nikolas Falco
37+
* @since 936.1.0
38+
*/
39+
public final class SCMUtils {
40+
41+
private SCMUtils() {
42+
}
43+
44+
/**
45+
* Returns the SHA1 hash for the given SCMRevision implementation.
46+
* <p>
47+
* In case of ChangeRequestSCMRevision returns the SHA1 hash of the target.
48+
*
49+
* @param revision extract from
50+
* @return a SHA1 commit hash in the revision if present
51+
*/
52+
@CheckForNull
53+
public static String getHash(@Nullable SCMRevision revision) {
54+
if (revision == null) {
55+
return null;
56+
} else if (revision instanceof SCMRevisionImpl gitRev) {
57+
return gitRev.getHash();
58+
} else if (revision instanceof ChangeRequestSCMRevision<?> prRev) {
59+
return getHash(prRev.getTarget());
60+
} else {
61+
throw new UnsupportedOperationException("Revision of type " + revision.getClass().getSimpleName() + " is not supported.\n"
62+
+ "Please fill an issue at https://issues.jenkins.io to the bitbucket-branch-source-plugin component.");
63+
}
64+
}
65+
66+
}

src/test/java/com/cloudbees/jenkins/plugins/bitbucket/BitbucketClientMockUtils.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,7 @@ private static BitbucketCloudPullRequest getPullRequest() {
211211
}
212212

213213
private static BitbucketCloudCommit buildCommit(String message, String date, String hash, String authorName) {
214-
BitbucketCloudAuthor author = new BitbucketCloudAuthor();
215-
author.setRaw(authorName);
214+
BitbucketCloudAuthor author = new BitbucketCloudAuthor(authorName);
216215
return new BitbucketCloudCommit(message, date, hash, author, author, Collections.emptyList());
217216
}
218217

0 commit comments

Comments
 (0)