Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* The MIT License
*
* Copyright (c) 2004-2010, Sun Microsystems, Inc., 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, Anton Kozak, Nikita Levyankov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.scm;
import hudson.Extension;
import hudson.FilePath;
import hudson.FilePath.FileCallable;
import hudson.Functions;
import hudson.Launcher;
import hudson.Util;
import hudson.XmlFile;
import hudson.model.BuildListener;
import hudson.model.TaskListener;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Computer;
import hudson.model.Hudson;
import hudson.model.Hudson.MasterComputer;
import hudson.model.Node;
import hudson.model.ParametersAction;
import hudson.model.Run;
import hudson.remoting.Channel;
import hudson.remoting.DelegatingCallable;
import hudson.remoting.VirtualChannel;
import hudson.scm.PollingResult.Change;
import hudson.scm.UserProvidedCredential.AuthenticationManagerImpl;
import hudson.scm.auth.ISVNAuthenticationManager;
import hudson.scm.auth.ISVNAuthenticationOutcomeListener;
import hudson.scm.subversion.Messages;
import hudson.scm.subversion.WorkspaceUpdaterDescriptor;
import hudson.scm.subversion.CheckoutUpdater;
import hudson.scm.subversion.UpdateUpdater;
import hudson.scm.subversion.UpdateWithRevertUpdater;
import hudson.scm.subversion.WorkspaceUpdater;
import hudson.scm.subversion.WorkspaceUpdater.UpdateTask;
import hudson.slaves.NodeProperty;
import hudson.slaves.EnvironmentVariablesNodeProperty;
import hudson.util.EditDistance;
import hudson.util.FormValidation;
import hudson.util.MultipartFormDataParser;
import hudson.util.TimeUnit2;
import hudson.util.XStream2;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StreamCorruptedException;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.servlet.ServletException;
import javax.xml.transform.stream.StreamResult;
import net.sf.json.JSONObject;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.tmatesoft.svn.core.ISVNLogEntryHandler;
import org.tmatesoft.svn.core.SVNAuthenticationException;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNDirEntry;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLogEntry;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationProvider;
import org.tmatesoft.svn.core.auth.SVNAuthentication;
import org.tmatesoft.svn.core.auth.SVNUserNameAuthentication;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.dav.http.DefaultHTTPConnectionFactory;
import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.internal.wc.SVNExternal;
import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminAreaFactory;
import org.tmatesoft.svn.core.io.SVNCapability;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNInfo;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNWCClient;
import org.tmatesoft.svn.core.wc.SVNWCUtil;
import com.thoughtworks.xstream.XStream;
import com.trilead.ssh2.DebugLogger;
import com.trilead.ssh2.SCPClient;
/**
* Subversion SCM.
*
*
Plugin Developer Notes
*
* Plugins that interact with Subversion can use {@link DescriptorImpl#createAuthenticationProvider(AbstractProject)}
* so that it can use the credentials (username, password, etc.) that the user entered for Hudson.
* See the javadoc of this method for the precautions you need to take if you run Subversion operations
* remotely on slaves.
*
*
Implementation Notes
*
* Because this instance refers to some other classes that are not necessarily
* Java serializable (like {@link #browser}), remotable {@link FileCallable}s all
* need to be declared as static inner classes.
*
* @author Kohsuke Kawaguchi
*/
public class SubversionSCM extends SCM implements Serializable {
protected static final String UNDEFINED_REVISION_VALUE = "UNDEFINED";
/**
* the locations field is used to store all configured SVN locations (with
* their local and remote part). Direct access to this field should be
* avoided and the getLocations() method should be used instead. This is
* needed to make importing of old hudson-configurations possible as
* getLocations() will check if the modules field has been set and import
* the data.
*
* @since 1.91
*/
private ModuleLocation[] locations = new ModuleLocation[0];
private final SubversionRepositoryBrowser browser;
private String excludedRegions;
private String includedRegions;
private String excludedUsers;
/**
* Revision property names that are ignored for the sake of polling. Whitespace separated, possibly null.
*/
private String excludedRevprop;
private String excludedCommitMessages;
private WorkspaceUpdater workspaceUpdater;
private boolean ignoreCommitNotification;
// No longer in use but left for serialization compatibility.
@Deprecated
private String modules;
private static File subversionConfigDir;
/**
* Supported revision policies.
*/
public enum RevisionPolicy {
QUEUE_TIME("Queue time"),
BUILD_TIME("Build time"),
HEAD("Head revision");
private String policyName;
/**
* Creates the policy.
*
* @param policyName policy name.
*/
RevisionPolicy(String policyName) {
this.policyName = policyName;
}
/**
* Returns the name of policy.
*
* @return the name of policy.
*/
public String getPolicyName() {
return this.policyName;
}
}
// No longer used but left for serialization compatibility
@Deprecated
private Boolean useUpdate;
@Deprecated
private Boolean doRevert;
/**
* @deprecated as of 1.286
*/
public SubversionSCM(String[] remoteLocations, String[] localLocations,
boolean useUpdate, SubversionRepositoryBrowser browser) {
this(remoteLocations, localLocations, useUpdate, browser, null, null, null);
}
/**
* @deprecated as of 1.311
*/
public SubversionSCM(String[] remoteLocations, String[] localLocations,
boolean useUpdate, SubversionRepositoryBrowser browser, String excludedRegions) {
this(ModuleLocation.parse(remoteLocations, localLocations, null, null), useUpdate, false, browser,
excludedRegions, null,
null, null);
}
/**
* @deprecated as of 1.315
*/
public SubversionSCM(String[] remoteLocations, String[] localLocations,
boolean useUpdate, SubversionRepositoryBrowser browser, String excludedRegions,
String excludedUsers, String excludedRevprop) {
this(ModuleLocation.parse(remoteLocations, localLocations, null, null), useUpdate, false, browser,
excludedRegions,
excludedUsers, excludedRevprop, null);
}
/**
* @deprecated as of 1.315
*/
public SubversionSCM(List locations,
boolean useUpdate, SubversionRepositoryBrowser browser, String excludedRegions) {
this(locations, useUpdate, false, browser, excludedRegions, null, null, null);
}
/**
* @deprecated as of 1.324
*/
public SubversionSCM(List locations,
boolean useUpdate, SubversionRepositoryBrowser browser, String excludedRegions,
String excludedUsers, String excludedRevprop) {
this(locations, useUpdate, false, browser, excludedRegions, excludedUsers, excludedRevprop, null);
}
/**
* @deprecated as of 1.328
*/
public SubversionSCM(List locations,
boolean useUpdate, SubversionRepositoryBrowser browser, String excludedRegions,
String excludedUsers, String excludedRevprop, String excludedCommitMessages) {
this(locations, useUpdate, false, browser, excludedRegions, excludedUsers, excludedRevprop,
excludedCommitMessages);
}
/**
* @deprecated as of 1.xxx
*/
public SubversionSCM(List locations,
boolean useUpdate, boolean doRevert, SubversionRepositoryBrowser browser,
String excludedRegions, String excludedUsers, String excludedRevprop,
String excludedCommitMessages) {
this(locations, useUpdate, doRevert, browser, excludedRegions, excludedUsers, excludedRevprop,
excludedCommitMessages, null);
}
/**
* @deprecated as of 1.23
*/
public SubversionSCM(List locations,
boolean useUpdate, boolean doRevert, SubversionRepositoryBrowser browser,
String excludedRegions, String excludedUsers, String excludedRevprop,
String excludedCommitMessages,
String includedRegions) {
this(locations,
useUpdate ? (doRevert ? new UpdateWithRevertUpdater() : new UpdateUpdater()) : new CheckoutUpdater(),
browser, excludedRegions, excludedUsers, excludedRevprop, excludedCommitMessages, includedRegions);
}
/**
* @deprecated as of 2.3.8
*/
public SubversionSCM(List locations, WorkspaceUpdater workspaceUpdater,
SubversionRepositoryBrowser browser, String excludedRegions, String excludedUsers,
String excludedRevprop, String excludedCommitMessages,
String includedRegions) {
this(locations, workspaceUpdater, browser, excludedRegions, excludedUsers, excludedRevprop,
excludedCommitMessages, includedRegions, false);
}
@DataBoundConstructor
public SubversionSCM(List locations, WorkspaceUpdater workspaceUpdater,
SubversionRepositoryBrowser browser, String excludedRegions, String excludedUsers,
String excludedRevprop, String excludedCommitMessages,
String includedRegions, boolean ignoreCommitNotification) {
for (Iterator itr = locations.iterator(); itr.hasNext(); ) {
ModuleLocation ml = itr.next();
if (ml.remote == null) {
itr.remove();
}
}
this.locations = locations.toArray(new ModuleLocation[locations.size()]);
this.workspaceUpdater = workspaceUpdater;
this.browser = browser;
this.excludedRegions = excludedRegions;
this.excludedUsers = excludedUsers;
this.excludedRevprop = excludedRevprop;
this.excludedCommitMessages = excludedCommitMessages;
this.includedRegions = includedRegions;
this.ignoreCommitNotification = ignoreCommitNotification;
}
/**
* Convenience constructor, especially during testing.
*/
public SubversionSCM(String svnUrl) {
this(svnUrl, ".");
}
/**
* Convenience constructor, especially during testing.
*/
public SubversionSCM(String svnUrl, String local) {
this(new String[]{svnUrl}, new String[]{local}, true, null, null, null, null);
}
/**
* @deprecated as of 1.91. Use {@link #getLocations()} instead.
*/
public String getModules() {
return null;
}
/**
* list of all configured svn locations
*
* @since 1.91
*/
@Exported
public ModuleLocation[] getLocations() {
return getLocations(null);
}
@Exported
public WorkspaceUpdater getWorkspaceUpdater() {
if (workspaceUpdater != null) {
return workspaceUpdater;
}
// data must have been read from old configuration.
if (useUpdate != null && !useUpdate.booleanValue()) {
return new CheckoutUpdater();
}
if (doRevert != null && doRevert.booleanValue()) {
return new UpdateWithRevertUpdater();
}
return new UpdateUpdater();
}
public void setWorkspaceUpdater(WorkspaceUpdater workspaceUpdater) {
this.workspaceUpdater = workspaceUpdater;
}
public boolean isIgnoreCommitNotification() {
return ignoreCommitNotification;
}
/**
* list of all configured svn locations, expanded according to
* build parameters values;
*
* @param build If non-null, variable expansions are performed against the build parameters.
* @since 1.252
*/
public ModuleLocation[] getLocations(AbstractBuild, ?> build) {
// check if we've got a old location
if (modules != null) {
// import the old configuration
List oldLocations = new ArrayList();
StringTokenizer tokens = new StringTokenizer(modules);
while (tokens.hasMoreTokens()) {
// the remote (repository location)
// the normalized name is always without the trailing '/'
String remoteLoc = Util.removeTrailingSlash(tokens.nextToken());
oldLocations.add(new ModuleLocation(remoteLoc, null));
}
locations = oldLocations.toArray(new ModuleLocation[oldLocations.size()]);
modules = null;
}
if (build == null) {
return locations;
}
ModuleLocation[] outLocations = new ModuleLocation[locations.length];
for (int i = 0; i < outLocations.length; i++) {
outLocations[i] = locations[i].getExpandedLocation(build);
}
return outLocations;
}
@Override
@Exported
public SubversionRepositoryBrowser getBrowser() {
return browser;
}
@Exported
public String getExcludedRegions() {
return excludedRegions;
}
public String[] getExcludedRegionsNormalized() {
return (excludedRegions == null || excludedRegions.trim().equals(""))
? null : excludedRegions.split("[\\r\\n]+");
}
private Pattern[] getExcludedRegionsPatterns() {
String[] excluded = getExcludedRegionsNormalized();
if (excluded != null) {
Pattern[] patterns = new Pattern[excluded.length];
int i = 0;
for (String excludedRegion : excluded) {
patterns[i++] = Pattern.compile(excludedRegion);
}
return patterns;
}
return new Pattern[0];
}
@Exported
public String getIncludedRegions() {
return includedRegions;
}
public String[] getIncludedRegionsNormalized() {
return (includedRegions == null || includedRegions.trim().equals(""))
? null : includedRegions.split("[\\r\\n]+");
}
private Pattern[] getIncludedRegionsPatterns() {
String[] included = getIncludedRegionsNormalized();
if (included != null) {
Pattern[] patterns = new Pattern[included.length];
int i = 0;
for (String includedRegion : included) {
patterns[i++] = Pattern.compile(includedRegion);
}
return patterns;
}
return new Pattern[0];
}
@Exported
public String getExcludedUsers() {
return excludedUsers;
}
public Set getExcludedUsersNormalized() {
String s = Util.fixEmptyAndTrim(excludedUsers);
if (s == null) {
return Collections.emptySet();
}
Set users = new HashSet();
for (String user : s.split("[\\r\\n]+")) {
users.add(user.trim());
}
return users;
}
@Exported
public String getExcludedRevprop() {
return excludedRevprop;
}
private String getExcludedRevpropNormalized() {
String s = Util.fixEmptyAndTrim(getExcludedRevprop());
if (s != null) {
return s;
}
return getDescriptor().getGlobalExcludedRevprop();
}
@Exported
public String getExcludedCommitMessages() {
return excludedCommitMessages;
}
public String[] getExcludedCommitMessagesNormalized() {
String s = Util.fixEmptyAndTrim(excludedCommitMessages);
return s == null ? new String[0] : s.split("[\\r\\n]+");
}
private Pattern[] getExcludedCommitMessagesPatterns() {
String[] excluded = getExcludedCommitMessagesNormalized();
Pattern[] patterns = new Pattern[excluded.length];
int i = 0;
for (String excludedCommitMessage : excluded) {
patterns[i++] = Pattern.compile(excludedCommitMessage);
}
return patterns;
}
/**
* Sets the SVN_REVISION environment variable during the build.
*/
@Override
public void buildEnvVars(AbstractBuild, ?> build, Map env) {
super.buildEnvVars(build, env);
ModuleLocation[] svnLocations = getLocations(build);
try {
Map revisions = parseRevisionFile(build);
if (svnLocations.length == 1) {
Long rev = revisions.get(svnLocations[0].remote);
if (rev != null) {
env.put("SVN_REVISION", rev.toString());
env.put("SVN_URL", svnLocations[0].getURL());
}
} else if (svnLocations.length > 1) {
for (int i = 0; i < svnLocations.length; i++) {
Long rev = revisions.get(svnLocations[i].remote);
if (rev != null) {
env.put("SVN_REVISION_" + (i + 1), rev.toString());
env.put("SVN_URL_" + (i + 1), svnLocations[i].getURL());
}
}
}
} catch (IOException e) {
// ignore this error
LOGGER.log(Level.FINEST, "Exception while building envVars. Error will be ignored.", e);
}
}
/**
* Called after checkout/update has finished to compute the changelog.
*/
private boolean calcChangeLog(AbstractBuild, ?> build, File changelogFile, BuildListener listener,
List externals) throws IOException, InterruptedException {
if (build.getPreviousBuild() == null) {
// nothing to compare against
return createEmptyChangeLog(changelogFile, listener, "log");
}
// some users reported that the file gets created with size 0. I suspect
// maybe some XSLT engine doesn't close the stream properly.
// so let's do it by ourselves to be really sure that the stream gets closed.
OutputStream os = new BufferedOutputStream(new FileOutputStream(changelogFile));
boolean created;
try {
created = new SubversionChangeLogBuilder(build, listener, this).run(externals, new StreamResult(os));
} finally {
os.close();
}
if (!created) {
createEmptyChangeLog(changelogFile, listener, "log");
}
return true;
}
/**
* Return subversion configuration directory.
*
* @return directory.
*/
public static File getSubversionConfigDir() {
if (null == subversionConfigDir) {
subversionConfigDir = new File(
Hudson.getInstance().getRootDir().getPath() + File.separator + ".subversion");
try {
FileUtils.forceMkdir(subversionConfigDir);
} catch (IOException e) {
subversionConfigDir = SVNWCUtil.getDefaultConfigurationDirectory();
}
}
return subversionConfigDir;
}
/*package*/
static Map parseRevisionFile(AbstractBuild, ?> build) throws IOException {
return parseRevisionFile(build, false);
}
/**
* Reads the revision file of the specified build (or the closest, if the flag is so specified.)
*
* @param findClosest If true, this method will go back the build history until it finds a revision file.
* A build may not have a revision file for any number of reasons (such as failure, interruption, etc.)
* @return map from {@link SvnInfo#url Subversion URL} to its revision.
*/
/*package*/
static Map parseRevisionFile(AbstractBuild, ?> build, boolean findClosest) throws IOException {
Map revisions = new HashMap(); // module -> revision
if (findClosest) {
for (AbstractBuild, ?> b = build; b != null; b = b.getPreviousBuild()) {
if (getRevisionFile(b).exists()) {
build = b;
break;
}
}
}
{// read the revision file of the build
File file = getRevisionFile(build);
if (!file.exists())
// nothing to compare against
{
return revisions;
}
BufferedReader br = new BufferedReader(new FileReader(file));
try {
String line;
while ((line = br.readLine()) != null) {
int index = line.lastIndexOf('/');
if (index < 0) {
continue; // invalid line?
}
try {
revisions.put(line.substring(0, index), Long.valueOf(line.substring(index + 1)));
} catch (NumberFormatException e) {
// perhaps a corrupted line. ignore
LOGGER.log(Level.FINEST, "Error parsing line", e);
}
}
} finally {
br.close();
}
}
return revisions;
}
/**
* Parses the file that stores the locations in the workspace where modules loaded by svn:external
* is placed.
*
*
* Note that the format of the file has changed in 1.180 from simple text file to XML.
*
* @return immutable list. Can be empty but never null.
*/
/*package*/
static List parseExternalsFile(AbstractProject project) {
File file = getExternalsFile(project);
if (file.exists()) {
try {
return (List) new XmlFile(External.XSTREAM, file).read();
} catch (IOException e) {
// in < 1.180 this file was a text file, so it may fail to parse as XML,
// in which case let's just fall back
LOGGER.log(Level.FINEST,
"Couldn't parse externals file. It is text file with version < 1.180. Expected XML", e);
}
}
return Collections.emptyList();
}
/**
* Polling can happen on the master and does not require a workspace.
*/
@Override
public boolean requiresWorkspaceForPolling() {
return false;
}
public boolean checkout(AbstractBuild build, Launcher launcher, FilePath workspace, final BuildListener listener,
File changelogFile) throws IOException, InterruptedException {
List externals = checkout(build, workspace, listener);
if (externals == null) {
return false;
}
// write out the revision file
PrintWriter w = new PrintWriter(new FileOutputStream(getRevisionFile(build)));
try {
Map revMap = workspace.act(new BuildRevisionMapTask(build, this, listener, externals));
for (Entry e : revMap.entrySet()) {
w.println(e.getKey() + '/' + e.getValue().revision);
}
build.addAction(new SubversionTagAction(build, revMap.values()));
} finally {
w.close();
}
// write out the externals info
new XmlFile(External.XSTREAM, getExternalsFile(build.getProject())).write(externals);
return calcChangeLog(build, changelogFile, listener, externals);
}
/**
* Performs the checkout or update, depending on the configuration and workspace state.
*
*
* Use canonical path to avoid SVNKit/symlink problem as described in
* https://wiki.svnkit.com/SVNKit_FAQ
*
* @return null
* if the operation failed. Otherwise the set of local workspace paths
* (relative to the workspace root) that has loaded due to svn:external.
*/
private List checkout(AbstractBuild build, FilePath workspace, TaskListener listener)
throws IOException, InterruptedException {
if (repositoryLocationsNoLongerExist(build, listener)) {
Run lsb = build.getProject().getLastSuccessfulBuild();
if (lsb != null && build.getNumber() - lsb.getNumber() > 10
&& build.getTimestamp().getTimeInMillis() - lsb.getTimestamp().getTimeInMillis() > TimeUnit2.DAYS
.toMillis(1)) {
// Disable this project if the location doesn't exist any more, see issue #763
// but only do so if there was at least some successful build,
// to make sure that initial configuration error won't disable the build. see issue #1567
// finally, only disable a build if the failure persists for some time.
// see http://www.nabble.com/Should-Hudson-have-an-option-for-a-content-fingerprint--td24022683.html
listener.getLogger()
.println("One or more repository locations do not exist anymore for " + build.getProject().getName()
+ ", project will be disabled.");
build.getProject().makeDisabled(true);
return null;
}
}
//TODO get build time from listener.
return workspace.act(new CheckOutTask(build, this, build.getTimestamp().getTime(),
new GregorianCalendar().getTime(), listener));
}
/**
* Either run "svn co" or "svn up" equivalent.
*/
private static class CheckOutTask extends UpdateTask implements FileCallable> {
private final UpdateTask task;
public CheckOutTask(AbstractBuild, ?> build, SubversionSCM scm, Date queueTime, Date buildTime,
TaskListener listener) {
this.authProvider = scm.getDescriptor().createAuthenticationProvider(build.getParent());
this.queueTime = queueTime;
this.buildTime = buildTime;
this.listener = listener;
this.locations = scm.getLocations(build);
this.revisionParameterAction = build.getAction(RevisionParameterAction.class);
this.task = scm.getWorkspaceUpdater().createTask();
this.revisionPolicy = (scm.getDescriptor() != null ? scm.getDescriptor().getRevisionPolicy() : null);
}
public List invoke(File ws, VirtualChannel channel) throws IOException, InterruptedException {
manager = createSvnClientManager(authProvider);
this.ws = ws;
try {
List externals = perform();
checkClockOutOfSync();
return externals;
} finally {
manager.dispose();
}
}
/**
* This round-about way of executing the task ensures that the error-prone {@link #delegateTo(UpdateTask)} method
* correctly copies everything.
*/
@Override
public List perform() throws IOException, InterruptedException {
return delegateTo(task);
}
private void checkClockOutOfSync() {
try {
for (ModuleLocation l : locations) {
SVNDirEntry dir = manager.createRepository(l.getSVNURL(), true).info("/", -1);
// see http://www.nabble.com/NullPointerException-in-SVN-Checkout-Update-td21609781.html that reported this being null.
if (dir != null && dir.getDate() != null && dir.getDate().after(new Date())) {
listener.getLogger().println(Messages.SubversionSCM_ClockOutOfSync());
}
}
} catch (SVNAuthenticationException e) {
// if we don't have access to '/', ignore. error
LOGGER.log(Level.FINE, "Failed to estimate the remote time stamp", e);
} catch (SVNException e) {
LOGGER.log(Level.INFO, "Failed to estimate the remote time stamp", e);
}
}
private static final long serialVersionUID = 1L;
}
/**
* Creates {@link SVNClientManager}.
*
*
* This method must be executed on the slave where svn operations are performed.
*
* @param authProvider The value obtained from {@link DescriptorImpl#createAuthenticationProvider(AbstractProject)}.
* If the operation runs on slaves,
* (and properly remoted, if the svn operations run on slaves.)
*/
public static SVNClientManager createSvnClientManager(ISVNAuthenticationProvider authProvider) {
SubversionWorkspaceSelector.syncWorkspaceFormatFromMaster();
ISVNAuthenticationManager sam = new DefaultSVNAuthenticationManager();
sam.setAuthenticationProvider(authProvider);
return SVNClientManager.newInstance(SVNWCUtil.createDefaultOptions(true), sam.getAuthenticationManager());
}
/**
* Creates {@link SVNClientManager} for code running on the master.
*
* CAUTION: this code only works when invoked on master. On slaves, use
* {@link #createSvnClientManager(ISVNAuthenticationProvider)} and get {@link ISVNAuthenticationProvider}
* from the master via remoting.
*/
public static SVNClientManager createSvnClientManager(AbstractProject context) {
return createSvnClientManager(
Hudson.getInstance().getDescriptorByType(DescriptorImpl.class).createAuthenticationProvider(context));
}
public static final class SvnInfo implements Serializable, Comparable {
/**
* Decoded repository URL.
*/
public final String url;
public final long revision;
public SvnInfo(String url, long revision) {
this.url = url;
this.revision = revision;
}
public SvnInfo(SVNInfo info) {
this(info.getURL().toDecodedString(), info.getCommittedRevision().getNumber());
}
public SVNURL getSVNURL() throws SVNException {
return SVNURL.parseURIDecoded(url);
}
public int compareTo(SvnInfo that) {
int r = this.url.compareTo(that.url);
if (r != 0) {
return r;
}
if (this.revision < that.revision) {
return -1;
}
if (this.revision > that.revision) {
return +1;
}
return 0;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SvnInfo)) {
return false;
}
SvnInfo svnInfo = (SvnInfo) o;
return revision == svnInfo.revision && url.equals(svnInfo.url);
}
@Override
public int hashCode() {
int result;
result = url.hashCode();
result = 31 * result + (int) (revision ^ (revision >>> 32));
return result;
}
@Override
public String toString() {
return String.format("%s (rev.%s)", url, Long.valueOf(revision));
}
private static final long serialVersionUID = 1L;
}
/**
* Information about svn:external
*/
public static final class External implements Serializable {
/**
* Relative path within the workspace where this svn:exteranls exist.
*/
public final String path;
/**
* External SVN URL to be fetched.
*/
public final String url;
/**
* If the svn:external link is with the -r option, its number.
* Otherwise -1 to indicate that the head revision of the external repository should be fetched.
*/
public final long revision;
/**
* @param modulePath The root of the current module that svn was checking out when it hits 'ext'.
* Since we call svnkit multiple times in general case to check out from multiple locations,
* we use this to make the path relative to the entire workspace, not just the particular module.
*/
public External(String modulePath, SVNExternal ext) {
this.path = modulePath + '/' + ext.getPath();
this.url = ext.getResolvedURL().toDecodedString();
this.revision = ext.getRevision().getNumber();
}
public External(String path, SVNURL externalURL, long revision) {
this.path = path;
this.url = externalURL.toDecodedString();
this.revision = revision;
}
/**
* Returns true if this reference is to a fixed revision.
*/
public boolean isRevisionFixed() {
return revision != -1;
}
private static final long serialVersionUID = 1L;
private static final XStream XSTREAM = new XStream2();
static {
XSTREAM.alias("external", External.class);
}
}
/**
* Gets the SVN metadata for the remote repository.
*
* @param remoteUrl The target to run "svn info".
*/
private static SVNInfo parseSvnInfo(SVNURL remoteUrl, ISVNAuthenticationProvider authProvider) throws SVNException {
final SVNClientManager manager = createSvnClientManager(authProvider);
try {
final SVNWCClient svnWc = manager.getWCClient();
return svnWc.doInfo(remoteUrl, SVNRevision.HEAD, SVNRevision.HEAD);
} finally {
manager.dispose();
}
}
/**
* Checks .svn files in the workspace and finds out revisions of the modules
* that the workspace has.
*
* @return null if the parsing somehow fails. Otherwise a map from the repository URL to revisions.
*/
private static class BuildRevisionMapTask implements FileCallable