You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by mi...@apache.org on 2018/06/04 09:39:01 UTC

[maven-wagon] 03/03: [WAGON-495] Fix checkoutDirectory leak

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 <ba...@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&lt;String&gt; 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
michaelo@apache.org.