This is an automated email from the ASF dual-hosted git repository. michaelo pushed a commit to branch wagon-scm-git in repository https://gitbox.apache.org/repos/asf/maven-wagon.git
commit 2e70c858b7031b105951ee33252410590ae5e785 Author: Ilya Basin <basini...@gmail.com> AuthorDate: Thu Feb 22 11:13:52 2018 +0300 [WAGON-495] Fix checkoutDirectory leak This closes #46 --- .../apache/maven/wagon/providers/scm/ScmWagon.java | 248 ++++++++++++--------- 1 file changed, 139 insertions(+), 109 deletions(-) diff --git a/wagon-providers/wagon-scm/src/main/java/org/apache/maven/wagon/providers/scm/ScmWagon.java b/wagon-providers/wagon-scm/src/main/java/org/apache/maven/wagon/providers/scm/ScmWagon.java index 09b014d..d6531ee 100644 --- a/wagon-providers/wagon-scm/src/main/java/org/apache/maven/wagon/providers/scm/ScmWagon.java +++ b/wagon-providers/wagon-scm/src/main/java/org/apache/maven/wagon/providers/scm/ScmWagon.java @@ -19,6 +19,13 @@ package org.apache.maven.wagon.providers.scm; * under the License. */ +import java.io.File; +import java.io.IOException; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + import org.apache.maven.scm.CommandParameter; import org.apache.maven.scm.CommandParameters; import org.apache.maven.scm.ScmBranch; @@ -32,6 +39,7 @@ import org.apache.maven.scm.ScmVersion; import org.apache.maven.scm.command.add.AddScmResult; import org.apache.maven.scm.command.checkout.CheckOutScmResult; import org.apache.maven.scm.command.list.ListScmResult; +import org.apache.maven.scm.command.update.UpdateScmResult; import org.apache.maven.scm.manager.NoSuchScmProviderException; import org.apache.maven.scm.manager.ScmManager; import org.apache.maven.scm.provider.ScmProvider; @@ -49,14 +57,6 @@ import org.apache.maven.wagon.resource.Resource; import org.codehaus.plexus.util.FileUtils; import org.codehaus.plexus.util.StringUtils; -import java.io.File; -import java.io.IOException; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.Stack; - /** * Wagon provider to get and put files from and to SCM systems, using Maven-SCM as underlying transport. * <p/> @@ -95,6 +95,11 @@ public class ScmWagon */ private String scmVersionType; + /** + * Empty string or subdir ending with slash. + */ + private String partCOSubdir = ""; + private File checkoutDirectory; /** @@ -371,18 +376,19 @@ public class ScmWagon ScmProvider scmProvider = getScmProvider( scmRepository.getProvider() ); - String checkoutTargetName = source.isDirectory() ? targetName : getDirname( targetName ); - String relPath = checkOut( scmProvider, scmRepository, checkoutTargetName, target ); + boolean isDirectory = source.isDirectory(); + String checkoutTargetName = isDirectory ? targetName : getDirname( targetName ); + String relPath = ensureDirs( scmProvider, scmRepository, checkoutTargetName, target ); File newCheckoutDirectory = new File( checkoutDirectory, relPath ); - File scmFile = new File( newCheckoutDirectory, source.isDirectory() ? "" : getFilename( targetName ) ); + File scmFile = new File( newCheckoutDirectory, isDirectory ? "" : FileUtils.removePath( targetName, '/' ) ); boolean fileAlreadyInScm = scmFile.exists(); if ( !scmFile.equals( source ) ) { - if ( source.isDirectory() ) + if ( isDirectory ) { FileUtils.copyDirectoryStructure( source, scmFile ); } @@ -395,7 +401,7 @@ public class ScmWagon if ( !fileAlreadyInScm || scmFile.isDirectory() ) { int addedFiles = addFiles( scmProvider, scmRepository, newCheckoutDirectory, - source.isDirectory() ? "" : scmFile.getName() ); + isDirectory ? "" : scmFile.getName() ); if ( !fileAlreadyInScm && addedFiles == 0 ) { @@ -439,14 +445,16 @@ public class ScmWagon * @param targetName * @return * @throws TransferFailedException + * @throws IOException */ - private String checkOut( ScmProvider scmProvider, ScmRepository scmRepository, String targetName, + private String ensureDirs( ScmProvider scmProvider, ScmRepository scmRepository, String targetName, Resource resource ) - throws TransferFailedException + throws TransferFailedException, IOException { - checkoutDirectory = createCheckoutDirectory(); - - Stack<String> stack = new Stack<String>(); + if ( checkoutDirectory == null ) + { + checkoutDirectory = createCheckoutDirectory(); + } String target = targetName; @@ -455,87 +463,82 @@ public class ScmWagon // Check whether targetName, which is a relative path into the scm, exists. // If it doesn't, check the parent, etc. - try + for ( ;; ) { - while ( target.length() > 0 && !scmProvider.list( scmRepository, - new ScmFileSet( new File( "." ), new File( target ) ), - false, makeScmVersion() ).isSuccess() ) + try + { + ScmResult res = tryPartialCheckout( target ); + if ( !res.isSuccess() ) + { + throw new ScmException( "command failed: " + res.getCommandOutput().trim() ); + } + break; + } + catch ( ScmException e ) { - stack.push( getFilename( target ) ); + if ( partCOSubdir.length() == 0 ) + { + fireTransferError( resource, e, TransferEvent.REQUEST_GET ); + + throw new TransferFailedException( "Error checking out: " + e.getMessage(), e ); + } target = getDirname( target ); } } - catch ( ScmException e ) - { - fireTransferError( resource, e, TransferEvent.REQUEST_GET ); - throw new TransferFailedException( "Error listing repository: " + e.getMessage(), e ); - } + // now create the subdirs in target, if it's a parent of targetName + + String res = + partCOSubdir.length() >= targetName.length() ? "" : targetName.substring( partCOSubdir.length() ) + '/'; - // ok, we've established that target exists, or is empty. - // Check the resource out; if it doesn't exist, that means we're in the svn repo url root, - // and the configuration is incorrect. We will not try repo.getParent since most scm's don't - // implement that. + ArrayList<File> createdDirs = new ArrayList<File>(); + File deepDir = new File( checkoutDirectory, res ); - target = target.replace( '\\', '/' ); + boolean added = false; try { - String repoUrl = getRepository().getUrl(); - if ( "svn".equals( scmProvider.getScmType() ) ) + mkdirsThrow( deepDir, createdDirs ); + if ( createdDirs.size() != 0 ) { - // Subversion is the only SCM that adds path structure to represent tags and branches. - // The rest use scmVersion and scmVersionType. - if ( target.length() > 0 ) - { - repoUrl += "/" + target; - target = ""; - } - } - scmRepository = getScmRepository( repoUrl ); - - CheckOutScmResult ret = - checkOut( scmProvider, scmRepository, new ScmFileSet( new File( checkoutDirectory, "" ) ) ); + File topNewDir = createdDirs.get( 0 ); + String relTopNewDir = + topNewDir.getPath().substring( checkoutDirectory.getPath().length() + 1 ).replace( '\\', '/' ); - checkScmResult( ret ); + addFiles( scmProvider, scmRepository, checkoutDirectory, relTopNewDir ); + added = true; + } } catch ( ScmException e ) { - fireTransferError( resource, e, TransferEvent.REQUEST_GET ); + fireTransferError( resource, e, TransferEvent.REQUEST_PUT ); - throw new TransferFailedException( "Error checking out: " + e.getMessage(), e ); + throw new TransferFailedException( "Failed to add directory " + createdDirs.get( 0 ) + " to working copy", + e ); } - - // now create the subdirs in target, if it's a parent of targetName - - String relPath = target.concat( target.length() > 0 ? "/" : "" ); - - while ( !stack.isEmpty() ) + finally { - String p = stack.pop(); - relPath += p + "/"; - - File newDir = new File( checkoutDirectory, relPath ); - newDir.mkdir(); - if ( !newDir.isDirectory() ) + if ( !added && createdDirs.size() != 0 ) { - throw new TransferFailedException( - "Failed to create directory " + newDir.getAbsolutePath() + "; parent should exist: " - + checkoutDirectory ); + FileUtils.deleteDirectory( createdDirs.get( 0 ) ); } + } - try - { - addFiles( scmProvider, scmRepository, checkoutDirectory, relPath ); - } - catch ( ScmException e ) - { - fireTransferError( resource, e, TransferEvent.REQUEST_PUT ); + return res; + } - throw new TransferFailedException( "Failed to add directory " + newDir + " to working copy", e ); + private static void mkdirsThrow( File f, List<File> createdDirs ) + throws IOException + { + if ( !f.isDirectory() ) + { + File parent = f.getParentFile(); + mkdirsThrow( parent, createdDirs ); + if ( !f.mkdir() ) + { + throw new IOException( "Failed to create directory " + f.getAbsolutePath() ); } + createdDirs.add( f ); } - - return relPath; } /** @@ -623,6 +626,12 @@ public class ScmWagon return true; } + private boolean supportsPartialCheckout( ScmProvider scmProvider ) + { + String scmType = scmProvider.getScmType(); + return ( "svn".equals( scmType ) || "cvs".equals( scmType ) ); + } + public void putDirectory( File sourceDirectory, String destinationDirectory ) throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException { @@ -677,16 +686,19 @@ public class ScmWagon fireGetInitiated( resource, destination ); - String url = getRepository().getUrl() + "/" + resourceName; - - // remove the file - url = url.substring( 0, url.lastIndexOf( '/' ) ); + fireGetStarted( resource, destination ); try { - ScmRepository scmRepository = getScmRepository( url ); - - fireGetStarted( resource, destination ); + String subdir = getDirname( resourceName ); + ScmResult res = tryPartialCheckout( subdir ); + if ( !res.isSuccess() && ( partCOSubdir.length() == 0 || res instanceof UpdateScmResult ) ) + { + // inability to checkout SVN or CVS subdir is not fatal. We just assume it doesn't exist + // inability to update existing subdir or checkout root is fatal + throw new ScmException( "command failed: " + res.getCommandOutput().trim() ); + } + resourceName = resourceName.substring( partCOSubdir.length() ); // TODO: limitations: // - destination filename must match that in the repository - should allow the "-d" CVS equiv to be passed @@ -697,24 +709,6 @@ public class ScmWagon File scmFile = new File( checkoutDirectory, resourceName ); - File basedir = scmFile.getParentFile(); - - ScmProvider scmProvider = getScmProvider( scmRepository.getProvider() ); - - String reservedScmFile = scmProvider.getScmSpecificFilename(); - - if ( reservedScmFile != null && new File( basedir, reservedScmFile ).exists() ) - { - scmProvider.update( scmRepository, new ScmFileSet( basedir ), makeScmVersion() ); - } - else - { - // TODO: this should be checking out a full hierarchy (requires the -d equiv) - basedir.mkdirs(); - - checkOut( scmProvider, scmRepository, new ScmFileSet( basedir ) ); - } - if ( !scmFile.exists() ) { throw new ResourceDoesNotExistException( "Unable to find resource " + destination + " after checkout" ); @@ -743,6 +737,49 @@ public class ScmWagon fireGetCompleted( resource, destination ); } + private ScmResult tryPartialCheckout( String subdir ) + throws ScmException, IOException + { + String url = getRepository().getUrl(); + + String desiredPartCOSubdir = ""; + + ScmRepository scmRepository = getScmRepository( url ); + ScmProvider scmProvider = getScmProvider( scmRepository.getProvider() ); + if ( subdir.length() != 0 && supportsPartialCheckout( scmProvider ) ) + { + url += ( url.endsWith( "/" ) ? "" : "/" ) + subdir; + + desiredPartCOSubdir = subdir + "/"; + + scmRepository = getScmRepository( url ); + } + + if ( !desiredPartCOSubdir.equals( partCOSubdir ) ) + { + FileUtils.deleteDirectory( checkoutDirectory ); + partCOSubdir = desiredPartCOSubdir; + } + + ScmResult res; + if ( checkoutDirExists( scmProvider ) ) + { + res = scmProvider.update( scmRepository, new ScmFileSet( checkoutDirectory ), makeScmVersion() ); + } + else + { + res = checkOut( scmProvider, scmRepository, new ScmFileSet( checkoutDirectory ) ); + } + return res; + } + + private boolean checkoutDirExists( ScmProvider scmProvider ) + { + String reservedScmFile = scmProvider.getScmSpecificFilename(); + File pathToCheck = reservedScmFile == null ? checkoutDirectory : new File( checkoutDirectory, reservedScmFile ); + return pathToCheck.exists(); + } + /** * @return a List<String> with filenames/directories at the resourcepath. * @see org.apache.maven.wagon.AbstractWagon#getFileList(java.lang.String) @@ -795,15 +832,8 @@ public class ScmWagon } } - private String getFilename( String filename ) - { - String fname = StringUtils.replace( filename, "/", File.separator ); - return FileUtils.filename( fname ); - } - - private String getDirname( String filename ) + private String getDirname( String resourceName ) { - String fname = StringUtils.replace( filename, "/", File.separator ); - return FileUtils.dirname( fname ); + return FileUtils.getPath( resourceName, '/' ); } } -- To stop receiving notification emails like this one, please contact micha...@apache.org.