Skip to content
Permalink
Browse files
Merge pull request #29 from petdr/jenkins-777
[FIXED JENKINS-777] Add ability to set depth and ignore externals for a module
  • Loading branch information
kutzi committed Jan 2, 2013
2 parents 52e83ab + b05fbad commit 4509e8b91f4edd55b01a798eff0ef1ec35a10309
@@ -114,6 +114,7 @@

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Chmod;
@@ -126,6 +127,7 @@
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.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
@@ -233,15 +235,15 @@ public SubversionSCM(String[] remoteLocations, String[] localLocations,
*/
public SubversionSCM(String[] remoteLocations, String[] localLocations,
boolean useUpdate, SubversionRepositoryBrowser browser, String excludedRegions) {
this(ModuleLocation.parse(remoteLocations,localLocations), useUpdate, false, browser, excludedRegions, null, null, null);
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), useUpdate, false, browser, excludedRegions, excludedUsers, excludedRevprop, null);
this(ModuleLocation.parse(remoteLocations,localLocations,null,null), useUpdate, false, browser, excludedRegions, excludedUsers, excludedRevprop, null);
}

/**
@@ -2283,16 +2285,38 @@ public static final class ModuleLocation implements Serializable {
@Exported
public final String local;

/**
* Subversion remote depth. Used as "--depth" option for checkout and update commands.
* Default value is "infinity".
*/
@Exported
public final String depthOption;

/**
* Flag to ignore subversion externals definitions.
*/
@Exported
public boolean ignoreExternalsOption;

/**
* Cache of the repository UUID.
*/
private transient volatile UUID repositoryUUID;
private transient volatile SVNURL repositoryRoot;

@DataBoundConstructor
/**
* Constructor to support backwards compatibility.
*/
public ModuleLocation(String remote, String local) {
this(remote, local, null, false);
}

@DataBoundConstructor
public ModuleLocation(String remote, String local, String depthOption, boolean ignoreExternalsOption) {
this.remote = Util.removeTrailingSlash(Util.fixNull(remote).trim());
this.local = fixEmptyAndTrim(local);
this.depthOption = StringUtils.isEmpty(depthOption) ? SVNDepth.INFINITY.getName() : depthOption;
this.ignoreExternalsOption = ignoreExternalsOption;
}

/**
@@ -2384,6 +2408,24 @@ private String getExpandedLocalDir(AbstractBuild<?,?> build) {
return outLocalDir;
}

/**
* Returns the value of remote depth option.
*
* @return the value of remote depth option.
*/
public String getDepthOption() {
return depthOption;
}

/**
* Determines if subversion externals definitions should be ignored.
*
* @return true if subversion externals definitions should be ignored.
*/
public boolean isIgnoreExternalsOption() {
return ignoreExternalsOption;
}

/**
* Expand location value based on Build parametric execution.
*
@@ -2402,7 +2444,7 @@ public ModuleLocation getExpandedLocation(AbstractBuild<?, ?> build) {
* @return Output ModuleLocation expanded according to specified env vars.
*/
public ModuleLocation getExpandedLocation(EnvVars env) {
return new ModuleLocation(env.expand(remote), env.expand(getLocalDir()));
return new ModuleLocation(env.expand(remote), env.expand(getLocalDir()), getDepthOption(), isIgnoreExternalsOption());
}

@Override
@@ -2412,7 +2454,7 @@ public String toString() {

private static final long serialVersionUID = 1L;

public static List<ModuleLocation> parse(String[] remoteLocations, String[] localLocations) {
public static List<ModuleLocation> parse(String[] remoteLocations, String[] localLocations, String[] depthOptions, boolean[] isIgnoreExternals) {
List<ModuleLocation> modules = new ArrayList<ModuleLocation>();
if (remoteLocations != null && localLocations != null) {
int entries = Math.min(remoteLocations.length, localLocations.length);
@@ -2423,7 +2465,9 @@ public static List<ModuleLocation> parse(String[] remoteLocations, String[] loca

if (remoteLoc != null) {// null if skipped
remoteLoc = Util.removeTrailingSlash(remoteLoc.trim());
modules.add(new ModuleLocation(remoteLoc, Util.nullify(localLocations[i])));
modules.add(new ModuleLocation(remoteLoc, Util.nullify(localLocations[i]),
depthOptions != null ? depthOptions[i] : null,
isIgnoreExternals != null && isIgnoreExternals[i]));
}
}
}
@@ -87,12 +87,15 @@ public List<External> perform() throws IOException, InterruptedException {
String revisionName = r.getDate() != null ?
fmt.format(r.getDate()) : r.toString();

listener.getLogger().println("Checking out " + location.remote + " at revision " + revisionName);
listener.getLogger().println("Checking out " + location.remote + " at revision " + revisionName +
" to depth " + location.getDepthOption() + " and ignoring externals: " + location.isIgnoreExternalsOption());

File local = new File(ws, location.getLocalDir());
svnuc.setIgnoreExternals(location.isIgnoreExternalsOption());
svnuc.setEventHandler(new SubversionUpdateEventHandler(new PrintStream(pos), externals, local, location.getLocalDir()));

svnuc.doCheckout(location.getSVNURL(), local.getCanonicalFile(), SVNRevision.HEAD, r, SVNDepth.INFINITY, true);
SVNDepth svnDepth = getSvnDepth(location.getDepthOption());
svnuc.doCheckout(location.getSVNURL(), local.getCanonicalFile(), SVNRevision.HEAD, r, svnDepth, true);
} catch (SVNCancelException e) {
if (isAuthenticationFailedError(e)) {
e.printStackTrace(listener.error("Failed to check out " + location.remote));
@@ -138,9 +138,12 @@ public List<External> perform() throws IOException, InterruptedException {
String revisionName = r.getDate() != null ?
fmt.format(r.getDate()) : r.toString();

svnuc.setIgnoreExternals(location.isIgnoreExternalsOption());
preUpdate(location, local);
listener.getLogger().println("Updating " + location.remote + " to revision " + revisionName);
svnuc.doUpdate(local.getCanonicalFile(), r, SVNDepth.INFINITY, true, false);
listener.getLogger().println("Updating " + location.remote + " at revision " + revisionName +
" to depth " + location.getDepthOption() + " and ignoring externals: " + location.isIgnoreExternalsOption());
SVNDepth svnDepth = getSvnDepth(location.getDepthOption());
svnuc.doUpdate(local.getCanonicalFile(), r, svnDepth, true, true);
} catch (SVNCancelException e) {
if (isAuthenticationFailedError(e)) {
e.printStackTrace(listener.error("Failed to check out " + location.remote));
@@ -28,6 +28,7 @@
import org.kohsuke.stapler.DataBoundConstructor;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.wc.SVNWCClient;

import java.io.File;
import java.io.IOException;
@@ -57,8 +58,10 @@ public static class TaskImpl extends UpdateUpdater.TaskImpl {

@Override
protected void preUpdate(ModuleLocation module, File local) throws SVNException, IOException {
listener.getLogger().println("Reverting " + local);
clientManager.getWCClient().doRevert(new File[]{local.getCanonicalFile()}, SVNDepth.INFINITY, null);
listener.getLogger().println("Reverting " + local + " to depth " + module.getDepthOption() + " with ignoreExternals: " + module.isIgnoreExternalsOption());
final SVNWCClient svnwc = manager.getWCClient();
svnwc.setIgnoreExternals(module.isIgnoreExternalsOption());
svnwc.doRevert(new File[]{local.getCanonicalFile()}, getSvnDepth(module.getDepthOption()), null);
}
}

@@ -34,6 +34,7 @@
import hudson.scm.SvnClientManager;
import org.kohsuke.stapler.export.ExportedBean;
import org.tmatesoft.svn.core.SVNCancelException;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationProvider;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNRevision;
@@ -180,6 +181,15 @@ protected SVNRevision getRevision(ModuleLocation l) {
return r;
}

/**
* Returns {@link org.tmatesoft.svn.core.SVNDepth} by string value.
*
* @return {@link org.tmatesoft.svn.core.SVNDepth} value.
*/
protected static SVNDepth getSvnDepth(String name) {
return SVNDepth.fromString(name);
}

private static final long serialVersionUID = 1L;
}
}
@@ -32,6 +32,17 @@ THE SOFTWARE.
<f:entry title="${%Local module directory} (${%optional})" field="local">
<f:textbox value="${loc!=null?loc.local:'.'}" />
</f:entry>
<f:entry title="${%Repository depth option}" field="depthOption">
<select name="depthOption">
<f:option value="infinity" selected="${loc.depthOption=='infinity'}">infinity</f:option>
<f:option value="empty" selected="${loc.depthOption=='empty'}">empty</f:option>
<f:option value="files" selected="${loc.depthOption=='files'}">files</f:option>
<f:option value="immediates" selected="${loc.depthOption=='immediates'}">immediates</f:option>
</select>
</f:entry>
<f:entry title="${%Ignore externals option}" field="ignoreExternalsOption">
<f:checkbox default="false" checked="${loc.ignoreExternalsOption}"/>
</f:entry>
<f:entry>
<div align="right">
<input type="button" value="${%Add more locations...}" class="repeatable-add show-if-last" />
@@ -0,0 +1,10 @@
<div>
"--depth" option for checkout and update commands. Default value is "infinity".
<p/> "empty" includes only the immediate target of the operation, not any of its file or directory children.
<p/> "files" includes the immediate target of the operation and any of its immediate file children.
<p/> "immediates" includes the immediate target of the operation and any of its immediate file or directory children. The directory children will themselves be empty.
<p/> "infinity" includes the immediate target, its file and directory children, its children's children, and so on to full recursion.
<p/>
More information can be found
<a href="http://svnbook.red-bean.com/en/1.7/svn.advanced.sparsedirs.html" target="_blank">here</a>.
</div>
@@ -0,0 +1,6 @@
<div>
"--ignore-externals" option will be used with svn checkout, svn update commands to disable externals definition processing.
<p/>
More information can be found
<a href="http://svnbook.red-bean.com/en/1.7/svn.advanced.externals.html" target="_blank">here</a>.
</div>
@@ -787,7 +787,7 @@ public void testExcludedRegions() throws Exception {
// SLAVE_DEBUG_PORT = 8001;
File repo = new CopyExisting(getClass().getResource("HUDSON-6030.zip")).allocate();
SubversionSCM scm = new SubversionSCM(ModuleLocation.parse(new String[]{"file://" + repo.toURI().toURL().getPath()},
new String[]{"."}),
new String[]{"."}, null, null),
new UpdateUpdater(), null, ".*/bar", "", "", "", "");

FreeStyleProject p = createFreeStyleProject("testExcludedRegions");
@@ -819,7 +819,7 @@ public void testIncludedRegions() throws Exception {
// SLAVE_DEBUG_PORT = 8001;
File repo = new CopyExisting(getClass().getResource("HUDSON-6030.zip")).allocate();
SubversionSCM scm = new SubversionSCM(ModuleLocation.parse(new String[]{"file://" + repo.toURI().toURL().getPath()},
new String[]{"."}),
new String[]{"."}, null, null),
new UpdateUpdater(), null, "", "", "", "", ".*/foo");

FreeStyleProject p = createFreeStyleProject("testExcludedRegions");
@@ -1408,5 +1408,112 @@ public void testGetLocalDirWithAtRevision() throws Exception {
assertEquals(1, locs.length);
assertEquals("project", locs[0].getLocalDir());
}


@Bug(777)
public void testIgnoreExternals() throws Exception {
Proc p = runSvnServe(getClass().getResource("JENKINS-777.zip"));

try {
FreeStyleProject b = createFreeStyleProject();

ModuleLocation[] locations = {
new ModuleLocation("svn://localhost/jenkins-777/proja", "no_externals", "infinity", true),
new ModuleLocation("svn://localhost/jenkins-777/proja", "with_externals", "infinity", false)
};

b.setScm(new SubversionSCM(Arrays.asList(locations), new CheckoutUpdater(), null, null, null, null, null, null));

FreeStyleBuild build = assertBuildStatusSuccess(b.scheduleBuild2(0));
FilePath ws = build.getWorkspace();

// Check that the external exists
assertTrue(ws.child("with_externals").child("externals").child("projb").exists());

// Check that the external doesn't exist
assertTrue(!(ws.child("no_externals").child("externals").child("projb").exists()));
} finally {
p.kill();
}
}

@Bug(777)
public void testDepthOptions() throws Exception {
Proc p = runSvnServe(getClass().getResource("JENKINS-777.zip"));

try {
FreeStyleProject b = createFreeStyleProject();

ModuleLocation[] locations = {
new ModuleLocation("svn://localhost/jenkins-777/proja", "empty", "empty", true),
new ModuleLocation("svn://localhost/jenkins-777/proja", "files", "files", true),
new ModuleLocation("svn://localhost/jenkins-777/proja", "immediates", "immediates", true),
new ModuleLocation("svn://localhost/jenkins-777/proja", "infinity", "infinity", true)
};

b.setScm(new SubversionSCM(Arrays.asList(locations), new CheckoutUpdater(), null, null, null, null, null, null));

FreeStyleBuild build = assertBuildStatusSuccess(b.scheduleBuild2(0));
FilePath ws = build.getWorkspace();

// Test if file file1 exists for various depths
assertTrue(!(ws.child("empty").child("file1").exists()));

assertTrue(ws.child("files").child("file1").exists());
assertTrue(ws.child("immediates").child("file1").exists());
assertTrue(ws.child("infinity").child("file1").exists());

// Test if directory subdir exists for various depths
assertTrue(!(ws.child("empty").child("subdir").exists()));
assertTrue(!(ws.child("files").child("subdir").exists()));

assertTrue(ws.child("immediates").child("subdir").exists());
assertTrue(ws.child("infinity").child("subdir").exists());

// Test if file subdir/file3 exists for various depths
assertTrue(!(ws.child("empty").child("subdir").child("file3").exists()));
assertTrue(!(ws.child("files").child("subdir").child("file3").exists()));
assertTrue(!(ws.child("immediates").child("subdir").child("file3").exists()));

assertTrue(ws.child("infinity").child("subdir").child("file3").exists());

} finally {
p.kill();
}
}

@Bug(777)
public void testChangingDepthWithUpdateUpdater() throws Exception {
Proc p = runSvnServe(getClass().getResource("JENKINS-777.zip"));

try {
// enable 1.6 mode
HtmlForm f = createWebClient().goTo("configure").getFormByName("config");
f.getSelectByName("svn.workspaceFormat").setSelectedAttribute("10",true);
submit(f);

FreeStyleProject b = createFreeStyleProject();

ModuleLocation[] locations = {
new ModuleLocation("svn://localhost/jenkins-777/proja", "proja", "infinity", true)
};

// Do initial update with infinite depth and check that file1 exists
b.setScm(new SubversionSCM(Arrays.asList(locations), new UpdateUpdater(), null, null, null, null, null, null));
FreeStyleBuild build = assertBuildStatusSuccess(b.scheduleBuild2(0));
FilePath ws = build.getWorkspace();
assertTrue(ws.child("proja").child("file1").exists());

// Trigger new build with depth empty and check that file1 no longer exists
ModuleLocation[] locations2 = {
new ModuleLocation("svn://localhost/jenkins-777/proja", "proja", "empty", true)
};
b.setScm(new SubversionSCM(Arrays.asList(locations2), new UpdateUpdater(), null, null, null, null, null, null));
FreeStyleBuild build2 = assertBuildStatusSuccess(b.scheduleBuild2(0));
ws = build2.getWorkspace();
assertTrue(!(ws.child("proja").child("file1").exists()));

} finally {
p.kill();
}
}
}
Binary file not shown.

0 comments on commit 4509e8b

Please sign in to comment.