/******************************************************************************* * * Copyright (c) 2004-2009 Oracle Corporation. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * Kohsuke Kawaguchi, Fulvio Cavarretta, Jean-Baptiste Quenot, Luca Domenico Milanesio, * Renaud Bruyeron, Stephen Connolly, Tom Huybrechts, Yahoo! Inc., Manufacture Francaise des Pneumatiques Michelin, * Romain Seguy * *******************************************************************************/ package hudson.scm.subversion; import hudson.Extension; import hudson.model.Hudson; import hudson.scm.SubversionSCM.External; import hudson.scm.SubversionSCM.ModuleLocation; import hudson.scm.SubversionSCM.SvnInfo; import hudson.triggers.SCMTrigger; import org.kohsuke.stapler.DataBoundConstructor; import org.tmatesoft.svn.core.SVNDepth; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.wc.SVNInfo; import org.tmatesoft.svn.core.wc.SVNRevision; import org.tmatesoft.svn.core.wc.SVNUpdateClient; import org.tmatesoft.svn.core.wc.SVNWCClient; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * {@link WorkspaceUpdater} that uses "svn update" as much as possible. * * @author Kohsuke Kawaguchi */ public class UpdateUpdater extends WorkspaceUpdater { @DataBoundConstructor public UpdateUpdater() { } @Override public UpdateTask createTask() { return new TaskImpl(); } public static class TaskImpl extends UpdateTask { /** * Returns true if we can use "svn update" instead of "svn checkout" */ protected boolean isUpdatable() throws IOException { for (ModuleLocation l : locations) { String moduleName = l.getLocalDir(); File module = new File(ws,moduleName).getCanonicalFile(); // canonicalize to remove ".." and ".". See #474 if(!module.exists()) { listener.getLogger().println("Checking out a fresh workspace because "+module+" doesn't exist"); return false; } try { SVNInfo svnkitInfo = parseSvnInfo(module); SvnInfo svnInfo = new SvnInfo(svnkitInfo); String url = l.getURL(); if(!svnInfo.url.equals(url)) { listener.getLogger().println("Checking out a fresh workspace because the workspace is not "+url); return false; } } catch (SVNException e) { if (e.getErrorMessage().getErrorCode()==SVNErrorCode.WC_NOT_DIRECTORY) { listener.getLogger().println("Checking out a fresh workspace because there's no workspace at "+module); } else { listener.getLogger().println("Checking out a fresh workspace because Hudson failed to detect the current workspace "+module); e.printStackTrace(listener.error(e.getMessage())); } return false; } } return true; } /** * Gets the SVN metadata for the given local workspace. * * @param workspace * The target to run "svn info". */ private SVNInfo parseSvnInfo(File workspace) throws SVNException { final SVNWCClient svnWc = manager.getWCClient(); return svnWc.doInfo(workspace,SVNRevision.WORKING); } @Override public List<External> perform() throws IOException, InterruptedException { if (!isUpdatable()) return delegateTo(new CheckoutUpdater()); final SVNUpdateClient svnuc = manager.getUpdateClient(); final List<External> externals = new ArrayList<External>(); // store discovered externals to here for (final ModuleLocation l : locations) { try { File local = new File(ws, l.getLocalDir()); svnuc.setEventHandler(new SubversionUpdateEventHandler(listener.getLogger(), externals, local, l.getLocalDir())); svnuc.setIgnoreExternals(l.isIgnoreExternalsOption()); preUpdate(l, local); SVNDepth svnDepth = getSvnDepth(l.getDepthOption()); SVNRevision revision = getRevision(l); listener.getLogger().println("Updating " + l.remote + " revision: " + (revision != null ? revision.toString() : "null") + " depth:" + svnDepth + " ignoreExternals: " + l.isIgnoreExternalsOption()); svnuc.doUpdate(local.getCanonicalFile(), revision, svnDepth, true, false); } catch (final SVNException e) { //TODO find better solution than this workaround, svnkit uses the same exception and // the same error code in case of aborted builds and builds with invalid credentials if (e.getMessage() != null && e.getMessage().contains(SVN_CANCEL_EXCEPTION_MESSAGE)) { listener.error("Svn command was aborted"); throw (InterruptedException) new InterruptedException().initCause(e); } if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_LOCKED) { // work space locked. try fresh check out listener.getLogger().println("Workspace appear to be locked, so getting a fresh workspace"); return delegateTo(new CheckoutUpdater()); } if (e.getErrorMessage().getErrorCode() == 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()); } e.printStackTrace(listener.error("Failed to update " + l.remote)); // trouble-shooting probe for #591 if (e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_NOT_LOCKED) { listener.getLogger().println("Polled jobs are " + Hudson.getInstance().getDescriptorByType(SCMTrigger.DescriptorImpl.class).getItemsBeingPolled()); } return null; } } return externals; } /** * 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 } } @Extension(ordinal=100) // this is the default, so given a higher ordinal public static class DescriptorImpl extends WorkspaceUpdaterDescriptor { @Override public String getDisplayName() { return Messages.UpdateUpdater_DisplayName(); } } }