001package org.apache.maven.wagon.providers.scm;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.File;
023import java.io.IOException;
024import java.text.DecimalFormat;
025import java.util.ArrayList;
026import java.util.List;
027import java.util.Random;
028
029import org.apache.maven.scm.CommandParameter;
030import org.apache.maven.scm.CommandParameters;
031import org.apache.maven.scm.ScmBranch;
032import org.apache.maven.scm.ScmException;
033import org.apache.maven.scm.ScmFile;
034import org.apache.maven.scm.ScmFileSet;
035import org.apache.maven.scm.ScmResult;
036import org.apache.maven.scm.ScmRevision;
037import org.apache.maven.scm.ScmTag;
038import org.apache.maven.scm.ScmVersion;
039import org.apache.maven.scm.command.add.AddScmResult;
040import org.apache.maven.scm.command.checkout.CheckOutScmResult;
041import org.apache.maven.scm.command.list.ListScmResult;
042import org.apache.maven.scm.command.update.UpdateScmResult;
043import org.apache.maven.scm.manager.NoSuchScmProviderException;
044import org.apache.maven.scm.manager.ScmManager;
045import org.apache.maven.scm.provider.ScmProvider;
046import org.apache.maven.scm.provider.ScmProviderRepository;
047import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost;
048import org.apache.maven.scm.repository.ScmRepository;
049import org.apache.maven.scm.repository.ScmRepositoryException;
050import org.apache.maven.wagon.AbstractWagon;
051import org.apache.maven.wagon.ConnectionException;
052import org.apache.maven.wagon.ResourceDoesNotExistException;
053import org.apache.maven.wagon.TransferFailedException;
054import org.apache.maven.wagon.authorization.AuthorizationException;
055import org.apache.maven.wagon.events.TransferEvent;
056import org.apache.maven.wagon.resource.Resource;
057import org.codehaus.plexus.util.FileUtils;
058import org.codehaus.plexus.util.StringUtils;
059
060/**
061 * Wagon provider to get and put files from and to SCM systems, using Maven-SCM as underlying transport.
062 * <p/>
063 * TODO it probably creates problems if the same wagon is used in two different SCM protocols, as instance variables can
064 * keep incorrect state.
065 * TODO: For doing releases, we either have to be able to add files with checking out the repository structure which may not be
066 * possible, or the checkout directory needs to be a constant. Doing releases won't scale if you have to checkout the
067 * whole repository structure in order to add 3 files.
068 *
069 * @author <a href="brett@apache.org">Brett Porter</a>
070 * @author <a href="evenisse@apache.org">Emmanuel Venisse</a>
071 * @author <a href="carlos@apache.org">Carlos Sanchez</a>
072 * @author Jason van Zyl
073 *
074 * @plexus.component role="org.apache.maven.wagon.Wagon" role-hint="scm" instantiation-strategy="per-lookup"
075 */
076public class ScmWagon
077    extends AbstractWagon
078{
079    /**
080     * @plexus.requirement
081     */
082    private volatile ScmManager scmManager;
083
084    /**
085     * The SCM version, if any.
086     *
087     * @parameter
088     */
089    private String scmVersion;
090
091    /**
092     * The SCM version type, if any. Defaults to "branch".
093     *
094     * @parameter
095     */
096    private String scmVersionType;
097
098    /**
099     * Empty string or subdir ending with slash.
100     */
101    private String partCOSubdir = "";
102
103    private boolean haveRecursiveCO;
104
105    private File checkoutDirectory;
106
107    /**
108     * Get the {@link ScmManager} used in this Wagon
109     *
110     * @return the {@link ScmManager}
111     */
112    public ScmManager getScmManager()
113    {
114        return scmManager;
115    }
116
117    /**
118     * Set the {@link ScmManager} used in this Wagon
119     *
120     * @param scmManager
121     */
122    public void setScmManager( ScmManager scmManager )
123    {
124        this.scmManager = scmManager;
125    }
126
127    /**
128     * Get the scmVersion used in this Wagon
129     *
130     * @return the scmVersion
131     */
132    public String getScmVersion()
133    {
134        return scmVersion;
135    }
136
137    /**
138     * Set the scmVersion
139     *
140     * @param scmVersion the scmVersion to set
141     */
142    public void setScmVersion( String scmVersion )
143    {
144        this.scmVersion = scmVersion;
145    }
146
147    /**
148     * Get the scmVersionType used in this Wagon
149     *
150     * @return the scmVersionType
151     */
152    public String getScmVersionType()
153    {
154        return scmVersionType;
155    }
156
157    /**
158     * Set the scmVersionType
159     *
160     * @param scmVersionType the scmVersionType to set
161     */
162    public void setScmVersionType( String scmVersionType )
163    {
164        this.scmVersionType = scmVersionType;
165    }
166
167    /**
168     * Get the directory where Wagon will checkout files from SCM. This directory will be deleted!
169     *
170     * @return directory
171     */
172    public File getCheckoutDirectory()
173    {
174        return checkoutDirectory;
175    }
176
177    /**
178     * Set the directory where Wagon will checkout files from SCM. This directory will be deleted!
179     *
180     * @param checkoutDirectory
181     */
182    public void setCheckoutDirectory( File checkoutDirectory )
183    {
184        this.checkoutDirectory = checkoutDirectory;
185    }
186
187    /**
188     * Convenience method to get the {@link ScmProvider} implementation to handle the provided SCM type
189     *
190     * @param scmType type of SCM, eg. <code>svn</code>, <code>cvs</code>
191     * @return the {@link ScmProvider} that will handle provided SCM type
192     * @throws NoSuchScmProviderException if there is no {@link ScmProvider} able to handle that SCM type
193     */
194    public ScmProvider getScmProvider( String scmType )
195        throws NoSuchScmProviderException
196    {
197        return getScmManager().getProviderByType( scmType );
198    }
199
200    /**
201     * This will cleanup the checkout directory
202     */
203    public void openConnectionInternal()
204        throws ConnectionException
205    {
206        if ( checkoutDirectory == null )
207        {
208            checkoutDirectory = createCheckoutDirectory();
209        }
210
211        if ( checkoutDirectory.exists() )
212        {
213            removeCheckoutDirectory();
214        }
215
216        checkoutDirectory.mkdirs();
217    }
218
219    private File createCheckoutDirectory()
220    {
221        File checkoutDirectory;
222
223        DecimalFormat fmt = new DecimalFormat( "#####" );
224
225        Random rand = new Random( System.currentTimeMillis() + Runtime.getRuntime().freeMemory() );
226
227        synchronized ( rand )
228        {
229            do
230            {
231                checkoutDirectory = new File( System.getProperty( "java.io.tmpdir" ),
232                                              "wagon-scm" + fmt.format( Math.abs( rand.nextInt() ) ) + ".checkout" );
233            }
234            while ( checkoutDirectory.exists() );
235        }
236
237        return checkoutDirectory;
238    }
239
240
241    private void removeCheckoutDirectory()
242        throws ConnectionException
243    {
244        if ( checkoutDirectory == null )
245        {
246            return; // Silently return.
247        }
248
249        try
250        {
251            deleteCheckoutDirectory();
252        }
253        catch ( IOException e )
254        {
255            throw new ConnectionException( "Unable to cleanup checkout directory", e );
256        }
257    }
258
259    /**
260     * Construct the ScmVersion to use for operations.
261     * <p/>
262     * <p>If scmVersion is supplied, scmVersionType must also be supplied to
263     * take effect.</p>
264     */
265    private ScmVersion makeScmVersion()
266    {
267        if ( StringUtils.isBlank( scmVersion ) )
268        {
269            return null;
270        }
271        if ( scmVersion.length() > 0 )
272        {
273            if ( "revision".equals( scmVersionType ) )
274            {
275                return new ScmRevision( scmVersion );
276            }
277            else if ( "tag".equals( scmVersionType ) )
278            {
279                return new ScmTag( scmVersion );
280            }
281            else if ( "branch".equals( scmVersionType ) )
282            {
283                return new ScmBranch( scmVersion );
284            }
285        }
286
287        return null;
288    }
289
290    private ScmRepository getScmRepository( String url )
291        throws ScmRepositoryException, NoSuchScmProviderException
292    {
293        String username = null;
294
295        String password = null;
296
297        String privateKey = null;
298
299        String passphrase = null;
300
301        if ( authenticationInfo != null )
302        {
303            username = authenticationInfo.getUserName();
304
305            password = authenticationInfo.getPassword();
306
307            privateKey = authenticationInfo.getPrivateKey();
308
309            passphrase = authenticationInfo.getPassphrase();
310        }
311
312        ScmRepository scmRepository = getScmManager().makeScmRepository( url );
313
314        ScmProviderRepository providerRepository = scmRepository.getProviderRepository();
315
316        if ( StringUtils.isNotEmpty( username ) )
317        {
318            providerRepository.setUser( username );
319        }
320
321        if ( StringUtils.isNotEmpty( password ) )
322        {
323            providerRepository.setPassword( password );
324        }
325
326        if ( providerRepository instanceof ScmProviderRepositoryWithHost )
327        {
328            ScmProviderRepositoryWithHost providerRepo = (ScmProviderRepositoryWithHost) providerRepository;
329
330            if ( StringUtils.isNotEmpty( privateKey ) )
331            {
332                providerRepo.setPrivateKey( privateKey );
333            }
334
335            if ( StringUtils.isNotEmpty( passphrase ) )
336            {
337                providerRepo.setPassphrase( passphrase );
338            }
339        }
340
341        return scmRepository;
342    }
343
344    public void put( File source, String targetName )
345        throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
346    {
347        if ( source.isDirectory() )
348        {
349            throw new IllegalArgumentException( "Source is a directory: " + source );
350        }
351        putInternal( source, targetName );
352    }
353
354    /**
355     * Puts both files and directories
356     *
357     * @param source
358     * @param targetName
359     * @throws TransferFailedException
360     */
361    private void putInternal( File source, String targetName )
362        throws TransferFailedException
363    {
364        Resource target = new Resource( targetName );
365
366        firePutInitiated( target, source );
367
368        try
369        {
370            ScmRepository scmRepository = getScmRepository( getRepository().getUrl() );
371
372            target.setContentLength( source.length() );
373            target.setLastModified( source.lastModified() );
374
375            firePutStarted( target, source );
376
377            String msg = "Wagon: Adding " + source.getName() + " to repository";
378
379            ScmProvider scmProvider = getScmProvider( scmRepository.getProvider() );
380
381            boolean isDirectory = source.isDirectory();
382            String checkoutTargetName = isDirectory ? targetName : getDirname( targetName );
383            boolean recursive = false;
384            if ( isDirectory )
385            {
386                for ( File f : source.listFiles() )
387                {
388                    if ( f.isDirectory() )
389                    {
390                        recursive = true;
391                        break;
392                    }
393                }
394            }
395
396            String relPath = ensureDirs( scmProvider, scmRepository, checkoutTargetName, target, recursive );
397
398            File newCheckoutDirectory = new File( checkoutDirectory, relPath );
399
400            File scmFile = new File( newCheckoutDirectory, isDirectory ? "" : FileUtils.removePath( targetName, '/' ) );
401
402            boolean fileAlreadyInScm = scmFile.exists();
403
404            if ( !scmFile.equals( source ) )
405            {
406                if ( isDirectory )
407                {
408                    FileUtils.copyDirectoryStructure( source, scmFile );
409                }
410                else
411                {
412                    FileUtils.copyFile( source, scmFile );
413                }
414            }
415
416            if ( !fileAlreadyInScm || scmFile.isDirectory() )
417            {
418                int addedFiles = addFiles( scmProvider, scmRepository, newCheckoutDirectory,
419                                           isDirectory ? "" : scmFile.getName() );
420
421                if ( !fileAlreadyInScm && addedFiles == 0 )
422                {
423                    throw new ScmException(
424                        "Unable to add file to SCM: " + scmFile + "; see error messages above for more information" );
425                }
426            }
427
428            ScmResult result =
429                scmProvider.checkIn( scmRepository, new ScmFileSet( checkoutDirectory ), makeScmVersion(), msg );
430
431            checkScmResult( result );
432        }
433        catch ( ScmException e )
434        {
435            fireTransferError( target, e, TransferEvent.REQUEST_PUT );
436
437            throw new TransferFailedException( "Error interacting with SCM: " + e.getMessage(), e );
438        }
439        catch ( IOException e )
440        {
441            fireTransferError( target, e, TransferEvent.REQUEST_PUT );
442
443            throw new TransferFailedException( "Error interacting with SCM: " + e.getMessage(), e );
444        }
445
446        if ( source.isFile() )
447        {
448            postProcessListeners( target, source, TransferEvent.REQUEST_PUT );
449        }
450
451        firePutCompleted( target, source );
452    }
453
454    /**
455     * Returns the relative path to targetName in the checkout dir. If the targetName already exists in the scm, this
456     * will be the empty string.
457     *
458     * @param scmProvider
459     * @param scmRepository
460     * @param targetName
461     * @return
462     * @throws TransferFailedException
463     * @throws IOException
464     */
465    private String ensureDirs( ScmProvider scmProvider, ScmRepository scmRepository, String targetName,
466                               Resource resource, boolean recursiveArg )
467        throws TransferFailedException, IOException
468    {
469        if ( checkoutDirectory == null )
470        {
471            checkoutDirectory = createCheckoutDirectory();
472        }
473
474        String target = targetName;
475
476        // totally ignore scmRepository parent stuff since that is not supported by all scms.
477        // Instead, assume that that url exists. If not, then that's an error.
478        // Check whether targetName, which is a relative path into the scm, exists.
479        // If it doesn't, check the parent, etc.
480
481        boolean recursive = recursiveArg;
482
483        for ( ;; )
484        {
485            try
486            {
487                ScmResult res = tryPartialCheckout( target, recursive );
488                if ( !res.isSuccess() )
489                {
490                    throw new ScmException( "command failed: " + res.getCommandOutput().trim() );
491                }
492                break;
493            }
494            catch ( ScmException e )
495            {
496                recursive = false;
497                if ( partCOSubdir.length() == 0 )
498                {
499                    fireTransferError( resource, e, TransferEvent.REQUEST_GET );
500
501                    throw new TransferFailedException( "Error checking out: " + e.getMessage(), e );
502                }
503                target = getDirname( target );
504            }
505        }
506
507        // now create the subdirs in target, if it's a parent of targetName
508
509        String res =
510            partCOSubdir.length() >= targetName.length() ? "" : targetName.substring( partCOSubdir.length() ) + '/';
511
512        ArrayList<File> createdDirs = new ArrayList<File>();
513        File deepDir = new File( checkoutDirectory, res );
514
515        boolean added = false;
516        try
517        {
518            mkdirsThrow( deepDir, createdDirs );
519            if ( createdDirs.size() != 0 )
520            {
521                File topNewDir = createdDirs.get( 0 );
522                String relTopNewDir =
523                    topNewDir.getPath().substring( checkoutDirectory.getPath().length() + 1 ).replace( '\\', '/' );
524
525                addFiles( scmProvider, scmRepository, checkoutDirectory, relTopNewDir );
526                added = true;
527            }
528        }
529        catch ( ScmException e )
530        {
531            fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
532
533            throw new TransferFailedException( "Failed to add directory " + createdDirs.get( 0 ) + " to working copy",
534                                               e );
535        }
536        finally
537        {
538            if ( !added && createdDirs.size() != 0 )
539            {
540                FileUtils.deleteDirectory( createdDirs.get( 0 ) );
541            }
542        }
543
544        return res;
545    }
546
547    private static void mkdirsThrow( File f, List<File> createdDirs )
548        throws IOException
549    {
550        if ( !f.isDirectory() )
551        {
552            File parent = f.getParentFile();
553            mkdirsThrow( parent, createdDirs );
554            if ( !f.mkdir() )
555            {
556                throw new IOException( "Failed to create directory " + f.getAbsolutePath() );
557            }
558            createdDirs.add( f );
559        }
560    }
561
562    /**
563     * Add a file or directory to a SCM repository. If it's a directory all its contents are added recursively.
564     * <p/>
565     * TODO this is less than optimal, SCM API should provide a way to add a directory recursively
566     *
567     * @param scmProvider   SCM provider
568     * @param scmRepository SCM repository
569     * @param basedir       local directory corresponding to scmRepository
570     * @param scmFilePath   path of the file or directory to add, relative to basedir
571     * @return the number of files added.
572     * @throws ScmException
573     */
574    private int addFiles( ScmProvider scmProvider, ScmRepository scmRepository, File basedir, String scmFilePath )
575        throws ScmException
576    {
577        int addedFiles = 0;
578
579        File scmFile = new File( basedir, scmFilePath );
580
581        if ( scmFilePath.length() != 0 )
582        {
583            AddScmResult result =
584                scmProvider.add( scmRepository, new ScmFileSet( basedir, new File( scmFilePath ) ), mkBinaryFlag() );
585
586            /*
587             * TODO dirty fix to work around files with property svn:eol-style=native if a file has that property, first
588             * time file is added it fails, second time it succeeds the solution is check if the scm provider is svn and
589             * unset that property when the SCM API allows it
590             */
591            if ( !result.isSuccess() )
592            {
593                result =
594                    scmProvider.add( scmRepository, new ScmFileSet( basedir, new File( scmFilePath ) ),
595                                     mkBinaryFlag() );
596            }
597
598            addedFiles = result.getAddedFiles().size();
599        }
600
601        String reservedScmFile = scmProvider.getScmSpecificFilename();
602
603        if ( scmFile.isDirectory() )
604        {
605            for ( File file : scmFile.listFiles() )
606            {
607                if ( reservedScmFile != null && !reservedScmFile.equals( file.getName() ) )
608                {
609                    addedFiles += addFiles( scmProvider, scmRepository, basedir,
610                                            ( scmFilePath.length() == 0 ? "" : scmFilePath + "/" )
611                                                + file.getName() );
612                }
613            }
614        }
615
616        return addedFiles;
617    }
618
619    private CheckOutScmResult checkOut( ScmProvider scmProvider, ScmRepository scmRepository, ScmFileSet fileSet,
620                                        boolean recursive )
621        throws ScmException
622    {
623        ScmVersion ver = makeScmVersion();
624        CommandParameters parameters = mkBinaryFlag();
625        // TODO: AbstractScmProvider 6f7dd0c ignores checkOut() parameter "version"
626        parameters.setScmVersion( CommandParameter.SCM_VERSION, ver );
627        parameters.setString( CommandParameter.RECURSIVE, Boolean.toString( recursive ) );
628        parameters.setString( CommandParameter.SHALLOW, Boolean.toString( true ) );
629
630        return scmProvider.checkOut( scmRepository, fileSet, ver, parameters );
631    }
632
633    private CommandParameters mkBinaryFlag() throws ScmException
634    {
635        CommandParameters parameters = new CommandParameters();
636        parameters.setString( CommandParameter.BINARY, Boolean.toString( true ) );
637        return parameters;
638    }
639
640    /**
641     * @return true
642     */
643    public boolean supportsDirectoryCopy()
644    {
645        return true;
646    }
647
648    private boolean supportsPartialCheckout( ScmProvider scmProvider )
649    {
650        String scmType = scmProvider.getScmType();
651        return ( "svn".equals( scmType ) || "cvs".equals( scmType ) );
652    }
653
654    private boolean isAlwaysRecursive( ScmProvider scmProvider )
655    {
656        String scmType = scmProvider.getScmType();
657        return ( "git".equals( scmType ) || "cvs".equals( scmType ) );
658    }
659
660    public void putDirectory( File sourceDirectory, String destinationDirectory )
661        throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
662    {
663        if ( !sourceDirectory.isDirectory() )
664        {
665            throw new IllegalArgumentException( "Source is not a directory: " + sourceDirectory );
666        }
667
668        putInternal( sourceDirectory, destinationDirectory );
669    }
670
671    /**
672     * Check that the ScmResult was a successful operation
673     *
674     * @param result
675     * @throws TransferFailedException if result was not a successful operation
676     * @throws ScmException
677     */
678    private void checkScmResult( ScmResult result )
679        throws ScmException
680    {
681        if ( !result.isSuccess() )
682        {
683            throw new ScmException(
684                "Unable to commit file. " + result.getProviderMessage() + " " + ( result.getCommandOutput() == null
685                    ? ""
686                    : result.getCommandOutput() ) );
687        }
688    }
689
690    public void closeConnection()
691        throws ConnectionException
692    {
693        removeCheckoutDirectory();
694    }
695
696    /**
697     * Not implemented
698     *
699     * @throws UnsupportedOperationException always
700     */
701    public boolean getIfNewer( String resourceName, File destination, long timestamp )
702        throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
703    {
704        throw new UnsupportedOperationException( "Not currently supported: getIfNewer" );
705    }
706
707    public void get( String resourceName, File destination )
708        throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
709    {
710        Resource resource = new Resource( resourceName );
711
712        fireGetInitiated( resource, destination );
713
714        fireGetStarted( resource, destination );
715
716        try
717        {
718            String subdir = getDirname( resourceName );
719            ScmResult res = tryPartialCheckout( subdir, false );
720            if ( !res.isSuccess() && ( partCOSubdir.length() == 0 || res instanceof UpdateScmResult ) )
721            {
722                // inability to checkout SVN or CVS subdir is not fatal. We just assume it doesn't exist
723                // inability to update existing subdir or checkout root is fatal
724                throw new ScmException( "command failed: " + res.getCommandOutput().trim() );
725            }
726            resourceName = resourceName.substring( partCOSubdir.length() );
727
728            // TODO: limitations:
729            // - destination filename must match that in the repository - should allow the "-d" CVS equiv to be passed
730            //   in
731            // - we don't get granular exceptions from SCM (ie, auth, not found)
732            // - need to make it non-recursive to save time
733            // - exists() check doesn't test if it is in SCM already
734
735            File scmFile = new File( checkoutDirectory, resourceName );
736
737            if ( !scmFile.exists() )
738            {
739                throw new ResourceDoesNotExistException( "Unable to find resource " + destination + " after checkout" );
740            }
741
742            if ( !scmFile.equals( destination ) )
743            {
744                FileUtils.copyFile( scmFile, destination );
745            }
746        }
747        catch ( ScmException e )
748        {
749            fireTransferError( resource, e, TransferEvent.REQUEST_GET );
750
751            throw new TransferFailedException( "Error getting file from SCM", e );
752        }
753        catch ( IOException e )
754        {
755            fireTransferError( resource, e, TransferEvent.REQUEST_GET );
756
757            throw new TransferFailedException( "Error getting file from SCM", e );
758        }
759
760        postProcessListeners( resource, destination, TransferEvent.REQUEST_GET );
761
762        fireGetCompleted( resource, destination );
763    }
764
765    private ScmResult tryPartialCheckout( String subdir, boolean recursiveArg )
766        throws ScmException, IOException
767    {
768        String url = getRepository().getUrl();
769
770        String desiredPartCOSubdir = "";
771
772        ScmRepository scmRepository = getScmRepository( url );
773        ScmProvider scmProvider = getScmProvider( scmRepository.getProvider() );
774        if ( subdir.length() != 0 && supportsPartialCheckout( scmProvider ) )
775        {
776            url += ( url.endsWith( "/" ) ? "" : "/" ) + subdir;
777
778            desiredPartCOSubdir = subdir + "/";
779
780            scmRepository = getScmRepository( url );
781        }
782
783        boolean recursive = recursiveArg | isAlwaysRecursive( scmProvider );
784
785        if ( !desiredPartCOSubdir.equals( partCOSubdir ) )
786        {
787            deleteCheckoutDirectory();
788            partCOSubdir = desiredPartCOSubdir;
789        }
790
791        if ( recursive && !haveRecursiveCO )
792        {
793            deleteCheckoutDirectory();
794        }
795
796        ScmResult res;
797        if ( checkoutDirExists( scmProvider ) )
798        {
799            res = scmProvider.update( scmRepository, new ScmFileSet( checkoutDirectory ), makeScmVersion() );
800        }
801        else
802        {
803            res = checkOut( scmProvider, scmRepository, new ScmFileSet( checkoutDirectory ), recursive );
804            haveRecursiveCO = recursive && res.isSuccess();
805        }
806        return res;
807    }
808
809    private void deleteCheckoutDirectory()
810        throws IOException
811    {
812        haveRecursiveCO = false;
813        FileUtils.deleteDirectory( checkoutDirectory );
814    }
815
816    private boolean checkoutDirExists( ScmProvider scmProvider )
817    {
818        String reservedScmFile = scmProvider.getScmSpecificFilename();
819        File pathToCheck = reservedScmFile == null ? checkoutDirectory : new File( checkoutDirectory, reservedScmFile );
820        return pathToCheck.exists();
821    }
822
823    /**
824     * @return a List&lt;String&gt; with filenames/directories at the resourcepath.
825     * @see org.apache.maven.wagon.AbstractWagon#getFileList(java.lang.String)
826     */
827    public List<String> getFileList( String resourcePath )
828        throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException
829    {
830        try
831        {
832            ScmRepository repository = getScmRepository( getRepository().getUrl() );
833
834            ScmProvider provider = getScmProvider( repository.getProvider() );
835
836            ListScmResult result =
837                provider.list( repository, new ScmFileSet( new File( "." ), new File( resourcePath ) ), false,
838                               makeScmVersion() );
839
840            if ( !result.isSuccess() )
841            {
842                throw new ResourceDoesNotExistException( result.getProviderMessage() );
843            }
844
845            List<String> files = new ArrayList<String>();
846
847            for ( ScmFile f : result.getFiles() )
848            {
849                files.add( f.getPath() );
850            }
851
852            return files;
853        }
854        catch ( ScmException e )
855        {
856            throw new TransferFailedException( "Error getting filelist from SCM", e );
857        }
858    }
859
860    public boolean resourceExists( String resourceName )
861        throws TransferFailedException, AuthorizationException
862    {
863        try
864        {
865            getFileList( resourceName );
866
867            return true;
868        }
869        catch ( ResourceDoesNotExistException e )
870        {
871            return false;
872        }
873    }
874
875    private String getDirname( String resourceName )
876    {
877        return FileUtils.getPath( resourceName, '/' );
878    }
879}