blob: a77555a3c43224df0504716c45fca6519b3d36f6 [file] [log] [blame]
package org.apache.maven.wagon.providers.scm;
/*
* Copyright 2001-2006 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.apache.maven.scm.ScmException;
import org.apache.maven.scm.ScmFileSet;
import org.apache.maven.scm.ScmResult;
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.manager.NoSuchScmProviderException;
import org.apache.maven.scm.manager.ScmManager;
import org.apache.maven.scm.provider.ScmProvider;
import org.apache.maven.scm.provider.ScmProviderRepository;
import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost;
import org.apache.maven.scm.repository.ScmRepository;
import org.apache.maven.scm.repository.ScmRepositoryException;
import org.apache.maven.wagon.AbstractWagon;
import org.apache.maven.wagon.ConnectionException;
import org.apache.maven.wagon.ResourceDoesNotExistException;
import org.apache.maven.wagon.TransferFailedException;
import org.apache.maven.wagon.authorization.AuthorizationException;
import org.apache.maven.wagon.events.TransferEvent;
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;
/**
* Wagon provider to get and put files form and to SCM systems, using Maven-SCM as underlying transport.
* <p/>
* TODO it probably creates problems if the same wagon is used in two different SCM protocols, as
* instance variables can keep incorrect state.
*
* @author <a href="brett@apache.org">Brett Porter</a>
* @author <a href="evenisse@apache.org">Emmanuel Venisse</a>
* @author <a href="carlos@apache.org">Carlos Sanchez</a>
* @version $Id$
*/
public class ScmWagon
extends AbstractWagon
{
private ScmManager scmManager;
private File checkoutDirectory;
/**
* Get the {@link ScmManager} used in this Wagon
*
* @return rhe {@link ScmManager}
*/
public ScmManager getScmManager()
{
return scmManager;
}
/**
* Set the {@link ScmManager} used in this Wagon
*
* @param scmManager
*/
public void setScmManager( ScmManager scmManager )
{
this.scmManager = scmManager;
}
/**
* Get the directory where Wagon will checkout files from SCM.
* This directory will be deleted!
*
* @return directory
*/
public File getCheckoutDirectory()
{
return checkoutDirectory;
}
/**
* Set the directory where Wagon will checkout files from SCM.
* This directory will be deleted!
*
* @param checkoutDirectory
*/
public void setCheckoutDirectory( File checkoutDirectory )
{
this.checkoutDirectory = checkoutDirectory;
}
/**
* Convenience method to get the {@link ScmProvider} implementation to handle the provided SCM type
*
* @param scmType type of SCM, eg. <code>svn</code>, <code>cvs</code>
* @return the {@link ScmProvider} that will handle provided SCM type
* @throws NoSuchScmProviderException if there is no {@link ScmProvider} able to handle that SCM type
*/
public ScmProvider getScmProvider( String scmType )
throws NoSuchScmProviderException
{
return getScmManager().getProviderByType( scmType );
}
/**
* This will cleanup the checkout directory
*/
public void openConnection()
throws ConnectionException
{
if ( checkoutDirectory.exists() )
{
try
{
FileUtils.deleteDirectory( checkoutDirectory );
}
catch ( IOException e )
{
throw new ConnectionException( "Unable to cleanup checkout directory", e );
}
}
checkoutDirectory.mkdirs();
}
private ScmRepository getScmRepository( String url )
throws TransferFailedException
{
String username = null;
String password = null;
String privateKey = null;
String passphrase = null;
if ( authenticationInfo != null )
{
username = authenticationInfo.getUserName();
password = authenticationInfo.getPassword();
privateKey = authenticationInfo.getPrivateKey();
passphrase = authenticationInfo.getPassphrase();
}
ScmRepository scmRepository;
try
{
scmRepository = getScmManager().makeScmRepository( url );
}
catch ( ScmRepositoryException e )
{
throw new TransferFailedException( "Error initialising SCM repository", e );
}
catch ( NoSuchScmProviderException e )
{
throw new TransferFailedException( "Unknown SCM type", e );
}
ScmProviderRepository providerRepository = scmRepository.getProviderRepository();
if ( StringUtils.isNotEmpty( username ) )
{
providerRepository.setUser( username );
}
if ( StringUtils.isNotEmpty( password ) )
{
providerRepository.setPassword( password );
}
if ( providerRepository instanceof ScmProviderRepositoryWithHost )
{
ScmProviderRepositoryWithHost providerRepo = (ScmProviderRepositoryWithHost) providerRepository;
if ( StringUtils.isNotEmpty( privateKey ) )
{
providerRepo.setPrivateKey( privateKey );
}
if ( StringUtils.isNotEmpty( passphrase ) )
{
providerRepo.setPassphrase( passphrase );
}
}
return scmRepository;
}
public void put( File source, String resourceName )
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
{
if ( source.isDirectory() )
{
throw new IllegalArgumentException( "Source is a directory: " + source );
}
putInternal( source, resourceName );
}
/**
* Puts both files and directories
*
* @param source
* @param resourceName
* @throws TransferFailedException
*/
private void putInternal( File source, String resourceName )
throws TransferFailedException
{
Resource resource = new Resource( resourceName );
firePutInitiated( resource, source );
String url = getRepository().getUrl();
ScmRepository scmRepository = getScmRepository( url );
firePutStarted( resource, source );
try
{
File basedir = checkoutDirectory;
String msg = "Wagon: Adding " + source.getName() + " to repository";
ScmProvider scmProvider = getScmProvider( scmRepository.getProvider() );
ScmResult result;
File newCheckoutDirectory = mkdirs( scmProvider, scmRepository, basedir );
File scmFile = new File( newCheckoutDirectory, resourceName );
boolean fileAlreadyInScm = scmFile.exists();
if ( !scmFile.equals( source ) )
{
if ( source.isDirectory() )
{
FileUtils.copyDirectoryStructure( source, scmFile );
}
else
{
FileUtils.copyFile( source, scmFile );
}
}
if ( !fileAlreadyInScm || scmFile.isDirectory() )
{
int addedFiles = addFiles( scmProvider, scmRepository, newCheckoutDirectory, scmFile.getName() );
if ( !fileAlreadyInScm && addedFiles == 0 )
{
throw new TransferFailedException(
"Unable to add file to SCM: " + scmFile + "; see error messages above for more information" );
}
}
result = scmProvider.checkIn( scmRepository,
new ScmFileSet( newCheckoutDirectory, scmFile.getName(), null ), null, msg );
checkScmResult( result );
}
catch ( ScmException e )
{
throw new TransferFailedException( "Error interacting with SCM", e );
}
catch ( IOException e )
{
throw new TransferFailedException( "Error interacting with SCM", e );
}
if ( source.isFile() )
{
postProcessListeners( resource, source, TransferEvent.REQUEST_PUT );
}
firePutCompleted( resource, source );
}
/**
* Add a file or directory to a SCM repository.
* If it's a directory all its contents are added recursively.
* <p/>
* TODO this is less than optimal, SCM API should provide a way to add a directory recursively
*
* @param scmProvider SCM provider
* @param scmRepository SCM repository
* @param basedir local directory corresponding to scmRepository
* @param scmFilePath path of the file or directory to add, relative to basedir
* @return the number of files added.
* @throws ScmException
*/
private int addFiles( ScmProvider scmProvider, ScmRepository scmRepository, File basedir, String scmFilePath )
throws ScmException, TransferFailedException
{
File scmFile = new File( basedir, scmFilePath );
AddScmResult result = scmProvider.add( scmRepository, new ScmFileSet( basedir, new File( scmFilePath ) ) );
/*
* TODO dirty fix to work around files with property svn:eol-style=native
* if a file has that property, first time file is added it fails, second time it succeeds
* the solution is check if the scm provider is svn and unset that property
* when the SCM API allows it
*/
if ( !result.isSuccess() )
{
result = scmProvider.add( scmRepository, new ScmFileSet( basedir, new File( scmFilePath ) ) );
}
int addedFiles = result.getAddedFiles().size();
String reservedScmFile = scmProvider.getScmSpecificFilename();
if ( scmFile.isDirectory() )
{
File[] files = scmFile.listFiles();
for ( int i = 0; i < files.length; i++ )
{
if ( reservedScmFile != null && !reservedScmFile.equals( files[i].getName() ) )
{
addedFiles +=
addFiles( scmProvider, scmRepository, basedir, scmFilePath + "/" + files[i].getName() );
}
}
}
return addedFiles;
}
/**
* Make the necessary directories in the SCM repository to commit the files in the place asked
*
* @param scmProvider
* @param repository
* @param basedir
* @return the new checkout directory. Will be <code>null</code> if it does not need to change.
* @throws ScmException
* @throws TransferFailedException
*/
private File mkdirs( ScmProvider scmProvider, ScmRepository repository, File basedir )
throws ScmException, TransferFailedException
{
ScmProviderRepository baseProviderRepository = repository.getProviderRepository();
ScmFileSet fileSet = new ScmFileSet( basedir, new File( "." ) );
ListScmResult listScmResult;
ScmRepository baseRepository = repository;
ScmProviderRepository lastBaseProviderRepository = baseProviderRepository;
while ( baseProviderRepository != null )
{
listScmResult = scmProvider.list( baseRepository, fileSet, false, null );
if ( listScmResult.isSuccess() )
{
break;
}
lastBaseProviderRepository = baseProviderRepository;
baseProviderRepository = baseProviderRepository.getParent();
baseRepository = new ScmRepository( repository.getProvider(), baseProviderRepository );
}
if ( baseProviderRepository != null )
{
String relativePath = repository.getProviderRepository().getRelativePath( baseProviderRepository );
if ( relativePath != null )
{
scmProvider.checkOut( baseRepository, new ScmFileSet( basedir ), null );
File path = new File( basedir, relativePath );
path.mkdirs();
String folderNameToCommit = lastBaseProviderRepository.getRelativePath( baseProviderRepository );
int addedFiles = addFiles( scmProvider, baseRepository, basedir, folderNameToCommit );
if ( addedFiles == 0 )
{
throw new TransferFailedException(
"Unable to add folder to SCM: " + new File( basedir, folderNameToCommit ) );
}
scmProvider.checkIn( baseRepository, new ScmFileSet( basedir, new File( folderNameToCommit ) ), null,
"Adding required folders for Wagon.put" );
return path;
}
else
{
/* folder already in SCM */
CheckOutScmResult result = scmProvider.checkOut( repository, new ScmFileSet( basedir ), null );
checkScmResult( result );
return basedir;
}
}
throw new TransferFailedException(
"Unable to create directories in the remote repository: " + repository.getProviderRepository() );
}
/**
* @return true
*/
public boolean supportsDirectoryCopy()
{
return true;
}
public void putDirectory( File sourceDirectory, String destinationDirectory )
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
{
if ( !sourceDirectory.isDirectory() )
{
throw new IllegalArgumentException( "Source is not a directory: " + sourceDirectory );
}
putInternal( sourceDirectory, destinationDirectory );
}
/**
* Check that the ScmResult was a successful operation
*
* @param result
* @throws TransferFailedException if result was not a successful operation
*/
private void checkScmResult( ScmResult result )
throws TransferFailedException
{
if ( !result.isSuccess() )
{
throw new TransferFailedException(
"Unable to commit file. " + result.getProviderMessage() + " " + result.getCommandOutput() );
}
}
public void closeConnection()
{
}
/**
* Not implemented
*
* @throws UnsupportedOperationException always
*/
public boolean getIfNewer( String resourceName, File destination, long timestamp )
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
{
throw new UnsupportedOperationException( "Not currently supported: getIfNewer" );
}
public void get( String resourceName, File destination )
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
{
Resource resource = new Resource( resourceName );
fireGetInitiated( resource, destination );
String url = getRepository().getUrl() + "/" + resourceName;
// remove the file
url = url.substring( 0, url.lastIndexOf( '/' ) );
ScmRepository scmRepository = getScmRepository( url );
fireGetStarted( resource, destination );
// TODO: limitations:
// - destination filename must match that in the repository - should allow the "-d" CVS equiv to be passed in
// - we don't get granular exceptions from SCM (ie, auth, not found)
// - need to make it non-recursive to save time
// - exists() check doesn't test if it is in SCM already
try
{
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 ), null );
}
else
{
// TODO: this should be checking out a full hierachy (requires the -d equiv)
basedir.mkdirs();
scmProvider.checkOut( scmRepository, new ScmFileSet( basedir ), null );
}
if ( !scmFile.exists() )
{
throw new ResourceDoesNotExistException( "Unable to find resource " + destination + " after checkout" );
}
if ( !scmFile.equals( destination ) )
{
FileUtils.copyFile( scmFile, destination );
}
}
catch ( ScmException e )
{
throw new TransferFailedException( "Error getting file from SCM", e );
}
catch ( IOException e )
{
throw new TransferFailedException( "Error getting file from SCM", e );
}
postProcessListeners( resource, destination, TransferEvent.REQUEST_GET );
fireGetCompleted( resource, destination );
}
}