Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions src/main/java/hudson/scm/SubversionSCM.java
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@
private boolean ignoreDirPropChanges;
private boolean filterChangelog;
private boolean quietOperation;
private boolean cleanupOnLockedWorkspace;

/**
* A cache of the svn:externals (keyed by project).
Expand Down Expand Up @@ -369,16 +370,29 @@
String excludedRevprop, String excludedCommitMessages,
String includedRegions, boolean ignoreDirPropChanges, boolean filterChangelog,
List<AdditionalCredentials> 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<ModuleLocation> locations, WorkspaceUpdater workspaceUpdater,

Check warning on line 380 in src/main/java/hudson/scm/SubversionSCM.java

View check run for this annotation

ci.jenkins.io / Java Compiler

compiler:compile

NORMAL: deprecated item is not annotated with @deprecated
SubversionRepositoryBrowser browser, String excludedRegions, String excludedUsers,
String excludedRevprop, String excludedCommitMessages,
String includedRegions, boolean ignoreDirPropChanges, boolean filterChangelog,
List<AdditionalCredentials> additionalCredentials, boolean quietOperation) {
this(locations, workspaceUpdater, browser, excludedRegions, excludedUsers, excludedRevprop, excludedCommitMessages,
includedRegions, ignoreDirPropChanges, filterChangelog, additionalCredentials, quietOperation, false);
}

@DataBoundConstructor
public SubversionSCM(List<ModuleLocation> locations, WorkspaceUpdater workspaceUpdater,
SubversionRepositoryBrowser browser, String excludedRegions, String excludedUsers,
String excludedRevprop, String excludedCommitMessages,
String includedRegions, boolean ignoreDirPropChanges, boolean filterChangelog,
List<AdditionalCredentials> additionalCredentials, boolean quietOperation,
boolean cleanupOnLockedWorkspace) {
for (Iterator<ModuleLocation> itr = locations.iterator(); itr.hasNext(); ) {
ModuleLocation ml = itr.next();
String remote = Util.fixEmptyAndTrim(ml.remote);
Expand All @@ -403,6 +417,7 @@
this.ignoreDirPropChanges = ignoreDirPropChanges;
this.filterChangelog = filterChangelog;
this.quietOperation = quietOperation;
this.cleanupOnLockedWorkspace = cleanupOnLockedWorkspace;
}

/**
Expand Down Expand Up @@ -714,6 +729,11 @@
return quietOperation;
}

@Exported
public boolean isCleanupOnLockedWorkspace() {
return cleanupOnLockedWorkspace;
}

/**
* Convenience method solely for testing.
*/
Expand Down Expand Up @@ -965,7 +985,8 @@
Set<String> 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<External> externals = new ArrayList<>(workspace.act(checkOutTask));
// save location <---> externals maps
externalsMap.put(location.remote, externals);
Expand Down Expand Up @@ -1046,15 +1067,17 @@
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;
this.location = location;
this.revisions = build.getAction(RevisionParameterAction.class);
this.task = parent.getWorkspaceUpdater().createTask(workspaceFormat);
this.quietOperation = quietOperation;
this.cleanupOnLockedWorkspace = cleanupOnLockedWorkspace;
}

List<External> run(File ws) throws IOException {
Expand Down
42 changes: 42 additions & 0 deletions src/main/java/hudson/scm/subversion/UpdateUpdater.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -155,97 +160,134 @@
fmt.format(r.getDate()) : r.toString();

svnuc.setIgnoreExternals(location.isIgnoreExternalsOption());
if (cleanupOnLockedWorkspace) {

Check warning on line 163 in src/main/java/hudson/scm/subversion/UpdateUpdater.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 163 is only partially covered, one branch is missing
cleanupWorkspaceIfLocked(local);
}
preUpdate(location, local);
SVNDepth svnDepth = location.getSvnDepthForUpdate();

switch (svnCommand) {
case UPDATE:
listener.getLogger().println("Updating " + location.remote + " at revision "
+ revisionName + (quietOperation ? " --quiet" : ""));
svnuc.doUpdate(local.getCanonicalFile(), r, svnDepth, true, true);
break;
case SWITCH:
listener.getLogger().println("Switching to " + location.remote + " at revision "
+ revisionName + (quietOperation ? " --quiet" : ""));
svnuc.doSwitch(local.getCanonicalFile(), location.getSVNURL(), r, r, svnDepth, true, true, true);
break;
case CHECKOUT:
// This case is handled by the (svnCommand == SvnCommandToUse.CHECKOUT) above.
break;
}
} catch (SVNCancelException e) {
e.printStackTrace(listener.getLogger());
if (isAuthenticationFailedError(e)) {
throw new AbortException("Failed to check out " + location.remote);
} else {
listener.error("Subversion update has been canceled");
throw (InterruptedException)new InterruptedException().initCause(e);
}
} catch (final SVNException e) {
SVNException cause = e;
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);
}
if (errorCode == SVNErrorCode.WC_OBSTRUCTED_UPDATE) {
// HUDSON-1882. If existence of local files cause an update to fail,
// revert to fresh check out
listener.getLogger().println(e.getMessage()); // show why this happened. Sometimes this is caused by having a build artifact in the repository.
listener.getLogger().println("Updated failed due to local files. Getting a fresh workspace");
return delegateTo(new CheckoutUpdater(), workspaceFormat);
}
if (errorCode == SVNErrorCode.WC_CORRUPT_TEXT_BASE || errorCode == SVNErrorCode.WC_CORRUPT || errorCode == SVNErrorCode.WC_UNWIND_EMPTY) {
// JENKINS-14550. if working copy is corrupted, revert to fresh check out
listener.getLogger().println(e.getMessage()); // show why this happened. Sometimes this is caused by having a build artifact in the repository.
listener.getLogger().println("Updated failed due to working copy corruption. Getting a fresh workspace");
return delegateTo(new CheckoutUpdater(), workspaceFormat);
}
// trouble-shooting probe for #591
if (errorCode == SVNErrorCode.WC_NOT_LOCKED) {
Jenkins instance = Jenkins.getInstanceOrNull();
if (instance != null) {
listener.getLogger().println("Polled jobs are " + instance.getDescriptorByType(SCMTrigger.DescriptorImpl.class).getItemsBeingPolled());
}
return delegateTo(new CheckoutUpdater(), workspaceFormat);
}

// recurse as long as we encounter nested SVNException
} while (null != (cause = getNestedSVNException(cause)));

e.printStackTrace(listener.error("Failed to update " + location.remote));
listener.error("Subversion update failed");
throw new IOException(new UpdaterException("failed to perform svn update", e));
}

return externals;
}

/**
* Retrieve nested SVNException.
* svnkit use to hide the root cause within nested {@link SVNException}. Also, SVNException cause in many cases
* is a {@link SVNErrorMessage}, that itself has a lower level SVNException as cause, and so on.
*/
private SVNException getNestedSVNException(Throwable e) {
Throwable t = e.getCause();
if (t instanceof SVNException) return (SVNException) t;
return null;
}


/**
* Hook for subtype to perform some cleanup activity before "svn update" takes place.
*
* @param module
* Remote repository that corresponds to the workspace.
* @param local
* Local directory that gets the update from the module.
*/
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();

Check warning on line 288 in src/main/java/hudson/scm/subversion/UpdateUpdater.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 164-288 are not covered by tests
}
}
}

@Extension(ordinal=100) // this is the default, so given a higher ordinal
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/hudson/scm/subversion/WorkspaceUpdater.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -164,6 +166,7 @@ protected List<External> 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();
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/hudson/scm/SubversionSCM/config.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ THE SOFTWARE.
<f:checkbox default="true"/>
</f:entry>

<f:entry title="${%Cleanup workspace when locked}" field="cleanupOnLockedWorkspace">
<f:checkbox default="false"/>
</f:entry>

<j:set var="scm" value="${instance}"/>
<t:listScmBrowsers name="svn.browser" />
<f:advanced>
Expand Down
Loading