1 package org.apache.maven.wagon.providers.scm;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76 public class ScmWagon
77 extends AbstractWagon
78 {
79
80
81
82 private volatile ScmManager scmManager;
83
84
85
86
87
88
89 private String scmVersion;
90
91
92
93
94
95
96 private String scmVersionType;
97
98
99
100
101 private String partCOSubdir = "";
102
103 private boolean haveRecursiveCO;
104
105 private File checkoutDirectory;
106
107
108
109
110
111
112 public ScmManager getScmManager()
113 {
114 return scmManager;
115 }
116
117
118
119
120
121
122 public void setScmManager( ScmManager scmManager )
123 {
124 this.scmManager = scmManager;
125 }
126
127
128
129
130
131
132 public String getScmVersion()
133 {
134 return scmVersion;
135 }
136
137
138
139
140
141
142 public void setScmVersion( String scmVersion )
143 {
144 this.scmVersion = scmVersion;
145 }
146
147
148
149
150
151
152 public String getScmVersionType()
153 {
154 return scmVersionType;
155 }
156
157
158
159
160
161
162 public void setScmVersionType( String scmVersionType )
163 {
164 this.scmVersionType = scmVersionType;
165 }
166
167
168
169
170
171
172 public File getCheckoutDirectory()
173 {
174 return checkoutDirectory;
175 }
176
177
178
179
180
181
182 public void setCheckoutDirectory( File checkoutDirectory )
183 {
184 this.checkoutDirectory = checkoutDirectory;
185 }
186
187
188
189
190
191
192
193
194 public ScmProvider getScmProvider( String scmType )
195 throws NoSuchScmProviderException
196 {
197 return getScmManager().getProviderByType( scmType );
198 }
199
200
201
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;
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
261
262
263
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
356
357
358
359
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
456
457
458
459
460
461
462
463
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
477
478
479
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
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
564
565
566
567
568
569
570
571
572
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
588
589
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
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
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
673
674
675
676
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
698
699
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
723
724 throw new ScmException( "command failed: " + res.getCommandOutput().trim() );
725 }
726 resourceName = resourceName.substring( partCOSubdir.length() );
727
728
729
730
731
732
733
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
825
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 }