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 ;
33- import java .io .Closeable ;
3435import java .io .IOException ;
3536import java .util .Collections ;
3637import java .util .EnumSet ;
3940import java .util .Map ;
4041import java .util .Set ;
4142import jenkins .scm .api .SCMHead ;
43+ import jenkins .scm .api .SCMHeadObserver ;
4244import jenkins .scm .api .SCMHeadOrigin ;
45+ import jenkins .scm .api .SCMRevision ;
46+ import jenkins .scm .api .SCMSourceCriteria ;
47+ import jenkins .scm .api .SCMSourceCriteria .Probe ;
4348import jenkins .scm .api .mixin .ChangeRequestCheckoutStrategy ;
4449import jenkins .scm .api .trait .SCMSourceRequest ;
4550
4954 * @since 2.2.0
5055 */
5156public class BitbucketSCMSourceRequest extends SCMSourceRequest {
57+
58+ private class BitbucketProbeFactory <I > implements SCMSourceRequest .ProbeLambda <SCMHead , I > {
59+ private final BitbucketApi client ;
60+
61+ public BitbucketProbeFactory (BitbucketApi client ) {
62+ this .client = client ;
63+ }
64+
65+ @ NonNull
66+ @ Override
67+ public Probe create (@ NonNull final SCMHead head , @ CheckForNull final I revisionInfo ) throws IOException , InterruptedException {
68+ final String hash = (revisionInfo instanceof BitbucketCommit bbRevision ) //
69+ ? bbRevision .getHash () //
70+ : (String ) revisionInfo ;
71+
72+ return new SCMSourceCriteria .Probe () {
73+ private static final long serialVersionUID = 1L ;
74+
75+ @ Override
76+ public String name () {
77+ return head .getName ();
78+ }
79+
80+ @ Override
81+ public long lastModified () {
82+ try {
83+ BitbucketCommit commit = null ;
84+ if (hash != null ) {
85+ commit = (revisionInfo instanceof BitbucketCommit bbRevision ) //
86+ ? bbRevision //
87+ : client .resolveCommit (hash );
88+ }
89+
90+ if (commit == null ) {
91+ listener ().getLogger ().format ("Can not resolve commit by hash [%s] on repository %s/%s%n" , //
92+ hash , client .getOwner (), client .getRepositoryName ());
93+ return 0 ;
94+ }
95+ return commit .getDateMillis ();
96+ } catch (InterruptedException | IOException e ) {
97+ listener ().getLogger ().format ("Can not resolve commit by hash [%s] on repository %s/%s%n" , //
98+ hash , client .getOwner (), client .getRepositoryName ());
99+ return 0 ;
100+ }
101+ }
102+
103+ @ Override
104+ public boolean exists (@ NonNull String path ) throws IOException {
105+ if (hash == null ) {
106+ listener ().getLogger () //
107+ .format ("Can not resolve path for hash [%s] on repository %s/%s%n" , //
108+ hash , client .getOwner (), client .getRepositoryName ());
109+ return false ;
110+ }
111+
112+ try {
113+ return client .checkPathExists (hash , path );
114+ } catch (InterruptedException e ) {
115+ throw new IOException ("Interrupted" , e );
116+ }
117+ }
118+ };
119+ }
120+ }
121+
122+ public static class BitbucketRevisionFactory <I > implements SCMSourceRequest .LazyRevisionLambda <SCMHead , SCMRevision , I > {
123+ private final BitbucketApi client ;
124+
125+ public BitbucketRevisionFactory (BitbucketApi client ) {
126+ this .client = client ;
127+ }
128+
129+ @ NonNull
130+ @ Override
131+ public SCMRevision create (@ NonNull SCMHead head , @ Nullable I input ) throws IOException , InterruptedException {
132+ return create (head , input , null );
133+ }
134+
135+ @ NonNull
136+ public SCMRevision create (@ NonNull SCMHead head ,
137+ @ Nullable I sourceInput ,
138+ @ Nullable I targetInput ) throws IOException , InterruptedException {
139+ BitbucketCommit sourceCommit = asCommit (sourceInput );
140+ BitbucketCommit targetCommit = asCommit (targetInput );
141+
142+ SCMRevision revision ;
143+ if (head instanceof PullRequestSCMHead prHead ) {
144+ SCMHead targetHead = prHead .getTarget ();
145+
146+ return new PullRequestSCMRevision ( //
147+ prHead , //
148+ new BitbucketGitSCMRevision (targetHead , targetCommit ), //
149+ new BitbucketGitSCMRevision (prHead , sourceCommit ));
150+ } else {
151+ revision = new BitbucketGitSCMRevision (head , sourceCommit );
152+ }
153+ return revision ;
154+ }
155+
156+ private BitbucketCommit asCommit (I input ) throws IOException , InterruptedException {
157+ if (input instanceof String value ) {
158+ return client .resolveCommit (value );
159+ } else if (input instanceof BitbucketCommit commit ) {
160+ return commit ;
161+ }
162+ return null ;
163+ }
164+ }
165+
166+ private class CriteriaWitness implements SCMSourceRequest .Witness {
167+ @ Override
168+ public void record (@ NonNull SCMHead scmHead , SCMRevision revision , boolean isMatch ) {
169+ if (revision == null ) {
170+ listener ().getLogger ().println (" Skipped" );
171+ } else {
172+ if (isMatch ) {
173+ listener ().getLogger ().println (" Met criteria" );
174+ } else {
175+ listener ().getLogger ().println (" Does not meet criteria" );
176+ }
177+
178+ }
179+ }
180+ }
181+
52182 /**
53183 * {@code true} if branch details need to be fetched.
54184 */
@@ -355,9 +485,14 @@ public final void setPullRequests(@CheckForNull Iterable<BitbucketPullRequest> p
355485 * or if the pull request details have not been provided by {@link #setPullRequests(Iterable)} yet.
356486 *
357487 * @return the pull request details (may be empty)
488+ * @throws InterruptedException
489+ * @throws IOException
358490 */
359491 @ NonNull
360- public final Iterable <BitbucketPullRequest > getPullRequests () {
492+ public final Iterable <BitbucketPullRequest > getPullRequests () throws IOException , InterruptedException {
493+ if (pullRequests == null ) {
494+ pullRequests = (Iterable <BitbucketPullRequest >) getBitbucketApiClient ().getPullRequests ();
495+ }
361496 return Util .fixNull (pullRequests );
362497 }
363498
@@ -399,9 +534,14 @@ public final void setBranches(@CheckForNull Iterable<BitbucketBranch> branches)
399534 * or if the branch details have not been provided by {@link #setBranches(Iterable)} yet.
400535 *
401536 * @return the branch details (may be empty)
537+ * @throws IOException if there was a network communications error.
538+ * @throws InterruptedException if interrupted while waiting on remote communications.
402539 */
403540 @ NonNull
404- public final Iterable <BitbucketBranch > getBranches () {
541+ public final Iterable <BitbucketBranch > getBranches () throws IOException , InterruptedException {
542+ if (branches == null ) {
543+ branches = (Iterable <BitbucketBranch >) getBitbucketApiClient ().getBranches ();
544+ }
405545 return Util .fixNull (branches );
406546 }
407547
@@ -419,9 +559,14 @@ public final void setTags(@CheckForNull Iterable<BitbucketBranch> tags) {
419559 * or if the tag details have not been provided by {@link #setTags(Iterable)} yet.
420560 *
421561 * @return the tag details (may be empty)
562+ * @throws IOException if there was a network communications error.
563+ * @throws InterruptedException if interrupted while waiting on remote communications.
422564 */
423565 @ NonNull
424- public final Iterable <BitbucketBranch > getTags () {
566+ public final Iterable <BitbucketBranch > getTags () throws IOException , InterruptedException {
567+ if (tags == null ) {
568+ tags = (Iterable <BitbucketBranch >) getBitbucketApiClient ().getTags ();
569+ }
425570 return Util .fixNull (tags );
426571 }
427572
@@ -430,12 +575,54 @@ public final Iterable<BitbucketBranch> getTags() {
430575 */
431576 @ Override
432577 public void close () throws IOException {
433- if (pullRequests instanceof Closeable closable ) {
434- closable .close ();
435- }
436- if (branches instanceof Closeable closable ) {
437- closable .close ();
578+ if (api != null ) {
579+ api .close ();
438580 }
439581 super .close ();
440582 }
583+
584+ /**
585+ * Processes a head in the context of the current request where an intermediary operation is required before
586+ * the {@link SCMRevision} can be instantiated.
587+ *
588+ * @param head the {@link SCMHead} to process.
589+ * @param intermediateFactory factory method that provides the seed information for both the {@link ProbeLambda}
590+ * and the {@link LazyRevisionLambda}.
591+ * @param <H> the type of {@link SCMHead}.
592+ * @param <I> the type of the intermediary operation result.
593+ * @param <R> the type of {@link SCMRevision}.
594+ * @return {@code true} if the {@link SCMHeadObserver} for this request has completed observing, {@code false} to
595+ * continue processing.
596+ * @throws IOException if there was an I/O error.
597+ * @throws InterruptedException if the processing was interrupted.
598+ */
599+ public final <H extends SCMHead , I , R extends SCMRevision > boolean process (@ NonNull H head ,
600+ @ CheckForNull IntermediateLambda <I > intermediateFactory )
601+ throws IOException , InterruptedException {
602+ return super .process (head , //
603+ intermediateFactory , //
604+ defaultProbeLamda (), //
605+ defaultRevisionLamda (), //
606+ new CriteriaWitness ());
607+ }
608+
609+ @ NonNull
610+ <I > ProbeLambda <SCMHead , I > defaultProbeLamda () {
611+ return this .new BitbucketProbeFactory <>(getBitbucketApiClient ());
612+ }
613+
614+ @ NonNull
615+ <I > ProbeLambda <SCMHead , I > buildProbeLamda (@ NonNull BitbucketApi client ) {
616+ return this .new BitbucketProbeFactory <>(client );
617+ }
618+
619+ @ NonNull
620+ <I > LazyRevisionLambda <SCMHead , SCMRevision , I > defaultRevisionLamda () {
621+ return new BitbucketRevisionFactory <>(getBitbucketApiClient ());
622+ }
623+
624+ @ NonNull
625+ Witness defaultWitness () {
626+ return this .new CriteriaWitness ();
627+ }
441628}
0 commit comments