From f3c1175f338b56eb80d0ed3571ccb5d6a59f571f Mon Sep 17 00:00:00 2001 From: Johno Crawford Date: Thu, 8 May 2025 18:25:10 +0200 Subject: [PATCH] Add support for cleaning workspace when it is locked. --- src/main/java/hudson/scm/SubversionSCM.java | 33 ++++++++++++--- .../hudson/scm/subversion/UpdateUpdater.java | 42 +++++++++++++++++++ .../scm/subversion/WorkspaceUpdater.java | 3 ++ .../hudson/scm/SubversionSCM/config.jelly | 4 ++ 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/src/main/java/hudson/scm/SubversionSCM.java b/src/main/java/hudson/scm/SubversionSCM.java index f369f911..3e824465 100755 --- a/src/main/java/hudson/scm/SubversionSCM.java +++ b/src/main/java/hudson/scm/SubversionSCM.java @@ -266,6 +266,7 @@ public class SubversionSCM extends SCM { private boolean ignoreDirPropChanges; private boolean filterChangelog; private boolean quietOperation; + private boolean cleanupOnLockedWorkspace; /** * A cache of the svn:externals (keyed by project). @@ -369,16 +370,29 @@ public SubversionSCM(List locations, WorkspaceUpdater workspaceU String excludedRevprop, String excludedCommitMessages, String includedRegions, boolean ignoreDirPropChanges, boolean filterChangelog, List additionalCredentials) { - this(locations, workspaceUpdater, browser, excludedRegions, excludedUsers, excludedRevprop, excludedCommitMessages, + this(locations, workspaceUpdater, browser, excludedRegions, excludedUsers, excludedRevprop, excludedCommitMessages, includedRegions, ignoreDirPropChanges, filterChangelog, additionalCredentials, false); } - @DataBoundConstructor + /** + * @deprecated by cleanupOnLockedWorkspace + */ public SubversionSCM(List locations, WorkspaceUpdater workspaceUpdater, SubversionRepositoryBrowser browser, String excludedRegions, String excludedUsers, String excludedRevprop, String excludedCommitMessages, String includedRegions, boolean ignoreDirPropChanges, boolean filterChangelog, List additionalCredentials, boolean quietOperation) { + this(locations, workspaceUpdater, browser, excludedRegions, excludedUsers, excludedRevprop, excludedCommitMessages, + includedRegions, ignoreDirPropChanges, filterChangelog, additionalCredentials, quietOperation, false); + } + + @DataBoundConstructor + public SubversionSCM(List locations, WorkspaceUpdater workspaceUpdater, + SubversionRepositoryBrowser browser, String excludedRegions, String excludedUsers, + String excludedRevprop, String excludedCommitMessages, + String includedRegions, boolean ignoreDirPropChanges, boolean filterChangelog, + List additionalCredentials, boolean quietOperation, + boolean cleanupOnLockedWorkspace) { for (Iterator itr = locations.iterator(); itr.hasNext(); ) { ModuleLocation ml = itr.next(); String remote = Util.fixEmptyAndTrim(ml.remote); @@ -403,6 +417,7 @@ public SubversionSCM(List locations, WorkspaceUpdater workspaceU this.ignoreDirPropChanges = ignoreDirPropChanges; this.filterChangelog = filterChangelog; this.quietOperation = quietOperation; + this.cleanupOnLockedWorkspace = cleanupOnLockedWorkspace; } /** @@ -714,6 +729,11 @@ public boolean isQuietOperation() { return quietOperation; } + @Exported + public boolean isCleanupOnLockedWorkspace() { + return cleanupOnLockedWorkspace; + } + /** * Convenience method solely for testing. */ @@ -965,7 +985,8 @@ private Map> checkout(Run build, FilePath workspace, Task Set unauthenticatedRealms = new LinkedHashSet<>(); for (ModuleLocation location : getLocations(env, build)) { CheckOutTask checkOutTask = - new CheckOutTask(new CheckOutUpdateTask(build, this, location, build.getTimestamp().getTime(), listener, env, quietOperation)); + new CheckOutTask(new CheckOutUpdateTask(build, this, location, build.getTimestamp().getTime(), + listener, env, quietOperation, cleanupOnLockedWorkspace)); List externals = new ArrayList<>(workspace.act(checkOutTask)); // save location <---> externals maps externalsMap.put(location.remote, externals); @@ -1046,8 +1067,9 @@ private static class CheckOutUpdateTask extends UpdateTask { private final boolean storeAuthToDisk = descriptor().isStoreAuthToDisk(); private final int workspaceFormat = descriptor().getWorkspaceFormat(); - CheckOutUpdateTask(Run build, SubversionSCM parent, ModuleLocation location, Date timestamp, - TaskListener listener, EnvVars env, boolean quietOperation) { + CheckOutUpdateTask(Run build, SubversionSCM parent, ModuleLocation location, Date timestamp, + TaskListener listener, EnvVars env, boolean quietOperation, + boolean cleanupOnLockedWorkspace) { this.authProvider = parent.createAuthenticationProvider(build.getParent(), location, listener); this.timestamp = timestamp; this.listener = listener; @@ -1055,6 +1077,7 @@ private static class CheckOutUpdateTask extends UpdateTask { this.revisions = build.getAction(RevisionParameterAction.class); this.task = parent.getWorkspaceUpdater().createTask(workspaceFormat); this.quietOperation = quietOperation; + this.cleanupOnLockedWorkspace = cleanupOnLockedWorkspace; } List run(File ws) throws IOException { diff --git a/src/main/java/hudson/scm/subversion/UpdateUpdater.java b/src/main/java/hudson/scm/subversion/UpdateUpdater.java index ecf1b844..e3808df5 100755 --- a/src/main/java/hudson/scm/subversion/UpdateUpdater.java +++ b/src/main/java/hudson/scm/subversion/UpdateUpdater.java @@ -39,6 +39,11 @@ import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc.SVNUpdateClient; import org.tmatesoft.svn.core.wc.SVNWCClient; +import org.tmatesoft.svn.core.wc2.SvnCleanup; +import org.tmatesoft.svn.core.wc2.SvnGetStatus; +import org.tmatesoft.svn.core.wc2.SvnOperationFactory; +import org.tmatesoft.svn.core.wc2.SvnStatus; +import org.tmatesoft.svn.core.wc2.SvnTarget; import java.io.File; import java.io.IOException; @@ -155,6 +160,9 @@ public List perform() throws IOException, InterruptedException { fmt.format(r.getDate()) : r.toString(); svnuc.setIgnoreExternals(location.isIgnoreExternalsOption()); + if (cleanupOnLockedWorkspace) { + cleanupWorkspaceIfLocked(local); + } preUpdate(location, local); SVNDepth svnDepth = location.getSvnDepthForUpdate(); @@ -186,6 +194,9 @@ public List perform() throws IOException, InterruptedException { do { SVNErrorCode errorCode = cause.getErrorMessage().getErrorCode(); if (errorCode == SVNErrorCode.WC_LOCKED) { + if (cleanupOnLockedWorkspace) { + throw new IOException(new UpdaterException("failed to perform svn cleanup, workspace is still locked", e)); + } // work space locked. try fresh check out listener.getLogger().println("Workspace appear to be locked, so getting a fresh workspace"); return delegateTo(new CheckoutUpdater(), workspaceFormat); @@ -246,6 +257,37 @@ private SVNException getNestedSVNException(Throwable e) { protected void preUpdate(ModuleLocation module, File local) throws SVNException, IOException { // noop by default } + + private void cleanupWorkspaceIfLocked(File local) throws IOException, SVNException { + if (!isWorkspaceLocked(local)) { + return; + } + listener.getLogger().println("Workspace appears to be locked, attempting to run 'svn cleanup'..."); + SvnOperationFactory operationFactory = new SvnOperationFactory(); + try { + SvnCleanup svnCleanup = operationFactory.createCleanup(); + svnCleanup.setSingleTarget(SvnTarget.fromFile(local.getCanonicalFile())); + svnCleanup.setBreakLocks(true); + svnCleanup.run(); + } finally { + operationFactory.dispose(); + } + listener.getLogger().println("Cleanup completed."); + } + + private boolean isWorkspaceLocked(File local) throws IOException, SVNException { + SvnOperationFactory operationFactory = new SvnOperationFactory(); + try { + SvnGetStatus statusOp = operationFactory.createGetStatus(); + statusOp.setSingleTarget(SvnTarget.fromFile(local.getCanonicalFile())); + statusOp.setDepth(SVNDepth.EMPTY); // avoid recursion + statusOp.setRemote(false); + SvnStatus status = statusOp.run(); + return status != null && status.isWcLocked(); + } finally { + operationFactory.dispose(); + } + } } @Extension(ordinal=100) // this is the default, so given a higher ordinal diff --git a/src/main/java/hudson/scm/subversion/WorkspaceUpdater.java b/src/main/java/hudson/scm/subversion/WorkspaceUpdater.java index 08de21ab..cc0aaafe 100644 --- a/src/main/java/hudson/scm/subversion/WorkspaceUpdater.java +++ b/src/main/java/hudson/scm/subversion/WorkspaceUpdater.java @@ -138,6 +138,8 @@ public static abstract class UpdateTask implements SerializableOnlyOverRemoting */ public boolean quietOperation; + public boolean cleanupOnLockedWorkspace; + /** * If the build parameter is specified with specific version numbers, this field captures that. Can be null. */ @@ -164,6 +166,7 @@ protected List delegateTo(UpdateTask t) throws IOException, Interrupte t.revisions = this.revisions; t.ws = this.ws; t.quietOperation = this.quietOperation; + t.cleanupOnLockedWorkspace = this.cleanupOnLockedWorkspace; return t.perform(); } diff --git a/src/main/resources/hudson/scm/SubversionSCM/config.jelly b/src/main/resources/hudson/scm/SubversionSCM/config.jelly index 33d433c4..3fe3ddc6 100644 --- a/src/main/resources/hudson/scm/SubversionSCM/config.jelly +++ b/src/main/resources/hudson/scm/SubversionSCM/config.jelly @@ -38,6 +38,10 @@ THE SOFTWARE. + + + +