2525
2626import com .cloudbees .jenkins .plugins .bitbucket .api .BitbucketApi ;
2727import com .cloudbees .jenkins .plugins .bitbucket .api .BitbucketBranch ;
28+ import com .cloudbees .jenkins .plugins .bitbucket .api .BitbucketCommit ;
2829import com .cloudbees .jenkins .plugins .bitbucket .api .BitbucketPullRequest ;
2930import edu .umd .cs .findbugs .annotations .CheckForNull ;
3031import edu .umd .cs .findbugs .annotations .NonNull ;
32+ import edu .umd .cs .findbugs .annotations .Nullable ;
3133import hudson .Util ;
3234import hudson .model .TaskListener ;
3335import java .io .Closeable ;
3941import java .util .Map ;
4042import java .util .Set ;
4143import jenkins .scm .api .SCMHead ;
44+ import jenkins .scm .api .SCMHeadObserver ;
4245import jenkins .scm .api .SCMHeadOrigin ;
46+ import jenkins .scm .api .SCMRevision ;
47+ import jenkins .scm .api .SCMSourceCriteria ;
48+ import jenkins .scm .api .SCMSourceCriteria .Probe ;
4349import jenkins .scm .api .mixin .ChangeRequestCheckoutStrategy ;
4450import jenkins .scm .api .trait .SCMSourceRequest ;
51+ import jenkins .scm .api .trait .SCMSourceRequest .LazyRevisionLambda ;
4552
4653/**
4754 * The {@link SCMSourceRequest} for bitbucket.
4855 *
4956 * @since 2.2.0
5057 */
5158public class BitbucketSCMSourceRequest extends SCMSourceRequest {
59+
60+ private class BitbucketProbeFactory <I > implements SCMSourceRequest .ProbeLambda <SCMHead , I > {
61+ private final BitbucketApi client ;
62+
63+ public BitbucketProbeFactory (BitbucketApi client ) {
64+ this .client = client ;
65+ }
66+
67+ @ NonNull
68+ @ Override
69+ public Probe create (@ NonNull final SCMHead head , @ CheckForNull final I revisionInfo ) throws IOException , InterruptedException {
70+ final String hash = (revisionInfo instanceof BitbucketCommit bbRevision ) //
71+ ? bbRevision .getHash () //
72+ : (String ) revisionInfo ;
73+
74+ return new SCMSourceCriteria .Probe () {
75+ private static final long serialVersionUID = 1L ;
76+
77+ @ Override
78+ public String name () {
79+ return head .getName ();
80+ }
81+
82+ @ Override
83+ public long lastModified () {
84+ try {
85+ BitbucketCommit commit = null ;
86+ if (hash != null ) {
87+ commit = (revisionInfo instanceof BitbucketCommit bbRevision ) //
88+ ? bbRevision //
89+ : client .resolveCommit (hash );
90+ }
91+
92+ if (commit == null ) {
93+ listener ().getLogger ().format ("Can not resolve commit by hash [%s] on repository %s/%s%n" , //
94+ hash , client .getOwner (), client .getRepositoryName ());
95+ return 0 ;
96+ }
97+ return commit .getDateMillis ();
98+ } catch (InterruptedException | IOException e ) {
99+ listener ().getLogger ().format ("Can not resolve commit by hash [%s] on repository %s/%s%n" , //
100+ hash , client .getOwner (), client .getRepositoryName ());
101+ return 0 ;
102+ }
103+ }
104+
105+ @ Override
106+ public boolean exists (@ NonNull String path ) throws IOException {
107+ if (hash == null ) {
108+ listener ().getLogger () //
109+ .format ("Can not resolve path for hash [%s] on repository %s/%s%n" , //
110+ hash , client .getOwner (), client .getRepositoryName ());
111+ return false ;
112+ }
113+
114+ try {
115+ return client .checkPathExists (hash , path );
116+ } catch (InterruptedException e ) {
117+ throw new IOException ("Interrupted" , e );
118+ }
119+ }
120+ };
121+ }
122+ }
123+
124+ public static class BitbucketRevisionFactory <I > implements SCMSourceRequest .LazyRevisionLambda <SCMHead , SCMRevision , I > {
125+ private final BitbucketApi client ;
126+
127+ public BitbucketRevisionFactory (BitbucketApi client ) {
128+ this .client = client ;
129+ }
130+
131+ @ NonNull
132+ @ Override
133+ public SCMRevision create (@ NonNull SCMHead head , @ Nullable I input ) throws IOException , InterruptedException {
134+ return create (head , input , null );
135+ }
136+
137+ @ NonNull
138+ public SCMRevision create (@ NonNull SCMHead head ,
139+ @ Nullable I sourceInput ,
140+ @ Nullable I targetInput ) throws IOException , InterruptedException {
141+ BitbucketCommit sourceCommit = asCommit (sourceInput );
142+ BitbucketCommit targetCommit = asCommit (targetInput );
143+
144+ SCMRevision revision ;
145+ if (head instanceof PullRequestSCMHead prHead ) {
146+ SCMHead targetHead = prHead .getTarget ();
147+
148+ return new PullRequestSCMRevision ( //
149+ prHead , //
150+ new BitbucketGitSCMRevision (targetHead , targetCommit ), //
151+ new BitbucketGitSCMRevision (prHead , sourceCommit ));
152+ } else {
153+ revision = new BitbucketGitSCMRevision (head , sourceCommit );
154+ }
155+ return revision ;
156+ }
157+
158+ private BitbucketCommit asCommit (I input ) throws IOException , InterruptedException {
159+ if (input instanceof String value ) {
160+ return client .resolveCommit (value );
161+ } else if (input instanceof BitbucketCommit commit ) {
162+ return commit ;
163+ }
164+ return null ;
165+ }
166+ }
167+
168+ private class CriteriaWitness implements SCMSourceRequest .Witness {
169+ @ Override
170+ public void record (@ NonNull SCMHead scmHead , SCMRevision revision , boolean isMatch ) {
171+ if (revision == null ) {
172+ listener ().getLogger ().println (" Skipped" );
173+ } else {
174+ if (isMatch ) {
175+ listener ().getLogger ().println (" Met criteria" );
176+ } else {
177+ listener ().getLogger ().println (" Does not meet criteria" );
178+ }
179+
180+ }
181+ }
182+ }
183+
52184 /**
53185 * {@code true} if branch details need to be fetched.
54186 */
@@ -355,9 +487,14 @@ public final void setPullRequests(@CheckForNull Iterable<BitbucketPullRequest> p
355487 * or if the pull request details have not been provided by {@link #setPullRequests(Iterable)} yet.
356488 *
357489 * @return the pull request details (may be empty)
490+ * @throws InterruptedException
491+ * @throws IOException
358492 */
359493 @ NonNull
360- public final Iterable <BitbucketPullRequest > getPullRequests () {
494+ public final Iterable <BitbucketPullRequest > getPullRequests () throws IOException , InterruptedException {
495+ if (pullRequests == null ) {
496+ pullRequests = (Iterable <BitbucketPullRequest >) getBitbucketApiClient ().getPullRequests ();
497+ }
361498 return Util .fixNull (pullRequests );
362499 }
363500
@@ -399,9 +536,14 @@ public final void setBranches(@CheckForNull Iterable<BitbucketBranch> branches)
399536 * or if the branch details have not been provided by {@link #setBranches(Iterable)} yet.
400537 *
401538 * @return the branch details (may be empty)
539+ * @throws IOException if there was a network communications error.
540+ * @throws InterruptedException if interrupted while waiting on remote communications.
402541 */
403542 @ NonNull
404- public final Iterable <BitbucketBranch > getBranches () {
543+ public final Iterable <BitbucketBranch > getBranches () throws IOException , InterruptedException {
544+ if (branches == null ) {
545+ branches = (Iterable <BitbucketBranch >) getBitbucketApiClient ().getBranches ();
546+ }
405547 return Util .fixNull (branches );
406548 }
407549
@@ -419,9 +561,14 @@ public final void setTags(@CheckForNull Iterable<BitbucketBranch> tags) {
419561 * or if the tag details have not been provided by {@link #setTags(Iterable)} yet.
420562 *
421563 * @return the tag details (may be empty)
564+ * @throws IOException if there was a network communications error.
565+ * @throws InterruptedException if interrupted while waiting on remote communications.
422566 */
423567 @ NonNull
424- public final Iterable <BitbucketBranch > getTags () {
568+ public final Iterable <BitbucketBranch > getTags () throws IOException , InterruptedException {
569+ if (tags == null ) {
570+ tags = (Iterable <BitbucketBranch >) getBitbucketApiClient ().getTags ();
571+ }
425572 return Util .fixNull (tags );
426573 }
427574
@@ -430,12 +577,54 @@ public final Iterable<BitbucketBranch> getTags() {
430577 */
431578 @ Override
432579 public void close () throws IOException {
433- if (pullRequests instanceof Closeable closable ) {
434- closable .close ();
435- }
436- if (branches instanceof Closeable closable ) {
437- closable .close ();
580+ if (api != null ) {
581+ api .close ();
438582 }
439583 super .close ();
440584 }
585+
586+ /**
587+ * Processes a head in the context of the current request where an intermediary operation is required before
588+ * the {@link SCMRevision} can be instantiated.
589+ *
590+ * @param head the {@link SCMHead} to process.
591+ * @param intermediateFactory factory method that provides the seed information for both the {@link ProbeLambda}
592+ * and the {@link LazyRevisionLambda}.
593+ * @param <H> the type of {@link SCMHead}.
594+ * @param <I> the type of the intermediary operation result.
595+ * @param <R> the type of {@link SCMRevision}.
596+ * @return {@code true} if the {@link SCMHeadObserver} for this request has completed observing, {@code false} to
597+ * continue processing.
598+ * @throws IOException if there was an I/O error.
599+ * @throws InterruptedException if the processing was interrupted.
600+ */
601+ public final <H extends SCMHead , I , R extends SCMRevision > boolean process (@ NonNull H head ,
602+ @ CheckForNull IntermediateLambda <I > intermediateFactory )
603+ throws IOException , InterruptedException {
604+ return super .process (head , //
605+ intermediateFactory , //
606+ defaultProbeLamda (), //
607+ defaultRevisionLamda (), //
608+ new CriteriaWitness ());
609+ }
610+
611+ @ NonNull
612+ <I > ProbeLambda <SCMHead , I > defaultProbeLamda () {
613+ return this .new BitbucketProbeFactory <>(getBitbucketApiClient ());
614+ }
615+
616+ @ NonNull
617+ <I > ProbeLambda <SCMHead , I > buildProbeLamda (@ NonNull BitbucketApi client ) {
618+ return this .new BitbucketProbeFactory <>(client );
619+ }
620+
621+ @ NonNull
622+ <I > LazyRevisionLambda <SCMHead , SCMRevision , I > defaultRevisionLamda () {
623+ return new BitbucketRevisionFactory <>(getBitbucketApiClient ());
624+ }
625+
626+ @ NonNull
627+ Witness defaultWitness () {
628+ return this .new CriteriaWitness ();
629+ }
441630}
0 commit comments