diff --git a/pom.xml b/pom.xml
index e677ef249..dd8178ac9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -293,6 +293,12 @@ THE SOFTWARE.
2.0.3
test
+
+ org.xerial
+ sqlite-jdbc
+ 3.32.3.2
+ test
+
diff --git a/src/main/java/hudson/scm/subversion/CheckoutUpdater.java b/src/main/java/hudson/scm/subversion/CheckoutUpdater.java
index f480a8a21..727ef4c18 100755
--- a/src/main/java/hudson/scm/subversion/CheckoutUpdater.java
+++ b/src/main/java/hudson/scm/subversion/CheckoutUpdater.java
@@ -37,6 +37,7 @@
import org.tmatesoft.svn.core.SVNCancelException;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb;
import org.tmatesoft.svn.core.internal.wc2.compat.SvnCodec;
import org.tmatesoft.svn.core.wc.SVNRevision;
@@ -137,8 +138,13 @@ public List perform() throws IOException, InterruptedException {
throw (InterruptedException)new InterruptedException().initCause(e);
}
} catch (SVNException e) {
- e.printStackTrace(listener.error("Failed to check out " + location.remote));
- throw new IOException("Failed to check out " + location.remote, e) ;
+ if (e.getErrorMessage().getErrorCode() == SVNErrorCode.CL_ERROR_PROCESSING_EXTERNALS &&
+ !location.isCancelProcessOnExternalsFail()) {
+ // we should not fail if external failed
+ } else {
+ e.printStackTrace(listener.error("Failed to check out " + location.remote));
+ throw new IOException("Failed to check out " + location.remote, e);
+ }
} finally {
try {
pos.close();
diff --git a/src/main/java/hudson/scm/subversion/SubversionUpdateEventHandler.java b/src/main/java/hudson/scm/subversion/SubversionUpdateEventHandler.java
index c48e86554..3cea647da 100755
--- a/src/main/java/hudson/scm/subversion/SubversionUpdateEventHandler.java
+++ b/src/main/java/hudson/scm/subversion/SubversionUpdateEventHandler.java
@@ -32,6 +32,7 @@
import org.tmatesoft.svn.core.SVNCancelException;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.wc.ISVNExternalsHandler;
import org.tmatesoft.svn.core.wc.SVNEvent;
@@ -139,10 +140,10 @@ public void handleEvent(SVNEvent event, double progress) throws SVNException {
+ " failed!");
}
- if (cancelProcessOnExternalsFailed) {
- throw new SVNException(new RemotableSVNErrorMessage(SVNErrorCode.CL_ERROR_PROCESSING_EXTERNALS,
- SVNErrorCode.CL_ERROR_PROCESSING_EXTERNALS.getDescription() + ": <" + file.getName() + ">"));
- }
+ SVNErrorMessage error = new RemotableSVNErrorMessage(SVNErrorCode.CL_ERROR_PROCESSING_EXTERNALS,
+ SVNErrorCode.CL_ERROR_PROCESSING_EXTERNALS.getDescription() + ": <" + file.getName() + ">");
+
+ throw new SVNException(error, new SVNException(event.getErrorMessage()));
}
super.handleEvent(event, progress);
diff --git a/src/main/java/hudson/scm/subversion/UpdateUpdater.java b/src/main/java/hudson/scm/subversion/UpdateUpdater.java
index 5b30261d8..5fbaece22 100755
--- a/src/main/java/hudson/scm/subversion/UpdateUpdater.java
+++ b/src/main/java/hudson/scm/subversion/UpdateUpdater.java
@@ -178,6 +178,11 @@ public List perform() throws IOException, InterruptedException {
} catch (final SVNException e) {
SVNException cause = e;
do {
+ if (location.isCancelProcessOnExternalsFail() &&
+ cause.getErrorMessage().getErrorCode() == SVNErrorCode.CL_ERROR_PROCESSING_EXTERNALS) {
+ break;
+ }
+
SVNErrorCode errorCode = cause.getErrorMessage().getErrorCode();
if (errorCode == SVNErrorCode.WC_LOCKED) {
// work space locked. try fresh check out
diff --git a/src/test/java/hudson/scm/SubversionSCMTest.java b/src/test/java/hudson/scm/SubversionSCMTest.java
index 8f62e0f70..c53975056 100644
--- a/src/test/java/hudson/scm/SubversionSCMTest.java
+++ b/src/test/java/hudson/scm/SubversionSCMTest.java
@@ -66,6 +66,9 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@@ -1807,4 +1810,80 @@ private void invokeTestPollingExternalsForFile() throws Exception {
// should detect change
assertTrue(p.poll(StreamTaskListener.fromStdout()).hasChanges());
}
+
+ void lockWorkspace(String dbFile, String pathToLock) throws Exception {
+ Class.forName("org.sqlite.SQLiteJDBCLoader");
+ Connection conn = DriverManager.getConnection("jdbc:sqlite:" + dbFile);
+ PreparedStatement stmt = conn.prepareStatement("INSERT INTO WC_LOCK VALUES(1, ?, 1)");
+ stmt.setString(1, pathToLock);
+ stmt.executeUpdate();
+ conn.close();
+ }
+
+ @Issue("JENKINS-47803")
+ @Test
+ public void lockedExternal() throws Exception {
+ Proc p = runSvnServe(getClass().getResource("JENKINS-777.zip"));
+
+ try {
+ configureSvnWorkspaceFormat(SubversionWorkspaceSelector.WC_FORMAT_17);
+
+ FreeStyleProject b = r.createFreeStyleProject();
+
+ ModuleLocation[] locations = {
+ new ModuleLocation("svn://localhost/jenkins-777/proja", "proja", "infinity", false)
+ };
+
+ // do initial checkout
+ b.setScm(new SubversionSCM(Arrays.asList(locations), new UpdateUpdater(), null, null, null, null, null, null));
+ FreeStyleBuild build = r.assertBuildStatusSuccess(b.scheduleBuild2(0));
+
+ // Check that the external exists
+ FilePath ws = build.getWorkspace();
+ assertTrue(ws.child("proja").child("externals").child("projb").exists());
+
+ // mark external as locked
+ String db = ws.child("proja").child("externals").child("projb").child(".svn").child("wc.db").getRemote();
+ lockWorkspace(db, "externals");
+
+ // start second build and check if workspace is being reset (just check for the log)
+ build = r.assertBuildStatusSuccess(b.scheduleBuild2(0));
+ r.assertLogContains("Workspace appear to be locked, so getting a fresh workspace", build);
+ } finally {
+ p.kill();
+ }
+ }
+
+ @Test
+ public void ensureCancelOnExternalsFail() throws Exception {
+ Proc p = runSvnServe(getClass().getResource("JENKINS-777.zip"));
+
+ try {
+ configureSvnWorkspaceFormat(SubversionWorkspaceSelector.WC_FORMAT_17);
+
+ FreeStyleProject b = r.createFreeStyleProject();
+
+ ModuleLocation[] locations = {
+ new ModuleLocation("svn://localhost/jenkins-777/proja", null, "proja", "infinity", false, true)
+ };
+
+ // do initial checkout
+ b.setScm(new SubversionSCM(Arrays.asList(locations), new UpdateUpdater(), null, null, null, null, null, null));
+ FreeStyleBuild build = r.assertBuildStatusSuccess(b.scheduleBuild2(0));
+
+ // Check that the external exists
+ FilePath ws = build.getWorkspace();
+ assertTrue(ws.child("proja").child("externals").child("projb").exists());
+
+ // mark external as locked
+ String db = ws.child("proja").child("externals").child("projb").child(".svn").child("wc.db").getRemote();
+ lockWorkspace(db, "externals");
+
+ // start second build and check if build is canceled, caused by external failure
+ build = r.assertBuildStatus(Result.FAILURE, b.scheduleBuild2(0));
+ r.assertLogContains("Failed processing one or more externals definitions", build);
+ } finally {
+ p.kill();
+ }
+ }
}