diff --git a/pom.xml b/pom.xml index 35933a2e..9252c6b1 100644 --- a/pom.xml +++ b/pom.xml @@ -101,6 +101,21 @@ THE SOFTWARE. org.jenkins-ci.plugins scm-api + + org.jenkins-ci.plugins + git + + + org.jenkins-ci.plugins + github-branch-source + 2.10.2 + + + org.apache.commons + commons-lang3 + + + org.jenkins-ci.plugins branch-api @@ -160,11 +175,6 @@ THE SOFTWARE. tests test - - org.jenkins-ci.plugins - git - test - org.jenkins-ci.plugins subversion diff --git a/src/main/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinder.java b/src/main/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinder.java index 79c14393..558ea3f8 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinder.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinder.java @@ -33,20 +33,26 @@ import hudson.console.ConsoleAnnotator; import hudson.console.ConsoleNote; import hudson.model.Action; +import hudson.model.Cause.UpstreamCause; import hudson.model.Descriptor; import hudson.model.DescriptorVisibilityFilter; import hudson.model.ItemGroup; import hudson.model.Queue; +import hudson.model.Run; import hudson.model.TaskListener; +import hudson.plugins.git.RevisionParameterAction; import hudson.scm.SCM; import java.io.IOException; import java.util.List; import jenkins.branch.Branch; +import jenkins.plugins.git.AbstractGitSCMSource; +import jenkins.plugins.git.AbstractGitSCMSource.SCMRevisionImpl; import jenkins.scm.api.SCMFileSystem; import jenkins.scm.api.SCMHead; import jenkins.scm.api.SCMRevision; import jenkins.scm.api.SCMRevisionAction; import jenkins.scm.api.SCMSource; +import org.jenkinsci.plugins.github_branch_source.PullRequestSCMRevision; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; import org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition; import org.jenkinsci.plugins.workflow.flow.FlowDefinition; @@ -89,18 +95,26 @@ public SCMBinder(String scriptPath) { throw new IllegalStateException("inappropriate context"); } Branch branch = property.getBranch(); - ItemGroup parent = job.getParent(); - if (!(parent instanceof WorkflowMultiBranchProject)) { - throw new IllegalStateException("inappropriate context"); + SCMSource scmSource = getScmSource(job, branch.getSourceId()); + + SCMRevision upstreamRevision = null; + if (scmSource instanceof AbstractGitSCMSource) { + upstreamRevision = getUpstreamGitScmRevisionForSameRemote(build, branch, (AbstractGitSCMSource) scmSource, listener); } - SCMSource scmSource = ((WorkflowMultiBranchProject) parent).getSCMSource(branch.getSourceId()); - if (scmSource == null) { - throw new IllegalStateException(branch.getSourceId() + " not found"); + + SCMHead head; + if (upstreamRevision != null) { + head = upstreamRevision.getHead(); + } else { + head = branch.getHead(); } - SCMHead head = branch.getHead(); + SCMRevision tip = scmSource.fetch(head, listener); SCM scm; if (tip != null) { + if (upstreamRevision != null) { + tip = upstreamRevision; + } build.addAction(new SCMRevisionAction(scmSource, tip)); SCMRevision rev = scmSource.getTrustedRevision(tip, listener); try (SCMFileSystem fs = USE_HEAVYWEIGHT_CHECKOUT ? null : SCMFileSystem.of(scmSource, head, rev)) { @@ -145,6 +159,75 @@ public SCMBinder(String scriptPath) { return new CpsScmFlowDefinition(scm, scriptPath).create(handle, listener, actions); } + private SCMSource getScmSource(WorkflowJob job, String scmSourceId) { + ItemGroup parent = job.getParent(); + if (!(parent instanceof WorkflowMultiBranchProject)) { + throw new IllegalStateException("inappropriate context"); + } + SCMSource scmSource = ((WorkflowMultiBranchProject) parent).getSCMSource(scmSourceId); + if (scmSource == null) { + throw new IllegalStateException(scmSourceId + " not found"); + } + return scmSource; + } + + /** + * This method will set a {@link RevisionParameterAction} (force the checkout of a specific rev) with the rev of the + * upstream as well as return the {@link SCMRevision} of the upstream job, if + *
  • the current run is triggered by an {@link UpstreamCause} + *
  • the upstream is a {@link WorkflowRun} that has a {@link SCMRevisionAction} + *
  • the {@link SCMSource} of that upstream is of the same (git) type as the current one (eg. GitHub - GitHub but + * not Git - Github) and they have the same remote URL + *
  • the (jenkins) branch names match (eg. PR-42 - PR-42) + *
  • the {@link SCMRevision} includes a rev, such as {@link PullRequestSCMRevision} or {@link SCMRevisionImpl}. + */ + private SCMRevision getUpstreamGitScmRevisionForSameRemote(WorkflowRun currentBuild, Branch currentBranch, + AbstractGitSCMSource currentGitScmSource, TaskListener listener) { + UpstreamCause upstreamCause = currentBuild.getCause(UpstreamCause.class); + if (upstreamCause == null) { + return null; + } + + Run upstreamRun = upstreamCause.getUpstreamRun(); + if (upstreamRun instanceof WorkflowRun) { + + SCMRevisionAction scmRevisionAction = upstreamRun.getAction(SCMRevisionAction.class); + if (scmRevisionAction == null) { + return null; + } + + WorkflowJob upstreamJob = ((WorkflowRun) upstreamRun).getParent(); + SCMSource upstreamScmSource; + try { + upstreamScmSource = getScmSource(upstreamJob, scmRevisionAction.getSourceId()); + } catch (IllegalStateException e) { + return null; + } + if (!(currentGitScmSource.getClass() == upstreamScmSource.getClass()) + || !(currentGitScmSource.getRemote().equalsIgnoreCase( + ((AbstractGitSCMSource) upstreamScmSource).getRemote()))) { + return null; + } + + SCMRevision upstreamScmRevision = scmRevisionAction.getRevision(); + if (!currentBranch.getHead().getName().equals(upstreamScmRevision.getHead().getName())) { + return null; + } + + if (upstreamScmRevision instanceof PullRequestSCMRevision) { + listener.getLogger().println("Using upstream PR-revision"); + currentBuild.addAction(new RevisionParameterAction(((PullRequestSCMRevision) upstreamScmRevision).getPullHash())); + return upstreamScmRevision; + + } else if (upstreamScmRevision instanceof SCMRevisionImpl) { + listener.getLogger().println("Using upstream git revision"); + currentBuild.addAction(new RevisionParameterAction(((SCMRevisionImpl) upstreamScmRevision).getHash())); + return upstreamScmRevision; + } + } + return null; + } + @Extension public static class DescriptorImpl extends FlowDefinitionDescriptor { @NonNull diff --git a/src/test/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinderTest.java b/src/test/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinderTest.java index 49a03e65..552173d0 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinderTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinderTest.java @@ -27,6 +27,9 @@ import com.cloudbees.hudson.plugins.folder.computed.DefaultOrphanedItemStrategy; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Util; +import hudson.model.Cause; +import hudson.model.Cause.UpstreamCause; +import hudson.model.CauseAction; import hudson.model.Item; import hudson.model.Result; import hudson.model.TaskListener; @@ -55,6 +58,7 @@ import jenkins.scm.impl.subversion.SubversionSampleRepoRule; import org.acegisecurity.Authentication; +import org.jenkinsci.plugins.github_branch_source.GitHubSCMSource; import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval; import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.jenkinsci.plugins.workflow.job.WorkflowRun; @@ -116,6 +120,39 @@ public class SCMBinderTest { assertFalse(iterator.hasNext()); } + @Test public void upstreamRevisionGit() throws Exception { + sampleGitRepo.init(); + ScriptApproval sa = ScriptApproval.get(); + sa.approveSignature("staticField hudson.model.Items XSTREAM2"); + sa.approveSignature("method com.thoughtworks.xstream.XStream toXML java.lang.Object"); + sa.approveSignature("method org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper getRawBuild"); + sa.approveSignature("method hudson.model.Run getCauses"); + sa.approveSignature("staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods inspect java.lang.Object"); + sampleGitRepo.write("Jenkinsfile", "echo hudson.model.Items.XSTREAM2.toXML(scm); echo currentBuild.rawBuild.causes.inspect(); semaphore 'wait'; node {checkout scm; echo readFile('file')}"); + sampleGitRepo.write("file", "initial content"); + sampleGitRepo.git("add", "Jenkinsfile"); + sampleGitRepo.git("commit", "--all", "--message=flow"); + WorkflowMultiBranchProject mp = r.jenkins.createProject(WorkflowMultiBranchProject.class, "p"); + mp.getSourcesList().add(new BranchSource(new GitSCMSource(null, sampleGitRepo.toString(), "", "*", "", false))); + WorkflowJob p = WorkflowMultiBranchProjectTest.scheduleAndFindBranchProject(mp, "master"); + SemaphoreStep.waitForStart("wait/1", null); + WorkflowRun b1 = p.getLastBuild(); + assertNotNull(b1); + assertEquals(1, b1.getNumber()); + assertRevisionAction(b1); + r.assertLogContains("Obtained Jenkinsfile from ", b1); + sampleGitRepo.write("file", "subsequent content"); + sampleGitRepo.git("commit", "--all", "--message=tweaked"); + SemaphoreStep.success("wait/1", null); + mp.scheduleBuild2(0, new CauseAction(new UpstreamCause(b1))); // this cause is ignored / replaced (?) with BranchIndexingCause + SemaphoreStep.waitForStart("wait/2", null); + WorkflowRun b2 = p.getLastBuild(); + assertEquals(2, b2.getNumber()); + r.assertLogContains("initial content", r.waitForCompletion(b1)); + r.assertLogContains("initial content", r.waitForCompletion(b2)); + r.assertLogNotContains("SUBSEQUENT CONTENT", b2); + } + public static void assertRevisionAction(WorkflowRun build) { SCMRevisionAction revisionAction = build.getAction(SCMRevisionAction.class); assertNotNull(revisionAction);