From f19ab8f9d5d9b3ac889abfcd412dfad5332fb542 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 2 Nov 2015 14:04:47 -0500 Subject: [PATCH] [FIXED JENKINS-30798] SCMBinder was calling the wrong overload of SCMSource.fetch. There is no need to consider SCMSourceCriteria here, nor to inspect unrelated branches. It was also not dealing gracefully with problems such as deleted branches. Originally-Committed-As: 1d791a950b927a5bc3ffb18ccda9aca7ebc5d0b4 --- .../workflow/multibranch/SCMBinder.java | 15 ++--- .../workflow/multibranch/SCMBinderTest.java | 60 +++++++++++++++++++ 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/multibranch/src/main/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinder.java b/multibranch/src/main/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinder.java index 5d1ecfdc..36cfaac0 100644 --- a/multibranch/src/main/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinder.java +++ b/multibranch/src/main/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinder.java @@ -84,14 +84,15 @@ class SCMBinder extends FlowDefinition { throw new IllegalStateException(branch.getSourceId() + " not found"); } SCMHead head = branch.getHead(); - SCMRevision tip = scmSource.fetch(SCMHeadObserver.select(head), listener).result(); - if (tip == null) { - // TODO observed (but not now reproducible) after trying to rebuild projects without rerunning branch indexing - // Perhaps because above we are calling the wrong `fetch` overload? (Simpler to pass SCMHead + TaskListener.) - throw new IllegalStateException("could not find branch tip on " + head); + SCMRevision tip = scmSource.fetch(head, listener); + SCM scm; + if (tip != null) { + scm = scmSource.build(head, tip); + } else { + listener.error("Could not determine exact tip revision of " + branch.getName() + "; falling back to nondeterministic checkout"); + // Build might fail later anyway, but reason should become clear: for example, branch was deleted before indexing could run. + scm = branch.getScm(); } - SCM scm = scmSource.build(head, tip); - // Fallback if one is needed: scm = branch.getScm() CpsFlowExecution execution = new CpsScmFlowDefinition(scm, WorkflowMultiBranchProject.SCRIPT).create(handle, listener, actions); scms.put(execution, scm); // stash for later return execution; diff --git a/multibranch/src/test/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinderTest.java b/multibranch/src/test/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinderTest.java index a3d1af06..73a38cf4 100644 --- a/multibranch/src/test/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinderTest.java +++ b/multibranch/src/test/java/org/jenkinsci/plugins/workflow/multibranch/SCMBinderTest.java @@ -25,6 +25,8 @@ package org.jenkinsci.plugins.workflow.multibranch; import hudson.ExtensionList; +import hudson.Util; +import hudson.model.Result; import hudson.model.RootAction; import hudson.plugins.mercurial.MercurialInstallation; import hudson.plugins.mercurial.MercurialSCMSource; @@ -229,4 +231,62 @@ public class SCMBinderTest { }); } + @Test public void deletedJenkinsfile() throws Exception { + story.addStep(new Statement() { + @Override + public void evaluate() throws Throwable { + sampleGitRepo.init(); + sampleGitRepo.write("Jenkinsfile", "node { echo 'Hello World' }"); + sampleGitRepo.git("add", "Jenkinsfile"); + sampleGitRepo.git("commit", "--all", "--message=flow"); + WorkflowMultiBranchProject mp = story.j.jenkins.createProject(WorkflowMultiBranchProject.class, "p"); + mp.getSourcesList().add(new BranchSource(new GitSCMSource(null, sampleGitRepo.toString(), "", "*", "", false), new DefaultBranchPropertyStrategy(new BranchProperty[0]))); + WorkflowJob p = WorkflowMultiBranchProjectTest.scheduleAndFindBranchProject(mp, "master"); + assertEquals(1, mp.getItems().size()); + story.j.waitUntilNoActivity(); + WorkflowRun b1 = p.getLastBuild(); + assertEquals(1, b1.getNumber()); + sampleGitRepo.git("rm", "Jenkinsfile"); + sampleGitRepo.git("commit", "--all", "--message=remove"); + WorkflowRun b2 = story.j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()); + story.j.assertLogContains("Jenkinsfile not found", b2); + } + }); + } + + @Test public void deletedBranch() throws Exception { + story.addStep(new Statement() { + @Override + public void evaluate() throws Throwable { + sampleGitRepo.init(); + // TODO GitSCMSource offers no way to set a GitSCMExtension such as CleanBeforeCheckout; work around with deleteDir + // (without cleaning, b2 will succeed since the workspace will still have a cached origin/feature ref) + sampleGitRepo.write("Jenkinsfile", "node {deleteDir(); checkout scm; echo 'Hello World'}"); + sampleGitRepo.git("add", "Jenkinsfile"); + sampleGitRepo.git("commit", "--all", "--message=flow"); + sampleGitRepo.git("checkout", "-b", "feature"); + sampleGitRepo.write("somefile", "stuff"); + sampleGitRepo.git("add", "somefile"); + sampleGitRepo.git("commit", "--all", "--message=tweaked"); + WorkflowMultiBranchProject mp = story.j.jenkins.createProject(WorkflowMultiBranchProject.class, "p"); + mp.getSourcesList().add(new BranchSource(new GitSCMSource(null, sampleGitRepo.toString(), "", "*", "", false), new DefaultBranchPropertyStrategy(new BranchProperty[0]))); + WorkflowJob p = WorkflowMultiBranchProjectTest.scheduleAndFindBranchProject(mp, "feature"); + assertEquals(2, mp.getItems().size()); + story.j.waitUntilNoActivity(); + WorkflowRun b1 = p.getLastBuild(); + assertEquals(1, b1.getNumber()); + sampleGitRepo.git("checkout", "master"); + sampleGitRepo.git("branch", "-D", "feature"); + { // TODO AbstractGitSCMSource.retrieve is incorrect: after fetching remote refs into the cache, the origin/feature ref remains locally even though it has been deleted upstream: + Util.deleteRecursive(new File(story.j.jenkins.getRootDir(), "caches")); + } + WorkflowRun b2 = story.j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()); + story.j.assertLogContains("nondeterministic checkout", b2); // SCMBinder + story.j.assertLogContains("any revision to build", b2); // checkout scm + mp.scheduleBuild2(0).getFuture().get(); + assertEquals(1, mp.getItems().size()); + } + }); + } + }