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);