View Javadoc
1   package org.apache.maven.wagon.providers.scm;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.text.DecimalFormat;
25  import java.util.ArrayList;
26  import java.util.List;
27  import java.util.Random;
28  
29  import org.apache.maven.scm.CommandParameter;
30  import org.apache.maven.scm.CommandParameters;
31  import org.apache.maven.scm.ScmBranch;
32  import org.apache.maven.scm.ScmException;
33  import org.apache.maven.scm.ScmFile;
34  import org.apache.maven.scm.ScmFileSet;
35  import org.apache.maven.scm.ScmResult;
36  import org.apache.maven.scm.ScmRevision;
37  import org.apache.maven.scm.ScmTag;
38  import org.apache.maven.scm.ScmVersion;
39  import org.apache.maven.scm.command.add.AddScmResult;
40  import org.apache.maven.scm.command.checkout.CheckOutScmResult;
41  import org.apache.maven.scm.command.list.ListScmResult;
42  import org.apache.maven.scm.command.update.UpdateScmResult;
43  import org.apache.maven.scm.manager.NoSuchScmProviderException;
44  import org.apache.maven.scm.manager.ScmManager;
45  import org.apache.maven.scm.provider.ScmProvider;
46  import org.apache.maven.scm.provider.ScmProviderRepository;
47  import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost;
48  import org.apache.maven.scm.repository.ScmRepository;
49  import org.apache.maven.scm.repository.ScmRepositoryException;
50  import org.apache.maven.wagon.AbstractWagon;
51  import org.apache.maven.wagon.ConnectionException;
52  import org.apache.maven.wagon.ResourceDoesNotExistException;
53  import org.apache.maven.wagon.TransferFailedException;
54  import org.apache.maven.wagon.authorization.AuthorizationException;
55  import org.apache.maven.wagon.events.TransferEvent;
56  import org.apache.maven.wagon.resource.Resource;
57  import org.codehaus.plexus.util.FileUtils;
58  import org.codehaus.plexus.util.StringUtils;
59  
60  /**
61   * Wagon provider to get and put files from and to SCM systems, using Maven-SCM as underlying transport.
62   * <p/>
63   * TODO it probably creates problems if the same wagon is used in two different SCM protocols, as instance variables can
64   * keep incorrect state.
65   * TODO: For doing releases, we either have to be able to add files with checking out the repository structure which may not be
66   * possible, or the checkout directory needs to be a constant. Doing releases won't scale if you have to checkout the
67   * whole repository structure in order to add 3 files.
68   *
69   * @author <a href="brett@apache.org">Brett Porter</a>
70   * @author <a href="evenisse@apache.org">Emmanuel Venisse</a>
71   * @author <a href="carlos@apache.org">Carlos Sanchez</a>
72   * @author Jason van Zyl
73   *
74   * @plexus.component role="org.apache.maven.wagon.Wagon" role-hint="scm" instantiation-strategy="per-lookup"
75   */
76  public class ScmWagon
77      extends AbstractWagon
78  {
79      /**
80       * @plexus.requirement
81       */
82      private volatile ScmManager scmManager;
83  
84      /**
85       * The SCM version, if any.
86       *
87       * @parameter
88       */
89      private String scmVersion;
90  
91      /**
92       * The SCM version type, if any. Defaults to "branch".
93       *
94       * @parameter
95       */
96      private String scmVersionType;
97  
98      /**
99       * 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 }